mirror of
https://github.com/NicolasConstant/sengi
synced 2025-02-09 00:18:44 +01:00
initial implementation of tag following
This commit is contained in:
parent
78f0f3ab5f
commit
39187c82fb
@ -2,15 +2,16 @@
|
|||||||
<div class="hashtag-header">
|
<div class="hashtag-header">
|
||||||
<a href (click)="goToTop()" class="hashtag-header__gototop" title="go to top">
|
<a href (click)="goToTop()" class="hashtag-header__gototop" title="go to top">
|
||||||
<h3 class="hashtag-header__title">#{{hashtagElement.tag}}</h3>
|
<h3 class="hashtag-header__title">#{{hashtagElement.tag}}</h3>
|
||||||
|
<button *ngIf="isHashtagFollowingAvailable && !isFollowingHashtag" class="btn-custom-secondary hashtag-header__follow-button" (click)="followThisHashtag($event)" title="follow hashtag" [disabled]="followingLoading">follow</button>
|
||||||
|
<button *ngIf="isHashtagFollowingAvailable && isFollowingHashtag" class="btn-custom-secondary hashtag-header__follow-button" (click)="unfollowThisHashtag($event)" title="unfollow hashtag" [disabled]="unfollowingLoading">unfollow</button>
|
||||||
<button class="btn-custom-secondary hashtag-header__add-column" (click)="addColumn($event)" title="add column to board" [hidden]="columnAdded">add column</button>
|
<button class="btn-custom-secondary hashtag-header__add-column" (click)="addColumn($event)" title="add column to board" [hidden]="columnAdded">add column</button>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<app-stream-statuses #appStreamStatuses class="hashtag-stream" *ngIf="hashtagElement"
|
<app-stream-statuses #appStreamStatuses class="hashtag-stream" *ngIf="hashtagElement"
|
||||||
[streamElement]="hashtagElement"
|
[streamElement]="hashtagElement"
|
||||||
[goToTop]="goToTopSubject.asObservable()"
|
[goToTop]="goToTopSubject.asObservable()"
|
||||||
[userLocked]="false"
|
[userLocked]="false"
|
||||||
(browseAccountEvent)="browseAccount($event)"
|
(browseAccountEvent)="browseAccount($event)"
|
||||||
(browseHashtagEvent)="browseHashtag($event)"
|
(browseHashtagEvent)="browseHashtag($event)"
|
||||||
(browseThreadEvent)="browseThread($event)"></app-stream-statuses>
|
(browseThreadEvent)="browseThread($event)"></app-stream-statuses>
|
||||||
</div>
|
</div>
|
@ -40,6 +40,14 @@ $inner-column-size: 320px;
|
|||||||
border: 1px solid black;
|
border: 1px solid black;
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
&__follow-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 7px;
|
||||||
|
right: 100px;
|
||||||
|
padding: 0 10px 0 10px;
|
||||||
|
border: 1px solid black;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.hashtag-stream {
|
.hashtag-stream {
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { Component, OnInit, Output, EventEmitter, Input, ViewChild, OnDestroy } from '@angular/core';
|
import { Component, OnInit, Output, EventEmitter, Input, ViewChild, OnDestroy } from '@angular/core';
|
||||||
import { Subject, Subscription } from 'rxjs';
|
import { Subject, Subscription, Observable } from 'rxjs';
|
||||||
import { Store } from '@ngxs/store';
|
import { Store } from '@ngxs/store';
|
||||||
|
|
||||||
import { StreamElement, StreamTypeEnum, AddStream } from '../../../states/streams.state';
|
import { StreamElement, StreamTypeEnum, AddStream } from '../../../states/streams.state';
|
||||||
import { OpenThreadEvent, ToolsService } from '../../../services/tools.service';
|
import { OpenThreadEvent, ToolsService } from '../../../services/tools.service';
|
||||||
import { StreamStatusesComponent } from '../stream-statuses/stream-statuses.component';
|
import { StreamStatusesComponent } from '../stream-statuses/stream-statuses.component';
|
||||||
import { AccountInfo } from '../../../states/accounts.state';
|
import { AccountInfo } from '../../../states/accounts.state';
|
||||||
|
import { MastodonWrapperService } from '../../../services/mastodon-wrapper.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-hashtag',
|
selector: 'app-hashtag',
|
||||||
@ -21,7 +22,7 @@ export class HashtagComponent implements OnInit, OnDestroy {
|
|||||||
@Output() browseThreadEvent = new EventEmitter<OpenThreadEvent>();
|
@Output() browseThreadEvent = new EventEmitter<OpenThreadEvent>();
|
||||||
|
|
||||||
private _hashtagElement: StreamElement;
|
private _hashtagElement: StreamElement;
|
||||||
@Input()
|
@Input()
|
||||||
set hashtagElement(hashtagElement: StreamElement){
|
set hashtagElement(hashtagElement: StreamElement){
|
||||||
this._hashtagElement = hashtagElement;
|
this._hashtagElement = hashtagElement;
|
||||||
this.lastUsedAccount = this.toolsService.getSelectedAccounts()[0];
|
this.lastUsedAccount = this.toolsService.getSelectedAccounts()[0];
|
||||||
@ -29,7 +30,7 @@ export class HashtagComponent implements OnInit, OnDestroy {
|
|||||||
get hashtagElement(): StreamElement{
|
get hashtagElement(): StreamElement{
|
||||||
return this._hashtagElement;
|
return this._hashtagElement;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ViewChild('appStreamStatuses') appStreamStatuses: StreamStatusesComponent;
|
@ViewChild('appStreamStatuses') appStreamStatuses: StreamStatusesComponent;
|
||||||
|
|
||||||
@ -38,12 +39,25 @@ export class HashtagComponent implements OnInit, OnDestroy {
|
|||||||
private lastUsedAccount: AccountInfo;
|
private lastUsedAccount: AccountInfo;
|
||||||
private refreshSubscription: Subscription;
|
private refreshSubscription: Subscription;
|
||||||
private goToTopSubscription: Subscription;
|
private goToTopSubscription: Subscription;
|
||||||
|
isHashtagFollowingAvailable: boolean;
|
||||||
|
isFollowingHashtag: boolean;
|
||||||
|
|
||||||
|
private accounts$: Observable<AccountInfo[]>;
|
||||||
|
|
||||||
|
private accountSub: Subscription;
|
||||||
|
|
||||||
|
followingLoading: boolean;
|
||||||
|
unfollowingLoading: boolean;
|
||||||
|
|
||||||
columnAdded: boolean;
|
columnAdded: boolean;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly store: Store,
|
private readonly store: Store,
|
||||||
private readonly toolsService: ToolsService) { }
|
private readonly toolsService: ToolsService,
|
||||||
|
private readonly mastodonService: MastodonWrapperService) {
|
||||||
|
this.accounts$ = this.store.select(state => state.registeredaccounts.accounts);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
if(this.refreshEventEmitter) {
|
if(this.refreshEventEmitter) {
|
||||||
@ -57,11 +71,22 @@ export class HashtagComponent implements OnInit, OnDestroy {
|
|||||||
this.goToTop();
|
this.goToTop();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
this.lastUsedAccount = this.toolsService.getSelectedAccounts()[0];
|
||||||
|
this.updateHashtagFollowStatus(this.lastUsedAccount);
|
||||||
|
|
||||||
|
this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => {
|
||||||
|
const selectedAccounts = accounts.filter(x => x.isSelected);
|
||||||
|
if (selectedAccounts.length > 0) {
|
||||||
|
this.lastUsedAccount = selectedAccounts[0];
|
||||||
|
this.updateHashtagFollowStatus(this.lastUsedAccount);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
if(this.refreshSubscription) this.refreshSubscription.unsubscribe();
|
if(this.refreshSubscription) this.refreshSubscription.unsubscribe();
|
||||||
if (this.goToTopSubscription) this.goToTopSubscription.unsubscribe();
|
if (this.goToTopSubscription) this.goToTopSubscription.unsubscribe();
|
||||||
|
if (this.accountSub) this.accountSub.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
goToTop(): boolean {
|
goToTop(): boolean {
|
||||||
@ -83,6 +108,10 @@ export class HashtagComponent implements OnInit, OnDestroy {
|
|||||||
|
|
||||||
refresh(): any {
|
refresh(): any {
|
||||||
this.lastUsedAccount = this.toolsService.getSelectedAccounts()[0];
|
this.lastUsedAccount = this.toolsService.getSelectedAccounts()[0];
|
||||||
|
this.updateHashtagFollowStatus(this.lastUsedAccount);
|
||||||
|
if (this.isHashtagFollowingAvailable) {
|
||||||
|
this.checkIfFollowingHashtag(this.lastUsedAccount);
|
||||||
|
}
|
||||||
this.appStreamStatuses.refresh();
|
this.appStreamStatuses.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,4 +128,41 @@ export class HashtagComponent implements OnInit, OnDestroy {
|
|||||||
browseThread(openThreadEvent: OpenThreadEvent): void {
|
browseThread(openThreadEvent: OpenThreadEvent): void {
|
||||||
this.browseThreadEvent.next(openThreadEvent);
|
this.browseThreadEvent.next(openThreadEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private updateHashtagFollowStatus(account: AccountInfo): void {
|
||||||
|
this.toolsService.getInstanceInfo(account).then(instanceInfo => {
|
||||||
|
if (instanceInfo.major >= 4) {
|
||||||
|
this.isHashtagFollowingAvailable = true;
|
||||||
|
this.checkIfFollowingHashtag(account);
|
||||||
|
} else {
|
||||||
|
this.isHashtagFollowingAvailable = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkIfFollowingHashtag(account: AccountInfo): void {
|
||||||
|
this.mastodonService.getHashtag(account, this.hashtagElement.tag).then(tag => {
|
||||||
|
this.isFollowingHashtag = tag.following;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
followThisHashtag(event): boolean {
|
||||||
|
this.followingLoading = true;
|
||||||
|
event.stopPropagation();
|
||||||
|
this.mastodonService.followHashtag(this.lastUsedAccount, this.hashtagElement.tag).then(tag => {
|
||||||
|
this.isFollowingHashtag = tag.following;
|
||||||
|
this.followingLoading = false;
|
||||||
|
});
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
unfollowThisHashtag(event): boolean {
|
||||||
|
this.unfollowingLoading = true;
|
||||||
|
event.stopPropagation();
|
||||||
|
this.mastodonService.unfollowHashtag(this.lastUsedAccount, this.hashtagElement.tag).then(tag => {
|
||||||
|
this.isFollowingHashtag = tag.following;
|
||||||
|
this.unfollowingLoading = false;
|
||||||
|
});
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { Store } from '@ngxs/store';
|
import { Store } from '@ngxs/store';
|
||||||
|
|
||||||
import { Account, Status, Results, Context, Relationship, Instance, Attachment, Notification, List, Poll, Emoji, Conversation, ScheduledStatus, TokenData } from "./models/mastodon.interfaces";
|
import { Account, Status, Results, Context, Relationship, Instance, Attachment, Notification, List, Poll, Emoji, Conversation, ScheduledStatus, TokenData, Tag } from "./models/mastodon.interfaces";
|
||||||
import { AccountInfo, UpdateAccount } from '../states/accounts.state';
|
import { AccountInfo, UpdateAccount } from '../states/accounts.state';
|
||||||
import { StreamTypeEnum, StreamElement } from '../states/streams.state';
|
import { StreamTypeEnum, StreamElement } from '../states/streams.state';
|
||||||
import { FavoriteResult, VisibilityEnum, PollParameters, MastodonService, BookmarkResult, FollowingResult } from './mastodon.service';
|
import { FavoriteResult, VisibilityEnum, PollParameters, MastodonService, BookmarkResult, FollowingResult } from './mastodon.service';
|
||||||
@ -12,7 +12,7 @@ import { SettingsService } from './settings.service';
|
|||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
export class MastodonWrapperService {
|
export class MastodonWrapperService {
|
||||||
private refreshingToken: { [id: string]: Promise<AccountInfo> } = {};
|
private refreshingToken: { [id: string]: Promise<AccountInfo> } = {};
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -29,7 +29,7 @@ export class MastodonWrapperService {
|
|||||||
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 {
|
||||||
@ -39,7 +39,7 @@ export class MastodonWrapperService {
|
|||||||
} else {
|
} else {
|
||||||
const nowEpoch = Date.now() / 1000 | 0;
|
const nowEpoch = Date.now() / 1000 | 0;
|
||||||
|
|
||||||
//Pleroma workaround
|
//Pleroma workaround
|
||||||
let expire_in = storedAccountInfo.token.expires_in;
|
let expire_in = storedAccountInfo.token.expires_in;
|
||||||
if (expire_in < 3600) {
|
if (expire_in < 3600) {
|
||||||
expire_in = 3600;
|
expire_in = 3600;
|
||||||
@ -74,7 +74,7 @@ export class MastodonWrapperService {
|
|||||||
p.then(() => {
|
p.then(() => {
|
||||||
this.refreshingToken[accountInfo.id] = null;
|
this.refreshingToken[accountInfo.id] = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
this.refreshingToken[accountInfo.id] = p;
|
this.refreshingToken[accountInfo.id] = p;
|
||||||
return p;
|
return p;
|
||||||
} else {
|
} else {
|
||||||
@ -267,6 +267,27 @@ export class MastodonWrapperService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
followHashtag(currentlyUsedAccount: AccountInfo, hashtag: string): Promise<Tag> {
|
||||||
|
return this.refreshAccountIfNeeded(currentlyUsedAccount)
|
||||||
|
.then((refreshedAccount: AccountInfo) => {
|
||||||
|
return this.mastodonService.followHashtag(refreshedAccount, hashtag);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unfollowHashtag(currentlyUsedAccount: AccountInfo, hashtag: string): Promise<Tag> {
|
||||||
|
return this.refreshAccountIfNeeded(currentlyUsedAccount)
|
||||||
|
.then((refreshedAccount: AccountInfo) => {
|
||||||
|
return this.mastodonService.unfollowHashtag(refreshedAccount, hashtag);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getHashtag(currentlyUsedAccount: AccountInfo, hashtag: string): Promise<Tag> {
|
||||||
|
return this.refreshAccountIfNeeded(currentlyUsedAccount)
|
||||||
|
.then((refreshedAccount: AccountInfo) => {
|
||||||
|
return this.mastodonService.getHashtag(refreshedAccount, hashtag);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
uploadMediaAttachment(account: AccountInfo, file: File, description: string): Promise<Attachment> {
|
uploadMediaAttachment(account: AccountInfo, file: File, description: string): Promise<Attachment> {
|
||||||
return this.refreshAccountIfNeeded(account)
|
return this.refreshAccountIfNeeded(account)
|
||||||
.then((refreshedAccount: AccountInfo) => {
|
.then((refreshedAccount: AccountInfo) => {
|
||||||
|
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
|||||||
import { HttpHeaders, HttpClient, HttpResponse } from '@angular/common/http';
|
import { HttpHeaders, HttpClient, HttpResponse } from '@angular/common/http';
|
||||||
|
|
||||||
import { ApiRoutes } from './models/api.settings';
|
import { ApiRoutes } from './models/api.settings';
|
||||||
import { Account, Status, Results, Context, Relationship, Instance, Attachment, Notification, List, Poll, Emoji, Conversation, ScheduledStatus } from "./models/mastodon.interfaces";
|
import { Account, Status, Results, Context, Relationship, Instance, Attachment, Notification, List, Poll, Emoji, Conversation, ScheduledStatus, Tag } from "./models/mastodon.interfaces";
|
||||||
import { AccountInfo } from '../states/accounts.state';
|
import { AccountInfo } from '../states/accounts.state';
|
||||||
import { StreamTypeEnum, StreamElement } from '../states/streams.state';
|
import { StreamTypeEnum, StreamElement } from '../states/streams.state';
|
||||||
|
|
||||||
@ -289,6 +289,24 @@ export class MastodonService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
followHashtag(currentlyUsedAccount: AccountInfo, hashtag: string): Promise<Tag> {
|
||||||
|
const route = `https://${currentlyUsedAccount.instance}${this.apiRoutes.followHashtag}`.replace('{0}', hashtag);
|
||||||
|
const headers = new HttpHeaders({ 'Authorization': `Bearer ${currentlyUsedAccount.token.access_token}` });
|
||||||
|
return this.httpClient.post<Tag>(route, null, { headers: headers }).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
unfollowHashtag(currentlyUsedAccount: AccountInfo, hashtag: string): Promise<Tag> {
|
||||||
|
const route = `https://${currentlyUsedAccount.instance}${this.apiRoutes.unfollowHashtag}`.replace('{0}', hashtag);
|
||||||
|
const headers = new HttpHeaders({ 'Authorization': `Bearer ${currentlyUsedAccount.token.access_token}` });
|
||||||
|
return this.httpClient.post<Tag>(route, null, { headers: headers }).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
|
getHashtag(currentlyUsedAccount: AccountInfo, hashtag: string): Promise<Tag> {
|
||||||
|
const route = `https://${currentlyUsedAccount.instance}${this.apiRoutes.getHashtag}`.replace('{0}', hashtag);
|
||||||
|
const headers = new HttpHeaders({ 'Authorization': `Bearer ${currentlyUsedAccount.token.access_token}` });
|
||||||
|
return this.httpClient.get<Tag>(route, { headers: headers }).toPromise();
|
||||||
|
}
|
||||||
|
|
||||||
uploadMediaAttachment(account: AccountInfo, file: File, description: string): Promise<Attachment> {
|
uploadMediaAttachment(account: AccountInfo, file: File, description: string): Promise<Attachment> {
|
||||||
let input = new FormData();
|
let input = new FormData();
|
||||||
input.append('file', file);
|
input.append('file', file);
|
||||||
@ -382,10 +400,10 @@ export class MastodonService {
|
|||||||
addAccountToList(account: AccountInfo, listId: string, accountId: number): Promise<any> {
|
addAccountToList(account: AccountInfo, listId: string, accountId: number): Promise<any> {
|
||||||
let route = `https://${account.instance}${this.apiRoutes.addAccountToList}`.replace('{0}', listId);
|
let route = `https://${account.instance}${this.apiRoutes.addAccountToList}`.replace('{0}', listId);
|
||||||
route += `?account_ids[]=${accountId}`;
|
route += `?account_ids[]=${accountId}`;
|
||||||
|
|
||||||
let data = new ListAccountData();
|
let data = new ListAccountData();
|
||||||
data.account_ids.push(accountId.toString());
|
data.account_ids.push(accountId.toString());
|
||||||
|
|
||||||
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
|
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
|
||||||
return this.httpClient.post(route, data, { headers: headers }).toPromise();
|
return this.httpClient.post(route, data, { headers: headers }).toPromise();
|
||||||
}
|
}
|
||||||
|
@ -75,4 +75,7 @@ export class ApiRoutes {
|
|||||||
getBookmarks = '/api/v1/bookmarks';
|
getBookmarks = '/api/v1/bookmarks';
|
||||||
getFollowers = '/api/v1/accounts/{0}/followers';
|
getFollowers = '/api/v1/accounts/{0}/followers';
|
||||||
getFollowing = '/api/v1/accounts/{0}/following';
|
getFollowing = '/api/v1/accounts/{0}/following';
|
||||||
|
followHashtag = '/api/v1/tags/{0}/follow';
|
||||||
|
unfollowHashtag = '/api/v1/tags/{0}/unfollow';
|
||||||
|
getHashtag = '/api/v1/tags/{0}';
|
||||||
}
|
}
|
||||||
|
@ -141,7 +141,7 @@ export interface Relationship {
|
|||||||
id: number;
|
id: number;
|
||||||
following: boolean;
|
following: boolean;
|
||||||
followed_by: boolean;
|
followed_by: boolean;
|
||||||
blocked_by: boolean;
|
blocked_by: boolean;
|
||||||
blocking: boolean;
|
blocking: boolean;
|
||||||
domain_blocking: boolean;
|
domain_blocking: boolean;
|
||||||
muting: boolean;
|
muting: boolean;
|
||||||
@ -190,7 +190,7 @@ export interface Status {
|
|||||||
muted: boolean;
|
muted: boolean;
|
||||||
bookmarked: boolean;
|
bookmarked: boolean;
|
||||||
card: Card;
|
card: Card;
|
||||||
poll: Poll;
|
poll: Poll;
|
||||||
|
|
||||||
pleroma: PleromaStatusInfo;
|
pleroma: PleromaStatusInfo;
|
||||||
}
|
}
|
||||||
@ -249,4 +249,17 @@ export interface StatusParams {
|
|||||||
visibility: 'public' | 'unlisted' | 'private' | 'direct';
|
visibility: 'public' | 'unlisted' | 'private' | 'direct';
|
||||||
scheduled_at: string;
|
scheduled_at: string;
|
||||||
application_id: string;
|
application_id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TagHistory {
|
||||||
|
day: string;
|
||||||
|
uses: number;
|
||||||
|
accounts: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Tag {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
history: TagHistory[];
|
||||||
|
following: boolean;
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user