Merge pull request #102 from NicolasConstant/topic_list-support
Topic list support
This commit is contained in:
commit
b7b84dae24
|
@ -28,7 +28,7 @@
|
|||
|
||||
<div class="header__download-box--description">
|
||||
A FLOSS multi-account Mastodon and Pleroma desktop client<br />
|
||||
Now available in Beta (v0.8.0)<br />
|
||||
Now available in Beta (v0.9.0)<br />
|
||||
<br />
|
||||
</div>
|
||||
|
||||
|
@ -43,9 +43,9 @@
|
|||
<br />
|
||||
|
||||
<h4 class="header__download-box--subtitle">Or download the desktop client:</h4>
|
||||
<a href="https://github.com/NicolasConstant/sengi/releases/download/0.8.0/Sengi-0.8.0-win.exe" class="download-button" title="download client for windows"><i class="fab fa-windows"></i></a>
|
||||
<a href="https://github.com/NicolasConstant/sengi/releases/download/0.8.0/Sengi-0.8.0-mac.dmg" class="download-button" title="download client for mac"><i class="fab fa-apple"></i></a>
|
||||
<a href="https://github.com/NicolasConstant/sengi/releases/download/0.8.0/Sengi-0.8.0-linux.deb" class="download-button" title="download client for debian-based distrib"><i class="fab fa-ubuntu"></i></a>
|
||||
<a href="https://github.com/NicolasConstant/sengi/releases/download/0.9.0/Sengi-0.9.0-win.exe" class="download-button" title="download client for windows"><i class="fab fa-windows"></i></a>
|
||||
<a href="https://github.com/NicolasConstant/sengi/releases/download/0.9.0/Sengi-0.9.0-mac.dmg" class="download-button" title="download client for mac"><i class="fab fa-apple"></i></a>
|
||||
<a href="https://github.com/NicolasConstant/sengi/releases/download/0.9.0/Sengi-0.9.0-linux.deb" class="download-button" title="download client for debian-based distrib"><i class="fab fa-ubuntu"></i></a>
|
||||
<a href="https://snapcraft.io/sengi" title="use Snap Store for linux"><img src="images/snap-store-white.png" /></a>
|
||||
</p>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "sengi",
|
||||
"version": "0.8.0",
|
||||
"version": "0.9.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "main-electron.js",
|
||||
"description": "A multi-account desktop client for Mastodon and Pleroma",
|
||||
|
|
|
@ -58,6 +58,8 @@ import { NotificationsComponent } from './components/floating-column/manage-acco
|
|||
import { SettingsState } from './states/settings.state';
|
||||
import { AccountEmojiPipe } from './pipes/account-emoji.pipe';
|
||||
import { CardComponent } from './components/stream/status/card/card.component';
|
||||
import { ListEditorComponent } from './components/floating-column/manage-account/my-account/list-editor/list-editor.component';
|
||||
import { ListAccountComponent } from './components/floating-column/manage-account/my-account/list-editor/list-account/list-account.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "", redirectTo: "home", pathMatch: "full" },
|
||||
|
@ -104,7 +106,9 @@ const routes: Routes = [
|
|||
MentionsComponent,
|
||||
NotificationsComponent,
|
||||
AccountEmojiPipe,
|
||||
CardComponent
|
||||
CardComponent,
|
||||
ListEditorComponent,
|
||||
ListAccountComponent
|
||||
],
|
||||
imports: [
|
||||
FontAwesomeModule,
|
||||
|
|
|
@ -29,7 +29,6 @@ export class DirectMessagesComponent implements OnInit {
|
|||
|
||||
@Input('account')
|
||||
set account(acc: AccountWrapper) {
|
||||
console.warn('account');
|
||||
this._account = acc;
|
||||
this.getDirectMessages();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
<div class="list-account">
|
||||
<div class="list-account__action">
|
||||
<a href class="list-account__action--button list-account__action--button--add" title="add account to list"
|
||||
*ngIf="!accountWrapper.isInList" (click)="add()">
|
||||
<fa-icon [icon]="faPlus"></fa-icon>
|
||||
</a>
|
||||
|
||||
<a href class="list-account__action--button list-account__action--button--remove" title="remove account from list"
|
||||
*ngIf="accountWrapper.isInList" (click)="remove()">
|
||||
<fa-icon [icon]="faTimes"></fa-icon>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
<div class="list-account__account">
|
||||
<img src="{{ accountWrapper.account.avatar }}" alt="" class="list-account__account--avatar">
|
||||
|
||||
<span class="list-account__account--display-name" title="{{ accountWrapper.account.display_name }}" innerHTML="{{ accountWrapper.account | accountEmoji }}"></span>
|
||||
<span class="list-account__account--acct" title="{{ accountWrapper.account.acct }}">{{ accountWrapper.account.acct }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,82 @@
|
|||
@import "variables";
|
||||
|
||||
.list-account {
|
||||
transition: all .2s;
|
||||
$actin-width: 50px;
|
||||
|
||||
&__action {
|
||||
float: right;
|
||||
width: $actin-width;
|
||||
position: relative;
|
||||
|
||||
&--button {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 12px;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
// outline: 1px solid greenyellow;
|
||||
|
||||
&:hover{
|
||||
color: darken(#fff, 20);
|
||||
}
|
||||
|
||||
// $add-color: rgb(167, 220, 255);
|
||||
// $remove-color: rgb(255, 154, 196);
|
||||
// &--add {
|
||||
// color: $add-color;
|
||||
// &:hover{
|
||||
// color: darken($add-color, 20);
|
||||
// }
|
||||
// }
|
||||
|
||||
&--remove {
|
||||
color: $font-link-primary;
|
||||
&:hover{
|
||||
color: lighten($font-link-primary, 40);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__account {
|
||||
width: calc(100% - #{ $actin-width });
|
||||
position: relative;
|
||||
height: 50px;
|
||||
|
||||
|
||||
&--avatar {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 5px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
$max-name-width: 190px;
|
||||
&--display-name {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 60px;
|
||||
color: white;
|
||||
|
||||
max-width: $max-name-width;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&--acct {
|
||||
position: absolute;
|
||||
top: 26px;
|
||||
left: 60px;
|
||||
color: $status-secondary-color;
|
||||
|
||||
max-width: $max-name-width;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ListAccountComponent } from './list-account.component';
|
||||
|
||||
describe('ListAccountComponent', () => {
|
||||
let component: ListAccountComponent;
|
||||
let fixture: ComponentFixture<ListAccountComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ListAccountComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ListAccountComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { faTimes, faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { Account } from "../../../../../../services/models/mastodon.interfaces";
|
||||
import { AccountListWrapper } from '../list-editor.component';
|
||||
import { isUndefined } from 'util';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-account',
|
||||
templateUrl: './list-account.component.html',
|
||||
styleUrls: ['./list-account.component.scss']
|
||||
})
|
||||
export class ListAccountComponent implements OnInit {
|
||||
faTimes = faTimes;
|
||||
faPlus = faPlus;
|
||||
|
||||
@Input() accountWrapper: AccountListWrapper;
|
||||
@Output() addEvent = new EventEmitter<AccountListWrapper>();
|
||||
@Output() removeEvent = new EventEmitter<AccountListWrapper>();
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
add(): boolean {
|
||||
if(this.accountWrapper && this.accountWrapper.isLoading) return;
|
||||
this.addEvent.emit(this.accountWrapper);
|
||||
return false;
|
||||
}
|
||||
|
||||
remove(): boolean {
|
||||
if(this.accountWrapper && this.accountWrapper.isLoading) return;
|
||||
this.removeEvent.emit(this.accountWrapper);
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<div class="list-editor">
|
||||
<a href class="list-editor__close-search" title="close search" *ngIf="searchOpen" (click)="closeSearch()">
|
||||
<fa-icon [icon]="faTimes"></fa-icon>
|
||||
</a>
|
||||
<input class="list-editor__search" placeholder="search account" [(ngModel)]="searchPattern"
|
||||
(keyup.enter)="search()" />
|
||||
<div class="list-editor__list flexcroll" *ngIf="!searchOpen">
|
||||
<app-list-account class="list-editor__account" *ngFor="let account of accountsInList"
|
||||
[accountWrapper]="account" (addEvent)="addEvent($event)" (removeEvent)="removeEvent($event)">
|
||||
</app-list-account>
|
||||
</div>
|
||||
<div class="list-editor__list flexcroll" *ngIf="searchOpen">
|
||||
<app-list-account class="list-editor__account" *ngFor="let account of accountsSearch"
|
||||
[accountWrapper]="account" (addEvent)="addEvent($event)" (removeEvent)="removeEvent($event)">
|
||||
</app-list-account>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,46 @@
|
|||
@import "variables";
|
||||
@import "commons";
|
||||
|
||||
.list-editor {
|
||||
background-color: $color-primary;
|
||||
min-height: 20px;
|
||||
margin-top: 1px;
|
||||
position: relative;
|
||||
|
||||
&__search {
|
||||
color: #fff;
|
||||
background-color: darken($color-primary, 4);
|
||||
border: 2px solid $color-primary;
|
||||
width: calc(100%);
|
||||
padding: 3px 5px;
|
||||
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&__close-search {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 5px;
|
||||
color: white;
|
||||
padding: 5px;
|
||||
&:hover {
|
||||
color: rgb(160, 160, 160);
|
||||
}
|
||||
}
|
||||
|
||||
&__list {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
border-top: 1px solid #000;
|
||||
}
|
||||
|
||||
&__account {
|
||||
display: block;
|
||||
&:not(:last-child){
|
||||
border-bottom: 1px solid #000;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ListEditorComponent } from './list-editor.component';
|
||||
|
||||
xdescribe('ListEditorComponent', () => {
|
||||
let component: ListEditorComponent;
|
||||
let fixture: ComponentFixture<ListEditorComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ListEditorComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ListEditorComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,148 @@
|
|||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { StreamWrapper } from '../my-account.component';
|
||||
import { MastodonService } from '../../../../../services/mastodon.service';
|
||||
import { AccountWrapper } from '../../../../../models/account.models';
|
||||
import { NotificationService } from '../../../../../services/notification.service';
|
||||
import { Account, Relationship, Instance } from "../../../../../services/models/mastodon.interfaces";
|
||||
import { of } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-list-editor',
|
||||
templateUrl: './list-editor.component.html',
|
||||
styleUrls: ['./list-editor.component.scss']
|
||||
})
|
||||
export class ListEditorComponent implements OnInit {
|
||||
faTimes = faTimes;
|
||||
|
||||
@Input() list: StreamWrapper;
|
||||
@Input() account: AccountWrapper;
|
||||
|
||||
accountsInList: AccountListWrapper[] = [];
|
||||
accountsSearch: AccountListWrapper[] = [];
|
||||
searchPattern: string;
|
||||
searchOpen: boolean;
|
||||
|
||||
constructor(
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly mastodonService: MastodonService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.accountsInList.length = 0;
|
||||
this.mastodonService.getListAccounts(this.account.info, this.list.listId)
|
||||
.then((accounts: Account[]) => {
|
||||
this.accountsInList.length = 0;
|
||||
for (const account of accounts) {
|
||||
this.accountsInList.push(new AccountListWrapper(account, true));
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
});
|
||||
}
|
||||
|
||||
search() {
|
||||
if (this.searchPattern === '')
|
||||
return this.closeSearch();
|
||||
|
||||
this.searchOpen = true;
|
||||
this.accountsSearch.length = 0;
|
||||
this.mastodonService.searchAccount(this.account.info, this.searchPattern, 15)
|
||||
.then((accounts: Account[]) => {
|
||||
this.accountsSearch.length = 0;
|
||||
for (const account of accounts) {
|
||||
const isInList = this.accountsInList.filter(x => x.account.id === account.id).length > 0;
|
||||
this.accountsSearch.push(new AccountListWrapper(account, isInList));
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
});
|
||||
}
|
||||
|
||||
closeSearch(): boolean {
|
||||
this.searchPattern = null;
|
||||
this.searchOpen = false;
|
||||
this.accountsSearch.length = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
addEvent(accountWrapper: AccountListWrapper) {
|
||||
console.log(accountWrapper);
|
||||
|
||||
accountWrapper.isLoading = true;
|
||||
this.mastodonService.getInstance(this.account.info.instance)
|
||||
.then((instance: Instance) => {
|
||||
console.log(instance);
|
||||
if (instance.version.toLowerCase().includes('pleroma')) {
|
||||
return Promise.resolve(true);
|
||||
} else {
|
||||
return this.followAccount(accountWrapper);
|
||||
}
|
||||
})
|
||||
.then(() => {
|
||||
return this.mastodonService.addAccountToList(this.account.info, this.list.listId, accountWrapper.account.id);
|
||||
})
|
||||
.then(() => {
|
||||
accountWrapper.isInList = true;
|
||||
this.accountsInList.push(accountWrapper);
|
||||
})
|
||||
.catch(err => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
})
|
||||
.then(() => {
|
||||
accountWrapper.isLoading = false;
|
||||
});
|
||||
}
|
||||
|
||||
private followAccount(accountWrapper: AccountListWrapper): Promise<boolean> {
|
||||
return this.mastodonService.getRelationships(this.account.info, [accountWrapper.account])
|
||||
.then((relationships: Relationship[]) => {
|
||||
var relationship = relationships.filter(x => x.id === accountWrapper.account.id)[0];
|
||||
return relationship;
|
||||
})
|
||||
.then((relationship: Relationship) => {
|
||||
if (relationship.following) {
|
||||
return Promise.resolve(true);
|
||||
} else {
|
||||
return this.mastodonService.follow(this.account.info, accountWrapper.account)
|
||||
.then((relationship: Relationship) => {
|
||||
return new Promise<boolean>((resolve) => setTimeout(resolve, 1500));
|
||||
// return Promise.resolve(relationship.following);
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// private delay(t, v) {
|
||||
// return new Promise(function(resolve) {
|
||||
// setTimeout(resolve.bind(null, v), t)
|
||||
// });
|
||||
// }
|
||||
|
||||
removeEvent(accountWrapper: AccountListWrapper) {
|
||||
console.log(accountWrapper);
|
||||
|
||||
accountWrapper.isLoading = true;
|
||||
this.mastodonService.removeAccountFromList(this.account.info, this.list.listId, accountWrapper.account.id)
|
||||
.then(() => {
|
||||
accountWrapper.isInList = false;
|
||||
this.accountsInList = this.accountsInList.filter(x => x.account.id !== accountWrapper.account.id);
|
||||
})
|
||||
.catch(err => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
})
|
||||
.then(() => {
|
||||
accountWrapper.isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountListWrapper {
|
||||
constructor(public account: Account, public isInList: boolean) {
|
||||
}
|
||||
|
||||
isProcessing: boolean;
|
||||
isLoading: boolean;
|
||||
}
|
|
@ -1,9 +1,45 @@
|
|||
<div class="my-account__body flexcroll">
|
||||
<h4 class="my-account__label">add column:</h4>
|
||||
<a class="my-account__link my-account__blue" href *ngFor="let stream of availableStreams"
|
||||
(click)="addStream(stream)" title="{{ stream.isAdded ? '' : 'add timeline'}}" [class.my-account__link--disabled]="stream.isAdded">
|
||||
{{ stream.name }} <fa-icon class="my-account__link--icon" *ngIf="stream.isAdded" [icon]="faCheckSquare"></fa-icon>
|
||||
<h4 class="my-account__label">add timeline:</h4>
|
||||
<a class="my-account__link my-account__link--margin-bottom my-account__blue" href
|
||||
*ngFor="let stream of availableStreams" (click)="addStream(stream)"
|
||||
title="{{ stream.isAdded ? '' : 'add timeline'}}" [class.my-account__link--disabled]="stream.isAdded">
|
||||
{{ stream.name }} <fa-icon class="my-account__link--icon" *ngIf="stream.isAdded" [icon]="faCheckSquare">
|
||||
</fa-icon>
|
||||
</a>
|
||||
|
||||
<h4 class="my-account__label my-account__margin-top">manage list:</h4>
|
||||
<div class="my-account__link--margin-bottom" *ngFor="let list of availableLists">
|
||||
<a href class="my-account__list--button" title="delete list"
|
||||
(click)="openCloseDeleteConfirmation(list, true)" *ngIf="!list.confirmDeletion">
|
||||
<fa-icon class="my-account__link--icon" [icon]="faTrash"></fa-icon>
|
||||
</a>
|
||||
<a href class="my-account__list--button" title="edit list"
|
||||
(click)="editList(list)" *ngIf="!list.confirmDeletion">
|
||||
<fa-icon class="my-account__link--icon" [icon]="faPenAlt"></fa-icon>
|
||||
</a>
|
||||
|
||||
<a href class="my-account__list--button" title="cancel"
|
||||
(click)="openCloseDeleteConfirmation(list, false)" *ngIf="list.confirmDeletion">
|
||||
<fa-icon class="my-account__link--icon" [icon]="faTimes"></fa-icon>
|
||||
</a>
|
||||
<a href class="my-account__list--button my-account__red" title="delete list"
|
||||
(click)="deleteList(list)" *ngIf="list.confirmDeletion">
|
||||
<fa-icon class="my-account__link--icon" [icon]="faCheck"></fa-icon>
|
||||
</a>
|
||||
|
||||
<a class="my-account__link my-account__list my-account__blue" href (click)="addStream(list)"
|
||||
title="{{ list.isAdded ? '' : 'add list'}}" [class.my-account__link--disabled]="list.isAdded">
|
||||
{{ list.name }} <fa-icon class="my-account__link--icon" *ngIf="list.isAdded" [icon]="faCheckSquare">
|
||||
</fa-icon>
|
||||
</a>
|
||||
|
||||
<app-list-editor *ngIf="list.editList" [list]="list" [account]="account"></app-list-editor>
|
||||
</div>
|
||||
<a href class="my-account__list--button" title="create list" (click)="createList()">
|
||||
<fa-icon class="my-account__link--icon" [icon]="faPlus"></fa-icon>
|
||||
</a>
|
||||
<input class="my-account__list--new-list-title" placeholder="new list title" [(ngModel)]="listTitle" (keyup.enter)="createList()" [disabled]="creationLoading"/>
|
||||
|
||||
<h4 class="my-account__label my-account__margin-top">remove account from sengi:</h4>
|
||||
<a class="my-account__link my-account__red" href (click)="removeAccount()">
|
||||
Delete
|
||||
|
|
|
@ -1,58 +1,105 @@
|
|||
@import "variables";
|
||||
@import "commons";
|
||||
|
||||
.my-account {
|
||||
.my-account {
|
||||
transition: all .2s;
|
||||
|
||||
&__body {
|
||||
overflow: auto;
|
||||
height: calc(100%);
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
padding-right: 10px;
|
||||
font-size: $small-font-size;
|
||||
padding-bottom: 20px;
|
||||
outline: 1px dotted greenyellow;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: $small-font-size;
|
||||
margin-top: 10px;
|
||||
margin-left: 5px;
|
||||
color: $font-color-secondary;
|
||||
}
|
||||
}
|
||||
|
||||
&__blue {
|
||||
background-color: $color-primary;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($color-primary, 15);
|
||||
}
|
||||
}
|
||||
|
||||
&__red {
|
||||
$red-button-color: rgb(65, 3, 3);
|
||||
background-color: $red-button-color;
|
||||
background-color: $red-button-color !important;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($red-button-color, 15);
|
||||
background-color: lighten($red-button-color, 15) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&__link {
|
||||
text-decoration: none;
|
||||
display: block; // width: calc(100% - 20px);
|
||||
width: 100%; // height: 30px;
|
||||
padding: 5px 10px; // border: solid 1px black;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
&--icon {
|
||||
float: right;
|
||||
}
|
||||
|
||||
&--disabled {
|
||||
cursor: default;
|
||||
background-color: darken($color-primary, 4);
|
||||
|
||||
&:hover {
|
||||
background-color: darken($color-primary, 4);
|
||||
}
|
||||
}
|
||||
|
||||
&--margin-bottom {
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__list {
|
||||
$list-width: 60px;
|
||||
width: calc(100% - #{$list-width} - 2px);
|
||||
|
||||
&--button {
|
||||
margin-left: 1px;
|
||||
width: calc(#{$list-width}/2);
|
||||
float: right;
|
||||
padding: 5px 10px;
|
||||
background-color: $color-primary;
|
||||
color: #fff;
|
||||
color: $font-color-secondary;
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
background-color: lighten($color-primary, 15);
|
||||
}
|
||||
}
|
||||
|
||||
&--new-list-title {
|
||||
color: #fff;
|
||||
background-color: darken($color-primary, 4);
|
||||
border: 2px solid $color-primary;
|
||||
width: calc(100% - #{$list-width}/2 - 1px);
|
||||
padding: 3px 5px;
|
||||
|
||||
&:focus {
|
||||
outline: none !important;
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__margin-top {
|
||||
margin-top: 25px;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,12 +2,14 @@ import { Component, OnInit, OnDestroy, Input } from '@angular/core';
|
|||
import { Observable, Subscription } from 'rxjs';
|
||||
import { Store, Select } from '@ngxs/store';
|
||||
import { faCheckSquare } from "@fortawesome/free-regular-svg-icons";
|
||||
import { faPenAlt, faTrash, faPlus, faCheck, faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { NotificationService } from '../../../../services/notification.service';
|
||||
import { StreamElement, StreamTypeEnum, AddStream, RemoveAllStreams } from '../../../../states/streams.state';
|
||||
import { StreamElement, StreamTypeEnum, AddStream, RemoveAllStreams, RemoveStream } from '../../../../states/streams.state';
|
||||
import { AccountWrapper } from '../../../../models/account.models';
|
||||
import { RemoveAccount } from '../../../../states/accounts.state';
|
||||
import { NavigationService } from '../../../../services/navigation.service';
|
||||
import { MastodonService } from '../../../../services/mastodon.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-my-account',
|
||||
|
@ -15,10 +17,15 @@ import { NavigationService } from '../../../../services/navigation.service';
|
|||
styleUrls: ['./my-account.component.scss']
|
||||
})
|
||||
export class MyAccountComponent implements OnInit, OnDestroy {
|
||||
|
||||
faPlus = faPlus;
|
||||
faTrash = faTrash;
|
||||
faPenAlt = faPenAlt;
|
||||
faCheckSquare = faCheckSquare;
|
||||
faCheck = faCheck;
|
||||
faTimes = faTimes;
|
||||
|
||||
availableStreams: StreamWrapper[] = [];
|
||||
availableLists: StreamWrapper[] = [];
|
||||
|
||||
private _account: AccountWrapper;
|
||||
@Input('account')
|
||||
|
@ -36,7 +43,8 @@ export class MyAccountComponent implements OnInit, OnDestroy {
|
|||
constructor(
|
||||
private readonly store: Store,
|
||||
private readonly navigationService: NavigationService,
|
||||
private notificationService: NotificationService) { }
|
||||
private readonly mastodonService: MastodonService,
|
||||
private readonly notificationService: NotificationService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.streamChangedSub = this.streamElements$.subscribe((streams: StreamElement[]) => {
|
||||
|
@ -53,9 +61,9 @@ export class MyAccountComponent implements OnInit, OnDestroy {
|
|||
private loadStreams(account: AccountWrapper){
|
||||
const instance = account.info.instance;
|
||||
this.availableStreams.length = 0;
|
||||
this.availableStreams.push(new StreamWrapper(new StreamElement(StreamTypeEnum.global, 'Federated Timeline', account.info.id, null, null, instance)));
|
||||
this.availableStreams.push(new StreamWrapper(new StreamElement(StreamTypeEnum.local, 'Local Timeline', account.info.id, null, null, instance)));
|
||||
this.availableStreams.push(new StreamWrapper(new StreamElement(StreamTypeEnum.personnal, 'Home', account.info.id, null, null, instance)));
|
||||
this.availableStreams.push(new StreamWrapper(new StreamElement(StreamTypeEnum.global, 'Federated Timeline', account.info.id, null, null, null, instance)));
|
||||
this.availableStreams.push(new StreamWrapper(new StreamElement(StreamTypeEnum.local, 'Local Timeline', account.info.id, null, null, null, instance)));
|
||||
this.availableStreams.push(new StreamWrapper(new StreamElement(StreamTypeEnum.personnal, 'Home', account.info.id, null, null, null, instance)));
|
||||
|
||||
const loadedStreams = <StreamElement[]>this.store.snapshot().streamsstatemodel.streams;
|
||||
this.availableStreams.forEach(s => {
|
||||
|
@ -65,6 +73,24 @@ export class MyAccountComponent implements OnInit, OnDestroy {
|
|||
s.isAdded = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.availableLists.length = 0;
|
||||
this.mastodonService.getLists(account.info)
|
||||
.then((streams: StreamElement[]) => {
|
||||
this.availableLists.length = 0;
|
||||
for (let stream of streams) {
|
||||
let wrappedStream = new StreamWrapper(stream);
|
||||
if(loadedStreams.find(x => x.id == stream.id)){
|
||||
wrappedStream.isAdded = true;
|
||||
} else {
|
||||
wrappedStream.isAdded = false;
|
||||
}
|
||||
this.availableLists.push(wrappedStream);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
});
|
||||
}
|
||||
|
||||
addStream(stream: StreamWrapper): boolean {
|
||||
|
@ -72,7 +98,6 @@ export class MyAccountComponent implements OnInit, OnDestroy {
|
|||
this.store.dispatch([new AddStream(stream)]).toPromise()
|
||||
.then(() => {
|
||||
stream.isAdded = true;
|
||||
//this.notificationService.notify(`stream added`, false);
|
||||
});
|
||||
}
|
||||
return false;
|
||||
|
@ -84,12 +109,62 @@ export class MyAccountComponent implements OnInit, OnDestroy {
|
|||
this.navigationService.closePanel();
|
||||
return false;
|
||||
}
|
||||
|
||||
listTitle: string;
|
||||
creationLoading: boolean;
|
||||
createList(): boolean {
|
||||
if(this.creationLoading || !this.listTitle || this.listTitle == '') return false;
|
||||
|
||||
this.creationLoading = true;
|
||||
this.mastodonService.createList(this.account.info, this.listTitle)
|
||||
.then((stream: StreamElement) => {
|
||||
this.listTitle = null;
|
||||
let wrappedStream = new StreamWrapper(stream);
|
||||
this.availableLists.push(wrappedStream);
|
||||
})
|
||||
.catch(err => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
})
|
||||
.then(() => {
|
||||
this.creationLoading = false;
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
editList(list: StreamWrapper): boolean {
|
||||
list.editList = !list.editList;
|
||||
return false;
|
||||
}
|
||||
|
||||
openCloseDeleteConfirmation(list: StreamWrapper, state: boolean): boolean {
|
||||
list.confirmDeletion = state;
|
||||
return false;
|
||||
}
|
||||
|
||||
deleteList(list: StreamWrapper): boolean {
|
||||
this.mastodonService.deleteList(this.account.info, list.listId)
|
||||
.then(() => {
|
||||
const isAdded = this.availableLists.find(x => x.id === list.id).isAdded;
|
||||
if(isAdded){
|
||||
this.store.dispatch([new RemoveStream(list.id)]);
|
||||
}
|
||||
this.availableLists = this.availableLists.filter(x => x.id !== list.id);
|
||||
})
|
||||
.catch(err => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class StreamWrapper extends StreamElement {
|
||||
export class StreamWrapper extends StreamElement {
|
||||
constructor(stream: StreamElement) {
|
||||
super(stream.type, stream.name, stream.accountId, stream.tag, stream.list, stream.instance);
|
||||
super(stream.type, stream.name, stream.accountId, stream.tag, stream.list, stream.listId, stream.instance);
|
||||
}
|
||||
|
||||
isAdded: boolean;
|
||||
confirmDeletion: boolean;
|
||||
editList: boolean;
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
@import "mixins";
|
||||
@import "panel";
|
||||
@import "commons";
|
||||
@import "buttons";
|
||||
.panel {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
|
@ -15,8 +16,35 @@
|
|||
.form-with-button {
|
||||
width: calc(100% - #{$button-size});
|
||||
float: left;
|
||||
|
||||
background-color: $column-color;
|
||||
border-color: $button-border-color;
|
||||
color: #fff;
|
||||
font-size: $default-font-size;
|
||||
&:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
height: 29px;
|
||||
padding: 0 5px 0 5px;
|
||||
}
|
||||
|
||||
|
||||
// .form-control {
|
||||
// margin: 0 0 5px 5px;
|
||||
// width: calc(100% - 10px);
|
||||
// background-color: $column-color;
|
||||
// border-color: $status-secondary-color;
|
||||
// color: #fff;
|
||||
// font-size: $default-font-size;
|
||||
// &:focus {
|
||||
// box-shadow: none;
|
||||
// }
|
||||
// // &--privacy {
|
||||
// // display: inline-block;
|
||||
// // width: calc(100% - 15px - #{$btn-send-status-width} - #{$counter-width});
|
||||
// // }
|
||||
// }
|
||||
|
||||
.form-button {
|
||||
width: $button-size;
|
||||
height: 29px;
|
||||
|
@ -25,11 +53,16 @@
|
|||
cursor: pointer;
|
||||
background-color: $button-background-color;
|
||||
color: $button-color;
|
||||
color: whitesmoke;
|
||||
transition: all .2s;
|
||||
&:hover {
|
||||
background-color: $button-background-color-hover;
|
||||
color: $button-color-hover;
|
||||
}
|
||||
|
||||
|
||||
border: 1px solid $button-border-color;
|
||||
border-width: 1px 1px 1px 0;
|
||||
}
|
||||
|
||||
$search-form-height: 70px;
|
||||
|
@ -113,4 +146,4 @@ $search-form-height: 70px;
|
|||
background-color: $button-background-color-hover;
|
||||
}
|
||||
@include clearfix;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<div class="panel">
|
||||
<h3 class="panel__title">settings</h3>
|
||||
|
||||
<p class="version">Sengi version: 0.8.0</p>
|
||||
<p class="version">Sengi version: {{version}}</p>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,15 +1,18 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings',
|
||||
templateUrl: './settings.component.html',
|
||||
styleUrls: ['./settings.component.scss']
|
||||
selector: 'app-settings',
|
||||
templateUrl: './settings.component.html',
|
||||
styleUrls: ['./settings.component.scss']
|
||||
})
|
||||
export class SettingsComponent implements OnInit {
|
||||
|
||||
constructor() { }
|
||||
version: string;
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
constructor() { }
|
||||
|
||||
}
|
||||
ngOnInit() {
|
||||
this.version = environment.VERSION;
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ $height-button: 40px;
|
|||
|
||||
.left-bar-link {
|
||||
color: $font-link-primary;
|
||||
color: darken(whitesmoke, 17);
|
||||
text-decoration: none;
|
||||
&:hover {
|
||||
color: $font-link-primary-hover;
|
||||
|
@ -28,23 +29,25 @@ $height-button: 40px;
|
|||
&--status {
|
||||
//margin-top: 3px;
|
||||
font-size: 26px;
|
||||
padding: 8px 0 2px 12px;
|
||||
font-size: 22px;
|
||||
padding: 8px 0 2px 13px;
|
||||
}
|
||||
&--search {
|
||||
font-size: 28px;
|
||||
padding: 0 0 0 11px;
|
||||
font-size: 22px;
|
||||
padding: 5px 0 0 13px;
|
||||
}
|
||||
&--add {
|
||||
padding: 0 0 0 12px;
|
||||
font-size: 26px;
|
||||
padding: 5px 0 5px 13px;
|
||||
font-size: 22px;
|
||||
}
|
||||
&--cog {
|
||||
padding: 2px 0 0 9px;
|
||||
position: absolute;
|
||||
bottom: 7px;
|
||||
opacity: .2;
|
||||
opacity: .3;
|
||||
transition: all .3s;
|
||||
filter: alpha(opacity=20);
|
||||
filter: alpha(opacity=30);
|
||||
|
||||
// color: darken($font-link-primary, 30);
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ export class HashtagComponent implements OnInit {
|
|||
event.stopPropagation();
|
||||
|
||||
const hashtag = this.hashtagElement.tag;
|
||||
const newStream = new StreamElement(StreamTypeEnum.tag, `${hashtag}`, this.lastUsedAccount.id, hashtag, null, this.lastUsedAccount.instance);
|
||||
const newStream = new StreamElement(StreamTypeEnum.tag, `${hashtag}`, this.lastUsedAccount.id, hashtag, null, null, this.lastUsedAccount.instance);
|
||||
this.store.dispatch([new AddStream(newStream)]);
|
||||
|
||||
return false;
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
display: block;
|
||||
text-decoration: none;
|
||||
color: whitesmoke;
|
||||
|
||||
|
||||
&--image {
|
||||
width: 55px;
|
||||
height: 55px;
|
||||
|
@ -52,14 +52,14 @@
|
|||
|
||||
|
||||
}
|
||||
|
||||
&__photo {
|
||||
|
||||
}
|
||||
&__photo {}
|
||||
|
||||
&__video {
|
||||
// border-radius: 3px;
|
||||
|
||||
$height-content: 150px;
|
||||
|
||||
&--preview {
|
||||
position: relative;
|
||||
|
||||
|
@ -83,7 +83,7 @@
|
|||
font-size: 16px;
|
||||
margin: 12px 5px 0 5px;
|
||||
float: left;
|
||||
|
||||
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
@ -91,7 +91,7 @@
|
|||
|
||||
&--image {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
height: $height-content;
|
||||
// border: 1px solid salmon;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
@ -100,12 +100,10 @@
|
|||
&--content {
|
||||
//width: 300px;
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
height: $height-content;
|
||||
background-color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
&__rich {
|
||||
|
||||
|
||||
}
|
||||
&__rich {}
|
||||
}
|
|
@ -67,7 +67,7 @@
|
|||
(accountSelected)="accountSelected($event)" (hashtagSelected)="hashtagSelected($event)"
|
||||
(textSelected)="textSelected()"></app-databinded-text>
|
||||
|
||||
<app-card class="status__card" *ngIf="displayedStatus.card && !hasAttachments" [card]="displayedStatus.card"></app-card>
|
||||
<app-card class="status__card" *ngIf="!isContentWarned && displayedStatus.card && !hasAttachments" [card]="displayedStatus.card"></app-card>
|
||||
|
||||
<app-attachements *ngIf="!isContentWarned && hasAttachments" class="attachments"
|
||||
[attachments]="displayedStatus.media_attachments">
|
||||
|
|
|
@ -156,7 +156,7 @@ export class StreamOverlayComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
const selectedAccount = this.toolsService.getSelectedAccounts()[0];
|
||||
const hashTagElement = new StreamElement(StreamTypeEnum.tag, hashtag, selectedAccount.id, hashtag, null, selectedAccount.instance);
|
||||
const hashTagElement = new StreamElement(StreamTypeEnum.tag, hashtag, selectedAccount.id, hashtag, null, null, selectedAccount.instance);
|
||||
const newElement = new OverlayBrowsing(hashTagElement, null, null);
|
||||
this.loadElement(newElement);
|
||||
// this.canGoForward = false;
|
||||
|
|
|
@ -187,7 +187,7 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
|||
this.isProcessingInfiniteScroll = true;
|
||||
|
||||
const lastStatus = this.statuses[this.statuses.length - 1];
|
||||
this.mastodonService.getTimeline(this.account, this._streamElement.type, lastStatus.status.id, null, this.streamingService.nbStatusPerIteration, this._streamElement.tag, this._streamElement.list)
|
||||
this.mastodonService.getTimeline(this.account, this._streamElement.type, lastStatus.status.id, null, this.streamingService.nbStatusPerIteration, this._streamElement.tag, this._streamElement.listId)
|
||||
.then((status: Status[]) => {
|
||||
for (const s of status) {
|
||||
const wrapper = new StatusWrapper(s, this.account);
|
||||
|
@ -210,7 +210,7 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
|||
|
||||
|
||||
private retrieveToots(): void {
|
||||
this.mastodonService.getTimeline(this.account, this._streamElement.type, null, null, this.streamingService.nbStatusPerIteration, this._streamElement.tag, this._streamElement.list)
|
||||
this.mastodonService.getTimeline(this.account, this._streamElement.type, null, null, this.streamingService.nbStatusPerIteration, this._streamElement.tag, this._streamElement.listId)
|
||||
.then((results: Status[]) => {
|
||||
this.isLoading = false;
|
||||
for (const s of results) {
|
||||
|
|
|
@ -2,12 +2,12 @@ import { Injectable } from '@angular/core';
|
|||
import { HttpHeaders, HttpClient, HttpResponse } from '@angular/common/http';
|
||||
|
||||
import { ApiRoutes } from './models/api.settings';
|
||||
import { Account, Status, Results, Context, Relationship, Instance, Attachment, Notification } from "./models/mastodon.interfaces";
|
||||
import { Account, Status, Results, Context, Relationship, Instance, Attachment, Notification, List } from "./models/mastodon.interfaces";
|
||||
import { AccountInfo } from '../states/accounts.state';
|
||||
import { StreamTypeEnum } from '../states/streams.state';
|
||||
import { StreamTypeEnum, StreamElement } from '../states/streams.state';
|
||||
|
||||
@Injectable()
|
||||
export class MastodonService {
|
||||
export class MastodonService {
|
||||
private apiRoutes = new ApiRoutes();
|
||||
|
||||
constructor(private readonly httpClient: HttpClient) { }
|
||||
|
@ -22,13 +22,13 @@ export class MastodonService {
|
|||
return this.httpClient.get<Account>('https://' + account.instance + this.apiRoutes.getCurrentAccount, { headers: headers }).toPromise();
|
||||
}
|
||||
|
||||
getTimeline(account: AccountInfo, type: StreamTypeEnum, max_id: string = null, since_id: string = null, limit: number = 20, tag: string = null, list: string = null): Promise<Status[]> {
|
||||
const route = `https://${account.instance}${this.getTimelineRoute(type, max_id, since_id, limit, tag, list)}`;
|
||||
getTimeline(account: AccountInfo, type: StreamTypeEnum, max_id: string = null, since_id: string = null, limit: number = 20, tag: string = null, listId: string = null): Promise<Status[]> {
|
||||
const route = `https://${account.instance}${this.getTimelineRoute(type, max_id, since_id, limit, tag, listId)}`;
|
||||
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
|
||||
return this.httpClient.get<Status[]>(route, { headers: headers }).toPromise();
|
||||
}
|
||||
|
||||
private getTimelineRoute(type: StreamTypeEnum, max_id: string, since_id: string, limit: number, tag: string, list: string): string {
|
||||
private getTimelineRoute(type: StreamTypeEnum, max_id: string, since_id: string, limit: number, tag: string, listId: string): string {
|
||||
let route: string;
|
||||
switch (type) {
|
||||
case StreamTypeEnum.personnal:
|
||||
|
@ -47,7 +47,7 @@ export class MastodonService {
|
|||
route = this.apiRoutes.getTagTimeline.replace('{0}', tag);
|
||||
break;
|
||||
case StreamTypeEnum.list:
|
||||
route = this.apiRoutes.getListTimeline.replace('{0}', list);
|
||||
route = this.apiRoutes.getListTimeline.replace('{0}', listId);
|
||||
break;
|
||||
default:
|
||||
throw new Error('StreamTypeEnum not supported');
|
||||
|
@ -155,8 +155,8 @@ export class MastodonService {
|
|||
});
|
||||
}
|
||||
|
||||
searchAccount(account: AccountInfo, query: string, limit: number = 40, following: boolean = false): Promise<Account[]> {
|
||||
const route = `https://${account.instance}${this.apiRoutes.searchForAccounts}?q=${query}&limit=${limit}&following=${following}`;
|
||||
searchAccount(account: AccountInfo, query: string, limit: number = 40, following: boolean = false, resolve = true): Promise<Account[]> {
|
||||
const route = `https://${account.instance}${this.apiRoutes.searchForAccounts}?q=${query}&limit=${limit}&following=${following}&resolve=${resolve}`;
|
||||
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
|
||||
return this.httpClient.get<Account[]>(route, { headers: headers }).toPromise()
|
||||
}
|
||||
|
@ -256,6 +256,55 @@ export class MastodonService {
|
|||
result += `${paramName}[]=${x}`;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
getLists(account: AccountInfo): Promise<StreamElement[]> {
|
||||
let route = `https://${account.instance}${this.apiRoutes.getLists}`;
|
||||
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
|
||||
return this.httpClient.get<List[]>(route, { headers: headers }).toPromise()
|
||||
.then((lists: List[]) => {
|
||||
const streams: StreamElement[] = [];
|
||||
for (const list of lists) {
|
||||
const stream = new StreamElement(StreamTypeEnum.list, list.title, account.id, null, list.title, list.id, account.instance);
|
||||
streams.push(stream);
|
||||
}
|
||||
return streams;
|
||||
});
|
||||
}
|
||||
|
||||
createList(account: AccountInfo, title: string): Promise<StreamElement> {
|
||||
let route = `https://${account.instance}${this.apiRoutes.postList}?title=${title}`;
|
||||
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
|
||||
return this.httpClient.post<List>(route, null, { headers: headers }).toPromise()
|
||||
.then((list: List) => {
|
||||
return new StreamElement(StreamTypeEnum.list, list.title, account.id, null, list.title, list.id, account.instance);
|
||||
});
|
||||
}
|
||||
|
||||
deleteList(account: AccountInfo, listId: string): Promise<any> {
|
||||
let route = `https://${account.instance}${this.apiRoutes.deleteList}`.replace('{0}', listId);
|
||||
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
|
||||
return this.httpClient.delete(route, { headers: headers }).toPromise();
|
||||
}
|
||||
|
||||
getListAccounts(account: AccountInfo, listId: string): Promise<Account[]> {
|
||||
let route = `https://${account.instance}${this.apiRoutes.getAccountsInList}`.replace('{0}', listId);
|
||||
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
|
||||
return this.httpClient.get<Account[]>(route, { headers: headers }).toPromise();
|
||||
}
|
||||
|
||||
addAccountToList(account: AccountInfo, listId: string, accountId: number): Promise<any> {
|
||||
let route = `https://${account.instance}${this.apiRoutes.addAccountToList}`.replace('{0}', listId);
|
||||
route += `?account_ids[]=${accountId}`;
|
||||
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
|
||||
return this.httpClient.post(route, null, { headers: headers }).toPromise();
|
||||
}
|
||||
|
||||
removeAccountFromList(account: AccountInfo, listId: string, accountId: number): Promise<any> {
|
||||
let route = `https://${account.instance}${this.apiRoutes.addAccountToList}`.replace('{0}', listId);
|
||||
route += `?account_ids[]=${accountId}`;
|
||||
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
|
||||
return this.httpClient.delete(route, { headers: headers }).toPromise();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,4 +50,13 @@ export class ApiRoutes {
|
|||
getTagTimeline = '/api/v1/timelines/tag/{0}';
|
||||
getListTimeline = '/api/v1/timelines/list/{0}';
|
||||
getStreaming = '/api/v1/streaming?access_token={0}&stream={1}';
|
||||
getLists = '/api/v1/lists';
|
||||
getList = '/api/v1/lists/{0}';
|
||||
getListsWithAccount = '/api/v1/accounts/{0}/lists';
|
||||
getAccountsInList = '/api/v1/lists/{0}/accounts';
|
||||
postList = '/api/v1/lists';
|
||||
putList = '/api/v1/lists/{0}';
|
||||
deleteList = '/api/v1/lists/{0}';
|
||||
addAccountToList = '/api/v1/lists/{0}/accounts';
|
||||
removeAccountFromList = '/api/v1/lists/{0}/accounts';
|
||||
}
|
||||
|
|
|
@ -188,3 +188,7 @@ export interface Tag {
|
|||
url: string;
|
||||
}
|
||||
|
||||
export interface List {
|
||||
id: string;
|
||||
title: string;
|
||||
}
|
|
@ -47,7 +47,7 @@ export class StreamingWrapper {
|
|||
this.eventSource = new WebSocket(route);
|
||||
this.eventSource.onmessage = x => this.statusParsing(<WebSocketEvent>JSON.parse(x.data));
|
||||
this.eventSource.onerror = x => this.webSocketGotError(x);
|
||||
this.eventSource.onopen = x => console.log(x);
|
||||
this.eventSource.onopen = x => {};
|
||||
this.eventSource.onclose = x => this.webSocketClosed(route, x);
|
||||
}
|
||||
|
||||
|
@ -65,7 +65,7 @@ export class StreamingWrapper {
|
|||
}
|
||||
|
||||
private pullNewStatuses(domain) {
|
||||
this.mastodonService.getTimeline(this.account, this.stream.type, null, this.since_id, this.nbStatusPerIteration, this.stream.tag, this.stream.list)
|
||||
this.mastodonService.getTimeline(this.account, this.stream.type, null, this.since_id, this.nbStatusPerIteration, this.stream.tag, this.stream.listId)
|
||||
.then((status: Status[]) => {
|
||||
// status = status.sort((n1, n2) => { return (<number>n1.id) < (<number>n2.id); });
|
||||
status = status.sort((a, b) => a.id.localeCompare(b.id));
|
||||
|
@ -112,7 +112,7 @@ export class StreamingWrapper {
|
|||
let route = `wss://${account.instance}${this.apiRoutes.getStreaming}`.replace('{0}', account.token.access_token).replace('{1}', streamingRouteType);
|
||||
|
||||
if (stream.tag) route = `${route}&tag=${stream.tag}`;
|
||||
if (stream.list) route = `${route}&tag=${stream.list}`;
|
||||
if (stream.list) route = `${route}&list=${stream.listId}`;
|
||||
|
||||
return route;
|
||||
}
|
||||
|
|
|
@ -98,6 +98,7 @@ export class StreamElement {
|
|||
public accountId: string,
|
||||
public tag: string,
|
||||
public list: string,
|
||||
public listId: string,
|
||||
public instance: string) {
|
||||
this.id = `${type}-${name}-${accountId}`;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export const environment = {
|
||||
production: true
|
||||
production: true,
|
||||
VERSION: require('../../package.json').version
|
||||
};
|
||||
|
|
|
@ -4,5 +4,6 @@
|
|||
// The list of which env maps to which file can be found in `.angular-cli.json`.
|
||||
|
||||
export const environment = {
|
||||
production: false
|
||||
production: false,
|
||||
VERSION: require('../../package.json').version
|
||||
};
|
||||
|
|
|
@ -47,7 +47,7 @@ $button-color: darken(white, 30);
|
|||
$button-color-hover: white;
|
||||
$button-background-color: $color-primary;
|
||||
$button-background-color-hover: lighten($color-primary, 20);
|
||||
|
||||
$button-border-color: #303957;
|
||||
|
||||
|
||||
$column-background: #0f111a;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"outDir": "../out-tsc/app",
|
||||
"baseUrl": "./",
|
||||
"module": "es2015",
|
||||
"types": []
|
||||
"types": ["node"]
|
||||
},
|
||||
"exclude": [
|
||||
"test.ts",
|
||||
|
|
Loading…
Reference in New Issue