commit
c3cd6fe79e
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "sengi",
|
||||
"version": "1.2.0",
|
||||
"version": "1.4.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "sengi",
|
||||
"version": "1.4.0",
|
||||
"version": "1.5.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "main-electron.js",
|
||||
"description": "A multi-account desktop client for Mastodon and Pleroma",
|
||||
|
@ -20,8 +20,8 @@
|
|||
"test": "ng test",
|
||||
"test-nowatch": "ng test --watch=false",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e",
|
||||
"dist": "npm run build"
|
||||
"e2e": "ng e2e",
|
||||
"dist": "npm run build"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
|
|
@ -5,8 +5,7 @@ import { HttpModule } from "@angular/http";
|
|||
import { HttpClientModule } from '@angular/common/http';
|
||||
import { NgModule, APP_INITIALIZER, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
|
||||
// import { NgxElectronModule } from "ngx-electron";
|
||||
// import { NgxElectronModule } from 'ngx-electron';
|
||||
|
||||
import { NgxsModule } from '@ngxs/store';
|
||||
import { NgxsStoragePluginModule } from '@ngxs/storage-plugin';
|
||||
|
@ -90,6 +89,7 @@ import { TutorialEnhancedComponent } from './components/tutorial-enhanced/tutori
|
|||
import { NotificationsTutorialComponent } from './components/tutorial-enhanced/notifications-tutorial/notifications-tutorial.component';
|
||||
import { LabelsTutorialComponent } from './components/tutorial-enhanced/labels-tutorial/labels-tutorial.component';
|
||||
import { ThankyouTutorialComponent } from './components/tutorial-enhanced/thankyou-tutorial/thankyou-tutorial.component';
|
||||
import { StatusTranslateComponent } from './components/stream/status/status-translate/status-translate.component';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "", component: StreamsMainDisplayComponent },
|
||||
|
@ -159,7 +159,8 @@ const routes: Routes = [
|
|||
TutorialEnhancedComponent,
|
||||
NotificationsTutorialComponent,
|
||||
LabelsTutorialComponent,
|
||||
ThankyouTutorialComponent
|
||||
ThankyouTutorialComponent,
|
||||
StatusTranslateComponent
|
||||
],
|
||||
entryComponents: [
|
||||
EmojiPickerComponent
|
||||
|
@ -176,6 +177,7 @@ const routes: Routes = [
|
|||
OwlDateTimeModule,
|
||||
OwlNativeDateTimeModule,
|
||||
OverlayModule,
|
||||
// NgxElectronModule,
|
||||
RouterModule.forRoot(routes),
|
||||
|
||||
NgxsModule.forRoot([
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
<form class="status-editor" (ngSubmit)="onSubmit()">
|
||||
<input [(ngModel)]="title" type="text" class="form-control form-control-sm status-editor__title" name="title"
|
||||
autocomplete="off" placeholder="Title, Content Warning (optional)" title="title, content warning (optional)" dir="auto" />
|
||||
<input #mytitle [(ngModel)]="title" type="text" class="form-control form-control-sm status-editor__title"
|
||||
name="title" autocomplete="off" placeholder="Title, Content Warning (optional)"
|
||||
title="title, content warning (optional)" dir="auto"
|
||||
(keydown.escape)="mytitle.blur()" />
|
||||
|
||||
<a class="status-editor__emoji" title="Insert Emoji"
|
||||
#emojiButton href (click)="openEmojiPicker($event)">
|
||||
<img class="status-editor__emoji--image" src="/assets/emoji/72x72/1f636.png">
|
||||
</a>
|
||||
|
||||
<textarea #reply [(ngModel)]="status" name="status" class="form-control form-control-sm status-editor__content" (paste)="onPaste($event)"
|
||||
<a class="status-editor__lang" title="Change language" href *ngIf="configuredLanguages && configuredLanguages.length > 1" (click)="onLangContextMenu($event)">
|
||||
{{ selectedLanguage.iso639 }}
|
||||
</a>
|
||||
|
||||
<textarea #reply [(ngModel)]="status" name="status" class="form-control form-control-sm status-editor__content" (paste)="onPaste($event)"
|
||||
rows="5" required title="content" placeholder="What's on your mind?"
|
||||
(keydown.control.enter)="onCtrlEnter()"
|
||||
(keydown.meta.enter)="onCtrlEnter()"
|
||||
(keydown.escape)="reply.blur()"
|
||||
(keydown)="handleKeyDown($event)" (blur)="statusTextEditorLostFocus()" dir="auto">
|
||||
</textarea>
|
||||
|
||||
|
@ -23,7 +30,7 @@
|
|||
(suggestionSelectedEvent)="suggestionSelected($event)" (hasSuggestionsEvent)="suggestionsChanged($event)">
|
||||
</app-autosuggest>
|
||||
|
||||
<app-poll-editor *ngIf="instanceSupportsPoll && pollIsActive"></app-poll-editor>
|
||||
<app-poll-editor *ngIf="instanceSupportsPoll && pollIsActive" [oldPoll]="oldPoll"></app-poll-editor>
|
||||
|
||||
<app-status-scheduler class="scheduler" *ngIf="instanceSupportsScheduling && scheduleIsActive"></app-status-scheduler>
|
||||
|
||||
|
@ -68,6 +75,10 @@
|
|||
<fa-icon [icon]="faClock"></fa-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="language-warning" *ngIf="!configuredLanguages || configuredLanguages.length === 0">
|
||||
You haven't set your language(s) yet, please <a href class="language-warning__link" (click)="onNavigateToSettings()">go in the settings</a> to provide it.
|
||||
</div>
|
||||
|
||||
<context-menu #contextMenu>
|
||||
<ng-template contextMenuItem (execute)="changePrivacy('Public')">
|
||||
|
@ -83,5 +94,12 @@
|
|||
<fa-icon [icon]="faEnvelope" class="context-menu-icon"></fa-icon> Direct
|
||||
</ng-template>
|
||||
</context-menu>
|
||||
|
||||
<context-menu #langContextMenu>
|
||||
<ng-template contextMenuItem (execute)="setLanguage(l)" *ngFor="let l of configuredLanguages">
|
||||
{{ l.name }}
|
||||
</ng-template>
|
||||
</context-menu>
|
||||
|
||||
<app-media></app-media>
|
||||
</form>
|
||||
|
|
|
@ -70,6 +70,32 @@ $counter-width: 90px;
|
|||
}
|
||||
}
|
||||
|
||||
&__lang {
|
||||
position: absolute;
|
||||
top: 64px;
|
||||
right: 12px;
|
||||
|
||||
font-weight: bolder;
|
||||
font-size: 12px;
|
||||
color: #a5a5a5;
|
||||
text-decoration: none;
|
||||
|
||||
display: block;
|
||||
width: 20px;
|
||||
height: 19px;
|
||||
border-radius: 2px;
|
||||
background-color: rgba(255, 255, 255, 0);
|
||||
|
||||
padding: 1px 0 0 2px;
|
||||
text-transform: uppercase;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color:black;
|
||||
background-color: #e6e6e6;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
border-width: 0;
|
||||
background-color: $status-editor-background;
|
||||
|
@ -207,6 +233,20 @@ $counter-width: 90px;
|
|||
border-bottom: 1px solid whitesmoke;
|
||||
}
|
||||
|
||||
.language-warning {
|
||||
padding: 5px 10px;
|
||||
color: orange;
|
||||
|
||||
&__link {
|
||||
text-decoration: underline;
|
||||
color: #f0d124;
|
||||
|
||||
&:hover {
|
||||
color: #d18800;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@import '~@angular/cdk/overlay-prebuilt.css';
|
||||
// ::ng-deep .cdk-overlay-backdrop {
|
||||
// // width: 100%;
|
||||
|
|
|
@ -11,7 +11,7 @@ import { ContextMenuService, ContextMenuComponent } from 'ngx-contextmenu';
|
|||
|
||||
import { VisibilityEnum, PollParameters } from '../../services/mastodon.service';
|
||||
import { MastodonWrapperService } from '../../services/mastodon-wrapper.service';
|
||||
import { Status, Attachment } from '../../services/models/mastodon.interfaces';
|
||||
import { Status, Attachment, Poll } from '../../services/models/mastodon.interfaces';
|
||||
import { ToolsService, InstanceInfo, InstanceType } from '../../services/tools.service';
|
||||
import { NotificationService } from '../../services/notification.service';
|
||||
import { StatusWrapper } from '../../models/common.model';
|
||||
|
@ -25,6 +25,9 @@ import { StatusSchedulerComponent } from './status-scheduler/status-scheduler.co
|
|||
import { ScheduledStatusService } from '../../services/scheduled-status.service';
|
||||
import { StatusesStateService } from '../../services/statuses-state.service';
|
||||
import { SettingsService } from '../../services/settings.service';
|
||||
import { LanguageService } from '../../services/language.service';
|
||||
import { ILanguage } from '../../states/settings.state';
|
||||
import { LeftPanelType, NavigationService } from '../../services/navigation.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-create-status',
|
||||
|
@ -140,9 +143,19 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
this.isSending = false;
|
||||
});
|
||||
}
|
||||
|
||||
if(value.status.poll){
|
||||
this.pollIsActive = true;
|
||||
this.oldPoll = value.status.poll;
|
||||
// setTimeout(() => {
|
||||
// if(this.pollEditor) this.pollEditor.loadPollParameters(value.status.poll);
|
||||
// }, 250);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oldPoll: Poll;
|
||||
|
||||
private maxCharLength: number;
|
||||
charCountLeft: number;
|
||||
postCounts: number = 1;
|
||||
|
@ -153,6 +166,8 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
instanceSupportsScheduling = true;
|
||||
isEditing: boolean;
|
||||
editingStatusId: string;
|
||||
configuredLanguages: ILanguage[] = [];
|
||||
selectedLanguage: ILanguage;
|
||||
private statusLoaded: boolean;
|
||||
private hasSuggestions: boolean;
|
||||
|
||||
|
@ -162,6 +177,7 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
@ViewChild('fileInput') fileInputElement: ElementRef;
|
||||
@ViewChild('footer') footerElement: ElementRef;
|
||||
@ViewChild(ContextMenuComponent) public contextMenu: ContextMenuComponent;
|
||||
@ViewChild('langContextMenu') public langContextMenu: ContextMenuComponent;
|
||||
@ViewChild(PollEditorComponent) pollEditor: PollEditorComponent;
|
||||
@ViewChild(StatusSchedulerComponent) statusScheduler: StatusSchedulerComponent;
|
||||
|
||||
|
@ -196,11 +212,15 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
|
||||
private accounts$: Observable<AccountInfo[]>;
|
||||
private accountSub: Subscription;
|
||||
private langSub: Subscription;
|
||||
private selectLangSub: Subscription;
|
||||
private selectedAccount: AccountInfo;
|
||||
|
||||
constructor(
|
||||
private readonly navigationService: NavigationService,
|
||||
private readonly languageService: LanguageService,
|
||||
private readonly settingsService: SettingsService,
|
||||
private statusStateService: StatusesStateService,
|
||||
private readonly statusStateService: StatusesStateService,
|
||||
private readonly scheduledStatusService: ScheduledStatusService,
|
||||
private readonly contextMenuService: ContextMenuService,
|
||||
private readonly store: Store,
|
||||
|
@ -216,7 +236,35 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
this.accounts$ = this.store.select(state => state.registeredaccounts.accounts);
|
||||
}
|
||||
|
||||
private initLanguages(){
|
||||
this.configuredLanguages = this.languageService.getConfiguredLanguages();
|
||||
this.selectedLanguage = this.languageService.getSelectedLanguage();
|
||||
this.langSub = this.languageService.configuredLanguagesChanged.subscribe(l => {
|
||||
this.configuredLanguages = l;
|
||||
// if(this.configuredLanguages.length > 0
|
||||
// && this.selectedLanguage
|
||||
// && this.configuredLanguages.findIndex(x => x.iso639 === this.selectedLanguage.iso639)){
|
||||
// this.languageService.setSelectedLanguage(this.configuredLanguages[0]);
|
||||
// }
|
||||
});
|
||||
this.selectLangSub = this.languageService.selectedLanguageChanged.subscribe(l => {
|
||||
this.selectedLanguage = l;
|
||||
});
|
||||
if(!this.selectedLanguage && this.configuredLanguages.length > 0){
|
||||
this.languageService.setSelectedLanguage(this.configuredLanguages[0]);
|
||||
}
|
||||
}
|
||||
|
||||
setLanguage(lang: ILanguage): boolean {
|
||||
if(lang){
|
||||
this.languageService.setSelectedLanguage(lang);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.initLanguages();
|
||||
|
||||
if (!this.isRedrafting) {
|
||||
this.status = this.statusStateService.getStatusContent(this.statusReplyingToWrapper);
|
||||
}
|
||||
|
@ -263,6 +311,13 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
this.accountSub.unsubscribe();
|
||||
this.langSub.unsubscribe();
|
||||
this.selectLangSub.unsubscribe();
|
||||
}
|
||||
|
||||
onNavigateToSettings(): boolean {
|
||||
this.navigationService.openPanel(LeftPanelType.Settings);
|
||||
return false;
|
||||
}
|
||||
|
||||
onPaste(e: any) {
|
||||
|
@ -613,6 +668,14 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
return false;
|
||||
}
|
||||
|
||||
private currentLang(): string {
|
||||
if(this.selectedLanguage){
|
||||
return this.selectedLanguage.iso639;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private sendStatus(account: AccountInfo, status: string, visibility: VisibilityEnum, title: string, previousStatus: Status, attachments: Attachment[], poll: PollParameters, scheduledAt: string, editingStatusId: string): Promise<Status> {
|
||||
let parsedStatus = this.parseStatus(status);
|
||||
let resultPromise = Promise.resolve(previousStatus);
|
||||
|
@ -630,9 +693,9 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
let postPromise: Promise<Status>;
|
||||
|
||||
if (this.isEditing) {
|
||||
postPromise = this.mastodonService.editStatus(account, editingStatusId, s, visibility, title, inReplyToId, attachments, poll, scheduledAt);
|
||||
postPromise = this.mastodonService.editStatus(account, editingStatusId, s, visibility, title, inReplyToId, attachments, poll, scheduledAt, this.currentLang());
|
||||
} else {
|
||||
postPromise = this.mastodonService.postNewStatus(account, s, visibility, title, inReplyToId, attachments.map(x => x.id), poll, scheduledAt);
|
||||
postPromise = this.mastodonService.postNewStatus(account, s, visibility, title, inReplyToId, attachments.map(x => x.id), poll, scheduledAt, this.currentLang());
|
||||
}
|
||||
|
||||
return postPromise
|
||||
|
@ -642,9 +705,9 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
});
|
||||
} else {
|
||||
if (this.isEditing) {
|
||||
return this.mastodonService.editStatus(account, editingStatusId, s, visibility, title, inReplyToId, [], null, scheduledAt);
|
||||
return this.mastodonService.editStatus(account, editingStatusId, s, visibility, title, inReplyToId, [], null, scheduledAt, this.currentLang());
|
||||
} else {
|
||||
return this.mastodonService.postNewStatus(account, s, visibility, title, inReplyToId, [], null, scheduledAt);
|
||||
return this.mastodonService.postNewStatus(account, s, visibility, title, inReplyToId, [], null, scheduledAt, this.currentLang());
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -887,6 +950,17 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
$event.stopPropagation();
|
||||
}
|
||||
|
||||
public onLangContextMenu($event: MouseEvent): void {
|
||||
this.contextMenuService.show.next({
|
||||
// Optional - if unspecified, all context menu components will open
|
||||
contextMenu: this.langContextMenu,
|
||||
event: $event,
|
||||
item: null
|
||||
});
|
||||
$event.preventDefault();
|
||||
$event.stopPropagation();
|
||||
}
|
||||
|
||||
//https://stackblitz.com/edit/overlay-demo
|
||||
@ViewChild('emojiButton') emojiButtonElement: ElementRef;
|
||||
overlayRef: OverlayRef;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, Input, OnInit, SimpleChanges } from '@angular/core';
|
||||
import { faPlus } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { PollEntry } from './poll-entry/poll-entry.component';
|
||||
import { PollParameters } from '../../../services/mastodon.service';
|
||||
import { retry } from 'rxjs/operators';
|
||||
import { Poll } from '../../../services/models/mastodon.interfaces';
|
||||
|
||||
@Component({
|
||||
selector: 'app-poll-editor',
|
||||
|
@ -19,6 +19,8 @@ export class PollEditorComponent implements OnInit {
|
|||
selectedId: string;
|
||||
private multiSelected: boolean;
|
||||
|
||||
@Input() oldPoll: Poll;
|
||||
|
||||
constructor() {
|
||||
this.entries.push(new PollEntry(this.getEntryUuid(), this.multiSelected));
|
||||
this.entries.push(new PollEntry(this.getEntryUuid(), this.multiSelected));
|
||||
|
@ -40,6 +42,12 @@ export class PollEditorComponent implements OnInit {
|
|||
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes['oldPoll']) {
|
||||
this.loadPollParameters(this.oldPoll);
|
||||
}
|
||||
}
|
||||
|
||||
private getEntryUuid(): number {
|
||||
this.entryUuid++;
|
||||
return this.entryUuid;
|
||||
|
@ -50,7 +58,7 @@ export class PollEditorComponent implements OnInit {
|
|||
return false;
|
||||
}
|
||||
|
||||
removeElement(entry: PollEntry){
|
||||
removeElement(entry: PollEntry) {
|
||||
this.entries = this.entries.filter(x => x.id != entry.id);
|
||||
}
|
||||
|
||||
|
@ -69,6 +77,17 @@ export class PollEditorComponent implements OnInit {
|
|||
params.hide_totals = false;
|
||||
return params;
|
||||
}
|
||||
|
||||
private loadPollParameters(poll: Poll) {
|
||||
const isMulti = poll.multiple;
|
||||
|
||||
this.entries.length = 0;
|
||||
for (let o of poll.options) {
|
||||
const entry = new PollEntry(this.getEntryUuid(), isMulti);
|
||||
entry.label = o.title;
|
||||
this.entries.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Delay {
|
||||
|
|
|
@ -60,8 +60,8 @@ export class ManageAccountComponent extends BrowseBase {
|
|||
private readonly mastodonService: MastodonWrapperService,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly userNotificationService: UserNotificationService) {
|
||||
super();
|
||||
}
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
@ -71,13 +71,9 @@ export class ManageAccountComponent extends BrowseBase {
|
|||
}
|
||||
|
||||
private checkIfBookmarksAreAvailable() {
|
||||
this.toolsService.getInstanceInfo(this.account.info)
|
||||
.then((instance: InstanceInfo) => {
|
||||
if (instance.major == 3 && instance.minor >= 1 || instance.major > 3) {
|
||||
this.isBookmarksAvailable = true;
|
||||
} else {
|
||||
this.isBookmarksAvailable = false;
|
||||
}
|
||||
this.toolsService.isBookmarksAreAvailable(this.account.info)
|
||||
.then((isAvailable: boolean) => {
|
||||
this.isBookmarksAvailable = isAvailable;
|
||||
})
|
||||
.catch(err => {
|
||||
this.isBookmarksAvailable = false;
|
||||
|
@ -128,15 +124,15 @@ export class ManageAccountComponent extends BrowseBase {
|
|||
}
|
||||
}
|
||||
|
||||
@ViewChild('bookmarks') bookmarksComp:BookmarksComponent;
|
||||
@ViewChild('notifications') notificationsComp:NotificationsComponent;
|
||||
@ViewChild('mentions') mentionsComp:MentionsComponent;
|
||||
@ViewChild('dm') dmComp:DirectMessagesComponent;
|
||||
@ViewChild('favorites') favoritesComp:FavoritesComponent;
|
||||
@ViewChild('bookmarks') bookmarksComp: BookmarksComponent;
|
||||
@ViewChild('notifications') notificationsComp: NotificationsComponent;
|
||||
@ViewChild('mentions') mentionsComp: MentionsComponent;
|
||||
@ViewChild('dm') dmComp: DirectMessagesComponent;
|
||||
@ViewChild('favorites') favoritesComp: FavoritesComponent;
|
||||
|
||||
loadSubPanel(subpanel: 'account' | 'notifications' | 'mentions' | 'dm' | 'favorites' | 'bookmarks'): boolean {
|
||||
if(this.subPanel === subpanel){
|
||||
switch(subpanel){
|
||||
if (this.subPanel === subpanel) {
|
||||
switch (subpanel) {
|
||||
case 'bookmarks':
|
||||
this.bookmarksComp.applyGoToTop();
|
||||
break;
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
(click)="acceptFollowRequest()">
|
||||
<fa-icon class="follow_request__icon" [icon]="faCheck"></fa-icon>
|
||||
</a>
|
||||
<a href title="Reject" class="follow_request__link follow_request__link--cross"
|
||||
<a href title="Reject" class="follow_request__link follow_request__link--cross"
|
||||
(click)="refuseFollowRequest()">
|
||||
<fa-icon class="follow_request__icon" [icon]="faTimes"></fa-icon>
|
||||
</a>
|
||||
|
@ -69,12 +69,18 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<app-status *ngIf="notification.status && notification.type !== 'mention'" class="stream__status" [statusWrapper]="notification.status"
|
||||
[notificationAccount]="notification.account" [notificationType]="notification.type"
|
||||
(browseAccountEvent)="browseAccount($event)" (browseHashtagEvent)="browseHashtag($event)"
|
||||
(browseThreadEvent)="browseThread($event)"></app-status>
|
||||
<app-status *ngIf="notification.status && notification.type === 'update'" class="stream__status"
|
||||
[statusWrapper]="notification.status" [notificationAccount]="notification.account"
|
||||
[notificationType]="notification.type" (browseAccountEvent)="browseAccount($event)"
|
||||
(browseHashtagEvent)="browseHashtag($event)" (browseThreadEvent)="browseThread($event)"></app-status>
|
||||
|
||||
<app-status *ngIf="notification.status && notification.type === 'mention'" class="stream__status"
|
||||
[statusWrapper]="notification.status" (browseAccountEvent)="browseAccount($event)"
|
||||
(browseHashtagEvent)="browseHashtag($event)" (browseThreadEvent)="browseThread($event)"></app-status>
|
||||
|
||||
<app-status *ngIf="notification.status && notification.type !== 'mention' && notification.type !== 'update'"
|
||||
class="stream__status" [statusWrapper]="notification.status" [notificationAccount]="notification.account"
|
||||
[notificationType]="notification.type" (browseAccountEvent)="browseAccount($event)"
|
||||
(browseHashtagEvent)="browseHashtag($event)" (browseThreadEvent)="browseThread($event)"></app-status>
|
||||
|
||||
<app-status *ngIf="notification.status && notification.type === 'mention'" class="stream__status" [statusWrapper]="notification.status"
|
||||
(browseAccountEvent)="browseAccount($event)" (browseHashtagEvent)="browseHashtag($event)"
|
||||
(browseThreadEvent)="browseThread($event)"></app-status>
|
||||
</div>
|
|
@ -101,7 +101,7 @@ export class NotificationsComponent extends BrowseBase {
|
|||
this.isLoading = true;
|
||||
this.isProcessingInfiniteScroll = true;
|
||||
|
||||
this.mastodonService.getNotifications(this.account.info, ['mention', 'update'], this.lastId)
|
||||
this.mastodonService.getNotifications(this.account.info, ['mention'], this.lastId)
|
||||
.then((notifications: Notification[]) => {
|
||||
if (notifications.length === 0) {
|
||||
this.maxReached = true;
|
||||
|
@ -152,6 +152,7 @@ export class NotificationWrapper {
|
|||
case 'reblog':
|
||||
case 'favourite':
|
||||
case 'poll':
|
||||
case 'update':
|
||||
this.status = new StatusWrapper(notification.status, provider, applyCw, hideStatus);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
<h3 class="panel__title">search</h3>
|
||||
|
||||
<form class="form-section" (ngSubmit)="onSubmit()">
|
||||
<input type="text" class="form-control form-control-sm form-with-button" [(ngModel)]="searchHandle"
|
||||
name="searchHandle" placeholder="Search" autocomplete="off" />
|
||||
<input #search type="text" class="form-control form-control-sm form-with-button" [(ngModel)]="searchHandle"
|
||||
name="searchHandle" placeholder="Search" autocomplete="off" (keydown.escape)="search.blur()"/>
|
||||
<button class="form-button" type="submit" title="search">GO</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { Component, OnInit, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
import { MastodonWrapperService } from '../../../services/mastodon-wrapper.service';
|
||||
|
@ -26,12 +26,15 @@ export class SearchComponent implements OnInit {
|
|||
@Output() browseHashtagEvent = new EventEmitter<string>();
|
||||
@Output() browseThreadEvent = new EventEmitter<OpenThreadEvent>();
|
||||
|
||||
@ViewChild('search') searchElement: ElementRef;
|
||||
|
||||
constructor(
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly toolsService: ToolsService,
|
||||
private readonly mastodonService: MastodonWrapperService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.searchElement.nativeElement.focus();
|
||||
}
|
||||
|
||||
onSubmit(): boolean {
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
</form>
|
||||
<a href class="form-button sound__play" type="submit" (click)="playNotificationSound()">play</a>
|
||||
</div>
|
||||
|
||||
|
||||
<h4 class="panel__subtitle">Shortcuts</h4>
|
||||
<div class="sub-section">
|
||||
<span class="sub-section__title">switch column:</span><br />
|
||||
|
@ -51,21 +51,42 @@
|
|||
<br>
|
||||
</div>
|
||||
|
||||
<h4 class="panel__subtitle">Languages</h4>
|
||||
<div class="sub-section">
|
||||
<div class="sub-section__content">
|
||||
<div *ngIf="!configuredLangs || configuredLangs.length === 0" class="language__warning">
|
||||
No language set.
|
||||
</div>
|
||||
<div *ngFor="let l of configuredLangs" class="language__entry">
|
||||
<span class="language__entry__name">{{ l.name }} ({{l.iso639}})</span>
|
||||
<a href (click)="onRemoveLang(l)" class="form-button language__entry__action sound__play">remove</a>
|
||||
</div>
|
||||
<input type="text" (input)="onSearchLang($event.target.value)" [(ngModel)]="searchLang"
|
||||
placeholder="Find Language" autocomplete="off" class="form-control form-control-sm language__search"/>
|
||||
<div *ngFor="let l of searchedLangs" class="language__entry">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<h4 class="panel__subtitle">Twitter Bridge</h4>
|
||||
<div class="sub-section">
|
||||
<input class="sub-section__checkbox" [(ngModel)]="twitterBridgeEnabled"
|
||||
(change)="onTwitterBridgeEnabledChanged()" type="checkbox" name="onTwitterBridgeEnabled"
|
||||
value="onTwitterBridgeEnabled" id="onTwitterBridgeEnabled">
|
||||
(change)="onTwitterBridgeEnabledChanged()" type="checkbox" name="onTwitterBridgeEnabled"
|
||||
value="onTwitterBridgeEnabled" id="onTwitterBridgeEnabled">
|
||||
<label class="noselect sub-section__label" for="onTwitterBridgeEnabled">enable bridge</label>
|
||||
<br>
|
||||
<div *ngIf="twitterBridgeEnabled">
|
||||
<p>Please provide your bridge instance:
|
||||
<input type="text" class="form-control form-control-sm sub_section__text-input"
|
||||
[(ngModel)]="setTwitterBridgeInstance" placeholder="bridge.tld" />
|
||||
If you don't know any, consider using <a href="https://github.com/NicolasConstant/BirdsiteLive" target="_blank" class="version__link">BirdsiteLIVE</a></p>
|
||||
<input type="text" class="form-control form-control-sm sub_section__text-input"
|
||||
[(ngModel)]="setTwitterBridgeInstance" placeholder="bridge.tld" />
|
||||
If you don't know any, consider using <a href="https://github.com/NicolasConstant/BirdsiteLive"
|
||||
target="_blank" class="version__link">BirdsiteLIVE</a>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="https://github.com/NicolasConstant/sengi/wiki/BirdsiteLIVE-integration" target="_blank" class="version__link">What is this?</a>
|
||||
<a href="https://github.com/NicolasConstant/sengi/wiki/BirdsiteLIVE-integration" target="_blank"
|
||||
class="version__link">What is this?</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -79,7 +100,7 @@
|
|||
|
||||
<input class="sub-section__checkbox" [checked]="contentWarningPolicy === 2" (change)="onCwPolicyChange(2)"
|
||||
type="radio" name="cw-hide-all" value="cw-hide-all" id="cw-hide-all">
|
||||
<label class="noselect sub-section__label" for="cw-hide-all">Hide all CWs</label>
|
||||
<label class="noselect sub-section__label" for="cw-hide-all">Expand all CWs</label>
|
||||
<br>
|
||||
<div class="sub-section__cw-settings" *ngIf="contentWarningPolicy === 2">
|
||||
<span class="sub-section__title">but add CW on content containing:</span><br />
|
||||
|
|
|
@ -31,6 +31,13 @@
|
|||
padding: 0 5px 15px 5px;
|
||||
position: relative;
|
||||
|
||||
&__content {
|
||||
display: block;
|
||||
padding: 0 0 0 5px;
|
||||
|
||||
// outline: 1px dotted greenyellow;
|
||||
}
|
||||
|
||||
&__checkbox {
|
||||
position: relative;
|
||||
top: 3px;
|
||||
|
@ -68,6 +75,41 @@
|
|||
}
|
||||
}
|
||||
|
||||
.language {
|
||||
&__warning {
|
||||
color: orange;
|
||||
}
|
||||
|
||||
&__entry {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: space-between;
|
||||
|
||||
&:not(:last-child){
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
&__name {
|
||||
display: block;
|
||||
align-items: stretch;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
&__action {
|
||||
align-items: stretch;
|
||||
min-width: 70px;
|
||||
text-align: center;
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
&__search {
|
||||
display: block;
|
||||
margin: 5px 0 5px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border: 1px solid $settings-text-input-border;
|
||||
color: $settings-text-input-foreground;
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { Howl } from 'howler';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { environment } from '../../../../environments/environment';
|
||||
import { ToolsService, InstanceType } from '../../../services/tools.service';
|
||||
import { UserNotificationService, NotificationSoundDefinition } from '../../../services/user-notification.service';
|
||||
import { ServiceWorkerService } from '../../../services/service-worker.service';
|
||||
import { ContentWarningPolicy, ContentWarningPolicyEnum, TimeLineModeEnum, TimeLineHeaderEnum } from '../../../states/settings.state';
|
||||
import { ContentWarningPolicy, ContentWarningPolicyEnum, TimeLineModeEnum, TimeLineHeaderEnum, ILanguage } from '../../../states/settings.state';
|
||||
import { NotificationService } from '../../../services/notification.service';
|
||||
import { NavigationService } from '../../../services/navigation.service';
|
||||
import { SettingsService } from '../../../services/settings.service';
|
||||
import { LanguageService } from '../../../services/language.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings',
|
||||
|
@ -17,7 +19,7 @@ import { SettingsService } from '../../../services/settings.service';
|
|||
styleUrls: ['./settings.component.scss']
|
||||
})
|
||||
|
||||
export class SettingsComponent implements OnInit {
|
||||
export class SettingsComponent implements OnInit, OnDestroy {
|
||||
|
||||
notificationSounds: NotificationSoundDefinition[];
|
||||
notificationSoundId: string;
|
||||
|
@ -39,6 +41,10 @@ export class SettingsComponent implements OnInit {
|
|||
timeLineMode: TimeLineModeEnum = TimeLineModeEnum.OnTop;
|
||||
contentWarningPolicy: ContentWarningPolicyEnum = ContentWarningPolicyEnum.None;
|
||||
|
||||
configuredLangs: ILanguage[] = [];
|
||||
searchedLangs: ILanguage[] = [];
|
||||
searchLang: string;
|
||||
|
||||
private addCwOnContent: string;
|
||||
set setAddCwOnContent(value: string) {
|
||||
this.setCwPolicy(null, value, null, null);
|
||||
|
@ -76,16 +82,25 @@ export class SettingsComponent implements OnInit {
|
|||
return this.twitterBridgeInstance;
|
||||
}
|
||||
|
||||
private languageSub: Subscription;
|
||||
|
||||
constructor(
|
||||
private readonly languageService: LanguageService,
|
||||
private readonly settingsService: SettingsService,
|
||||
private readonly navigationService: NavigationService,
|
||||
private formBuilder: FormBuilder,
|
||||
private serviceWorkersService: ServiceWorkerService,
|
||||
private readonly toolsService: ToolsService,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly userNotificationsService: UserNotificationService) { }
|
||||
private readonly userNotificationsService: UserNotificationService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.languageSub = this.languageService.configuredLanguagesChanged.subscribe(l => {
|
||||
if(l){
|
||||
this.configuredLangs = l;
|
||||
}
|
||||
});
|
||||
|
||||
this.version = environment.VERSION;
|
||||
|
||||
const settings = this.settingsService.getSettings();
|
||||
|
@ -129,6 +144,34 @@ export class SettingsComponent implements OnInit {
|
|||
|
||||
this.twitterBridgeEnabled = settings.twitterBridgeEnabled;
|
||||
this.twitterBridgeInstance = settings.twitterBridgeInstance;
|
||||
|
||||
this.configuredLangs = this.languageService.getConfiguredLanguages();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if(this.languageSub) this.languageSub.unsubscribe();
|
||||
}
|
||||
|
||||
onSearchLang(input: string) {
|
||||
this.searchedLangs = this.languageService.searchLanguage(input);
|
||||
}
|
||||
|
||||
onAddLang(lang: ILanguage): boolean {
|
||||
if(this.configuredLangs.findIndex(x => x.iso639 === lang.iso639) >= 0) return false;
|
||||
|
||||
// this.configuredLangs.push(lang);
|
||||
this.languageService.addLanguage(lang);
|
||||
|
||||
this.searchLang = '';
|
||||
this.searchedLangs.length = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
onRemoveLang(lang: ILanguage): boolean {
|
||||
// this.configuredLangs = this.configuredLangs.filter(x => x.iso639 !== lang.iso639);
|
||||
this.languageService.removeLanguage(lang);
|
||||
return false;
|
||||
}
|
||||
|
||||
onShortcutChange(id: ColumnShortcut) {
|
||||
|
|
|
@ -43,7 +43,7 @@ $inner-column-size: 320px;
|
|||
&__follow-button {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 100px;
|
||||
right: 114px;
|
||||
padding: 0 10px 0 10px;
|
||||
border: 1px solid black;
|
||||
color: white;
|
||||
|
|
|
@ -342,13 +342,9 @@ export class ActionBarComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
private checkIfBookmarksAreAvailable(account: AccountInfo) {
|
||||
this.toolsService.getInstanceInfo(account)
|
||||
.then((instance: InstanceInfo) => {
|
||||
if (instance.major == 3 && instance.minor >= 1 || instance.major > 3) {
|
||||
this.isBookmarksAvailable = true;
|
||||
} else {
|
||||
this.isBookmarksAvailable = false;
|
||||
}
|
||||
this.toolsService.isBookmarksAreAvailable(account)
|
||||
.then((isAvailable: boolean) => {
|
||||
this.isBookmarksAvailable = isAvailable;
|
||||
})
|
||||
.catch(err => {
|
||||
this.isBookmarksAvailable = false;
|
||||
|
|
|
@ -64,6 +64,7 @@ $expand-color: $column-color;
|
|||
|
||||
& p {
|
||||
margin: 0px;
|
||||
white-space: pre-wrap;
|
||||
//font-size: .9em;
|
||||
// font-size: 14px;
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ export class DatabindedTextComponent implements OnInit {
|
|||
let extractedUrl = extractedLinkAndNext[0].split('href="')[1].split('"')[0];
|
||||
|
||||
let classname = this.getClassNameForHastag(extractedHashtag);
|
||||
this.processedText += ` <a href="${extractedUrl}" class="${classname}" title="#${extractedHashtag}" target="_blank" rel="noopener noreferrer">#${extractedHashtag}</a>`;
|
||||
this.processedText += `<a href="${extractedUrl}" class="${classname}" title="#${extractedHashtag}" target="_blank" rel="noopener noreferrer">#${extractedHashtag}</a>`;
|
||||
if (extractedLinkAndNext[1]) this.processedText += extractedLinkAndNext[1];
|
||||
this.hashtags.push(extractedHashtag);
|
||||
}
|
||||
|
@ -205,6 +205,10 @@ export class DatabindedTextComponent implements OnInit {
|
|||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.processEventBindings();
|
||||
}
|
||||
|
||||
processEventBindings(){
|
||||
for (const hashtag of this.hashtags) {
|
||||
let classname = this.getClassNameForHastag(hashtag);
|
||||
let els = <Element[]>this.contentElement.nativeElement.querySelectorAll(`.${classname}`);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<div class="translation translation__button-display" *ngIf="isTranslationAvailable && showTranslationButton">
|
||||
<a href class="translation__link translation__button-display__link" (click)="translate()">Translate</a>
|
||||
</div>
|
||||
<div class="translation translation__display" *ngIf="isTranslationAvailable && !showTranslationButton">
|
||||
<span class="translation__by">Translated by {{translatedBy}}</span> <a href (click)="revertTranslation()" class="translation__link translation__display__link">revert</a>
|
||||
</div>
|
|
@ -0,0 +1,44 @@
|
|||
@import "variables";
|
||||
@import "commons";
|
||||
|
||||
$translation-color: #656b8f;
|
||||
$translation-color-hover: #9fa5ca;
|
||||
|
||||
.translation {
|
||||
margin: 0 10px 0 $avatar-column-space;
|
||||
color: $translation-color;
|
||||
font-size: 12px;
|
||||
|
||||
&__button-display {
|
||||
text-align: center;
|
||||
|
||||
&__link {
|
||||
display: block;
|
||||
padding: 5px 5px 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&__display {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
&__link {
|
||||
padding: 5px 0 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__link {
|
||||
color: $translation-color;
|
||||
transition: all .2s;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
color: $translation-color-hover;
|
||||
}
|
||||
}
|
||||
|
||||
&__by {
|
||||
display: block;
|
||||
text-align: left;
|
||||
padding: 5px 0 0 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { StatusTranslateComponent } from './status-translate.component';
|
||||
|
||||
xdescribe('StatusTranslateComponent', () => {
|
||||
let component: StatusTranslateComponent;
|
||||
let fixture: ComponentFixture<StatusTranslateComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ StatusTranslateComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(StatusTranslateComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,117 @@
|
|||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Output } from '@angular/core';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
import { StatusWrapper } from '../../../../models/common.model';
|
||||
import { ILanguage } from '../../../../states/settings.state';
|
||||
import { LanguageService } from '../../../../services/language.service';
|
||||
import { InstancesInfoService } from '../../../../services/instances-info.service';
|
||||
import { MastodonWrapperService } from '../../../../services/mastodon-wrapper.service';
|
||||
import { Translation } from '../../../../services/models/mastodon.interfaces';
|
||||
import { NotificationService } from '../../../../services/notification.service';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
@Component({
|
||||
selector: 'app-status-translate',
|
||||
templateUrl: './status-translate.component.html',
|
||||
styleUrls: ['./status-translate.component.scss']
|
||||
})
|
||||
export class StatusTranslateComponent implements OnInit, OnDestroy {
|
||||
|
||||
private languageSub: Subscription;
|
||||
private languagesSub: Subscription;
|
||||
private loadedTranslation: Translation;
|
||||
|
||||
selectedLanguage: ILanguage;
|
||||
configuredLanguages: ILanguage[] = [];
|
||||
|
||||
isTranslationAvailable: boolean;
|
||||
showTranslationButton: boolean = true;
|
||||
translatedBy: string;
|
||||
|
||||
@Input() status: StatusWrapper;
|
||||
@Output() translation = new EventEmitter<Translation>();
|
||||
|
||||
constructor(
|
||||
private readonly mastodonWrapperService: MastodonWrapperService,
|
||||
private readonly languageService: LanguageService,
|
||||
private readonly instancesInfoService: InstancesInfoService,
|
||||
private readonly notificationService: NotificationService
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.languageSub = this.languageService.selectedLanguageChanged.subscribe(l => {
|
||||
if (l) {
|
||||
this.selectedLanguage = l;
|
||||
this.analyseAvailability();
|
||||
}
|
||||
});
|
||||
|
||||
this.languagesSub = this.languageService.configuredLanguagesChanged.subscribe(l => {
|
||||
if (l) {
|
||||
this.configuredLanguages = l;
|
||||
this.analyseAvailability();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.languageSub) this.languageSub.unsubscribe();
|
||||
if (this.languagesSub) this.languagesSub.unsubscribe();
|
||||
}
|
||||
|
||||
private analyseAvailability() {
|
||||
this.instancesInfoService.getTranslationAvailability(this.status.provider)
|
||||
.then(canTranslate => {
|
||||
if (canTranslate
|
||||
&& !this.status.isRemote
|
||||
&& this.configuredLanguages.length > 0
|
||||
&& this.configuredLanguages.findIndex(x => x.iso639 === this.status.status.language) === -1) {
|
||||
|
||||
this.isTranslationAvailable = true;
|
||||
}
|
||||
else {
|
||||
this.isTranslationAvailable = false;
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
this.isTranslationAvailable = false;
|
||||
});
|
||||
}
|
||||
|
||||
translate(): boolean {
|
||||
if(this.loadedTranslation){
|
||||
this.translation.next(this.loadedTranslation);
|
||||
this.showTranslationButton = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
this.mastodonWrapperService.translate(this.status.provider, this.status.status.id, this.selectedLanguage.iso639)
|
||||
.then(x => {
|
||||
this.loadedTranslation = x;
|
||||
this.translation.next(x);
|
||||
this.translatedBy = x.provider;
|
||||
this.showTranslationButton = false;
|
||||
})
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
console.error(err);
|
||||
this.notificationService.notifyHttpError(err, this.status.provider);
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
revertTranslation(): boolean {
|
||||
let revertTranslate: Translation;
|
||||
revertTranslate = {
|
||||
content: this.status.status.content,
|
||||
language: this.loadedTranslation.detected_source_language,
|
||||
detected_source_language: this.loadedTranslation.language,
|
||||
provider: this.loadedTranslation.provider,
|
||||
spoiler_text: this.status.status.spoiler_text
|
||||
};
|
||||
this.translation.next(revertTranslate);
|
||||
|
||||
this.showTranslationButton = true;
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -34,6 +34,17 @@
|
|||
boosted your status
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="notificationType === 'update'">
|
||||
<div class="notification--icon">
|
||||
<fa-icon class="update" [icon]="faEdit"></fa-icon>
|
||||
</div>
|
||||
<div class="notification--label">
|
||||
<a href class="notification--link" title="{{ notificationAccount.acct }}"
|
||||
(click)="openAccount(notificationAccount)"
|
||||
(auxclick)="openUrl(notificationAccount.url)" innerHTML="{{ notificationAccount | accountEmoji }}"></a>
|
||||
edited the status you boosted
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="notificationType === 'poll'">
|
||||
<div class="notification--icon">
|
||||
<fa-icon class="boost" [icon]="faList"></fa-icon>
|
||||
|
@ -98,10 +109,12 @@
|
|||
<span class="status__content-warning--title">sensitive content</span>
|
||||
<span innerHTML="{{ contentWarningText }}"></span>
|
||||
</a>
|
||||
<app-databinded-text 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)"
|
||||
(textSelected)="textSelected()"></app-databinded-text>
|
||||
|
||||
<app-status-translate [status]="displayedStatusWrapper" (translation)="onTranslation($event)"></app-status-translate>
|
||||
|
||||
<app-poll class="status__poll" *ngIf="!isContentWarned && displayedStatus.poll"
|
||||
[poll]="displayedStatus.poll" [statusWrapper]="displayedStatusWrapper"></app-poll>
|
||||
|
||||
|
|
|
@ -258,6 +258,10 @@
|
|||
color: $boost-color;
|
||||
}
|
||||
|
||||
.update {
|
||||
color: $update-color;
|
||||
}
|
||||
|
||||
.favorite {
|
||||
color: $favorite-color;
|
||||
}
|
||||
|
@ -272,4 +276,4 @@
|
|||
&__label{
|
||||
color: $status-secondary-color;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,15 +1,15 @@
|
|||
import { Component, OnInit, Input, Output, EventEmitter, ViewChild, ElementRef } from "@angular/core";
|
||||
import { faStar, faRetweet, faList, faThumbtack } from "@fortawesome/free-solid-svg-icons";
|
||||
import { faStar, faRetweet, faList, faThumbtack, faEdit } from "@fortawesome/free-solid-svg-icons";
|
||||
import { Subscription } from "rxjs";
|
||||
|
||||
import { Status, Account } from "../../../services/models/mastodon.interfaces";
|
||||
import { Status, Account, Translation } from "../../../services/models/mastodon.interfaces";
|
||||
import { OpenThreadEvent, ToolsService } from "../../../services/tools.service";
|
||||
import { ActionBarComponent } from "./action-bar/action-bar.component";
|
||||
import { StatusWrapper } from '../../../models/common.model';
|
||||
import { EmojiConverter, EmojiTypeEnum } from '../../../tools/emoji.tools';
|
||||
import { ContentWarningPolicyEnum } from '../../../states/settings.state';
|
||||
import { StatusesStateService, StatusState } from "../../../services/statuses-state.service";
|
||||
|
||||
import { DatabindedTextComponent } from "./databinded-text/databinded-text.component";
|
||||
|
||||
@Component({
|
||||
selector: "app-status",
|
||||
|
@ -23,6 +23,7 @@ export class StatusComponent implements OnInit {
|
|||
faRetweet = faRetweet;
|
||||
faList = faList;
|
||||
faThumbtack = faThumbtack;
|
||||
faEdit = faEdit;
|
||||
|
||||
displayedStatus: Status;
|
||||
displayedStatusWrapper: StatusWrapper;
|
||||
|
@ -52,7 +53,7 @@ export class StatusComponent implements OnInit {
|
|||
|
||||
@Input() isThreadDisplay: boolean;
|
||||
|
||||
@Input() notificationType: 'mention' | 'reblog' | 'favourite' | 'poll';
|
||||
@Input() notificationType: 'mention' | 'reblog' | 'favourite' | 'poll' | 'update';
|
||||
@Input() notificationAccount: Account;
|
||||
|
||||
private _statusWrapper: StatusWrapper;
|
||||
|
@ -106,27 +107,27 @@ export class StatusComponent implements OnInit {
|
|||
|
||||
ngOnInit() {
|
||||
this.statusesStateServiceSub = this.statusesStateService.stateNotification.subscribe(notification => {
|
||||
if(this._statusWrapper.status.url === notification.statusId && notification.isEdited) {
|
||||
if (this._statusWrapper.status.url === notification.statusId && notification.isEdited) {
|
||||
this.statusWrapper = notification.editedStatus;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(){
|
||||
if(this.statusesStateServiceSub) this.statusesStateServiceSub.unsubscribe();
|
||||
ngOnDestroy() {
|
||||
if (this.statusesStateServiceSub) this.statusesStateServiceSub.unsubscribe();
|
||||
}
|
||||
|
||||
|
||||
private ensureMentionAreDisplayed(data: string): string {
|
||||
const mentions = this.displayedStatus.mentions;
|
||||
if(!mentions || mentions.length === 0) return data;
|
||||
|
||||
if (!mentions || mentions.length === 0) return data;
|
||||
|
||||
let textMentions = '';
|
||||
for (const m of mentions) {
|
||||
if(!data.includes(m.url)){
|
||||
if (!data.includes(m.url)) {
|
||||
textMentions += `<span class="h-card"><a class="u-url mention" data-user="${m.id}" href="${m.url}" rel="ugc">@<span>${m.username}</span></a></span> `
|
||||
}
|
||||
}
|
||||
if(textMentions !== ''){
|
||||
if (textMentions !== '') {
|
||||
data = textMentions + data;
|
||||
}
|
||||
return data;
|
||||
|
@ -156,6 +157,31 @@ export class StatusComponent implements OnInit {
|
|||
changeCw(cwIsActive: boolean) {
|
||||
this.isContentWarned = cwIsActive;
|
||||
}
|
||||
|
||||
|
||||
@ViewChild('databindedtext') public databindedText: DatabindedTextComponent;
|
||||
|
||||
onTranslation(translation: Translation) {
|
||||
let statusContent = translation.content;
|
||||
|
||||
// clean up a bit some issues (not reliable)
|
||||
while (statusContent.includes('<span>@')) {
|
||||
statusContent = statusContent.replace('<span>@', '@<span>');
|
||||
}
|
||||
while (statusContent.includes('h<span class="invisible">')){
|
||||
statusContent = statusContent.replace('h<span class="invisible">', '<span class="invisible">h');
|
||||
}
|
||||
while (statusContent.includes('<span>#')){
|
||||
statusContent = statusContent.replace('<span>#', '#<span>');
|
||||
}
|
||||
|
||||
statusContent = this.emojiConverter.applyEmojis(this.displayedStatus.emojis, statusContent, EmojiTypeEnum.medium);
|
||||
this.statusContent = this.ensureMentionAreDisplayed(statusContent);
|
||||
|
||||
setTimeout(x => {
|
||||
this.databindedText.processEventBindings();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
private checkLabels(status: Status) {
|
||||
//since API is limited with federated status...
|
||||
|
|
|
@ -126,7 +126,7 @@ export class StreamNotificationsComponent extends BrowseBase {
|
|||
this.loadMentions(userNotifications);
|
||||
});
|
||||
|
||||
this.mastodonService.getNotifications(this.account, ['update'], null, null, 10) //FIXME: disable edition update until supported
|
||||
this.mastodonService.getNotifications(this.account, [], null, null, 10)
|
||||
.then((notifications: Notification[]) => {
|
||||
this.isNotificationsLoading = false;
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ElectronService } from './electron.service';
|
||||
|
||||
describe('ElectronService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: ElectronService = TestBed.get(ElectronService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MyElectronService {
|
||||
constructor() {
|
||||
}
|
||||
|
||||
setLang(lang: string) {
|
||||
try {
|
||||
if ((<any>window).api) {
|
||||
(<any>window).api.send("changeSpellchecker", lang);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ import { AccountInfo } from '../states/accounts.state';
|
|||
export class InstancesInfoService {
|
||||
private defaultMaxChars = 500;
|
||||
private cachedMaxInstanceChar: { [id: string]: Promise<number>; } = {};
|
||||
private cachedTranslationAvailability: { [id: string]: Promise<boolean>; } = {};
|
||||
private cachedDefaultPrivacy: { [id: string]: Promise<VisibilityEnum>; } = {};
|
||||
|
||||
constructor(private mastodonService: MastodonWrapperService) { }
|
||||
|
@ -65,4 +66,30 @@ export class InstancesInfoService {
|
|||
}
|
||||
return this.cachedDefaultPrivacy[instance];
|
||||
}
|
||||
|
||||
getTranslationAvailability(account: AccountInfo): Promise<boolean> {
|
||||
const instance = account.instance;
|
||||
if (!this.cachedTranslationAvailability[instance]) {
|
||||
this.cachedTranslationAvailability[instance] = this.mastodonService.getInstance(instance)
|
||||
.then((instance: Instance) => {
|
||||
if (+instance.version.split('.')[0] >= 4) {
|
||||
const instanceV2 = <Instancev2>instance;
|
||||
if (instanceV2
|
||||
&& instanceV2.configuration
|
||||
&& instanceV2.configuration.translation)
|
||||
return instanceV2.configuration.translation.enabled;
|
||||
} else {
|
||||
const instanceV1 = <Instancev1>instance;
|
||||
if (instanceV1 && instanceV1.max_toot_chars)
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
.catch(() => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
return this.cachedTranslationAvailability[instance];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LanguageService } from './language.service';
|
||||
|
||||
xdescribe('LanguageService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: LanguageService = TestBed.get(LanguageService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,279 @@
|
|||
import { T } from '@angular/cdk/keycodes';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { ILanguage } from '../states/settings.state';
|
||||
import { MyElectronService } from './electron.service';
|
||||
import { SettingsService } from './settings.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class LanguageService {
|
||||
configuredLanguagesChanged = new BehaviorSubject<ILanguage[]>([]);
|
||||
selectedLanguageChanged = new BehaviorSubject<ILanguage>(null);
|
||||
|
||||
constructor(
|
||||
private settingsService: SettingsService,
|
||||
private electronService: MyElectronService
|
||||
) {
|
||||
this.configuredLanguagesChanged.next(this.getConfiguredLanguages());
|
||||
this.selectedLanguageChanged.next(this.getSelectedLanguage());
|
||||
}
|
||||
|
||||
getSelectedLanguage(): ILanguage {
|
||||
const lang = this.settingsService.getSettings().selectedLanguage;
|
||||
return lang;
|
||||
}
|
||||
|
||||
setSelectedLanguage(lang: ILanguage): void {
|
||||
var settings = this.settingsService.getSettings();
|
||||
settings.selectedLanguage = lang;
|
||||
this.settingsService.saveSettings(settings);
|
||||
|
||||
this.selectedLanguageChanged.next(lang);
|
||||
|
||||
if (lang) {
|
||||
this.electronService.setLang(lang.iso639);
|
||||
}
|
||||
}
|
||||
|
||||
getConfiguredLanguages(): ILanguage[] {
|
||||
const langs = this.settingsService.getSettings().configuredLanguages;
|
||||
return langs;
|
||||
}
|
||||
|
||||
addLanguage(lang: ILanguage) {
|
||||
var settings = this.settingsService.getSettings();
|
||||
settings.configuredLanguages.push(lang);
|
||||
settings.configuredLanguages.sort((a, b) => a.name.localeCompare(b.name));
|
||||
this.settingsService.saveSettings(settings);
|
||||
|
||||
this.configuredLanguagesChanged.next(settings.configuredLanguages);
|
||||
|
||||
if (settings.configuredLanguages.length === 1) {
|
||||
this.setSelectedLanguage(lang);
|
||||
}
|
||||
}
|
||||
|
||||
removeLanguage(lang: ILanguage) {
|
||||
var settings = this.settingsService.getSettings();
|
||||
settings.configuredLanguages = settings.configuredLanguages.filter(x => x.iso639 !== lang.iso639);
|
||||
this.settingsService.saveSettings(settings);
|
||||
|
||||
this.configuredLanguagesChanged.next(settings.configuredLanguages);
|
||||
|
||||
if (this.getSelectedLanguage().iso639 === lang.iso639) {
|
||||
if (settings.configuredLanguages.length > 0) {
|
||||
this.setSelectedLanguage(settings.configuredLanguages[0]);
|
||||
} else {
|
||||
this.setSelectedLanguage(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
searchLanguage(input: string): ILanguage[] {
|
||||
if (!input) return [];
|
||||
|
||||
const avLangs = this.getAllAvaialbleLaguages();
|
||||
let found = avLangs.filter(x => x.name.toLowerCase().includes(input.toLowerCase()) || x.iso639.toLowerCase().includes(input.toLowerCase()));
|
||||
found.sort((a, b) => a.name.localeCompare(b.name));
|
||||
found = found.slice(0, 5);
|
||||
return found;
|
||||
}
|
||||
|
||||
private getAllAvaialbleLaguages(): Language[] {
|
||||
return [
|
||||
new Language("aa", "Afar"),
|
||||
new Language("ab", "Abkhazian"),
|
||||
new Language("af", "Afrikaans"),
|
||||
new Language("ak", "Akan"),
|
||||
new Language("am", "Amharic"),
|
||||
new Language("an", "Aragonese"),
|
||||
new Language("ar", "Arabic"),
|
||||
new Language("as", "Assamese"),
|
||||
new Language("av", "Avar"),
|
||||
new Language("ay", "Aymara"),
|
||||
new Language("az", "Azerbaijani"),
|
||||
new Language("ba", "Bashkir"),
|
||||
new Language("be", "Belarusian"),
|
||||
new Language("bg", "Bulgarian"),
|
||||
new Language("bh", "Bihari"),
|
||||
new Language("bi", "Bislama"),
|
||||
new Language("bm", "Bambara"),
|
||||
new Language("bn", "Bengali"),
|
||||
new Language("bo", "Tibetan"),
|
||||
new Language("br", "Breton"),
|
||||
new Language("bs", "Bosnian"),
|
||||
new Language("ca", "Catalan"),
|
||||
new Language("ce", "Chechen"),
|
||||
new Language("ch", "Chamorro"),
|
||||
new Language("co", "Corsican"),
|
||||
new Language("cr", "Cree"),
|
||||
new Language("cs", "Czech"),
|
||||
new Language("cu", "Old Church Slavonic"),
|
||||
new Language("cv", "Chuvash"),
|
||||
new Language("cy", "Welsh"),
|
||||
new Language("da", "Danish"),
|
||||
new Language("de", "German"),
|
||||
new Language("dv", "Divehi"),
|
||||
new Language("dz", "Dzongkha"),
|
||||
new Language("ee", "Ewe"),
|
||||
new Language("el", "Greek"),
|
||||
new Language("en", "English"),
|
||||
new Language("eo", "Esperanto"),
|
||||
new Language("es", "Spanish"),
|
||||
new Language("et", "Estonian"),
|
||||
new Language("eu", "Basque"),
|
||||
new Language("fa", "Persian"),
|
||||
new Language("ff", "Peul"),
|
||||
new Language("fi", "Finnish"),
|
||||
new Language("fj", "Fijian"),
|
||||
new Language("fo", "Faroese"),
|
||||
new Language("fr", "French"),
|
||||
new Language("fy", "West Frisian"),
|
||||
new Language("ga", "Irish"),
|
||||
new Language("gd", "Scottish Gaelic"),
|
||||
new Language("gl", "Galician"),
|
||||
new Language("gn", "Guarani"),
|
||||
new Language("gu", "Gujarati"),
|
||||
new Language("gv", "Manx"),
|
||||
new Language("ha", "Hausa"),
|
||||
new Language("he", "Hebrew"),
|
||||
new Language("hi", "Hindi"),
|
||||
new Language("ho", "Hiri Motu"),
|
||||
new Language("hr", "Croatian"),
|
||||
new Language("ht", "Haitian"),
|
||||
new Language("hu", "Hungarian"),
|
||||
new Language("hy", "Armenian"),
|
||||
new Language("hz", "Herero"),
|
||||
new Language("ia", "Interlingua"),
|
||||
new Language("id", "Indonesian"),
|
||||
new Language("ie", "Interlingue"),
|
||||
new Language("ig", "Igbo"),
|
||||
new Language("ii", "Sichuan Yi"),
|
||||
new Language("ik", "Inupiak"),
|
||||
new Language("io", "Ido"),
|
||||
new Language("is", "Icelandic"),
|
||||
new Language("it", "Italian"),
|
||||
new Language("iu", "Inuktitut"),
|
||||
new Language("ja", "Japanese"),
|
||||
new Language("jv", "Javanese"),
|
||||
new Language("ka", "Georgian"),
|
||||
new Language("kg", "Kongo"),
|
||||
new Language("ki", "Kikuyu"),
|
||||
new Language("kj", "Kuanyama"),
|
||||
new Language("kk", "Kazakh"),
|
||||
new Language("kl", "Greenlandic"),
|
||||
new Language("km", "Cambodian"),
|
||||
new Language("kn", "Kannada"),
|
||||
new Language("ko", "Korean"),
|
||||
new Language("kr", "Kanuri"),
|
||||
new Language("ks", "Kashmiri"),
|
||||
new Language("ku", "Kurdish"),
|
||||
new Language("kv", "Komi"),
|
||||
new Language("kw", "Cornish"),
|
||||
new Language("ky", "Kirghiz"),
|
||||
new Language("la", "Latin"),
|
||||
new Language("lb", "Luxembourgish"),
|
||||
new Language("lg", "Ganda"),
|
||||
new Language("li", "Limburgian"),
|
||||
new Language("ln", "Lingala"),
|
||||
new Language("lo", "Laotian"),
|
||||
new Language("lt", "Lithuanian"),
|
||||
new Language("lu", "Luba-Katanga"),
|
||||
new Language("lv", "Latvian"),
|
||||
new Language("mg", "Malagasy"),
|
||||
new Language("mh", "Marshallese"),
|
||||
new Language("mi", "Maori"),
|
||||
new Language("mk", "Macedonian"),
|
||||
new Language("ml", "Malayalam"),
|
||||
new Language("mn", "Mongolian"),
|
||||
new Language("mo", "Moldovan"),
|
||||
new Language("mr", "Marathi"),
|
||||
new Language("ms", "Malay"),
|
||||
new Language("mt", "Maltese"),
|
||||
new Language("my", "Burmese"),
|
||||
new Language("na", "Nauruan"),
|
||||
new Language("nb", "Norwegian Bokmål"),
|
||||
new Language("nd", "North Ndebele"),
|
||||
new Language("ne", "Nepali"),
|
||||
new Language("ng", "Ndonga"),
|
||||
new Language("nl", "Dutch"),
|
||||
new Language("nn", "Norwegian Nynorsk"),
|
||||
new Language("no", "Norwegian"),
|
||||
new Language("nr", "South Ndebele"),
|
||||
new Language("nv", "Navajo"),
|
||||
new Language("ny", "Chichewa"),
|
||||
new Language("oc", "Occitan"),
|
||||
new Language("oj", "Ojibwa"),
|
||||
new Language("om", "Oromo"),
|
||||
new Language("or", "Oriya"),
|
||||
new Language("os", "Ossetian"),
|
||||
new Language("pa", "Panjabi"),
|
||||
new Language("pi", "Pali"),
|
||||
new Language("pl", "Polish"),
|
||||
new Language("ps", "Pashto"),
|
||||
new Language("pt", "Portuguese"),
|
||||
new Language("qu", "Quechua"),
|
||||
new Language("rm", "Raeto Romance"),
|
||||
new Language("rn", "Kirundi"),
|
||||
new Language("ro", "Romanian"),
|
||||
new Language("ru", "Russian"),
|
||||
new Language("rw", "Rwandi"),
|
||||
new Language("sa", "Sanskrit"),
|
||||
new Language("sc", "Sardinian"),
|
||||
new Language("sd", "Sindhi"),
|
||||
new Language("se", "Northern Sami"),
|
||||
new Language("sg", "Sango"),
|
||||
new Language("sh", "Serbo-Croatian"),
|
||||
new Language("si", "Sinhalese"),
|
||||
new Language("sk", "Slovak"),
|
||||
new Language("sl", "Slovenian"),
|
||||
new Language("sm", "Samoan"),
|
||||
new Language("sn", "Shona"),
|
||||
new Language("so", "Somalia"),
|
||||
new Language("sq", "Albanian"),
|
||||
new Language("sr", "Serbian"),
|
||||
new Language("ss", "Swati"),
|
||||
new Language("st", "Southern Sotho"),
|
||||
new Language("su", "Sundanese"),
|
||||
new Language("sv", "Swedish"),
|
||||
new Language("sw", "Swahili"),
|
||||
new Language("ta", "Tamil"),
|
||||
new Language("te", "Telugu"),
|
||||
new Language("tg", "Tajik"),
|
||||
new Language("th", "Thai"),
|
||||
new Language("ti", "Tigrinya"),
|
||||
new Language("tk", "Turkmen"),
|
||||
new Language("tl", "Tagalog"),
|
||||
new Language("tn", "Tswana"),
|
||||
new Language("to", "Tonga"),
|
||||
new Language("tr", "Turkish"),
|
||||
new Language("ts", "Tsonga"),
|
||||
new Language("tt", "Tatar"),
|
||||
new Language("tw", "Twi"),
|
||||
new Language("ty", "Tahitian"),
|
||||
new Language("ug", "Uyghur"),
|
||||
new Language("uk", "Ukrainian"),
|
||||
new Language("ur", "Urdu"),
|
||||
new Language("uz", "Uzbek"),
|
||||
new Language("ve", "Venda"),
|
||||
new Language("vi", "Vietnamese"),
|
||||
new Language("vo", "Volapük"),
|
||||
new Language("wa", "Walloon"),
|
||||
new Language("wo", "Wolof"),
|
||||
new Language("xh", "Xhosa"),
|
||||
new Language("yi", "Yiddish"),
|
||||
new Language("yo", "Yoruba"),
|
||||
new Language("za", "Zhuang"),
|
||||
new Language("zh", "Chinese"),
|
||||
new Language("zu", "Zulu"),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export class Language {
|
||||
constructor(public iso639: string, public name: string) {
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { Store } from '@ngxs/store';
|
||||
|
||||
import { Account, Status, Results, Context, Relationship, Instance, Attachment, Notification, List, Poll, Emoji, Conversation, ScheduledStatus, TokenData, Tag } from "./models/mastodon.interfaces";
|
||||
import { Account, Status, Results, Context, Relationship, Instance, Attachment, Notification, List, Poll, Emoji, Conversation, ScheduledStatus, TokenData, Tag, Translation } from "./models/mastodon.interfaces";
|
||||
import { AccountInfo, UpdateAccount } from '../states/accounts.state';
|
||||
import { StreamTypeEnum, StreamElement } from '../states/streams.state';
|
||||
import { FavoriteResult, VisibilityEnum, PollParameters, MastodonService, BookmarkResult, FollowingResult } from './mastodon.service';
|
||||
|
@ -96,6 +96,13 @@ export class MastodonWrapperService {
|
|||
return this.mastodonService.getInstance(instance);
|
||||
}
|
||||
|
||||
translate(account: AccountInfo, statusId: string, lang: string): Promise<Translation>{
|
||||
return this.refreshAccountIfNeeded(account)
|
||||
.then((refreshedAccount: AccountInfo) => {
|
||||
return this.mastodonService.translate(refreshedAccount, statusId, lang);
|
||||
});
|
||||
}
|
||||
|
||||
retrieveAccountDetails(account: AccountInfo): Promise<Account> {
|
||||
return this.refreshAccountIfNeeded(account)
|
||||
.then((refreshedAccount: AccountInfo) => {
|
||||
|
@ -117,17 +124,17 @@ export class MastodonWrapperService {
|
|||
});
|
||||
}
|
||||
|
||||
postNewStatus(account: AccountInfo, status: string, visibility: VisibilityEnum, spoiler: string = null, in_reply_to_id: string = null, mediaIds: string[], poll: PollParameters = null, scheduled_at: string = null): Promise<Status> {
|
||||
postNewStatus(account: AccountInfo, status: string, visibility: VisibilityEnum, spoiler: string = null, in_reply_to_id: string = null, mediaIds: string[], poll: PollParameters = null, scheduled_at: string = null, lang: string = null): Promise<Status> {
|
||||
return this.refreshAccountIfNeeded(account)
|
||||
.then((refreshedAccount: AccountInfo) => {
|
||||
return this.mastodonService.postNewStatus(refreshedAccount, status, visibility, spoiler, in_reply_to_id, mediaIds, poll, scheduled_at);
|
||||
return this.mastodonService.postNewStatus(refreshedAccount, status, visibility, spoiler, in_reply_to_id, mediaIds, poll, scheduled_at, lang);
|
||||
});
|
||||
}
|
||||
|
||||
editStatus(account: AccountInfo, statusId: string, status: string, visibility: VisibilityEnum, spoiler: string = null, in_reply_to_id: string = null, attachements: Attachment[], poll: PollParameters = null, scheduled_at: string = null): Promise<Status> {
|
||||
editStatus(account: AccountInfo, statusId: string, status: string, visibility: VisibilityEnum, spoiler: string = null, in_reply_to_id: string = null, attachements: Attachment[], poll: PollParameters = null, scheduled_at: string = null, lang: string = null): Promise<Status> {
|
||||
return this.refreshAccountIfNeeded(account)
|
||||
.then((refreshedAccount: AccountInfo) => {
|
||||
return this.mastodonService.editStatus(refreshedAccount, statusId, status, visibility, spoiler, in_reply_to_id, attachements, poll, scheduled_at);
|
||||
return this.mastodonService.editStatus(refreshedAccount, statusId, status, visibility, spoiler, in_reply_to_id, attachements, poll, scheduled_at, lang);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ 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, List, Poll, Emoji, Conversation, ScheduledStatus, Tag, Instancev2, Instancev1 } from "./models/mastodon.interfaces";
|
||||
import { Account, Status, Results, Context, Relationship, Instance, Attachment, Notification, List, Poll, Emoji, Conversation, ScheduledStatus, Tag, Instancev2, Instancev1, Translation } from "./models/mastodon.interfaces";
|
||||
import { AccountInfo } from '../states/accounts.state';
|
||||
import { StreamTypeEnum, StreamElement } from '../states/streams.state';
|
||||
|
||||
|
@ -21,6 +21,13 @@ export class MastodonService {
|
|||
});
|
||||
}
|
||||
|
||||
translate(account: AccountInfo, statusId: string, lang: string): Promise<Translation>{
|
||||
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
|
||||
let route = `https://${account.instance}${this.apiRoutes.translate.replace('{0}', statusId)}`;
|
||||
|
||||
return this.httpClient.post<Translation>(route, { 'lang': lang }, { headers: headers }).toPromise();
|
||||
}
|
||||
|
||||
retrieveAccountDetails(account: AccountInfo): Promise<Account> {
|
||||
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
|
||||
return this.httpClient.get<Account>('https://' + account.instance + this.apiRoutes.getCurrentAccount, { headers: headers }).toPromise();
|
||||
|
@ -88,7 +95,7 @@ export class MastodonService {
|
|||
return origString.replace(regEx, "");
|
||||
};
|
||||
|
||||
postNewStatus(account: AccountInfo, status: string, visibility: VisibilityEnum, spoiler: string = null, in_reply_to_id: string = null, mediaIds: string[], poll: PollParameters = null, scheduled_at: string = null): Promise<Status> {
|
||||
postNewStatus(account: AccountInfo, status: string, visibility: VisibilityEnum, spoiler: string = null, in_reply_to_id: string = null, mediaIds: string[], poll: PollParameters = null, scheduled_at: string = null, lang: string = null): Promise<Status> {
|
||||
const url = `https://${account.instance}${this.apiRoutes.postNewStatus}`;
|
||||
|
||||
const statusData = new StatusData();
|
||||
|
@ -106,10 +113,16 @@ export class MastodonService {
|
|||
if (in_reply_to_id) {
|
||||
statusData.in_reply_to_id = in_reply_to_id;
|
||||
}
|
||||
|
||||
if (spoiler) {
|
||||
statusData.sensitive = true;
|
||||
statusData.spoiler_text = spoiler;
|
||||
}
|
||||
|
||||
if(lang) {
|
||||
statusData.language = lang;
|
||||
}
|
||||
|
||||
switch (visibility) {
|
||||
case VisibilityEnum.Public:
|
||||
statusData.visibility = 'public';
|
||||
|
@ -132,7 +145,7 @@ export class MastodonService {
|
|||
return this.httpClient.post<Status>(url, statusData, { headers: headers }).toPromise();
|
||||
}
|
||||
|
||||
editStatus(account: AccountInfo, statusId: string, status: string, visibility: VisibilityEnum, spoiler: string = null, in_reply_to_id: string = null, attachements: Attachment[], poll: PollParameters = null, scheduled_at: string = null): Promise<Status> {
|
||||
editStatus(account: AccountInfo, statusId: string, status: string, visibility: VisibilityEnum, spoiler: string = null, in_reply_to_id: string = null, attachements: Attachment[], poll: PollParameters = null, scheduled_at: string = null, lang: string = null): Promise<Status> {
|
||||
const url = `https://${account.instance}${this.apiRoutes.editStatus.replace('{0}', statusId)}`;
|
||||
|
||||
const statusData = new StatusData();
|
||||
|
@ -151,10 +164,16 @@ export class MastodonService {
|
|||
if (in_reply_to_id) {
|
||||
statusData.in_reply_to_id = in_reply_to_id;
|
||||
}
|
||||
|
||||
if (spoiler) {
|
||||
statusData.sensitive = true;
|
||||
statusData.spoiler_text = spoiler;
|
||||
}
|
||||
|
||||
if(lang) {
|
||||
statusData.language = lang;
|
||||
}
|
||||
|
||||
switch (visibility) {
|
||||
case VisibilityEnum.Public:
|
||||
statusData.visibility = 'public';
|
||||
|
@ -651,6 +670,8 @@ class StatusData {
|
|||
spoiler_text: string;
|
||||
visibility: string;
|
||||
// scheduled_at: string;
|
||||
|
||||
language: string;
|
||||
}
|
||||
|
||||
class MediaAttributes {
|
||||
|
|
|
@ -80,4 +80,5 @@ export class ApiRoutes {
|
|||
followHashtag = '/api/v1/tags/{0}/follow';
|
||||
unfollowHashtag = '/api/v1/tags/{0}/unfollow';
|
||||
getHashtag = '/api/v1/tags/{0}';
|
||||
translate = '/api/v1/statuses/{0}/translate';
|
||||
}
|
||||
|
|
|
@ -132,7 +132,8 @@ export interface Instancev2 extends Instance {
|
|||
|
||||
export interface Instancev2Configuration {
|
||||
urls: Instancev2Urls;
|
||||
statuses: Instancev2Statuses
|
||||
statuses: Instancev2Statuses;
|
||||
translation: Instancev2Translation;
|
||||
}
|
||||
|
||||
export interface InstanceUrls {
|
||||
|
@ -147,6 +148,10 @@ export interface Instancev2Statuses {
|
|||
max_characters: number;
|
||||
}
|
||||
|
||||
export interface Instancev2Translation {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
export interface Mention {
|
||||
url: string;
|
||||
username: string;
|
||||
|
@ -284,4 +289,12 @@ export interface Tag {
|
|||
url: string;
|
||||
history: TagHistory[];
|
||||
following: boolean;
|
||||
}
|
||||
|
||||
export interface Translation {
|
||||
content: string;
|
||||
language: string;
|
||||
detected_source_language: string;
|
||||
provider: string;
|
||||
spoiler_text: string;
|
||||
}
|
|
@ -33,6 +33,11 @@ export class SettingsService {
|
|||
this.saveSettings(settings);
|
||||
}
|
||||
|
||||
if(!settings.configuredLanguages){
|
||||
settings.configuredLanguages = [];
|
||||
this.saveSettings(settings);
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
|
|
@ -96,7 +96,7 @@ export class StreamingWrapper {
|
|||
}
|
||||
|
||||
private pullNewNotifications() {
|
||||
this.mastodonService.getNotifications(this.account, ['update'], null, this.since_id_notifications, 10)
|
||||
this.mastodonService.getNotifications(this.account, [], null, this.since_id_notifications, 10)
|
||||
.then((notifications: Notification[]) => {
|
||||
//notifications = notifications.sort((a, b) => a.id.localeCompare(b.id));
|
||||
let soundMuted = !this.since_id_notifications;
|
||||
|
@ -168,9 +168,6 @@ export class StreamingWrapper {
|
|||
newUpdate.type = EventEnum.unknow;
|
||||
}
|
||||
|
||||
if(newUpdate.notification && newUpdate.notification.type === 'update') { //FIXME: disabling edition update until supported
|
||||
return;
|
||||
}
|
||||
|
||||
this.statusUpdateSubjet.next(newUpdate);
|
||||
}
|
||||
|
|
|
@ -77,21 +77,47 @@ export class ToolsService {
|
|||
return Promise.resolve(this.instanceInfos[acc.instance]);
|
||||
} else {
|
||||
return this.mastodonService.getInstance(acc.instance)
|
||||
.then(instance => {
|
||||
let type = InstanceType.Mastodon;
|
||||
if (instance.version.toLowerCase().includes('pleroma')) {
|
||||
type = InstanceType.Pleroma;
|
||||
} else if (instance.version.toLowerCase().includes('+glitch')) {
|
||||
type = InstanceType.GlitchSoc;
|
||||
} else if (instance.version.toLowerCase().includes('+florence')) {
|
||||
type = InstanceType.Florence;
|
||||
} else if (instance.version.toLowerCase().includes('pixelfed')) {
|
||||
type = InstanceType.Pixelfed;
|
||||
}
|
||||
|
||||
.then(instance => {
|
||||
const splittedVersion = instance.version.split('.');
|
||||
const major = +splittedVersion[0];
|
||||
const minor = +splittedVersion[1];
|
||||
let major = +splittedVersion[0];
|
||||
let minor = +splittedVersion[1];
|
||||
|
||||
let altMajor = 0;
|
||||
let altMinor = 0;
|
||||
|
||||
let type = InstanceType.Mastodon;
|
||||
|
||||
const version = instance.version.toLowerCase();
|
||||
|
||||
if (version.includes('pleroma')) {
|
||||
type = InstanceType.Pleroma;
|
||||
|
||||
const pleromaVersion = version.split('pleroma ')[1].split('.');
|
||||
altMajor = +pleromaVersion[0];
|
||||
altMinor = +pleromaVersion[1];
|
||||
|
||||
} else if (version.includes('+glitch')) {
|
||||
type = InstanceType.GlitchSoc;
|
||||
} else if (version.includes('+florence')) {
|
||||
type = InstanceType.Florence;
|
||||
} else if (version.includes('pixelfed')) {
|
||||
type = InstanceType.Pixelfed;
|
||||
} else if (version.includes('takahe')) {
|
||||
type = InstanceType.Takahe;
|
||||
major = 1; //FIXME: when a clearer set of feature are available
|
||||
minor = 0; //FIXME: when a clearer set of feature are available
|
||||
|
||||
const takaheVersion = version.split('takahe/')[1].split('.');
|
||||
altMajor = +takaheVersion[0];
|
||||
altMinor = +takaheVersion[1];
|
||||
|
||||
} else if (version.includes('akkoma')) {
|
||||
type = InstanceType.Akkoma;
|
||||
|
||||
const akkomaVersion = version.split('akkoma ')[1].split('.');
|
||||
altMajor = +akkomaVersion[0];
|
||||
altMinor = +akkomaVersion[1];
|
||||
}
|
||||
|
||||
let streamingApi = "";
|
||||
|
||||
|
@ -108,7 +134,7 @@ export class ToolsService {
|
|||
streamingApi = instanceV1.urls.streaming_api;
|
||||
}
|
||||
|
||||
let instanceInfo = new InstanceInfo(type, major, minor, streamingApi);
|
||||
let instanceInfo = new InstanceInfo(type, major, minor, streamingApi, altMajor, altMinor);
|
||||
this.instanceInfos[acc.instance] = instanceInfo;
|
||||
|
||||
return instanceInfo;
|
||||
|
@ -116,6 +142,25 @@ export class ToolsService {
|
|||
}
|
||||
}
|
||||
|
||||
isBookmarksAreAvailable(account: AccountInfo): Promise<boolean> {
|
||||
return this.getInstanceInfo(account)
|
||||
.then((instance: InstanceInfo) => {
|
||||
if (instance.major == 3 && instance.minor >= 1
|
||||
|| instance.major > 3
|
||||
|| instance.type === InstanceType.Pleroma && instance.altMajor >= 2 && instance.altMinor >= 5
|
||||
|| instance.type === InstanceType.Akkoma && instance.altMajor >= 3 && instance.altMinor >= 9
|
||||
|| instance.type === InstanceType.Takahe && instance.altMajor >= 0 && instance.altMinor >= 9) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
getAvatar(acc: AccountInfo): Promise<string> {
|
||||
if (this.accountAvatar[acc.id]) {
|
||||
return Promise.resolve(this.accountAvatar[acc.id]);
|
||||
|
@ -247,16 +292,20 @@ export class InstanceInfo {
|
|||
public readonly type: InstanceType,
|
||||
public readonly major: number,
|
||||
public readonly minor: number,
|
||||
public readonly streamingApi: string) {
|
||||
public readonly streamingApi: string,
|
||||
public readonly altMajor: number,
|
||||
public readonly altMinor: number) {
|
||||
}
|
||||
}
|
||||
|
||||
export enum InstanceType {
|
||||
Mastodon = 1,
|
||||
Pleroma = 2,
|
||||
GlitchSoc = 3,
|
||||
Pleroma = 2, // "2.7.2 (compatible; Pleroma 2.5.1)"
|
||||
GlitchSoc = 3, // "4.1.5+glitch_0801_3b49b5a"
|
||||
Florence = 4,
|
||||
Pixelfed = 5
|
||||
Pixelfed = 5,
|
||||
Takahe = 6, // "takahe/0.9.0"
|
||||
Akkoma = 7, // "2.7.2 (compatible; Akkoma 3.9.2-develop)"
|
||||
}
|
||||
|
||||
export class StatusWithCwPolicyResult {
|
||||
|
|
|
@ -66,7 +66,7 @@ export class UserNotificationService {
|
|||
this.notificationService.notifyHttpError(err, account);
|
||||
});
|
||||
|
||||
let getNotificationPromise = this.mastodonService.getNotifications(account, ['mention', 'update'], null, null, 10)
|
||||
let getNotificationPromise = this.mastodonService.getNotifications(account, ['mention'], null, null, 10)
|
||||
.then((notifications: Notification[]) => {
|
||||
this.processMentionsAndNotifications(account, notifications, NotificationTypeEnum.UserNotification);
|
||||
})
|
||||
|
|
|
@ -80,7 +80,15 @@ export class GlobalSettings {
|
|||
|
||||
columnSwitchingWinAlt = false;
|
||||
|
||||
accountSettings: AccountSettings[] = [];
|
||||
accountSettings: AccountSettings[] = [];
|
||||
|
||||
configuredLanguages: ILanguage[] = [];
|
||||
selectedLanguage: ILanguage;
|
||||
}
|
||||
|
||||
export interface ILanguage {
|
||||
iso639: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface SettingsStateModel {
|
||||
|
@ -171,6 +179,8 @@ export class SettingsState {
|
|||
newSettings.autoFollowOnListEnabled = oldSettings.autoFollowOnListEnabled;
|
||||
newSettings.twitterBridgeEnabled = oldSettings.twitterBridgeEnabled;
|
||||
newSettings.twitterBridgeInstance = oldSettings.twitterBridgeInstance;
|
||||
newSettings.configuredLanguages = oldSettings.configuredLanguages;
|
||||
newSettings.selectedLanguage = oldSettings.selectedLanguage;
|
||||
|
||||
return newSettings;
|
||||
}
|
||||
|
|
|
@ -1,17 +1,27 @@
|
|||
@import "variables";
|
||||
|
||||
::ng-deep .ngx-contextmenu {
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||
-o-box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||
$shadow: 0.4;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, $shadow);
|
||||
-moz-box-shadow: 0 0 10px rgba(0, 0, 0, $shadow);
|
||||
-webkit-box-shadow: 0 0 10px rgba(0, 0, 0, $shadow);
|
||||
-o-box-shadow: 0 0 10px rgba(0, 0, 0, $shadow);
|
||||
|
||||
padding: 0;
|
||||
|
||||
border-radius: 7px;
|
||||
overflow: hidden;
|
||||
|
||||
& .dropdown-menu {
|
||||
//border: solid 1px $context-menu-border-color;
|
||||
border: none;
|
||||
background-color: $context-menu-background;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border-radius: 0px;
|
||||
|
||||
border-radius: 7px;
|
||||
overflow: hidden;
|
||||
// padding: 2px 0;
|
||||
// border-radius: 2px;
|
||||
//border: solid 2px $context-menu-border-color;
|
||||
|
@ -44,6 +54,6 @@
|
|||
}
|
||||
|
||||
& .divider {
|
||||
border-top: solid 2px $context-menu-border-color;
|
||||
border-top: solid 1px $context-menu-border-color;
|
||||
}
|
||||
}
|
|
@ -21,6 +21,7 @@ $status-primary-color: #fff;
|
|||
$status-secondary-color: #4e5572;
|
||||
$status-links-color: #d9e1e8;
|
||||
$boost-color : #5098eb;
|
||||
$update-color : #95e470;
|
||||
$favorite-color: #ffc16f;
|
||||
$bookmarked-color: #ff5050;
|
||||
|
||||
|
@ -52,9 +53,12 @@ $column-background: #0f111a;
|
|||
$card-border-color: #2b344d;
|
||||
|
||||
$context-menu-background: #d9e1e8;
|
||||
$context-menu-background: #ffffff;
|
||||
$context-menu-background-hover: #a9c9e6;
|
||||
$context-menu-background-hover: #d7dfeb;
|
||||
$context-menu-font-color: #000000;
|
||||
$context-menu-border-color: #c0cdd9;
|
||||
$context-menu-border-color: #cbd3df;
|
||||
|
||||
$direct-message-background: #090a0f;
|
||||
|
||||
|
|
Loading…
Reference in New Issue