diff --git a/main-electron.js b/main-electron.js index 1249be75..02a9914d 100644 --- a/main-electron.js +++ b/main-electron.js @@ -1,15 +1,28 @@ +const { join } = require("path"); const { app, Menu, MenuItem, BrowserWindow, shell } = require("electron"); // Keep a global reference of the window object, if you don't, the window will // be closed automatically when the JavaScript object is garbage collected. let win; +const globalAny = global; + +if (process.env.NODE_ENV !== 'development') { + globalAny.__static = require('path').join(__dirname, '/assets/icons').replace(/\\/g, '\\\\'); +} function createWindow() { - // Create the browser window. + // Set icon + let icon = join(globalAny.__static, '/png/512x512.png'); + if (process.platform === "win32") { + icon = join(globalAny.__static, '/win/icon.ico'); + } + + // Create the browser window win = new BrowserWindow({ width: 377, height: 800, title: "Sengi", + icon: icon, backgroundColor: "#131925", useContentSize: true, webPreferences: { diff --git a/package.json b/package.json index 8a612428..5558d5cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sengi", - "version": "0.32.0", + "version": "0.33.0", "license": "AGPL-3.0-or-later", "main": "main-electron.js", "description": "A multi-account desktop client for Mastodon and Pleroma", diff --git a/src/app/components/floating-column/manage-account/notifications/notification/notification.component.html b/src/app/components/floating-column/manage-account/notifications/notification/notification.component.html index bc3f23ea..a91f1481 100644 --- a/src/app/components/floating-column/manage-account/notifications/notification/notification.component.html +++ b/src/app/components/floating-column/manage-account/notifications/notification/notification.component.html @@ -1,4 +1,35 @@
+
+
+ +
+
+ + submitted a follow request +
+ + + + +
+ +
diff --git a/src/app/components/floating-column/manage-account/notifications/notification/notification.component.scss b/src/app/components/floating-column/manage-account/notifications/notification/notification.component.scss index 0b156f04..c928bf96 100644 --- a/src/app/components/floating-column/manage-account/notifications/notification/notification.component.scss +++ b/src/app/components/floating-column/manage-account/notifications/notification/notification.component.scss @@ -46,6 +46,7 @@ color: $boost-color; } +$acccount-info-left: 70px; .follow-account { padding: 5px; height: 60px; @@ -62,8 +63,7 @@ height: 45px; border-radius: 2px; } - - $acccount-info-left: 70px; + &__display-name { position: absolute; top: 7px; @@ -81,5 +81,44 @@ left: $acccount-info-left; font-size: 13px; color: $status-links-color; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + width: calc(100% - #{$acccount-info-left}); + } +} + +.follow_request { + width: calc(100% - #{$acccount-info-left}); + margin-left: $acccount-info-left; + + &__link { + display: inline-block; + width: calc(50%); + padding: 2px; + + text-align: center; + color: rgb(182, 182, 182); + transition: all .2s; + + // outline: 1px dotted greenyellow; + + &--check { + &:hover { + color: greenyellow; + } + } + + &--cross { + &:hover { + color: orangered; + } + } + } + + &__icon { + text-align: center; } } \ No newline at end of file diff --git a/src/app/components/floating-column/manage-account/notifications/notification/notification.component.ts b/src/app/components/floating-column/manage-account/notifications/notification/notification.component.ts index 48505220..547a58fb 100644 --- a/src/app/components/floating-column/manage-account/notifications/notification/notification.component.ts +++ b/src/app/components/floating-column/manage-account/notifications/notification/notification.component.ts @@ -1,22 +1,30 @@ import { Component, Input } from '@angular/core'; -import { faUserPlus } from "@fortawesome/free-solid-svg-icons"; +import { faUserPlus, faUserClock, faCheck, faTimes } from "@fortawesome/free-solid-svg-icons"; import { NotificationWrapper } from '../notifications.component'; import { ToolsService } from '../../../../../services/tools.service'; import { Account } from '../../../../../services/models/mastodon.interfaces'; import { BrowseBase } from '../../../../../components/common/browse-base'; +import { MastodonWrapperService } from '../../../../../services/mastodon-wrapper.service'; +import { NotificationService } from '../../../../../services/notification.service'; @Component({ selector: 'app-notification', templateUrl: './notification.component.html', styleUrls: ['./notification.component.scss'] }) -export class NotificationComponent extends BrowseBase { +export class NotificationComponent extends BrowseBase { faUserPlus = faUserPlus; + faUserClock = faUserClock; + faCheck = faCheck; + faTimes = faTimes; @Input() notification: NotificationWrapper; - constructor(private readonly toolsService: ToolsService) { + constructor( + private readonly notificationsService: NotificationService, + private readonly mastodonService: MastodonWrapperService, + private readonly toolsService: ToolsService) { super(); } @@ -31,9 +39,47 @@ export class NotificationComponent extends BrowseBase { this.browseAccountEvent.next(accountName); return false; } - + openUrl(url: string): boolean { window.open(url, '_blank'); return false; } + + followRequestWorking: boolean; + followRequestProcessed: boolean; + acceptFollowRequest(): boolean { + if(this.followRequestWorking) return false; + this.followRequestWorking = true; + + this.mastodonService.authorizeFollowRequest(this.notification.provider, this.notification.notification.account.id) + .then(res => { + this.followRequestProcessed = true; + }) + .catch(err => { + this.notificationsService.notifyHttpError(err, this.notification.provider); + }) + .then(res => { + this.followRequestWorking = false; + }); + + return false; + } + + refuseFollowRequest(): boolean { + if(this.followRequestWorking) return false; + this.followRequestWorking = true; + + this.mastodonService.rejectFollowRequest(this.notification.provider, this.notification.notification.account.id) + .then(res => { + this.followRequestProcessed = true; + }) + .catch(err => { + this.notificationsService.notifyHttpError(err, this.notification.provider); + }) + .then(res => { + this.followRequestWorking = false; + }); + + return false; + } } diff --git a/src/app/components/floating-column/manage-account/notifications/notifications.component.ts b/src/app/components/floating-column/manage-account/notifications/notifications.component.ts index c9ab4157..c8a9db9c 100644 --- a/src/app/components/floating-column/manage-account/notifications/notifications.component.ts +++ b/src/app/components/floating-column/manage-account/notifications/notifications.component.ts @@ -158,11 +158,13 @@ export class NotificationWrapper { this.account = notification.account; this.wrapperId = `${this.type}-${notification.id}`; this.notification = notification; + this.provider = provider; } + provider: AccountInfo; notification: Notification; wrapperId: string; account: Account; status: StatusWrapper; - type: 'mention' | 'reblog' | 'favourite' | 'follow' | 'poll'; + type: 'mention' | 'reblog' | 'favourite' | 'follow' | 'poll' | 'follow_request'; } \ No newline at end of file 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 d31e9306..339b83cb 100644 --- a/src/app/components/stream/stream-notifications/stream-notifications.component.ts +++ b/src/app/components/stream/stream-notifications/stream-notifications.component.ts @@ -128,6 +128,8 @@ export class StreamNotificationsComponent extends BrowseBase { this.mastodonService.getNotifications(this.account, null, null, null, 10) .then((notifications: Notification[]) => { + console.warn(notifications); + this.isNotificationsLoading = false; this.notifications = notifications.map(x => { @@ -235,7 +237,7 @@ export class StreamNotificationsComponent extends BrowseBase { this.isMentionsLoading = true; - this.mastodonService.getNotifications(this.account, ['follow', 'favourite', 'reblog', 'poll'], this.lastMentionId) + this.mastodonService.getNotifications(this.account, ['follow', 'favourite', 'reblog', 'poll', 'follow_request'], this.lastMentionId) .then((result: Notification[]) => { if (result.length === 0) { this.mentionsMaxReached = true; diff --git a/src/app/components/stream/user-profile/user-profile.component.html b/src/app/components/stream/user-profile/user-profile.component.html index 5e3341a0..cc154f6c 100644 --- a/src/app/components/stream/user-profile/user-profile.component.html +++ b/src/app/components/stream/user-profile/user-profile.component.html @@ -13,7 +13,7 @@

@{{displayedAccount.acct}} + target="_blank" title="{{displayedAccount.acct}}">@{{displayedAccount.acct}}
diff --git a/src/app/components/stream/user-profile/user-profile.component.scss b/src/app/components/stream/user-profile/user-profile.component.scss index 0eed5661..cab3bebb 100644 --- a/src/app/components/stream/user-profile/user-profile.component.scss +++ b/src/app/components/stream/user-profile/user-profile.component.scss @@ -402,6 +402,12 @@ $floating-header-height: 60px; } } +.fa-lock { + margin-left: 5px; + color: gray; + font-size: 14px; +} + //Mastodon styling :host ::ng-deep .profile-fields__field--value { // font-size: 14px; diff --git a/src/app/components/stream/user-profile/user-profile.component.ts b/src/app/components/stream/user-profile/user-profile.component.ts index 8b613382..3677530b 100644 --- a/src/app/components/stream/user-profile/user-profile.component.ts +++ b/src/app/components/stream/user-profile/user-profile.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core'; import { HttpErrorResponse } from '@angular/common/http'; -import { faUser, faHourglassHalf, faUserCheck, faExclamationTriangle, faLink } from "@fortawesome/free-solid-svg-icons"; +import { faUser, faHourglassHalf, faUserCheck, faExclamationTriangle, faLink, faLock } from "@fortawesome/free-solid-svg-icons"; import { faUser as faUserRegular } from "@fortawesome/free-regular-svg-icons"; import { Observable, Subscription } from 'rxjs'; import { Store } from '@ngxs/store'; @@ -29,6 +29,7 @@ export class UserProfileComponent extends BrowseBase { faUserCheck = faUserCheck; faExclamationTriangle = faExclamationTriangle; faLink = faLink; + faLock = faLock; displayedAccount: Account; hasNote: boolean; diff --git a/src/app/services/mastodon-wrapper.service.ts b/src/app/services/mastodon-wrapper.service.ts index 28b3afc6..4dde19f8 100644 --- a/src/app/services/mastodon-wrapper.service.ts +++ b/src/app/services/mastodon-wrapper.service.ts @@ -252,7 +252,7 @@ export class MastodonWrapperService { }); } - getNotifications(account: AccountInfo, excludeTypes: ('follow' | 'favourite' | 'reblog' | 'mention' | 'poll')[] = null, maxId: string = null, sinceId: string = null, limit: number = 15): Promise { + getNotifications(account: AccountInfo, excludeTypes: ('follow' | 'favourite' | 'reblog' | 'mention' | 'poll' | 'follow_request')[] = null, maxId: string = null, sinceId: string = null, limit: number = 15): Promise { return this.refreshAccountIfNeeded(account) .then((refreshedAccount: AccountInfo) => { return this.mastodonService.getNotifications(refreshedAccount, excludeTypes, maxId, sinceId, limit); @@ -405,4 +405,18 @@ export class MastodonWrapperService { return this.mastodonService.getFollowers(refreshedAccount, accountId, maxId, sinceId, limit); }); } + + authorizeFollowRequest(account: AccountInfo, id: number): Promise { + return this.refreshAccountIfNeeded(account) + .then((refreshedAccount: AccountInfo) => { + return this.mastodonService.authorizeFollowRequest(refreshedAccount, id); + }); + } + + rejectFollowRequest(account: AccountInfo, id: number): Promise { + return this.refreshAccountIfNeeded(account) + .then((refreshedAccount: AccountInfo) => { + return this.mastodonService.rejectFollowRequest(refreshedAccount, id); + }); + } } diff --git a/src/app/services/mastodon.service.ts b/src/app/services/mastodon.service.ts index 003ef106..09302276 100644 --- a/src/app/services/mastodon.service.ts +++ b/src/app/services/mastodon.service.ts @@ -311,7 +311,7 @@ export class MastodonService { return this.httpClient.put(route, input, { headers: headers }).toPromise(); } - getNotifications(account: AccountInfo, excludeTypes: ('follow' | 'favourite' | 'reblog' | 'mention' | 'poll')[] = null, maxId: string = null, sinceId: string = null, limit: number = 15): Promise { + getNotifications(account: AccountInfo, excludeTypes: ('follow' | 'favourite' | 'reblog' | 'mention' | 'poll' | 'follow_request')[] = null, maxId: string = null, sinceId: string = null, limit: number = 15): Promise { let route = `https://${account.instance}${this.apiRoutes.getNotifications}?limit=${limit}`; if (maxId) { @@ -490,6 +490,7 @@ export class MastodonService { return new FollowingResult(lastId, res.body) }); } + getFollowing(account: AccountInfo, targetAccountId: number, maxId: string, sinceId: string, limit: number = 40): Promise { const route = `https://${account.instance}${this.apiRoutes.getFollowing}`.replace('{0}', targetAccountId.toString()); @@ -511,6 +512,20 @@ export class MastodonService { return new FollowingResult(lastId, res.body) }); } + + authorizeFollowRequest(account: AccountInfo, id: number): Promise { + const route = `https://${account.instance}${this.apiRoutes.authorizeFollowRequest}`.replace('{0}', id.toString()); + + const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` }); + return this.httpClient.post(route, null, { headers: headers }).toPromise(); + } + + rejectFollowRequest(account: AccountInfo, id: number): Promise { + const route = `https://${account.instance}${this.apiRoutes.rejectFollowRequest}`.replace('{0}', id.toString()); + + const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` }); + return this.httpClient.post(route, null, { headers: headers }).toPromise(); + } } export enum VisibilityEnum { diff --git a/src/app/services/models/api.settings.ts b/src/app/services/models/api.settings.ts index f6c28816..08573c16 100644 --- a/src/app/services/models/api.settings.ts +++ b/src/app/services/models/api.settings.ts @@ -21,8 +21,8 @@ export class ApiRoutes { getBlocks = '/api/v1/blocks'; getFavourites = '/api/v1/favourites'; getFollowRequests = '/api/v1/follow_requests'; - authorizeFollowRequest = '/api/v1/follow_requests/authorize'; - rejectFollowRequest = '/api/v1/follow_requests/reject'; + authorizeFollowRequest = '/api/v1/follow_requests/{0}/authorize'; + rejectFollowRequest = '/api/v1/follow_requests/{0}/reject'; followRemote = '/api/v1/follows'; getInstance = '/api/v1/instance'; uploadMediaAttachment = '/api/v1/media'; diff --git a/src/app/services/models/mastodon.interfaces.ts b/src/app/services/models/mastodon.interfaces.ts index 844585e6..6aa2956a 100644 --- a/src/app/services/models/mastodon.interfaces.ts +++ b/src/app/services/models/mastodon.interfaces.ts @@ -26,7 +26,7 @@ export interface Account { username: string; acct: string; display_name: string; - locked: string; + locked: boolean; created_at: string; followers_count: number; following_count: number; @@ -130,7 +130,7 @@ export interface Mention { export interface Notification { id: string; - type: 'mention' | 'reblog' | 'favourite' | 'follow' | 'poll'; + type: 'mention' | 'reblog' | 'favourite' | 'follow' | 'poll' | 'follow_request'; created_at: string; account: Account; status?: Status; diff --git a/src/app/services/user-notification.service.ts b/src/app/services/user-notification.service.ts index f2545b53..f8cc94cb 100644 --- a/src/app/services/user-notification.service.ts +++ b/src/app/services/user-notification.service.ts @@ -58,7 +58,7 @@ export class UserNotificationService { } private startFetchingNotifications(account: AccountInfo) { - let getMentionsPromise = this.mastodonService.getNotifications(account, ['favourite', 'follow', 'reblog', 'poll'], null, null, 10) + let getMentionsPromise = this.mastodonService.getNotifications(account, ['favourite', 'follow', 'reblog', 'poll', 'follow_request'], null, null, 10) .then((notifications: Notification[]) => { this.processMentionsAndNotifications(account, notifications, NotificationTypeEnum.UserMention); })