Merge pull request #247 from NicolasConstant/develop

0.24.0 PR
This commit is contained in:
Nicolas Constant 2020-04-11 00:16:53 -04:00 committed by GitHub
commit 2b2d467e74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 139 additions and 29 deletions

View File

@ -1,6 +1,6 @@
{
"name": "sengi",
"version": "0.23.1",
"version": "0.24.0",
"license": "AGPL-3.0-or-later",
"main": "main-electron.js",
"description": "A multi-account desktop client for Mastodon and Pleroma",

View File

@ -456,7 +456,11 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
let globalUniqueMentions = [];
for (let mention of uniqueMentions) {
if (!mention.includes('@')) {
mention += `@${providerInfo.instance}`;
if (providerInfo) {
mention += `@${providerInfo.instance}`;
} else {
mention += `@${status.url.replace('https://', '').split('/')[0]}`;
}
}
globalUniqueMentions.push(mention);
}

View File

@ -94,6 +94,15 @@
<span class="sub-section__title" *ngIf="contentWarningPolicyChanged"><br/>this settings needs a <a href (click)="reload()">reload</a> to be effective.</span>
</div>
<h4 class="panel__subtitle">Other</h4>
<div class="sub-section">
<input class="sub-section__checkbox" [(ngModel)]="disableRemoteStatusFetchingEnabled"
(change)="onDisableRemoteStatusFetchingChanged()" type="checkbox" name="disableRemoteFetching" value="disableRemoteFetching"
id="disableRemoteFetching">
<label class="noselect sub-section__label" for="disableRemoteFetching">disable remote status fetching</label>
<br>
</div>
<h4 class="panel__subtitle">About</h4>
<p class="version">
Sengi version: {{version}}<br />

View File

@ -21,6 +21,7 @@ export class SettingsComponent implements OnInit {
notificationForm: FormGroup;
disableAutofocusEnabled: boolean;
disableRemoteStatusFetchingEnabled: boolean;
disableAvatarNotificationsEnabled: boolean;
disableSoundsEnabled: boolean;
version: string;
@ -78,6 +79,7 @@ export class SettingsComponent implements OnInit {
this.disableAutofocusEnabled = settings.disableAutofocus;
this.disableAvatarNotificationsEnabled = settings.disableAvatarNotifications;
this.disableSoundsEnabled = settings.disableSounds;
this.disableRemoteStatusFetchingEnabled = settings.disableRemoteStatusFetching;
if (!settings.columnSwitchingWinAlt) {
this.columnShortcutEnabled = ColumnShortcut.Ctrl;
@ -172,6 +174,12 @@ export class SettingsComponent implements OnInit {
this.toolsService.saveSettings(settings);
}
onDisableRemoteStatusFetchingChanged() {
let settings = this.toolsService.getSettings();
settings.disableRemoteStatusFetching = this.disableRemoteStatusFetchingEnabled;
this.toolsService.saveSettings(settings);
}
onDisableAvatarNotificationsChanged() {
let settings = this.toolsService.getSettings();
settings.disableAvatarNotifications = this.disableAvatarNotificationsEnabled;

View File

@ -76,14 +76,18 @@ export class ActionBarComponent implements OnInit, OnDestroy {
ngOnInit() {
this.displayedStatus = this.statusWrapper.status;
const account = this.statusWrapper.provider;
let accountId = 'remote';
if (account) {
accountId = account.id;
}
if (this.displayedStatus.reblog) {
this.displayedStatus = this.displayedStatus.reblog;
}
this.favoriteStatePerAccountId[account.id] = this.displayedStatus.favourited;
this.bootedStatePerAccountId[account.id] = this.displayedStatus.reblogged;
this.bookmarkStatePerAccountId[account.id] = this.displayedStatus.bookmarked;
this.favoriteStatePerAccountId[accountId] = this.displayedStatus.favourited;
this.bootedStatePerAccountId[accountId] = this.displayedStatus.reblogged;
this.bookmarkStatePerAccountId[accountId] = this.displayedStatus.bookmarked;
this.analyseMemoryStatus();
@ -134,8 +138,13 @@ export class ActionBarComponent implements OnInit, OnDestroy {
private checkStatus(accounts: AccountInfo[]): void {
const status = this.statusWrapper.status;
const provider = this.statusWrapper.provider;
this.selectedAccounts = accounts.filter(x => x.isSelected);
this.isProviderSelected = this.selectedAccounts.filter(x => x.id === provider.id).length > 0;
this.selectedAccounts = accounts.filter(x => x.isSelected);
if (!this.statusWrapper.isRemote) {
this.isProviderSelected = this.selectedAccounts.filter(x => x.id === provider.id).length > 0;
} else {
this.isProviderSelected = false;
}
if (status.visibility === 'direct' || status.visibility === 'private') {
this.isBoostLocked = true;

View File

@ -84,6 +84,9 @@
<div class="status__labels--label status__labels--old" title="this status is old" *ngIf="isOld && !statusWrapper.status.pinned">
old
</div>
<div class="status__labels--label status__labels--remote" title="this status isn't federated with this instance" *ngIf="isRemote">
remote
</div>
</div>

View File

@ -99,6 +99,11 @@
&--old {
background-color: rgb(150, 0, 0);
}
&--remote {
background-color: rgb(161, 64, 140);
background-color: rgb(33, 69, 136);
background-color: rgb(38, 77, 148);
}
}
&__name {
display: inline-block;

View File

@ -39,6 +39,7 @@ export class StatusComponent implements OnInit {
contentWarningText: string;
isDirectMessage: boolean;
isSelected: boolean;
isRemote: boolean;
hideStatus: boolean = false;
@ -58,9 +59,9 @@ export class StatusComponent implements OnInit {
@Input('statusWrapper')
set statusWrapper(value: StatusWrapper) {
this._statusWrapper = value;
// console.warn(value.status);
this.status = value.status;
this.isSelected = value.isSelected;
this.isRemote = value.isRemote;
if (this.status.reblog) {
this.reblog = true;
@ -72,6 +73,7 @@ export class StatusComponent implements OnInit {
this.isDirectMessage = this.displayedStatus.visibility === 'direct';
let cwPolicy = this.toolsService.checkContentWarning(this.displayedStatus);
this.displayedStatusWrapper = new StatusWrapper(cwPolicy.status, value.provider, cwPolicy.applyCw, cwPolicy.hide);
this.displayedStatusWrapper.isRemote = value.isRemote;
this.checkLabels(this.displayedStatus);
this.setContentWarning(this.displayedStatusWrapper);

View File

@ -1,5 +1,5 @@
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ViewChildren, QueryList, ViewChild, ElementRef } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { HttpErrorResponse, HttpClient, HttpHeaders } from '@angular/common/http';
import { Subscription } from 'rxjs';
import { MastodonWrapperService } from '../../../services/mastodon-wrapper.service';
@ -22,6 +22,7 @@ export class ThreadComponent implements OnInit, OnDestroy {
isLoading = true;
isThread = true;
hasContentWarnings = false;
private remoteStatusFetchingDisabled = false;
bufferStream: Status[] = []; //html compatibility only
@ -51,11 +52,15 @@ export class ThreadComponent implements OnInit, OnDestroy {
private goToTopSubscription: Subscription;
constructor(
private readonly httpClient: HttpClient,
private readonly notificationService: NotificationService,
private readonly toolsService: ToolsService,
private readonly mastodonService: MastodonWrapperService) { }
ngOnInit() {
let settings = this.toolsService.getSettings();
this.remoteStatusFetchingDisabled = settings.disableRemoteStatusFetching;
if (this.refreshEventEmitter) {
this.refreshSubscription = this.refreshEventEmitter.subscribe(() => {
this.refresh();
@ -130,7 +135,7 @@ export class ThreadComponent implements OnInit, OnDestroy {
if (status.visibility === 'public' || status.visibility === 'unlisted') {
var statusPromise: Promise<Status> = Promise.resolve(status);
if (sourceAccount.id !== currentAccount.id) {
if (!sourceAccount || sourceAccount.id !== currentAccount.id) {
statusPromise = this.toolsService.getInstanceInfo(currentAccount)
.then(instance => {
let version: 'v1' | 'v2' = 'v1';
@ -148,7 +153,7 @@ export class ThreadComponent implements OnInit, OnDestroy {
this.retrieveThread(currentAccount, statusPromise);
} else if (sourceAccount.id === currentAccount.id) {
} else if (sourceAccount && sourceAccount.id === currentAccount.id) {
var statusPromise = Promise.resolve(status);
this.retrieveThread(currentAccount, statusPromise);
} else {
@ -161,33 +166,52 @@ export class ThreadComponent implements OnInit, OnDestroy {
pipeline
.then((status: Status) => {
return this.mastodonService.getStatusContext(currentAccount, status.id)
.then((context: Context) => {
.then((context: Context) => {
let contextStatuses = [...context.ancestors, status, ...context.descendants]
const position = context.ancestors.length;
for (let i = 0; i < contextStatuses.length; i++) {
let localStatuses = [];
for (let i = 0; i < contextStatuses.length; i++) {
let s = contextStatuses[i];
let cwPolicy = this.toolsService.checkContentWarning(s);
const wrapper = new StatusWrapper(cwPolicy.status, currentAccount, cwPolicy.applyCw, cwPolicy.hide);
if(i == position) wrapper.isSelected = true;
const wrapper = new StatusWrapper(cwPolicy.status, currentAccount, cwPolicy.applyCw, cwPolicy.hide);
this.statuses.push(wrapper);
if (i == position) wrapper.isSelected = true;
// this.statuses.push(wrapper);
localStatuses.push(wrapper);
}
return localStatuses;
})
.then(async (localStatuses: StatusWrapper[]) => {
let remoteStatuses = await this.retrieveRemoteThread(status);
let unknownStatuses = remoteStatuses.filter(x => !localStatuses.find(y => y.status.uri == x.uri));
for(let s of unknownStatuses){
let cwPolicy = this.toolsService.checkContentWarning(s);
let wrapper = new StatusWrapper(s, null, cwPolicy.applyCw, cwPolicy.hide);
wrapper.isRemote = true;
localStatuses.push(wrapper);
}
localStatuses.sort((a,b) => {
if(a.status.created_at > b.status.created_at) return 1;
if(a.status.created_at < b.status.created_at) return -1;
return 0;
});
this.statuses = localStatuses;
this.hasContentWarnings = this.statuses.filter(x => x.applyCw).length > 1;
return position;
let newPosition = this.statuses.findIndex(x => x.isSelected);
return newPosition;
});
})
.then((position: number) => {
setTimeout(() => {
const el = this.statusChildren.toArray()[position];
//el.elem.nativeElement.scrollIntoViewIfNeeded({ behavior: 'auto', block: 'start', inline: 'nearest' });
scrollIntoView(el.elem.nativeElement, { behavior: 'smooth', block: 'nearest'});
//el.elem.nativeElement.scrollIntoViewIfNeeded({ behavior: 'auto', block: 'start', inline: 'nearest' });
scrollIntoView(el.elem.nativeElement, { behavior: 'smooth', block: 'nearest' });
}, 250);
})
.catch((err: HttpErrorResponse) => {
@ -198,6 +222,35 @@ export class ThreadComponent implements OnInit, OnDestroy {
});
}
private async retrieveRemoteThread(status: Status): Promise<Status[]> {
if(this.remoteStatusFetchingDisabled) return [];
try {
let url = status.url;
let splitUrl = url.replace('https://', '').split('/');
let id = splitUrl[splitUrl.length - 1];
let instance = splitUrl[0];
//Pleroma
if(url.includes('/objects/')){
var webpage = await this.httpClient.get(url, { responseType: 'text' }).toPromise();
id = webpage.split(`<meta content="https://${instance}/notice/`)[1].split('" property="og:url"')[0];
}
let context = await this.mastodonService.getRemoteStatusContext(instance, id);
let remoteStatuses = [...context.ancestors, ...context.descendants];
remoteStatuses.forEach(s => {
if(!s.account.acct.includes('@')){
s.account.acct += `@${instance}`;
}
});
return remoteStatuses;
} catch (err) {
return [];
};
}
refresh(): any {
this.isLoading = true;
this.displayError = null;
@ -225,9 +278,9 @@ export class ThreadComponent implements OnInit, OnDestroy {
const statuses = this.statusChildren.toArray();
statuses.forEach(x => {
x.removeContentWarning();
if(x.isSelected){
if (x.isSelected) {
setTimeout(() => {
scrollIntoView(x.elem.nativeElement, { behavior: 'auto', block: 'nearest'});
scrollIntoView(x.elem.nativeElement, { behavior: 'auto', block: 'nearest' });
}, 0);
}
});

View File

@ -21,4 +21,5 @@ export class StatusWrapper {
) { }
public isSelected: boolean;
public isRemote: boolean;
}

View File

@ -141,6 +141,10 @@ export class MastodonWrapperService {
});
}
getRemoteStatusContext(instance: string, targetStatusId: string): Promise<Context> {
return this.mastodonService.getRemoteStatusContext(instance, targetStatusId);
}
getFavorites(account: AccountInfo, maxId: string = null): Promise<FavoriteResult> { //, minId: string = null
return this.refreshAccountIfNeeded(account)
.then((refreshedAccount: AccountInfo) => {

View File

@ -167,6 +167,13 @@ export class MastodonService {
return this.httpClient.get<Context>(route, { headers: headers }).toPromise();
}
getRemoteStatusContext(instance: string, targetStatusId: string): Promise<Context> {
const params = this.apiRoutes.getStatusContext.replace('{0}', targetStatusId);
const route = `https://${instance}${params}`;
return this.httpClient.get<Context>(route).toPromise();
}
getFavorites(account: AccountInfo, maxId: string = null): Promise<FavoriteResult> { //, minId: string = null
let route = `https://${account.instance}${this.apiRoutes.getFavourites}`; //?limit=${limit}

View File

@ -205,7 +205,10 @@ export class ToolsService {
}
getStatusUsableByAccount(account: AccountInfo, originalStatus: StatusWrapper): Promise<Status> {
const isProvider = originalStatus.provider.id === account.id;
let isProvider = false;
if(!originalStatus.isRemote){
isProvider = originalStatus.provider.id === account.id;
}
let statusPromise: Promise<Status> = Promise.resolve(originalStatus.status);

View File

@ -51,14 +51,15 @@ export class GlobalSettings {
disableAutofocus = false;
disableAvatarNotifications = false;
disableSounds = false;
disableRemoteStatusFetching = false;
notificationSoundFileId: string = '0';
contentWarningPolicy: ContentWarningPolicy = new ContentWarningPolicy();
columnSwitchingWinAlt = false;
accountSettings: AccountSettings[] = [];
accountSettings: AccountSettings[] = [];
}
export interface SettingsStateModel {
@ -143,6 +144,7 @@ export class SettingsState {
newSettings.disableSounds = oldSettings.disableSounds;
newSettings.notificationSoundFileId = oldSettings.notificationSoundFileId;
newSettings.columnSwitchingWinAlt = oldSettings.columnSwitchingWinAlt;
newSettings.disableRemoteStatusFetching = oldSettings.disableRemoteStatusFetching;
return newSettings;
}