Merge pull request #123 from NicolasConstant/topic_quality-of-life
Topic quality of life
This commit is contained in:
commit
cd699b9da4
279
main-electron.js
279
main-electron.js
|
@ -9,166 +9,181 @@ const fs = require("fs");
|
|||
let win;
|
||||
|
||||
function createWindow() {
|
||||
// Create the browser window.
|
||||
win = new BrowserWindow({
|
||||
width: 377,
|
||||
height: 800,
|
||||
title: "Sengi",
|
||||
backgroundColor: "#FFF",
|
||||
useContentSize: true
|
||||
});
|
||||
// Create the browser window.
|
||||
win = new BrowserWindow({
|
||||
width: 377,
|
||||
height: 800,
|
||||
title: "Sengi",
|
||||
backgroundColor: "#FFF",
|
||||
useContentSize: true
|
||||
});
|
||||
|
||||
var server = http.createServer(requestHandler).listen(9527);
|
||||
win.loadURL("http://localhost:9527");
|
||||
var server = http.createServer(requestHandler).listen(9527);
|
||||
win.loadURL("http://localhost:9527");
|
||||
|
||||
const template = [
|
||||
{
|
||||
label: "View",
|
||||
submenu: [
|
||||
{ role: "reload" },
|
||||
{ role: "forcereload" },
|
||||
{ type: "separator" },
|
||||
{ role: "close" },
|
||||
{ role: 'quit' }
|
||||
]
|
||||
},
|
||||
{
|
||||
role: "help",
|
||||
submenu: [
|
||||
{ role: "toggledevtools" },
|
||||
const template = [
|
||||
{
|
||||
label: "Open GitHub project",
|
||||
click() {
|
||||
require("electron").shell.openExternal(
|
||||
"https://github.com/NicolasConstant/sengi"
|
||||
);
|
||||
}
|
||||
label: "View",
|
||||
submenu: [
|
||||
{ role: "reload" },
|
||||
{ role: "forcereload" },
|
||||
{ type: "separator" },
|
||||
{ role: "close" },
|
||||
{ role: 'quit' }
|
||||
]
|
||||
},
|
||||
{
|
||||
role: "help",
|
||||
submenu: [
|
||||
{ role: "toggledevtools" },
|
||||
{
|
||||
label: "Open GitHub project",
|
||||
click() {
|
||||
require("electron").shell.openExternal(
|
||||
"https://github.com/NicolasConstant/sengi"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
win.setMenu(menu);
|
||||
|
||||
// Check if we are on a MAC
|
||||
if (process.platform === "darwin") {
|
||||
// Create our menu entries so that we can use MAC shortcuts
|
||||
Menu.setApplicationMenu(
|
||||
Menu.buildFromTemplate([
|
||||
{
|
||||
label: "Edit",
|
||||
submenu: [
|
||||
{ role: "undo" },
|
||||
{ role: "redo" },
|
||||
{ type: "separator" },
|
||||
{ role: "cut" },
|
||||
{ role: "copy" },
|
||||
{ role: "paste" },
|
||||
{ role: "pasteandmatchstyle" },
|
||||
{ role: "delete" },
|
||||
{ role: "selectall" },
|
||||
{ type: "separator" },
|
||||
{ role: "close" },
|
||||
{ role: 'quit' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "View",
|
||||
submenu: [{ role: "reload" }, { role: "forcereload" }]
|
||||
},
|
||||
{
|
||||
role: "help",
|
||||
submenu: [
|
||||
{ role: "toggledevtools" },
|
||||
{
|
||||
label: "Open GitHub project",
|
||||
click() {
|
||||
require("electron").shell.openExternal(
|
||||
"https://github.com/NicolasConstant/sengi"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
);
|
||||
}
|
||||
];
|
||||
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
win.setMenu(menu);
|
||||
// Open the DevTools.
|
||||
// win.webContents.openDevTools()
|
||||
|
||||
// Check if we are on a MAC
|
||||
if (process.platform === "darwin") {
|
||||
// Create our menu entries so that we can use MAC shortcuts
|
||||
Menu.setApplicationMenu(
|
||||
Menu.buildFromTemplate([
|
||||
{
|
||||
label: "Edit",
|
||||
submenu: [
|
||||
{ role: "undo" },
|
||||
{ role: "redo" },
|
||||
{ type: "separator" },
|
||||
{ role: "cut" },
|
||||
{ role: "copy" },
|
||||
{ role: "paste" },
|
||||
{ role: "pasteandmatchstyle" },
|
||||
{ role: "delete" },
|
||||
{ role: "selectall" },
|
||||
{ type: "separator" },
|
||||
{ role: "close" },
|
||||
{ role: 'quit' }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: "View",
|
||||
submenu: [{ role: "reload" }, { role: "forcereload" }]
|
||||
},
|
||||
{
|
||||
role: "help",
|
||||
submenu: [
|
||||
{ role: "toggledevtools" },
|
||||
{
|
||||
label: "Open GitHub project",
|
||||
click() {
|
||||
require("electron").shell.openExternal(
|
||||
"https://github.com/NicolasConstant/sengi"
|
||||
);
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
])
|
||||
);
|
||||
}
|
||||
//open external links to browser
|
||||
win.webContents.on("new-window", function (event, url) {
|
||||
event.preventDefault();
|
||||
shell.openExternal(url);
|
||||
});
|
||||
|
||||
// Open the DevTools.
|
||||
// win.webContents.openDevTools()
|
||||
|
||||
//open external links to browser
|
||||
win.webContents.on("new-window", function(event, url) {
|
||||
event.preventDefault();
|
||||
shell.openExternal(url);
|
||||
});
|
||||
|
||||
// Emitted when the window is closed.
|
||||
win.on("closed", () => {
|
||||
// Dereference the window object, usually you would store windows
|
||||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
win = null;
|
||||
});
|
||||
// Emitted when the window is closed.
|
||||
win.on("closed", () => {
|
||||
// Dereference the window object, usually you would store windows
|
||||
// in an array if your app supports multi windows, this is the time
|
||||
// when you should delete the corresponding element.
|
||||
win = null;
|
||||
});
|
||||
}
|
||||
|
||||
function requestHandler(req, res) {
|
||||
var file = req.url == "/" ? "/index.html" : req.url,
|
||||
root = __dirname + "/dist",
|
||||
page404 = root + "/404.html";
|
||||
var file = req.url == "/" ? "/index.html" : req.url,
|
||||
root = __dirname + "/dist",
|
||||
page404 = root + "/404.html";
|
||||
|
||||
if (file.includes("register") || file.includes("home")) file = "/index.html";
|
||||
if (file.includes("register") || file.includes("home")) file = "/index.html";
|
||||
|
||||
getFile(root + file, res, page404);
|
||||
getFile(root + file, res, page404);
|
||||
}
|
||||
|
||||
function getFile(filePath, res, page404) {
|
||||
console.warn(`filePath: ${filePath}`);
|
||||
fs.exists(filePath, function(exists) {
|
||||
if (exists) {
|
||||
fs.readFile(filePath, function(err, contents) {
|
||||
if (!err) {
|
||||
res.end(contents);
|
||||
console.warn(`filePath: ${filePath}`);
|
||||
fs.exists(filePath, function (exists) {
|
||||
if (exists) {
|
||||
fs.readFile(filePath, function (err, contents) {
|
||||
if (!err) {
|
||||
res.end(contents);
|
||||
} else {
|
||||
console.dir(err);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.dir(err);
|
||||
fs.readFile(page404, function (err, contents) {
|
||||
if (!err) {
|
||||
res.writeHead(404, { "Content-Type": "text/html" });
|
||||
res.end(contents);
|
||||
} else {
|
||||
console.dir(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
fs.readFile(page404, function(err, contents) {
|
||||
if (!err) {
|
||||
res.writeHead(404, { "Content-Type": "text/html" });
|
||||
res.end(contents);
|
||||
} else {
|
||||
console.dir(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
app.commandLine.appendSwitch("force-color-profile", "srgb");
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on("ready", createWindow);
|
||||
|
||||
const gotTheLock = app.requestSingleInstanceLock();
|
||||
|
||||
if (!gotTheLock) {
|
||||
app.quit();
|
||||
} else {
|
||||
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (win) {
|
||||
if (win.isMinimized()) win.restore()
|
||||
win.focus()
|
||||
}
|
||||
});
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on("ready", createWindow);
|
||||
}
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on("window-all-closed", () => {
|
||||
// On macOS it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if (process.platform !== "darwin") {
|
||||
app.quit();
|
||||
}
|
||||
// On macOS it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if (process.platform !== "darwin") {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on("activate", () => {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (win === null) {
|
||||
createWindow();
|
||||
}
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (win === null) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
// In this file you can include the rest of your app's specific main process
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "sengi",
|
||||
"version": "0.10.1",
|
||||
"version": "0.11.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "main-electron.js",
|
||||
"description": "A multi-account desktop client for Mastodon and Pleroma",
|
||||
|
|
|
@ -20,7 +20,14 @@ import { identifierModuleUrl } from '@angular/compiler';
|
|||
styleUrls: ['./create-status.component.scss']
|
||||
})
|
||||
export class CreateStatusComponent implements OnInit, OnDestroy {
|
||||
title: string;
|
||||
private _title: string;
|
||||
set title(value: string){
|
||||
this._title = value;
|
||||
this.countStatusChar(this.status);
|
||||
}
|
||||
get title(): string {
|
||||
return this._title;
|
||||
}
|
||||
|
||||
private _status: string = '';
|
||||
set status(value: string) {
|
||||
|
@ -106,14 +113,21 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
private accountChanged(accounts: AccountInfo[]): void {
|
||||
if (accounts && accounts.length > 0) {
|
||||
const selectedAccount = accounts.filter(x => x.isSelected)[0];
|
||||
this.instancesInfoService.getMaxStatusChars(selectedAccount.instance)
|
||||
.then((maxChars: number) => {
|
||||
this.maxCharLength = maxChars;
|
||||
this.countStatusChar(this.status);
|
||||
})
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
});
|
||||
|
||||
const settings = this.toolsService.getAccountSettings(selectedAccount);
|
||||
if (settings.customStatusCharLengthEnabled) {
|
||||
this.maxCharLength = settings.customStatusCharLength;
|
||||
this.countStatusChar(this.status);
|
||||
} else {
|
||||
this.instancesInfoService.getMaxStatusChars(selectedAccount.instance)
|
||||
.then((maxChars: number) => {
|
||||
this.maxCharLength = maxChars;
|
||||
this.countStatusChar(this.status);
|
||||
})
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
});
|
||||
}
|
||||
|
||||
if (!this.statusReplyingToWrapper) {
|
||||
this.instancesInfoService.getDefaultPrivacy(selectedAccount)
|
||||
|
@ -169,10 +183,18 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
const statusExtraChars = this.getMentionExtraChars(status);
|
||||
|
||||
const statusLength = currentStatus.length - statusExtraChars;
|
||||
this.charCountLeft = this.maxCharLength - statusLength;
|
||||
this.charCountLeft = this.maxCharLength - statusLength - this.getCwLength();
|
||||
this.postCounts = parseStatus.length;
|
||||
}
|
||||
|
||||
private getCwLength(): number {
|
||||
let cwLength = 0;
|
||||
if (this.title) {
|
||||
cwLength = this.title.length;
|
||||
}
|
||||
return cwLength;
|
||||
}
|
||||
|
||||
private getMentions(status: Status, providerInfo: AccountInfo): string[] {
|
||||
const mentions = [...status.mentions.map(x => x.acct), status.account.acct];
|
||||
|
||||
|
@ -238,9 +260,6 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
return this.sendStatus(acc, this.status, visibility, this.title, status, mediaAttachments);
|
||||
})
|
||||
.then((res: Status) => {
|
||||
if (this.statusReplyingToWrapper) {
|
||||
this.notificationService.newStatusPosted(this.statusReplyingToWrapper.status.id, new StatusWrapper(res, acc));
|
||||
}
|
||||
this.title = '';
|
||||
this.status = '';
|
||||
this.onClose.emit();
|
||||
|
@ -261,22 +280,30 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
|
||||
for (let i = 0; i < parsedStatus.length; i++) {
|
||||
let s = parsedStatus[i];
|
||||
resultPromise = resultPromise.then((pStatus: Status) => {
|
||||
let inReplyToId = null;
|
||||
if (pStatus) {
|
||||
inReplyToId = pStatus.id;
|
||||
}
|
||||
resultPromise = resultPromise
|
||||
.then((pStatus: Status) => {
|
||||
let inReplyToId = null;
|
||||
if (pStatus) {
|
||||
inReplyToId = pStatus.id;
|
||||
}
|
||||
|
||||
if (i === 0) {
|
||||
return this.mastodonService.postNewStatus(account, s, visibility, title, inReplyToId, attachments.map(x => x.id))
|
||||
.then((status: Status) => {
|
||||
this.mediaService.clearMedia();
|
||||
return status;
|
||||
});
|
||||
} else {
|
||||
return this.mastodonService.postNewStatus(account, s, visibility, title, inReplyToId, []);
|
||||
}
|
||||
});
|
||||
if (i === 0) {
|
||||
return this.mastodonService.postNewStatus(account, s, visibility, title, inReplyToId, attachments.map(x => x.id))
|
||||
.then((status: Status) => {
|
||||
this.mediaService.clearMedia();
|
||||
return status;
|
||||
});
|
||||
} else {
|
||||
return this.mastodonService.postNewStatus(account, s, visibility, title, inReplyToId, []);
|
||||
}
|
||||
})
|
||||
.then((status: Status) => {
|
||||
if (this.statusReplyingToWrapper) {
|
||||
this.notificationService.newStatusPosted(this.statusReplyingToWrapper.status.id, new StatusWrapper(status, account));
|
||||
}
|
||||
|
||||
return status;
|
||||
});
|
||||
}
|
||||
|
||||
return resultPromise;
|
||||
|
@ -293,7 +320,7 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
aggregateMention += `${x} `;
|
||||
});
|
||||
|
||||
const currentMaxCharLength = this.maxCharLength + mentionExtraChars;
|
||||
const currentMaxCharLength = this.maxCharLength + mentionExtraChars - this.getCwLength();
|
||||
const maxChars = currentMaxCharLength - 6;
|
||||
|
||||
while (trucatedStatus.length > currentMaxCharLength) {
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
<h3 class="panel__title">Manage Account</h3>
|
||||
|
||||
<div class="account__header">
|
||||
<img class="account__avatar" src="{{account.avatar}}" title="{{ account.info.id }} " />
|
||||
<a href (click)="browseLocalAccount()" (auxclick)="openLocalAccount()" title="open {{ account.info.id }}">
|
||||
<img class="account__avatar" src="{{account.avatar}}"/>
|
||||
</a>
|
||||
|
||||
<!-- <a href class="account__header--button"><fa-icon [icon]="faUserPlus"></fa-icon></a> -->
|
||||
<a href class="account__header--button" title="favorites" (click)="loadSubPanel('favorites')"
|
||||
|
@ -17,8 +19,7 @@
|
|||
[ngClass]="{ 'account__header--button--selected': subPanel === 'mentions', 'account__header--button--notification': hasMentions }">
|
||||
<fa-icon [icon]="faAt"></fa-icon>
|
||||
</a>
|
||||
<a href class="account__header--button" title="notifications" (click)="loadSubPanel('notifications')"
|
||||
[ngClass]="{ 'account__header--button--selected': subPanel === 'notifications',
|
||||
<a href class="account__header--button" title="notifications" (click)="loadSubPanel('notifications')" [ngClass]="{ 'account__header--button--selected': subPanel === 'notifications',
|
||||
'account__header--button--notification': hasNotifications }">
|
||||
<fa-icon [icon]="faBell"></fa-icon>
|
||||
</a>
|
||||
|
@ -27,27 +28,18 @@
|
|||
<fa-icon [icon]="faUser"></fa-icon>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<app-direct-messages class="account__body" *ngIf="subPanel === 'dm'"
|
||||
[account]="account"
|
||||
(browseAccountEvent)="browseAccount($event)"
|
||||
(browseHashtagEvent)="browseHashtag($event)"
|
||||
|
||||
<app-direct-messages class="account__body" *ngIf="subPanel === 'dm'" [account]="account"
|
||||
(browseAccountEvent)="browseAccount($event)" (browseHashtagEvent)="browseHashtag($event)"
|
||||
(browseThreadEvent)="browseThread($event)"></app-direct-messages>
|
||||
<app-favorites class="account__body" *ngIf="subPanel === 'favorites'"
|
||||
[account]="account"
|
||||
(browseAccountEvent)="browseAccount($event)"
|
||||
(browseHashtagEvent)="browseHashtag($event)"
|
||||
<app-favorites class="account__body" *ngIf="subPanel === 'favorites'" [account]="account"
|
||||
(browseAccountEvent)="browseAccount($event)" (browseHashtagEvent)="browseHashtag($event)"
|
||||
(browseThreadEvent)="browseThread($event)"></app-favorites>
|
||||
<app-mentions class="account__body" *ngIf="subPanel === 'mentions'"
|
||||
[account]="account"
|
||||
(browseAccountEvent)="browseAccount($event)"
|
||||
(browseHashtagEvent)="browseHashtag($event)"
|
||||
<app-mentions class="account__body" *ngIf="subPanel === 'mentions'" [account]="account"
|
||||
(browseAccountEvent)="browseAccount($event)" (browseHashtagEvent)="browseHashtag($event)"
|
||||
(browseThreadEvent)="browseThread($event)"></app-mentions>
|
||||
<app-my-account class="account__body" *ngIf="subPanel === 'account'"
|
||||
[account]="account"></app-my-account>
|
||||
<app-notifications class="account__body" *ngIf="subPanel === 'notifications'"
|
||||
[account]="account"
|
||||
(browseAccountEvent)="browseAccount($event)"
|
||||
(browseHashtagEvent)="browseHashtag($event)"
|
||||
<app-my-account class="account__body" *ngIf="subPanel === 'account'" [account]="account"></app-my-account>
|
||||
<app-notifications class="account__body" *ngIf="subPanel === 'notifications'" [account]="account"
|
||||
(browseAccountEvent)="browseAccount($event)" (browseHashtagEvent)="browseHashtag($event)"
|
||||
(browseThreadEvent)="browseThread($event)"></app-notifications>
|
||||
</div>
|
|
@ -6,6 +6,10 @@ import { Subscription } from 'rxjs';
|
|||
import { AccountWrapper } from '../../../models/account.models';
|
||||
import { UserNotificationService, UserNotification } from '../../../services/user-notification.service';
|
||||
import { OpenThreadEvent } from '../../../services/tools.service';
|
||||
import { MastodonService } from '../../../services/mastodon.service';
|
||||
import { Account } from "../../../services/models/mastodon.interfaces";
|
||||
import { NotificationService } from '../../../services/notification.service';
|
||||
import { AccountInfo } from '../../../states/accounts.state';
|
||||
|
||||
|
||||
@Component({
|
||||
|
@ -25,6 +29,8 @@ export class ManageAccountComponent implements OnInit, OnDestroy {
|
|||
hasNotifications = false;
|
||||
hasMentions = false;
|
||||
|
||||
userAccount: Account;
|
||||
|
||||
@Output() browseAccountEvent = new EventEmitter<string>();
|
||||
@Output() browseHashtagEvent = new EventEmitter<string>();
|
||||
@Output() browseThreadEvent = new EventEmitter<OpenThreadEvent>();
|
||||
|
@ -33,6 +39,7 @@ export class ManageAccountComponent implements OnInit, OnDestroy {
|
|||
set account(acc: AccountWrapper) {
|
||||
this._account = acc;
|
||||
this.checkNotifications();
|
||||
this.getUserUrl(acc.info);
|
||||
}
|
||||
get account(): AccountWrapper {
|
||||
return this._account;
|
||||
|
@ -42,6 +49,8 @@ export class ManageAccountComponent implements OnInit, OnDestroy {
|
|||
private _account: AccountWrapper;
|
||||
|
||||
constructor(
|
||||
private readonly mastodonService: MastodonService,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly userNotificationService: UserNotificationService) { }
|
||||
|
||||
ngOnInit() {
|
||||
|
@ -52,6 +61,16 @@ export class ManageAccountComponent implements OnInit, OnDestroy {
|
|||
this.userNotificationServiceSub.unsubscribe();
|
||||
}
|
||||
|
||||
private getUserUrl(account: AccountInfo){
|
||||
this.mastodonService.retrieveAccountDetails(this.account.info)
|
||||
.then((acc: Account) => {
|
||||
this.userAccount = acc;
|
||||
})
|
||||
.catch(err => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
});
|
||||
}
|
||||
|
||||
private checkNotifications(){
|
||||
if(this.userNotificationServiceSub){
|
||||
this.userNotificationServiceSub.unsubscribe();
|
||||
|
@ -75,6 +94,17 @@ export class ManageAccountComponent implements OnInit, OnDestroy {
|
|||
this.browseAccountEvent.next(accountName);
|
||||
}
|
||||
|
||||
browseLocalAccount(): boolean {
|
||||
var accountName = `@${this.account.info.username}@${this.account.info.instance}`;
|
||||
this.browseAccountEvent.next(accountName);
|
||||
return false;
|
||||
}
|
||||
|
||||
openLocalAccount(): boolean {
|
||||
window.open(this.userAccount.url, '_blank');
|
||||
return false;
|
||||
}
|
||||
|
||||
browseHashtag(hashtag: string): void {
|
||||
this.browseHashtagEvent.next(hashtag);
|
||||
}
|
||||
|
|
|
@ -9,21 +9,21 @@
|
|||
|
||||
<h4 class="my-account__label my-account__margin-top">manage list:</h4>
|
||||
<div class="my-account__link--margin-bottom" *ngFor="let list of availableLists">
|
||||
<a href class="my-account__list--button" title="delete list"
|
||||
(click)="openCloseDeleteConfirmation(list, true)" *ngIf="!list.confirmDeletion">
|
||||
<a href class="my-account__list--button" title="delete list" (click)="openCloseDeleteConfirmation(list, true)"
|
||||
*ngIf="!list.confirmDeletion">
|
||||
<fa-icon class="my-account__link--icon" [icon]="faTrash"></fa-icon>
|
||||
</a>
|
||||
<a href class="my-account__list--button" title="edit list"
|
||||
(click)="editList(list)" *ngIf="!list.confirmDeletion">
|
||||
<a href class="my-account__list--button" title="edit list" (click)="editList(list)"
|
||||
*ngIf="!list.confirmDeletion">
|
||||
<fa-icon class="my-account__link--icon" [icon]="faPenAlt"></fa-icon>
|
||||
</a>
|
||||
|
||||
<a href class="my-account__list--button" title="cancel"
|
||||
(click)="openCloseDeleteConfirmation(list, false)" *ngIf="list.confirmDeletion">
|
||||
<a href class="my-account__list--button" title="cancel" (click)="openCloseDeleteConfirmation(list, false)"
|
||||
*ngIf="list.confirmDeletion">
|
||||
<fa-icon class="my-account__link--icon" [icon]="faTimes"></fa-icon>
|
||||
</a>
|
||||
<a href class="my-account__list--button my-account__red" title="delete list"
|
||||
(click)="deleteList(list)" *ngIf="list.confirmDeletion">
|
||||
<a href class="my-account__list--button my-account__red" title="delete list" (click)="deleteList(list)"
|
||||
*ngIf="list.confirmDeletion">
|
||||
<fa-icon class="my-account__link--icon" [icon]="faCheck"></fa-icon>
|
||||
</a>
|
||||
|
||||
|
@ -38,7 +38,17 @@
|
|||
<a href class="my-account__list--button" title="create list" (click)="createList()">
|
||||
<fa-icon class="my-account__link--icon" [icon]="faPlus"></fa-icon>
|
||||
</a>
|
||||
<input class="my-account__list--new-list-title" placeholder="new list title" [(ngModel)]="listTitle" (keyup.enter)="createList()" [disabled]="creationLoading"/>
|
||||
<input class="my-account__list--new-list-title" placeholder="new list title" [(ngModel)]="listTitle"
|
||||
(keyup.enter)="createList()" [disabled]="creationLoading" />
|
||||
|
||||
<h4 class="my-account__label my-account__margin-top">advanced settings:</h4>
|
||||
<div class="advanced-settings">
|
||||
<input class="advanced-settings__checkbox" [(ngModel)]="customStatusLengthEnabled" (change)="onCustomLengthEnabledChanged()"
|
||||
type="checkbox" name="customCharLength" value="customCharLength" id="customCharLength"> <label class="noselect advanced-settings__label" for="customCharLength">status'
|
||||
custom length</label><br>
|
||||
<p *ngIf="customStatusLengthEnabled" class="advanced-settings__text">use this only if your instance doesn't support custom length detection (i.e. not a Pleroma or glitch-soc instance)</p>
|
||||
<input *ngIf="customStatusLengthEnabled" [(ngModel)]="customStatusLength" class="themed-form advanced-settings__input" type="number" (keyup)="customStatusLengthChanged($event)" />
|
||||
</div>
|
||||
|
||||
<h4 class="my-account__label my-account__margin-top">remove account from sengi:</h4>
|
||||
<a class="my-account__link my-account__red" href (click)="removeAccount()">
|
||||
|
|
|
@ -102,4 +102,30 @@
|
|||
&__margin-top {
|
||||
margin-top: 25px;
|
||||
}
|
||||
}
|
||||
|
||||
.advanced-settings {
|
||||
position: relative;
|
||||
|
||||
&__checkbox{
|
||||
position: relative;
|
||||
top:3px;
|
||||
left: 5px;
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
&__label {
|
||||
|
||||
|
||||
}
|
||||
|
||||
&__text {
|
||||
display: block;
|
||||
margin: 0 6px 9px 6px;
|
||||
color: rgb(140, 152, 173);
|
||||
}
|
||||
|
||||
&__input {
|
||||
margin-left: 5px;
|
||||
}
|
||||
}
|
|
@ -10,6 +10,8 @@ import { AccountWrapper } from '../../../../models/account.models';
|
|||
import { RemoveAccount } from '../../../../states/accounts.state';
|
||||
import { NavigationService } from '../../../../services/navigation.service';
|
||||
import { MastodonService } from '../../../../services/mastodon.service';
|
||||
import { ToolsService } from '../../../../services/tools.service';
|
||||
import { AccountSettings } from '../../../../states/settings.state';
|
||||
|
||||
@Component({
|
||||
selector: 'app-my-account',
|
||||
|
@ -23,6 +25,10 @@ export class MyAccountComponent implements OnInit, OnDestroy {
|
|||
faCheckSquare = faCheckSquare;
|
||||
faCheck = faCheck;
|
||||
faTimes = faTimes;
|
||||
|
||||
customStatusLengthEnabled: boolean;
|
||||
customStatusLength: number;
|
||||
private accountSettings: AccountSettings;
|
||||
|
||||
availableStreams: StreamWrapper[] = [];
|
||||
availableLists: StreamWrapper[] = [];
|
||||
|
@ -32,6 +38,7 @@ export class MyAccountComponent implements OnInit, OnDestroy {
|
|||
set account(acc: AccountWrapper) {
|
||||
this._account = acc;
|
||||
this.loadStreams(acc);
|
||||
this.loadAccountSettings();
|
||||
}
|
||||
get account(): AccountWrapper {
|
||||
return this._account;
|
||||
|
@ -42,6 +49,7 @@ export class MyAccountComponent implements OnInit, OnDestroy {
|
|||
|
||||
constructor(
|
||||
private readonly store: Store,
|
||||
private readonly toolsService: ToolsService,
|
||||
private readonly navigationService: NavigationService,
|
||||
private readonly mastodonService: MastodonService,
|
||||
private readonly notificationService: NotificationService) { }
|
||||
|
@ -49,7 +57,7 @@ export class MyAccountComponent implements OnInit, OnDestroy {
|
|||
ngOnInit() {
|
||||
this.streamChangedSub = this.streamElements$.subscribe((streams: StreamElement[]) => {
|
||||
this.loadStreams(this.account);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
|
@ -58,6 +66,24 @@ export class MyAccountComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
}
|
||||
|
||||
private loadAccountSettings(){
|
||||
this.accountSettings = this.toolsService.getAccountSettings(this.account.info);
|
||||
|
||||
this.customStatusLengthEnabled = this.accountSettings.customStatusCharLengthEnabled; this.customStatusLength = this.accountSettings.customStatusCharLength;
|
||||
}
|
||||
|
||||
onCustomLengthEnabledChanged(): boolean {
|
||||
this.accountSettings.customStatusCharLengthEnabled = this.customStatusLengthEnabled;
|
||||
this.toolsService.saveAccountSettings(this.accountSettings);
|
||||
return false;
|
||||
}
|
||||
|
||||
customStatusLengthChanged(event): boolean{
|
||||
this.accountSettings.customStatusCharLength = this.customStatusLength;
|
||||
this.toolsService.saveAccountSettings(this.accountSettings);
|
||||
return false;
|
||||
}
|
||||
|
||||
private loadStreams(account: AccountWrapper){
|
||||
const instance = account.info.instance;
|
||||
this.availableStreams.length = 0;
|
||||
|
|
|
@ -1,23 +1,34 @@
|
|||
<div class="media-viewer-canvas" (click)="close()">
|
||||
<button class="media-viewer-canvas__close media-viewer-canvas__button" title="close">
|
||||
<div class="media-viewer-canvas noselect">
|
||||
<div class="background__close" (click)="close()"></div>
|
||||
|
||||
<button class="media-viewer-canvas__close media-viewer-canvas__button" title="close" (click)="close()">
|
||||
<fa-icon [icon]="faTimes"></fa-icon>
|
||||
</button>
|
||||
|
||||
<button class="media-viewer-canvas__previous media-viewer-canvas__button" title="previous" (click)="previous($event)" *ngIf="previousAvailable">
|
||||
<button class="media-viewer-canvas__previous media-viewer-canvas__button" title="previous"
|
||||
(click)="previous($event)" *ngIf="previousAvailable">
|
||||
<fa-icon [icon]="faAngleLeft"></fa-icon>
|
||||
</button>
|
||||
|
||||
<button class="media-viewer-canvas__next media-viewer-canvas__button" title="next" (click)="next($event)" *ngIf="nextAvailable">
|
||||
<button class="media-viewer-canvas__next media-viewer-canvas__button" title="next" (click)="next($event)"
|
||||
*ngIf="nextAvailable">
|
||||
<fa-icon [icon]="faAngleRight"></fa-icon>
|
||||
</button>
|
||||
|
||||
<img class="media-viewer-canvas__image" *ngIf="imageUrl" src="{{imageUrl}}" (click)="blockClick($event)"/>
|
||||
<video class="media-viewer-canvas__image" *ngIf="gifvUrl" role="application" loop autoplay (click)="blockClick($event)">
|
||||
<source src="{{ gifvUrl }}" type="video/mp4">
|
||||
</video>
|
||||
<video class="media-viewer-canvas__image" *ngIf="videoUrl" role="application" loop controls="controls" (click)="blockClick($event)">
|
||||
<source src="{{ videoUrl }}" type="video/mp4">
|
||||
</video>
|
||||
|
||||
<div *ngFor="let att of attachments" class="media-viewer-canvas__attachement"
|
||||
[ngClass]="{ 'collapsed': currentIndex !== att.index }">
|
||||
<a href="{{att.url}}" target="_blank" title="open image">
|
||||
<img *ngIf="att.type === 'image'" src="{{att.url}}" class="media-viewer-canvas__image" />
|
||||
</a>
|
||||
|
||||
<video *ngIf="att.type === 'gifv'" class="media-viewer-canvas__image" role="application" loop autoplay>
|
||||
<source src="{{att.url}}" type="video/mp4">
|
||||
</video>
|
||||
<video *ngIf="att.type === 'video'" class="media-viewer-canvas__image" role="application" loop autoplay
|
||||
controls="controls">
|
||||
<source src="{{att.url}}" type="video/mp4">
|
||||
</video>
|
||||
</div>
|
||||
|
||||
<div #video *ngIf="html" class="media-viewer-canvas__image media-viewer-canvas__iframe" [innerHTML]="html">
|
||||
</div>
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
@import "variables";
|
||||
@import "commons";
|
||||
@import "mixins";
|
||||
|
||||
.media-viewer-canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
// background-color: rgba(0, 0, 0, 0.8);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
position: relative;
|
||||
|
||||
&__button {
|
||||
@include clearButton;
|
||||
padding: 5px;
|
||||
|
@ -60,7 +61,14 @@
|
|||
padding: 10px;
|
||||
}
|
||||
|
||||
&__attachement {
|
||||
// max-width: 100%;
|
||||
// height: 100vh;
|
||||
}
|
||||
|
||||
&__image {
|
||||
z-index: 10;
|
||||
|
||||
@media screen and (min-width: $screen-break) {
|
||||
max-width: 85%;
|
||||
}
|
||||
|
@ -72,6 +80,8 @@
|
|||
margin-top: 50vh;
|
||||
margin-left: 50vw;
|
||||
transform: translate(-50%, -50%);
|
||||
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
&__iframe {
|
||||
|
@ -80,4 +90,18 @@
|
|||
max-height: 600px;
|
||||
max-width: 950px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.background__close {
|
||||
position: absolute;
|
||||
top:0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 0;
|
||||
background-color: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.collapsed {
|
||||
height: 0;
|
||||
}
|
||||
|
|
|
@ -18,26 +18,30 @@ export class MediaViewerComponent implements OnInit {
|
|||
faAngleLeft = faAngleLeft;
|
||||
faAngleRight = faAngleRight;
|
||||
|
||||
imageUrl: string;
|
||||
gifvUrl: string;
|
||||
videoUrl: string;
|
||||
attachments: AttachmentsWrapper[] = [];
|
||||
html: SafeHtml;
|
||||
|
||||
previousAvailable: boolean;
|
||||
nextAvailable: boolean;
|
||||
private currentIndex: number;
|
||||
currentIndex: number;
|
||||
|
||||
@Input('openedMediaEvent')
|
||||
set openedMediaEvent(value: OpenMediaEvent) {
|
||||
this._mediaEvent = value;
|
||||
|
||||
this.attachments.length = 0;
|
||||
|
||||
if (value.iframe) {
|
||||
this.html = value.iframe;
|
||||
this.autoplayIframe();
|
||||
} else {
|
||||
const attachment = value.attachments[value.selectedIndex];
|
||||
|
||||
for(let i = 0; i < value.attachments.length; i++){
|
||||
let att = value.attachments[i];
|
||||
this.attachments.push(new AttachmentsWrapper(att, i));
|
||||
}
|
||||
|
||||
this.currentIndex = value.selectedIndex;
|
||||
this.loadAttachment(attachment);
|
||||
this.setBrowsing();
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +56,7 @@ export class MediaViewerComponent implements OnInit {
|
|||
handleKeyboardEvent(event: KeyboardEvent) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
|
||||
if (event.key === 'ArrowRight') {
|
||||
this.next(event);
|
||||
} else if (event.key === 'ArrowLeft') {
|
||||
|
@ -65,24 +69,10 @@ export class MediaViewerComponent implements OnInit {
|
|||
ngOnInit() {
|
||||
}
|
||||
|
||||
private loadAttachment(attachment: Attachment) {
|
||||
if (attachment.type === 'image') {
|
||||
this.imageUrl = attachment.url;
|
||||
} else if (attachment.type === 'gifv') {
|
||||
this.gifvUrl = attachment.url;
|
||||
} else if (attachment.type === 'video') {
|
||||
this.videoUrl = attachment.url;
|
||||
}
|
||||
}
|
||||
|
||||
private setBrowsing() {
|
||||
var index = this.currentIndex;
|
||||
var attachments = this.openedMediaEvent.attachments;
|
||||
|
||||
console.log(`index ${index}`);
|
||||
console.log(`attachments.length ${attachments.length}`);
|
||||
|
||||
if (index < attachments.length - 1) {
|
||||
if (index < this.attachments.length - 1) {
|
||||
this.nextAvailable = true;
|
||||
} else {
|
||||
this.nextAvailable = false;
|
||||
|
@ -120,7 +110,6 @@ export class MediaViewerComponent implements OnInit {
|
|||
if (this.currentIndex <= 0) return false;
|
||||
|
||||
this.currentIndex--;
|
||||
this.imageUrl = this.openedMediaEvent.attachments[this.currentIndex].url;
|
||||
this.setBrowsing();
|
||||
|
||||
return false;
|
||||
|
@ -131,9 +120,34 @@ export class MediaViewerComponent implements OnInit {
|
|||
if (this.currentIndex >= this.openedMediaEvent.attachments.length - 1) return false;
|
||||
|
||||
this.currentIndex++;
|
||||
this.imageUrl = this.openedMediaEvent.attachments[this.currentIndex].url;
|
||||
this.setBrowsing();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class AttachmentsWrapper implements Attachment {
|
||||
constructor(attachment: Attachment, index: number) {
|
||||
this.id = attachment.id;
|
||||
this.type = attachment.type;
|
||||
this.url = attachment.url;
|
||||
this.remote_url = attachment.remote_url;
|
||||
this.preview_url = attachment.preview_url;
|
||||
this.text_url = attachment.text_url;
|
||||
this.meta = attachment.meta;
|
||||
this.description = attachment.description;
|
||||
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
id: string;
|
||||
type: "image" | "video" | "gifv";
|
||||
url: string;
|
||||
remote_url: string;
|
||||
preview_url: string;
|
||||
text_url: string;
|
||||
meta: any;
|
||||
description: string;
|
||||
|
||||
index: number;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<div class="poll">
|
||||
<div *ngIf="!poll.voted && !poll.expired">
|
||||
<div *ngFor="let o of options">
|
||||
<label class="poll__container">{{o.title}}
|
||||
<label class="poll__container">
|
||||
<span class="poll__container__title">{{o.title}}</span>
|
||||
<input class="poll__container__input" type="{{choiceType}}" name="{{pollName}}" value="{{o.title}}"
|
||||
(change)="onSelectionChange(o)">
|
||||
<span class="poll__container__checkmark" *ngIf="!pollLocked"
|
||||
|
@ -15,7 +16,7 @@
|
|||
<div class="poll__result" title="{{ o.votes_count }} votes">
|
||||
<div class="poll__result--progress-bar" [style.width]="o.percentage + '%'" [ngClass]="{ 'poll__result--progress-bar--most-votes': o.isMax }"></div>
|
||||
<div class="poll__result--data"> <span class="poll__result--percentage">{{ o.percentage }}%</span>
|
||||
{{o.title}}</div>
|
||||
<span class="poll__container__title">{{o.title}}</span></div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
// @import "variables";
|
||||
// @import "commons";
|
||||
@import "variables";
|
||||
@import "commons";
|
||||
// @import "panel";
|
||||
@import "buttons";
|
||||
|
||||
.poll {
|
||||
|
||||
|
||||
color: white;
|
||||
color: rgb(228, 228, 228);
|
||||
|
||||
|
@ -44,6 +44,11 @@
|
|||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
&__title {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&:hover &__input~&__checkmark {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
@ -125,7 +130,7 @@
|
|||
|
||||
&__result {
|
||||
transition: width 2s;
|
||||
|
||||
|
||||
margin: 0 0 5px 5px;
|
||||
padding: 0 5px 0 5px;
|
||||
position: relative;
|
||||
|
@ -141,15 +146,15 @@
|
|||
color: white;
|
||||
display: inline;
|
||||
margin-right: 10px;
|
||||
|
||||
|
||||
}
|
||||
|
||||
&--progress-bar {
|
||||
position: absolute;
|
||||
background-color: rgb(47, 68, 100);
|
||||
// background-color: rgb(43, 62, 92);
|
||||
top:0;
|
||||
left:0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: calc(100%);
|
||||
height: 22px;
|
||||
z-index: 1;
|
||||
|
@ -166,19 +171,4 @@
|
|||
display: inline-block;
|
||||
margin: 0 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.noselect {
|
||||
-webkit-touch-callout: none;
|
||||
/* iOS Safari */
|
||||
-webkit-user-select: none;
|
||||
/* Safari */
|
||||
-khtml-user-select: none;
|
||||
/* Konqueror HTML */
|
||||
-moz-user-select: none;
|
||||
/* Firefox */
|
||||
-ms-user-select: none;
|
||||
/* Internet Explorer/Edge */
|
||||
user-select: none;
|
||||
/* Non-prefixed version, currently supported by Chrome and Opera */
|
||||
}
|
|
@ -53,8 +53,8 @@ export class PollComponent implements OnInit {
|
|||
return this._poll;
|
||||
}
|
||||
|
||||
@Input() provider: AccountInfo;
|
||||
@Input() status: Status;
|
||||
// @Input() provider: AccountInfo;
|
||||
@Input() statusWrapper: StatusWrapper;
|
||||
|
||||
private accounts$: Observable<AccountInfo[]>;
|
||||
private accountSub: Subscription;
|
||||
|
@ -71,8 +71,8 @@ export class PollComponent implements OnInit {
|
|||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.pollPerAccountId[this.provider.id] = Promise.resolve(this.poll);
|
||||
this.selectedAccount = this.provider;
|
||||
this.pollPerAccountId[this.statusWrapper.provider.id] = Promise.resolve(this.poll);
|
||||
this.selectedAccount = this.statusWrapper.provider;
|
||||
|
||||
this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => {
|
||||
this.checkStatus(accounts);
|
||||
|
@ -84,10 +84,10 @@ export class PollComponent implements OnInit {
|
|||
var newSelectedAccount = accounts.find(x => x.isSelected);
|
||||
|
||||
const accountChanged = this.selectedAccount.id !== newSelectedAccount.id;
|
||||
if (accountChanged && !this.pollPerAccountId[newSelectedAccount.id] && (this.status.visibility === 'public' || this.status.visibility === 'unlisted')) {
|
||||
if (accountChanged && !this.pollPerAccountId[newSelectedAccount.id] && (this.statusWrapper.status.visibility === 'public' || this.statusWrapper.status.visibility === 'unlisted')) {
|
||||
this.setStatsAtZero();
|
||||
|
||||
this.pollPerAccountId[newSelectedAccount.id] = this.toolsService.getStatusUsableByAccount(newSelectedAccount, new StatusWrapper(this.status, this.provider))
|
||||
this.pollPerAccountId[newSelectedAccount.id] = this.toolsService.getStatusUsableByAccount(newSelectedAccount, new StatusWrapper(this.statusWrapper.status, this.statusWrapper.provider))
|
||||
.then((status: Status) => {
|
||||
return this.mastodonService.getPoll(newSelectedAccount, status.poll.id);
|
||||
})
|
||||
|
@ -99,7 +99,7 @@ export class PollComponent implements OnInit {
|
|||
this.notificationService.notifyHttpError(err);
|
||||
return null;
|
||||
});
|
||||
} else if (this.status.visibility !== 'public' && this.status.visibility !== 'unlisted' && this.provider.id !== newSelectedAccount.id) {
|
||||
} else if (this.statusWrapper.status.visibility !== 'public' && this.statusWrapper.status.visibility !== 'unlisted' && this.statusWrapper.provider.id !== newSelectedAccount.id) {
|
||||
this.pollLocked = true;
|
||||
} else {
|
||||
this.pollPerAccountId[newSelectedAccount.id]
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<div class="reblog" *ngIf="reblog">
|
||||
<a class="reblog__profile-link" href (click)="openAccount(status.account)"><span
|
||||
innerHTML="{{ status.account | accountEmoji }}"></span> <img *ngIf="reblog" class="reblog__avatar"
|
||||
src="{{ status.account.avatar }}" /></a> boosted
|
||||
<a class="reblog__profile-link" href (click)="openAccount(status.account)"
|
||||
(auxclick)="openUrl(status.account.url)"><span innerHTML="{{ status.account | accountEmoji }}"></span> <img
|
||||
*ngIf="reblog" class="reblog__avatar" src="{{ status.account.avatar }}" /></a> boosted
|
||||
</div>
|
||||
<div *ngIf="notificationType === 'favourite'">
|
||||
<div class="notification--icon">
|
||||
|
@ -30,9 +30,12 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="status">
|
||||
<a href class="status__navigation" title="open status" (click)="textSelected()">
|
||||
|
||||
</a>
|
||||
<div [ngClass]="{'notification--status': notificationAccount }">
|
||||
<a href class="status__profile-link" title="{{displayedStatus.account.acct}}"
|
||||
(click)="openAccount(displayedStatus.account)">
|
||||
(click)="openAccount(displayedStatus.account)" (auxclick)="openUrl(displayedStatus.account.url)">
|
||||
<img [class.status__avatar--boosted]="reblog || notificationAccount" class="status__avatar"
|
||||
src="{{ displayedStatus.account.avatar }}" />
|
||||
<!-- <img *ngIf="reblog" class="status__avatar--reblog" src="{{ status.account.avatar }}" /> -->
|
||||
|
@ -44,12 +47,12 @@
|
|||
</span>
|
||||
</a>
|
||||
<div class="status__created-at" title="{{ displayedStatus.created_at | date: 'full' }}">
|
||||
<a href class="status__created-at--link" (click)="textSelected()" (auxclick)="openUrl()">
|
||||
{{ status.created_at | timeAgo | async }}
|
||||
<a href class="status__created-at--link" (click)="textSelected()" (auxclick)="openUrl(displayedStatus.url)">
|
||||
{{ displayedStatus.created_at | timeAgo | async }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="status__labels">
|
||||
<div class="status__labels--label status__labels--bot" title="bot" *ngIf="status.account.bot">
|
||||
<div class="status__labels--label status__labels--bot" title="bot" *ngIf="displayedStatus.account.bot">
|
||||
bot
|
||||
</div>
|
||||
<div class="status__labels--label status__labels--xpost" title="this status was cross-posted"
|
||||
|
@ -63,7 +66,12 @@
|
|||
*ngIf="hasReply">
|
||||
replies
|
||||
</div>
|
||||
<div class="status__labels--label status__labels--old" title="this status is old" *ngIf="isOld">
|
||||
old
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- <div #content class="status__content" innerHTML="{{displayedStatus.content}}"></div> -->
|
||||
|
||||
<a href class="status__content-warning" *ngIf="isContentWarned" title="show content"
|
||||
|
@ -75,18 +83,20 @@
|
|||
(accountSelected)="accountSelected($event)" (hashtagSelected)="hashtagSelected($event)"
|
||||
(textSelected)="textSelected()"></app-databinded-text>
|
||||
|
||||
<app-poll class="status__poll" *ngIf="!isContentWarned && displayedStatus.poll"
|
||||
[poll]="displayedStatus.poll" [status]="displayedStatus" [provider]="statusWrapper.provider"></app-poll>
|
||||
<app-poll class="status__poll" *ngIf="!isContentWarned && displayedStatus.poll" [poll]="displayedStatus.poll"
|
||||
[statusWrapper]="displayedStatusWrapper"></app-poll>
|
||||
|
||||
<app-card class="status__card" *ngIf="!isContentWarned && displayedStatus.card && !hasAttachments" [card]="displayedStatus.card"></app-card>
|
||||
<app-card class="status__card" *ngIf="!isContentWarned && displayedStatus.card && !hasAttachments"
|
||||
[card]="displayedStatus.card"></app-card>
|
||||
|
||||
<app-attachements *ngIf="!isContentWarned && hasAttachments" class="attachments"
|
||||
[attachments]="displayedStatus.media_attachments">
|
||||
</app-attachements>
|
||||
|
||||
<app-action-bar #appActionBar [statusWrapper]="statusWrapper" (cwIsActiveEvent)="changeCw($event)"
|
||||
<app-action-bar #appActionBar [statusWrapper]="displayedStatusWrapper" (cwIsActiveEvent)="changeCw($event)"
|
||||
(replyEvent)="openReply()"></app-action-bar>
|
||||
</div>
|
||||
<app-create-status *ngIf="replyingToStatus" [statusReplyingToWrapper]="statusWrapper" (onClose)="closeReply()">
|
||||
<app-create-status *ngIf="replyingToStatus" [statusReplyingToWrapper]="displayedStatusWrapper"
|
||||
(onClose)="closeReply()">
|
||||
</app-create-status>
|
||||
</div>
|
|
@ -80,6 +80,9 @@
|
|||
&--discuss {
|
||||
background-color: rgb(90, 0, 143);
|
||||
}
|
||||
&--old {
|
||||
background-color: rgb(150, 0, 0);
|
||||
}
|
||||
}
|
||||
&__name {
|
||||
display: inline-block;
|
||||
|
@ -123,9 +126,9 @@
|
|||
}
|
||||
&__content-warning {
|
||||
min-height: 80px;
|
||||
display: block; // border: 1px solid greenyellow;
|
||||
display: block;
|
||||
margin: 0 10px 0 $avatar-column-space;
|
||||
padding: 3px 5px 3px 5px;
|
||||
padding: 3px 5px 12px 5px;
|
||||
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
|
@ -159,6 +162,16 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__navigation{
|
||||
display: block;
|
||||
position: absolute;
|
||||
top:65px;
|
||||
bottom: 40px;
|
||||
width: 65px;
|
||||
min-height: 40px;
|
||||
// outline: 1px solid greenyellow;
|
||||
}
|
||||
}
|
||||
|
||||
.attachments {
|
||||
|
|
|
@ -6,6 +6,7 @@ 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 { TrustedString } from '@angular/core/src/sanitization/bypass';
|
||||
|
||||
@Component({
|
||||
selector: "app-status",
|
||||
|
@ -20,6 +21,7 @@ export class StatusComponent implements OnInit {
|
|||
faList = faList;
|
||||
|
||||
displayedStatus: Status;
|
||||
displayedStatusWrapper: StatusWrapper;
|
||||
|
||||
// statusAccountName: string;
|
||||
statusContent: string;
|
||||
|
@ -29,6 +31,7 @@ export class StatusComponent implements OnInit {
|
|||
replyingToStatus: boolean;
|
||||
isCrossPoster: boolean;
|
||||
isThread: boolean;
|
||||
isOld: boolean;
|
||||
isContentWarned: boolean;
|
||||
hasReply: boolean;
|
||||
contentWarningText: string;
|
||||
|
@ -58,6 +61,8 @@ export class StatusComponent implements OnInit {
|
|||
this.displayedStatus = this.status;
|
||||
}
|
||||
|
||||
this.displayedStatusWrapper = new StatusWrapper(this.displayedStatus, value.provider);
|
||||
|
||||
this.checkLabels(this.displayedStatus);
|
||||
this.checkContentWarning(this.displayedStatus);
|
||||
|
||||
|
@ -120,6 +125,13 @@ export class StatusComponent implements OnInit {
|
|||
}
|
||||
|
||||
this.hasReply = status.replies_count > 0;
|
||||
|
||||
let createdAt = new Date(status.created_at);
|
||||
let now = new Date();
|
||||
now.setMonth(now.getMonth() - 3);
|
||||
if (now > createdAt) {
|
||||
this.isOld = true;
|
||||
}
|
||||
}
|
||||
|
||||
openAccount(account: Account): boolean {
|
||||
|
@ -165,9 +177,9 @@ export class StatusComponent implements OnInit {
|
|||
return false;
|
||||
}
|
||||
|
||||
openUrl(): boolean {
|
||||
openUrl(url: string): boolean {
|
||||
event.preventDefault();
|
||||
window.open(this.displayedStatus.url, "_blank");
|
||||
window.open(url, "_blank");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,33 @@
|
|||
<div class="stream-edition">
|
||||
<button (click)="moveLeft()" class="stream-edition__button" title="move left"><fa-icon [icon]="faChevronLeft"></fa-icon></button>
|
||||
<button (click)="moveRight()" class="stream-edition__button" title="move right"><fa-icon [icon]="faChevronRight"></fa-icon></button>
|
||||
<button (click)="delete()" class="stream-edition__button stream-edition__button--delete" title="remove column"><fa-icon [icon]="faTimes"></fa-icon></button>
|
||||
<div class="stream-edition__settings">
|
||||
<div class="stream-edition__setting">
|
||||
<input [(ngModel)]="hideBoosts" (change)="settingsChanged()"
|
||||
class="stream-edition__setting--checkbox" type="checkbox" id="hideBoosts"> <label for="hideBoosts" class="noselect">hide
|
||||
boosts</label><br />
|
||||
</div>
|
||||
<div class="stream-edition__setting">
|
||||
<input [(ngModel)]="hideReplies" (change)="settingsChanged()"
|
||||
class="stream-edition__setting--checkbox" type="checkbox" id="hideReplies"> <label for="hideReplies" class="noselect">hide
|
||||
replies</label><br />
|
||||
</div>
|
||||
<div class="stream-edition__setting">
|
||||
<input [(ngModel)]="hideBots" (change)="settingsChanged()"
|
||||
class="stream-edition__setting--checkbox" type="checkbox" id="hideBots" > <label for="hideBots" class="noselect">hide
|
||||
bots</label><br />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button (click)="delete()" class="stream-edition__button stream-edition__button--delete" title="remove column">
|
||||
<fa-icon [icon]="faTimes"></fa-icon> <span class="stream-edition__button--delete--label">remove</span>
|
||||
</button>
|
||||
|
||||
<button (click)="moveRight()"
|
||||
class="stream-edition__button stream-edition__button--move stream-edition__button--move--right"
|
||||
title="move right">
|
||||
<fa-icon [icon]="faChevronRight"></fa-icon>
|
||||
</button>
|
||||
<button (click)="moveLeft()" class="stream-edition__button stream-edition__button--move" title="move left">
|
||||
<fa-icon [icon]="faChevronLeft"></fa-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
|
@ -1,17 +1,51 @@
|
|||
@import "variables";
|
||||
@import "commons";
|
||||
@import "mixins";
|
||||
|
||||
.stream-edition {
|
||||
width: 100%;
|
||||
// min-height: 50px;
|
||||
background-color: #222736;
|
||||
border-bottom: 1px solid $color-secondary;
|
||||
|
||||
&__settings{
|
||||
padding: 5px 5px 0 5px;
|
||||
border-bottom: 1px solid $color-secondary;
|
||||
}
|
||||
|
||||
&__setting{
|
||||
// outline: 1px solid greenyellow;
|
||||
padding: 0 10px 0 10px;
|
||||
|
||||
&--checkbox {
|
||||
position: relative;
|
||||
top: 2px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&__button {
|
||||
@include clearButton;
|
||||
padding: 5px 10px 5px 10px;
|
||||
margin: 3px 0;
|
||||
&--delete{
|
||||
|
||||
&--delete {
|
||||
// float: right;
|
||||
margin-left: 5px;
|
||||
|
||||
&--label {
|
||||
position: relative;
|
||||
top: -1px;
|
||||
left: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
&--move {
|
||||
float: right;
|
||||
margin-right: 5px;
|
||||
|
||||
&--right {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Component, OnInit, Input } from '@angular/core';
|
|||
import { Store } from '@ngxs/store';
|
||||
import { faChevronLeft, faChevronRight, faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { StreamElement, RemoveStream, MoveStreamUp, MoveStreamDown } from '../../../states/streams.state';
|
||||
import { StreamElement, RemoveStream, MoveStreamUp, MoveStreamDown, UpdateStream } from '../../../states/streams.state';
|
||||
|
||||
@Component({
|
||||
selector: 'app-stream-edition',
|
||||
|
@ -14,11 +14,27 @@ export class StreamEditionComponent implements OnInit {
|
|||
faChevronRight = faChevronRight;
|
||||
faTimes = faTimes;
|
||||
|
||||
hideBoosts: boolean;
|
||||
hideReplies: boolean;
|
||||
hideBots: boolean;
|
||||
|
||||
@Input() streamElement: StreamElement;
|
||||
|
||||
constructor(private readonly store: Store) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.hideBoosts = this.streamElement.hideBoosts;
|
||||
this.hideReplies = this.streamElement.hideReplies;
|
||||
this.hideBots = this.streamElement.hideBots;
|
||||
}
|
||||
|
||||
settingsChanged(): boolean {
|
||||
this.streamElement.hideBoosts = this.hideBoosts;
|
||||
this.streamElement.hideReplies = this.hideReplies;
|
||||
this.streamElement.hideBots = this.hideBots;
|
||||
|
||||
this.store.dispatch([new UpdateStream(this.streamElement)]);
|
||||
return false;
|
||||
}
|
||||
|
||||
moveLeft(): boolean {
|
||||
|
|
|
@ -18,7 +18,7 @@ import { StatusWrapper } from '../../../models/common.model';
|
|||
styleUrls: ['./stream-statuses.component.scss']
|
||||
})
|
||||
export class StreamStatusesComponent implements OnInit, OnDestroy {
|
||||
isLoading = true;
|
||||
isLoading = true;
|
||||
isThread = false;
|
||||
displayError: string;
|
||||
hasContentWarnings = false;
|
||||
|
@ -31,6 +31,10 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
|||
private bufferStream: Status[] = [];
|
||||
private bufferWasCleared: boolean;
|
||||
|
||||
private hideBoosts: boolean;
|
||||
private hideReplies: boolean;
|
||||
private hideBots: boolean;
|
||||
|
||||
@Output() browseAccountEvent = new EventEmitter<string>();
|
||||
@Output() browseHashtagEvent = new EventEmitter<string>();
|
||||
@Output() browseThreadEvent = new EventEmitter<OpenThreadEvent>();
|
||||
|
@ -38,6 +42,11 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
|||
@Input()
|
||||
set streamElement(streamElement: StreamElement) {
|
||||
this._streamElement = streamElement;
|
||||
|
||||
this.hideBoosts = streamElement.hideBoosts;
|
||||
this.hideBots = streamElement.hideBots;
|
||||
this.hideReplies = streamElement.hideReplies;
|
||||
|
||||
this.load(this._streamElement);
|
||||
}
|
||||
get streamElement(): StreamElement {
|
||||
|
@ -49,6 +58,8 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
|||
@Input() userLocked = true;
|
||||
|
||||
private goToTopSubscription: Subscription;
|
||||
private streamsSubscription: Subscription;
|
||||
private streams$: Observable<StreamElement[]>;
|
||||
|
||||
constructor(
|
||||
private readonly store: Store,
|
||||
|
@ -56,16 +67,29 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
|||
private readonly notificationService: NotificationService,
|
||||
private readonly streamingService: StreamingService,
|
||||
private readonly mastodonService: MastodonService) {
|
||||
|
||||
this.streams$ = this.store.select(state => state.streamsstatemodel.streams);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.goToTopSubscription = this.goToTop.subscribe(() => {
|
||||
this.applyGoToTop();
|
||||
});
|
||||
|
||||
this.streamsSubscription = this.streams$.subscribe((streams: StreamElement[]) => {
|
||||
let updatedStream = streams.find(x => x.id === this.streamElement.id);
|
||||
|
||||
if (this.hideBoosts !== updatedStream.hideBoosts
|
||||
|| this.hideBots !== updatedStream.hideBots
|
||||
|| this.hideReplies !== updatedStream.hideReplies) {
|
||||
this.streamElement = updatedStream;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.goToTopSubscription) this.goToTopSubscription.unsubscribe();
|
||||
if (this.streamsSubscription) this.streamsSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
refresh(): any {
|
||||
|
@ -101,6 +125,10 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
|||
if (update.type === EventEnum.update) {
|
||||
if (!this.statuses.find(x => x.status.id == update.status.id)) {
|
||||
if (this.streamPositionnedAtTop) {
|
||||
if (this.isFiltered(update.status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const wrapper = new StatusWrapper(update.status, this.account);
|
||||
this.statuses.unshift(wrapper);
|
||||
} else {
|
||||
|
@ -173,6 +201,10 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
for (const status of this.bufferStream) {
|
||||
if (this.isFiltered(status)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const wrapper = new StatusWrapper(status, this.account);
|
||||
this.statuses.unshift(wrapper);
|
||||
}
|
||||
|
@ -181,7 +213,7 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
|
||||
private scrolledToBottom() {
|
||||
if(this.isLoading) return;
|
||||
if (this.isLoading) return;
|
||||
|
||||
this.isLoading = true;
|
||||
this.isProcessingInfiniteScroll = true;
|
||||
|
@ -190,6 +222,10 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
|||
this.mastodonService.getTimeline(this.account, this._streamElement.type, lastStatus.status.id, null, this.streamingService.nbStatusPerIteration, this._streamElement.tag, this._streamElement.listId)
|
||||
.then((status: Status[]) => {
|
||||
for (const s of status) {
|
||||
if (this.isFiltered(s)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const wrapper = new StatusWrapper(s, this.account);
|
||||
this.statuses.push(wrapper);
|
||||
}
|
||||
|
@ -214,6 +250,10 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
|||
.then((results: Status[]) => {
|
||||
this.isLoading = false;
|
||||
for (const s of results) {
|
||||
if (this.isFiltered(s)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const wrapper = new StatusWrapper(s, this.account);
|
||||
this.statuses.push(wrapper);
|
||||
}
|
||||
|
@ -232,6 +272,25 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
|||
this.bufferWasCleared = true;
|
||||
this.bufferStream.length = 2 * this.streamingService.nbStatusPerIteration;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private isFiltered(status: Status): boolean {
|
||||
if (this.streamElement.hideBoosts) {
|
||||
if (status.reblog) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (this.streamElement.hideBots) {
|
||||
if (status.account.bot) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (this.streamElement.hideReplies) {
|
||||
if (status.in_reply_to_account_id && status.account.id !== status.in_reply_to_account_id) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,11 +2,15 @@
|
|||
<app-waiting-animation *ngIf="isLoading" class="waiting-icon"></app-waiting-animation>
|
||||
|
||||
|
||||
<div *ngIf="displayedAccount" class="profile-header" [ngStyle]="{'background-image':'url('+displayedAccount.header+')'}">
|
||||
<div *ngIf="displayedAccount" class="profile-header"
|
||||
[ngStyle]="{'background-image':'url('+displayedAccount.header+')'}">
|
||||
<div class="profile-header__inner">
|
||||
<img class="profile-header__avatar" src="{{displayedAccount.avatar}}" alt="header" />
|
||||
<a href (click)="showAvatar(displayedAccount.avatar)" title="open avatar">
|
||||
<img class="profile-header__avatar" src="{{displayedAccount.avatar}}" alt="header" />
|
||||
</a>
|
||||
<h2 class="profile-header__display-name" innerHTML="{{displayedAccount | accountEmoji }}"></h2>
|
||||
<h2 class="profile-header__fullhandle"><a href="{{displayedAccount.url}}" target="_blank">@{{displayedAccount.acct}}</a></h2>
|
||||
<h2 class="profile-header__fullhandle"><a href="{{displayedAccount.url}}"
|
||||
target="_blank">@{{displayedAccount.acct}}</a></h2>
|
||||
|
||||
<div class="profile-header__follow" *ngIf="relationship">
|
||||
<button class="profile-header__follow--button profile-header__follow--unfollowed" title="follow"
|
||||
|
@ -38,7 +42,8 @@
|
|||
</div>
|
||||
<div class="profile-fields" *ngIf="displayedAccount && displayedAccount.fields.length > 0">
|
||||
<div class="profile-fields__field" *ngFor="let field of displayedAccount.fields">
|
||||
<div class="profile-fields__field--value" innerHTML="{{ displayedAccount | accountEmoji:field.value}}" [ngClass]="{'profile-fields__field--validated': field.verified_at }">
|
||||
<div class="profile-fields__field--value" innerHTML="{{ displayedAccount | accountEmoji:field.value}}"
|
||||
[ngClass]="{'profile-fields__field--validated': field.verified_at }">
|
||||
</div>
|
||||
<div class="profile-fields__field--name">
|
||||
{{ field.name }}
|
||||
|
@ -57,7 +62,7 @@
|
|||
</app-status>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<app-waiting-animation *ngIf="statusLoading" class="waiting-icon"></app-waiting-animation>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -5,13 +5,14 @@ import { faUser as faUserRegular } from "@fortawesome/free-regular-svg-icons";
|
|||
import { Observable, Subscription } from 'rxjs';
|
||||
import { Store } from '@ngxs/store';
|
||||
|
||||
import { Account, Status, Relationship } from "../../../services/models/mastodon.interfaces";
|
||||
import { Account, Status, Relationship, Attachment } from "../../../services/models/mastodon.interfaces";
|
||||
import { MastodonService } from '../../../services/mastodon.service';
|
||||
import { ToolsService, OpenThreadEvent } from '../../../services/tools.service';
|
||||
import { NotificationService } from '../../../services/notification.service';
|
||||
import { AccountInfo } from '../../../states/accounts.state';
|
||||
import { StatusWrapper } from '../../../models/common.model';
|
||||
import { EmojiConverter, EmojiTypeEnum } from '../../../tools/emoji.tools';
|
||||
import { NavigationService } from '../../../services/navigation.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-profile',
|
||||
|
@ -32,7 +33,7 @@ export class UserProfileComponent implements OnInit {
|
|||
note: string;
|
||||
|
||||
isLoading: boolean;
|
||||
|
||||
|
||||
private maxReached = false;
|
||||
private maxId: string;
|
||||
statusLoading: boolean;
|
||||
|
@ -60,6 +61,7 @@ export class UserProfileComponent implements OnInit {
|
|||
|
||||
constructor(
|
||||
private readonly store: Store,
|
||||
private readonly navigationService: NavigationService,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly mastodonService: MastodonService,
|
||||
private readonly toolsService: ToolsService) {
|
||||
|
@ -103,7 +105,7 @@ export class UserProfileComponent implements OnInit {
|
|||
|
||||
this.displayedAccount = account;
|
||||
this.hasNote = account && account.note && account.note !== '<p></p>';
|
||||
if(this.hasNote){
|
||||
if (this.hasNote) {
|
||||
this.note = this.emojiConverter.applyEmojis(account.emojis, account.note, EmojiTypeEnum.medium);
|
||||
}
|
||||
|
||||
|
@ -155,6 +157,26 @@ export class UserProfileComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
showAvatar(avatarUrl: string): boolean {
|
||||
const att: Attachment = {
|
||||
id: '',
|
||||
type: 'image',
|
||||
remote_url: avatarUrl,
|
||||
preview_url: avatarUrl,
|
||||
url: avatarUrl,
|
||||
meta: null,
|
||||
text_url: '',
|
||||
description: ''
|
||||
}
|
||||
this.navigationService.openMedia({
|
||||
selectedIndex: 0,
|
||||
attachments: [att],
|
||||
iframe: null
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
refresh(): any {
|
||||
this.load(this.lastAccountName);
|
||||
}
|
||||
|
@ -187,7 +209,7 @@ export class UserProfileComponent implements OnInit {
|
|||
}
|
||||
|
||||
unfollow(): boolean {
|
||||
const userAccount = this.toolsService.getSelectedAccounts()[0];
|
||||
const userAccount = this.toolsService.getSelectedAccounts()[0];
|
||||
this.toolsService.findAccount(userAccount, this.lastAccountName)
|
||||
.then((account: Account) => {
|
||||
return this.mastodonService.unfollow(userAccount, account);
|
||||
|
@ -209,7 +231,7 @@ export class UserProfileComponent implements OnInit {
|
|||
this.scrolledToBottom();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private scrolledToBottom() {
|
||||
if (this.statusLoading || this.maxReached) return;
|
||||
|
||||
|
@ -227,7 +249,7 @@ export class UserProfileComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
|
||||
private loadStatus(userAccount: AccountInfo, statuses: Status[]){
|
||||
private loadStatus(userAccount: AccountInfo, statuses: Status[]) {
|
||||
if (statuses.length === 0) {
|
||||
this.maxReached = true;
|
||||
return;
|
||||
|
|
|
@ -5,10 +5,6 @@ export class AccountWrapper {
|
|||
constructor() {
|
||||
}
|
||||
|
||||
// id: number;
|
||||
// username: string;
|
||||
// display_name: string;
|
||||
|
||||
info: AccountInfo;
|
||||
avatar: string;
|
||||
}
|
||||
|
|
|
@ -4,10 +4,11 @@ import { ActivatedRoute, Router } from "@angular/router";
|
|||
import { HttpErrorResponse } from "@angular/common/http";
|
||||
|
||||
import { AuthService, CurrentAuthProcess } from "../../services/auth.service";
|
||||
import { TokenData } from "../../services/models/mastodon.interfaces";
|
||||
import { TokenData, Account } from "../../services/models/mastodon.interfaces";
|
||||
import { RegisteredAppsStateModel, AppInfo } from "../../states/registered-apps.state";
|
||||
import { AccountInfo, AddAccount } from "../../states/accounts.state";
|
||||
import { AccountInfo, AddAccount, AccountsStateModel } from "../../states/accounts.state";
|
||||
import { NotificationService } from "../../services/notification.service";
|
||||
import { MastodonService } from '../../services/mastodon.service';
|
||||
|
||||
@Component({
|
||||
selector: "app-register-new-account",
|
||||
|
@ -23,6 +24,7 @@ export class RegisterNewAccountComponent implements OnInit {
|
|||
private authStorageKey: string = 'tempAuth';
|
||||
|
||||
constructor(
|
||||
private readonly mastodonService: MastodonService,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly store: Store,
|
||||
|
@ -45,13 +47,26 @@ export class RegisterNewAccountComponent implements OnInit {
|
|||
}
|
||||
|
||||
const appInfo = this.getAllSavedApps().filter(x => x.instance === appDataWrapper.instance)[0];
|
||||
|
||||
let usedTokenData: TokenData;
|
||||
this.authService.getToken(appDataWrapper.instance, appInfo.app.client_id, appInfo.app.client_secret, code, appInfo.app.redirect_uri)
|
||||
.then((tokenData: TokenData) => {
|
||||
usedTokenData = tokenData;
|
||||
return this.mastodonService.retrieveAccountDetails({ 'instance': appDataWrapper.instance, 'id': '', 'username': '', 'order': 0, 'isSelected': true, 'token': tokenData });
|
||||
})
|
||||
.then((account: Account) => {
|
||||
var username = account.username.toLowerCase();
|
||||
var instance = appDataWrapper.instance.toLowerCase();
|
||||
|
||||
if(this.isAccountAlreadyPresent(username, instance)){
|
||||
this.notificationService.notify(`Account @${username}@${instance} is already registered`, true);
|
||||
this.router.navigate(['/home']);
|
||||
return;
|
||||
}
|
||||
|
||||
const accountInfo = new AccountInfo();
|
||||
accountInfo.username = appDataWrapper.username.toLowerCase();
|
||||
accountInfo.instance = appDataWrapper.instance.toLowerCase();
|
||||
accountInfo.token = tokenData;
|
||||
accountInfo.username = username;
|
||||
accountInfo.instance = instance;
|
||||
accountInfo.token = usedTokenData;
|
||||
|
||||
this.store.dispatch([new AddAccount(accountInfo)])
|
||||
.subscribe(() => {
|
||||
|
@ -68,6 +83,16 @@ export class RegisterNewAccountComponent implements OnInit {
|
|||
ngOnInit() {
|
||||
}
|
||||
|
||||
private isAccountAlreadyPresent(username: string, instance: string): boolean{
|
||||
const accounts = <AccountInfo[]>this.store.snapshot().registeredaccounts.accounts;
|
||||
for (let acc of accounts) {
|
||||
if(acc.instance === instance && acc.username == username){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private displayError(type: RegistrationErrorTypes) {
|
||||
this.hasError = true;
|
||||
switch (type) {
|
||||
|
|
|
@ -32,11 +32,11 @@ export class TimeAgoPipe implements PipeTransform {
|
|||
// const months = days / 30.416;
|
||||
// const years = days / 365;
|
||||
|
||||
if (seconds <= 60) {
|
||||
if (seconds <= 59) {
|
||||
text = Math.round(seconds) + 's';
|
||||
} else if (minutes <= 90) {
|
||||
} else if (minutes <= 59) {
|
||||
text = Math.round(minutes) + 'm';
|
||||
} else if (hours <= 24) {
|
||||
} else if (hours <= 23) {
|
||||
text = Math.round(hours) + 'h';
|
||||
} else {
|
||||
text = Math.round(days) + 'd';
|
||||
|
|
|
@ -31,11 +31,11 @@ export class TimeLeftPipe implements PipeTransform {
|
|||
|
||||
if (seconds < 0) {
|
||||
text = '0 seconds left';
|
||||
} else if (seconds <= 60) {
|
||||
} else if (seconds <= 59) {
|
||||
text = Math.round(seconds) + ' seconds left';
|
||||
} else if (minutes <= 90) {
|
||||
} else if (minutes <= 59) {
|
||||
text = Math.round(minutes) + ' minutes left';
|
||||
} else if (hours <= 24) {
|
||||
} else if (hours <= 23) {
|
||||
text = Math.round(hours) + ' hours left';
|
||||
} else {
|
||||
text = Math.round(days) + ' days left';
|
||||
|
|
|
@ -30,6 +30,10 @@ export class ToolsService {
|
|||
accountSettings.accountId = account.id;
|
||||
this.saveAccountSettings(accountSettings);
|
||||
}
|
||||
if(!accountSettings.customStatusCharLength){
|
||||
accountSettings.customStatusCharLength = 500;
|
||||
this.saveAccountSettings(accountSettings);
|
||||
}
|
||||
return accountSettings;
|
||||
}
|
||||
|
||||
|
|
|
@ -44,22 +44,16 @@ export class AccountsState {
|
|||
@Action(SelectAccount)
|
||||
SelectAccount(ctx: StateContext<AccountsStateModel>, action: SelectAccount){
|
||||
const state = ctx.getState();
|
||||
// const multiSelection = action.multiselection;
|
||||
|
||||
const selectedAccount = action.account;
|
||||
|
||||
|
||||
// const copyAccounts = [...state.accounts];
|
||||
// copyAccounts
|
||||
// .filter(x => x.id !== selectedAccount.id)
|
||||
// .forEach(x => x.isSelected = false);
|
||||
|
||||
const oldSelectedAccount = state.accounts.find(x => x.isSelected);
|
||||
|
||||
if(selectedAccount.id === oldSelectedAccount.id) return;
|
||||
if(oldSelectedAccount != null && selectedAccount.id === oldSelectedAccount.id) return;
|
||||
|
||||
const acc = state.accounts.find(x => x.id === selectedAccount.id);
|
||||
acc.isSelected = true;
|
||||
oldSelectedAccount.isSelected = false;
|
||||
|
||||
if(oldSelectedAccount != null) oldSelectedAccount.isSelected = false;
|
||||
|
||||
ctx.patchState({
|
||||
accounts: [...state.accounts]
|
||||
|
|
|
@ -21,6 +21,9 @@ export class AccountSettings {
|
|||
displayNotifications: boolean = true;
|
||||
lastMentionCreationDate: string;
|
||||
lastNotificationCreationDate: string;
|
||||
|
||||
customStatusCharLengthEnabled: boolean = false;
|
||||
customStatusCharLength: number = 500;
|
||||
}
|
||||
|
||||
export class GlobalSettings {
|
||||
|
|
|
@ -5,6 +5,11 @@ export class AddStream {
|
|||
constructor(public stream: StreamElement) {}
|
||||
}
|
||||
|
||||
export class UpdateStream {
|
||||
static readonly type = '[Streams] Update stream';
|
||||
constructor(public stream: StreamElement) {}
|
||||
}
|
||||
|
||||
export class RemoveAllStreams {
|
||||
static readonly type = '[Streams] Remove all streams';
|
||||
constructor(public accountId :string) {}
|
||||
|
@ -43,6 +48,20 @@ export class StreamsState {
|
|||
streams: [...state.streams, action.stream]
|
||||
});
|
||||
}
|
||||
@Action(UpdateStream)
|
||||
UpdateStream(ctx: StateContext<StreamsStateModel>, action: UpdateStream){
|
||||
const state = ctx.getState();
|
||||
|
||||
const updatedStream = state.streams.find(x => x.id === action.stream.id);
|
||||
|
||||
updatedStream.hideBoosts = action.stream.hideBoosts;
|
||||
updatedStream.hideReplies = action.stream.hideReplies;
|
||||
updatedStream.hideBots = action.stream.hideBots;
|
||||
|
||||
ctx.patchState({
|
||||
streams: [...state.streams]
|
||||
});
|
||||
}
|
||||
@Action(RemoveAllStreams)
|
||||
RemoveAllStreams(ctx: StateContext<StreamsStateModel>, action: RemoveAllStreams){
|
||||
const state = ctx.getState();
|
||||
|
@ -92,6 +111,10 @@ export class StreamsState {
|
|||
export class StreamElement {
|
||||
public id: string;
|
||||
|
||||
public hideBoosts: boolean = false;
|
||||
public hideReplies: boolean = false;
|
||||
public hideBots: boolean = false;
|
||||
|
||||
constructor(
|
||||
public type: StreamTypeEnum,
|
||||
public name: string,
|
||||
|
|
|
@ -1,7 +1,27 @@
|
|||
@import "variables";
|
||||
|
||||
.themed-form{
|
||||
background-color: $column-color;
|
||||
border: 1px $button-border-color solid;
|
||||
padding: 2px 0 2px 5px;
|
||||
// border-width: 1px;
|
||||
color: #fff;
|
||||
&:focus {
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
outline: 0px;
|
||||
}
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
|
||||
.waiting-icon {
|
||||
width: 40px;
|
||||
display: block;
|
||||
margin: 5px auto;
|
||||
margin: 5px auto;
|
||||
}
|
||||
|
||||
.flexcroll {
|
||||
|
@ -12,9 +32,11 @@
|
|||
scrollbar-darkshadow-color: #08090d;
|
||||
scrollbar-track-color: #08090d;
|
||||
scrollbar-arrow-color: #08090d;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 7px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
-webkit-border-radius: 0px;
|
||||
border-radius: 0px;
|
||||
|
@ -35,4 +57,19 @@
|
|||
width: 20px;
|
||||
height: 20px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.noselect {
|
||||
-webkit-touch-callout: none;
|
||||
/* iOS Safari */
|
||||
-webkit-user-select: none;
|
||||
/* Safari */
|
||||
-khtml-user-select: none;
|
||||
/* Konqueror HTML */
|
||||
-moz-user-select: none;
|
||||
/* Firefox */
|
||||
-ms-user-select: none;
|
||||
/* Internet Explorer/Edge */
|
||||
user-select: none;
|
||||
/* Non-prefixed version, currently supported by Chrome and Opera */
|
||||
}
|
Loading…
Reference in New Issue