but add CW on content containing:
diff --git a/src/app/components/floating-column/settings/settings.component.scss b/src/app/components/floating-column/settings/settings.component.scss
index f2fbd754..cd272593 100644
--- a/src/app/components/floating-column/settings/settings.component.scss
+++ b/src/app/components/floating-column/settings/settings.component.scss
@@ -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;
diff --git a/src/app/components/floating-column/settings/settings.component.ts b/src/app/components/floating-column/settings/settings.component.ts
index 7c364388..051b23f5 100644
--- a/src/app/components/floating-column/settings/settings.component.ts
+++ b/src/app/components/floating-column/settings/settings.component.ts
@@ -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) {
diff --git a/src/app/components/stream/hashtag/hashtag.component.scss b/src/app/components/stream/hashtag/hashtag.component.scss
index cdf3620f..07521008 100644
--- a/src/app/components/stream/hashtag/hashtag.component.scss
+++ b/src/app/components/stream/hashtag/hashtag.component.scss
@@ -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;
diff --git a/src/app/components/stream/status/action-bar/action-bar.component.ts b/src/app/components/stream/status/action-bar/action-bar.component.ts
index 6d279743..b067fbf5 100644
--- a/src/app/components/stream/status/action-bar/action-bar.component.ts
+++ b/src/app/components/stream/status/action-bar/action-bar.component.ts
@@ -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;
diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.scss b/src/app/components/stream/status/databinded-text/databinded-text.component.scss
index 32946ee5..cdbfa121 100644
--- a/src/app/components/stream/status/databinded-text/databinded-text.component.scss
+++ b/src/app/components/stream/status/databinded-text/databinded-text.component.scss
@@ -64,6 +64,7 @@ $expand-color: $column-color;
& p {
margin: 0px;
+ white-space: pre-wrap;
//font-size: .9em;
// font-size: 14px;
}
diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.ts
index f9fe2ec0..f5de70de 100644
--- a/src/app/components/stream/status/databinded-text/databinded-text.component.ts
+++ b/src/app/components/stream/status/databinded-text/databinded-text.component.ts
@@ -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 += `
#${extractedHashtag}`;
+ this.processedText += `
#${extractedHashtag}`;
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 =
this.contentElement.nativeElement.querySelectorAll(`.${classname}`);
diff --git a/src/app/components/stream/status/status-translate/status-translate.component.html b/src/app/components/stream/status/status-translate/status-translate.component.html
new file mode 100644
index 00000000..9314ba13
--- /dev/null
+++ b/src/app/components/stream/status/status-translate/status-translate.component.html
@@ -0,0 +1,6 @@
+
+
+
Translated by {{translatedBy}} revert
+
diff --git a/src/app/components/stream/status/status-translate/status-translate.component.scss b/src/app/components/stream/status/status-translate/status-translate.component.scss
new file mode 100644
index 00000000..59d1765c
--- /dev/null
+++ b/src/app/components/stream/status/status-translate/status-translate.component.scss
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/app/components/stream/status/status-translate/status-translate.component.spec.ts b/src/app/components/stream/status/status-translate/status-translate.component.spec.ts
new file mode 100644
index 00000000..af6146f7
--- /dev/null
+++ b/src/app/components/stream/status/status-translate/status-translate.component.spec.ts
@@ -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;
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ declarations: [ StatusTranslateComponent ]
+ })
+ .compileComponents();
+ }));
+
+ beforeEach(() => {
+ fixture = TestBed.createComponent(StatusTranslateComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/src/app/components/stream/status/status-translate/status-translate.component.ts b/src/app/components/stream/status/status-translate/status-translate.component.ts
new file mode 100644
index 00000000..87d55804
--- /dev/null
+++ b/src/app/components/stream/status/status-translate/status-translate.component.ts
@@ -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();
+
+ 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;
+ }
+}
diff --git a/src/app/components/stream/status/status.component.html b/src/app/components/stream/status/status.component.html
index be44235a..7257da73 100644
--- a/src/app/components/stream/status/status.component.html
+++ b/src/app/components/stream/status/status.component.html
@@ -34,6 +34,17 @@
boosted your status
+
@@ -98,10 +109,12 @@
sensitive content
-
+
+
diff --git a/src/app/components/stream/status/status.component.scss b/src/app/components/stream/status/status.component.scss
index aa1796c5..da273f0e 100644
--- a/src/app/components/stream/status/status.component.scss
+++ b/src/app/components/stream/status/status.component.scss
@@ -258,6 +258,10 @@
color: $boost-color;
}
+.update {
+ color: $update-color;
+}
+
.favorite {
color: $favorite-color;
}
@@ -272,4 +276,4 @@
&__label{
color: $status-secondary-color;
}
-}
+}
\ No newline at end of file
diff --git a/src/app/components/stream/status/status.component.ts b/src/app/components/stream/status/status.component.ts
index 9cea97a7..6782037f 100644
--- a/src/app/components/stream/status/status.component.ts
+++ b/src/app/components/stream/status/status.component.ts
@@ -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 += `
@${m.username} `
}
}
- 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('
@')) {
+ statusContent = statusContent.replace('@', '@');
+ }
+ while (statusContent.includes('h')){
+ statusContent = statusContent.replace('h', 'h');
+ }
+ while (statusContent.includes('#')){
+ statusContent = statusContent.replace('#', '#');
+ }
+
+ 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...
diff --git a/src/app/components/stream/stream-notifications/stream-notifications.component.ts b/src/app/components/stream/stream-notifications/stream-notifications.component.ts
index 05d202d7..c79b4ed2 100644
--- a/src/app/components/stream/stream-notifications/stream-notifications.component.ts
+++ b/src/app/components/stream/stream-notifications/stream-notifications.component.ts
@@ -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;
diff --git a/src/app/services/electron.service.spec.ts b/src/app/services/electron.service.spec.ts
new file mode 100644
index 00000000..38fe2f0e
--- /dev/null
+++ b/src/app/services/electron.service.spec.ts
@@ -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();
+ });
+});
diff --git a/src/app/services/electron.service.ts b/src/app/services/electron.service.ts
new file mode 100644
index 00000000..65c82c1c
--- /dev/null
+++ b/src/app/services/electron.service.ts
@@ -0,0 +1,20 @@
+import { Injectable } from '@angular/core';
+
+@Injectable({
+ providedIn: 'root'
+})
+export class MyElectronService {
+ constructor() {
+ }
+
+ setLang(lang: string) {
+ try {
+ if ((window).api) {
+ (window).api.send("changeSpellchecker", lang);
+ }
+ }
+ catch (err) {
+ console.error(err);
+ }
+ }
+}
diff --git a/src/app/services/instances-info.service.ts b/src/app/services/instances-info.service.ts
index 3d64fc5e..b4a2ece5 100644
--- a/src/app/services/instances-info.service.ts
+++ b/src/app/services/instances-info.service.ts
@@ -11,6 +11,7 @@ import { AccountInfo } from '../states/accounts.state';
export class InstancesInfoService {
private defaultMaxChars = 500;
private cachedMaxInstanceChar: { [id: string]: Promise; } = {};
+ private cachedTranslationAvailability: { [id: string]: Promise; } = {};
private cachedDefaultPrivacy: { [id: string]: Promise; } = {};
constructor(private mastodonService: MastodonWrapperService) { }
@@ -65,4 +66,30 @@ export class InstancesInfoService {
}
return this.cachedDefaultPrivacy[instance];
}
+
+ getTranslationAvailability(account: AccountInfo): Promise {
+ 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 = instance;
+ if (instanceV2
+ && instanceV2.configuration
+ && instanceV2.configuration.translation)
+ return instanceV2.configuration.translation.enabled;
+ } else {
+ const instanceV1 = instance;
+ if (instanceV1 && instanceV1.max_toot_chars)
+ return false;
+ }
+
+ return false;
+ })
+ .catch(() => {
+ return false;
+ });
+ }
+ return this.cachedTranslationAvailability[instance];
+ }
}
diff --git a/src/app/services/language.service.spec.ts b/src/app/services/language.service.spec.ts
new file mode 100644
index 00000000..ee77cfbb
--- /dev/null
+++ b/src/app/services/language.service.spec.ts
@@ -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();
+ });
+});
diff --git a/src/app/services/language.service.ts b/src/app/services/language.service.ts
new file mode 100644
index 00000000..a51b2090
--- /dev/null
+++ b/src/app/services/language.service.ts
@@ -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([]);
+ selectedLanguageChanged = new BehaviorSubject(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) {
+ }
+}
diff --git a/src/app/services/mastodon-wrapper.service.ts b/src/app/services/mastodon-wrapper.service.ts
index c7c51b88..38717d37 100644
--- a/src/app/services/mastodon-wrapper.service.ts
+++ b/src/app/services/mastodon-wrapper.service.ts
@@ -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{
+ return this.refreshAccountIfNeeded(account)
+ .then((refreshedAccount: AccountInfo) => {
+ return this.mastodonService.translate(refreshedAccount, statusId, lang);
+ });
+ }
+
retrieveAccountDetails(account: AccountInfo): Promise {
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 {
+ 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 {
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 {
+ 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 {
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);
});
}
diff --git a/src/app/services/mastodon.service.ts b/src/app/services/mastodon.service.ts
index 4b985817..e4ce63b2 100644
--- a/src/app/services/mastodon.service.ts
+++ b/src/app/services/mastodon.service.ts
@@ -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{
+ 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(route, { 'lang': lang }, { headers: headers }).toPromise();
+ }
+
retrieveAccountDetails(account: AccountInfo): Promise {
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
return this.httpClient.get('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 {
+ 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 {
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(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 {
+ 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 {
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 {
diff --git a/src/app/services/models/api.settings.ts b/src/app/services/models/api.settings.ts
index 50e8d614..3a204317 100644
--- a/src/app/services/models/api.settings.ts
+++ b/src/app/services/models/api.settings.ts
@@ -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';
}
diff --git a/src/app/services/models/mastodon.interfaces.ts b/src/app/services/models/mastodon.interfaces.ts
index ec31ddbf..492eede7 100644
--- a/src/app/services/models/mastodon.interfaces.ts
+++ b/src/app/services/models/mastodon.interfaces.ts
@@ -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;
}
\ No newline at end of file
diff --git a/src/app/services/settings.service.ts b/src/app/services/settings.service.ts
index b0727fa5..67e98681 100644
--- a/src/app/services/settings.service.ts
+++ b/src/app/services/settings.service.ts
@@ -33,6 +33,11 @@ export class SettingsService {
this.saveSettings(settings);
}
+ if(!settings.configuredLanguages){
+ settings.configuredLanguages = [];
+ this.saveSettings(settings);
+ }
+
return settings;
}
diff --git a/src/app/services/streaming.service.ts b/src/app/services/streaming.service.ts
index 92f91ff3..2e610657 100644
--- a/src/app/services/streaming.service.ts
+++ b/src/app/services/streaming.service.ts
@@ -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);
}
diff --git a/src/app/services/tools.service.ts b/src/app/services/tools.service.ts
index ea1da0d6..324dd61b 100644
--- a/src/app/services/tools.service.ts
+++ b/src/app/services/tools.service.ts
@@ -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 {
+ 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 {
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 {
diff --git a/src/app/services/user-notification.service.ts b/src/app/services/user-notification.service.ts
index 370d4dc1..6ca0fb9d 100644
--- a/src/app/services/user-notification.service.ts
+++ b/src/app/services/user-notification.service.ts
@@ -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);
})
diff --git a/src/app/states/settings.state.ts b/src/app/states/settings.state.ts
index 8fbb7afd..2c6b9f7b 100644
--- a/src/app/states/settings.state.ts
+++ b/src/app/states/settings.state.ts
@@ -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;
}
diff --git a/src/sass/_context-menu.scss b/src/sass/_context-menu.scss
index 5323dda0..0445c1f6 100644
--- a/src/sass/_context-menu.scss
+++ b/src/sass/_context-menu.scss
@@ -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;
}
}
\ No newline at end of file
diff --git a/src/sass/_variables.scss b/src/sass/_variables.scss
index 1e80c59d..54741cb5 100644
--- a/src/sass/_variables.scss
+++ b/src/sass/_variables.scss
@@ -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;