Merge pull request #156 from NicolasConstant/develop

0.15.0 Merge
This commit is contained in:
Nicolas Constant 2019-08-16 23:22:53 -04:00 committed by GitHub
commit 5cd55393f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 430 additions and 249 deletions

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "sengi",
"version": "0.12.2",
"version": "0.14.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@ -1,6 +1,6 @@
{
"name": "sengi",
"version": "0.14.0",
"version": "0.15.0",
"license": "AGPL-3.0-or-later",
"main": "main-electron.js",
"description": "A multi-account desktop client for Mastodon and Pleroma",
@ -129,7 +129,9 @@
"category": "Network"
},
"snap": {
"publish": ["github"]
"publish": [
"github"
]
}
}
}

View File

@ -21,7 +21,7 @@
(suggestionSelectedEvent)="suggestionSelected($event)" (hasSuggestionsEvent)="suggestionsChanged($event)">
</app-autosuggest>
<div class="status-editor__footer">
<div class="status-editor__footer" #footer>
<button type="submit" title="reply" class="status-editor__footer--send-button" *ngIf="statusReplyingToWrapper">
<span *ngIf="!isSending">REPLY!</span>
<app-waiting-animation class="waiting-icon" *ngIf="isSending"></app-waiting-animation>

View File

@ -30,6 +30,7 @@ $counter-width: 90px;
.status-editor {
position: relative;
font-size: $default-font-size;
margin-bottom: 5px;
&__title {
background-color: $status-editor-title-background;

View File

@ -111,6 +111,7 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
@Output() onClose = new EventEmitter();
@ViewChild('reply') replyElement: ElementRef;
@ViewChild('fileInput') fileInputElement: ElementRef;
@ViewChild('footer') footerElement: ElementRef;
@ViewChild(ContextMenuComponent) public contextMenu: ContextMenuComponent;
private _isDirectMention: boolean;
@ -223,7 +224,7 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
}
private getWordByPos(str, pos) {
str = str.replace(/(\r\n|\n|\r)/gm,"");
str = str.replace(/(\r\n|\n|\r)/gm, "");
var left = str.substr(0, pos);
var right = str.substr(pos);
@ -512,7 +513,7 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
let mentionExtraChars = 0;
let links = status.split(' ').filter(x => x.startsWith('http://') || x.startsWith('https://'));
for (let link of links) {
if(link.length > 23){
if (link.length > 23) {
mentionExtraChars += link.length - 23;
}
}
@ -610,11 +611,24 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
let scrolling = (this.replyElement.nativeElement.scrollHeight);
if (scrolling > 110) {
this.replyElement.nativeElement.style.height = `0px`;
const isVisible = this.checkVisible(this.footerElement.nativeElement);
//this.replyElement.nativeElement.style.height = `0px`;
this.replyElement.nativeElement.style.height = `${this.replyElement.nativeElement.scrollHeight}px`;
if (isVisible) {
setTimeout(() => {
this.footerElement.nativeElement.scrollIntoViewIfNeeded({ behavior: 'instant', block: 'end', inline: 'start' });
}, 0);
}
}
}
private checkVisible(elm) {
var rect = elm.getBoundingClientRect();
var viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
return !(rect.bottom < 0 || rect.top - viewHeight >= 0);
}
public onContextMenu($event: MouseEvent): void {
this.contextMenuService.show.next({
// Optional - if unspecified, all context menu components will open

View File

@ -1,28 +1,30 @@
<div class="floating-column">
<app-stream-overlay class="stream-overlay" *ngIf="overlayActive" (closeOverlay)="closeOverlay()"
[browseAccountData]="overlayAccountToBrowse"
[browseHashtagData]="overlayHashtagToBrowse"
[browseThreadData]="overlayThreadToBrowse"></app-stream-overlay>
<div class="floating-column__inner">
<div class="sliding-column" [class.sliding-column__right-display]="overlayActive">
<app-stream-overlay class="stream-overlay" *ngIf="overlayActive"
(closeOverlay)="closeOverlay()"
[browseAccountData]="overlayAccountToBrowse"
[browseHashtagData]="overlayHashtagToBrowse"
[browseThreadData]="overlayThreadToBrowse"></app-stream-overlay>
<div class="floating-column__header">
<a class="close-button" href (click)="closePanel()" title="close">
<fa-icon [icon]="faTimes"></fa-icon>
</a>
<div class="floating-column__inner--left">
<div class="floating-column__header">
<a class="close-button" href (click)="closePanel()" title="close">
<fa-icon [icon]="faTimes"></fa-icon>
</a>
</div>
<app-manage-account *ngIf="openPanel === 'manageAccount'" [account]="userAccountUsed"
(browseAccountEvent)="browseAccount($event)" (browseHashtagEvent)="browseHashtag($event)"
(browseThreadEvent)="browseThread($event)"></app-manage-account>
<app-add-new-status *ngIf="openPanel === 'createNewStatus'" [isDirectMention]="isDirectMention"
[userHandle]="userHandle" [redraftedStatus]="redraftedStatus"></app-add-new-status>
<app-add-new-account *ngIf="openPanel === 'addNewAccount'"></app-add-new-account>
<app-search *ngIf="openPanel === 'search'" (browseAccountEvent)="browseAccount($event)"
(browseHashtagEvent)="browseHashtag($event)" (browseThreadEvent)="browseThread($event)">
</app-search>
<app-settings *ngIf="openPanel === 'settings'"></app-settings>
</div>
</div>
</div>
<app-manage-account *ngIf="openPanel === 'manageAccount'"
[account]="userAccountUsed"
(browseAccountEvent)="browseAccount($event)"
(browseHashtagEvent)="browseHashtag($event)"
(browseThreadEvent)="browseThread($event)"></app-manage-account>
<app-add-new-status *ngIf="openPanel === 'createNewStatus'"
[isDirectMention]="isDirectMention"
[userHandle]="userHandle"
[redraftedStatus]="redraftedStatus"></app-add-new-status>
<app-add-new-account *ngIf="openPanel === 'addNewAccount'"></app-add-new-account>
<app-search *ngIf="openPanel === 'search'"
(browseAccountEvent)="browseAccount($event)"
(browseHashtagEvent)="browseHashtag($event)"
(browseThreadEvent)="browseThread($event)"></app-search>
<app-settings *ngIf="openPanel === 'settings'"></app-settings>
</div>

View File

@ -1,11 +1,8 @@
@import "variables";
@import "mixins";
@import "panel";
.floating-column {
width: calc(100%);
width: $floating-column-size;
.floating-column {
background-color: $color-secondary;
overflow: hidden;
z-index: 200;
@ -16,16 +13,19 @@
white-space: normal;
// &__header {
// }
}
&__inner {
position: relative;
width: $stream-column-width;
height: calc(100%);
.stream-overlay {
position: absolute;
z-index: 50;
width: $floating-column-size;
height: calc(100%);
margin: 0 0 0 $stream-column-separator;
overflow: hidden;
&--left {
width: $stream-column-width;
height: calc(100%);
}
}
}
.close-button {
@ -34,27 +34,4 @@
font-size: 14px;
color: white;
margin: 10px 16px 0 0;
// display: inline-block;
// background-color: $color-primary;
// color: darken(white, 30);
// border-radius: 999px;
// width: 26px;
// height: 26px;
// text-align: center;
// text-decoration: none;
// padding: 1px;
// z-index: 9999;
// float: right;
// margin: 10px;
// transition: all .2s;
// &:hover {
// background-color: lighten($color-primary, 20);
// color: white;
// // transform: scale(1.2);
// }
}

View File

@ -1,5 +1,5 @@
import { Component, OnInit, Output, EventEmitter, Input, ViewChild } from '@angular/core';
import { Subject } from 'rxjs';
import { Component, OnInit, Output, EventEmitter, Input, ViewChild, OnDestroy } from '@angular/core';
import { Subject, Subscription } from 'rxjs';
import { Store } from '@ngxs/store';
import { StreamElement, StreamTypeEnum, AddStream } from '../../../states/streams.state';
@ -12,8 +12,10 @@ import { AccountInfo } from '../../../states/accounts.state';
templateUrl: './hashtag.component.html',
styleUrls: ['./hashtag.component.scss']
})
export class HashtagComponent implements OnInit {
export class HashtagComponent implements OnInit, OnDestroy {
@Input() refreshEventEmitter: EventEmitter<any>;
@Input() goToTopEventEmitter: EventEmitter<any>;
@Output() browseAccountEvent = new EventEmitter<string>();
@Output() browseHashtagEvent = new EventEmitter<string>();
@Output() browseThreadEvent = new EventEmitter<OpenThreadEvent>();
@ -27,18 +29,37 @@ export class HashtagComponent implements OnInit {
get hashtagElement(): StreamElement{
return this._hashtagElement;
}
@ViewChild('appStreamStatuses') appStreamStatuses: StreamStatusesComponent;
goToTopSubject: Subject<void> = new Subject<void>();
private lastUsedAccount: AccountInfo;
private refreshSubscription: Subscription;
private goToTopSubscription: Subscription;
constructor(
private readonly store: Store,
private readonly toolsService: ToolsService) { }
ngOnInit() {
if(this.refreshEventEmitter) {
this.refreshSubscription = this.refreshEventEmitter.subscribe(() => {
this.refresh();
})
}
if(this.goToTopEventEmitter) {
this.goToTopSubscription = this.goToTopEventEmitter.subscribe(() => {
this.goToTop();
})
}
}
ngOnDestroy(): void {
if(this.refreshSubscription) this.refreshSubscription.unsubscribe();
if (this.goToTopSubscription) this.goToTopSubscription.unsubscribe();
}
goToTop(): boolean {
@ -66,6 +87,8 @@ export class HashtagComponent implements OnInit {
}
browseHashtag(hashtag: string) {
if(this.hashtagElement.tag === hashtag) return false;
this.browseHashtagEvent.next(hashtag);
}

View File

@ -49,20 +49,19 @@ export class StatusUserContextMenuComponent implements OnInit, OnDestroy {
ngOnInit() {
if (this.statusWrapper) {
const status = this.statusWrapper.status;
if (status.reblog) {
this.displayedStatus = status.reblog;
} else {
this.displayedStatus = status;
}
this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => {
this.loadedAccounts = accounts;
this.checkStatus(accounts);
});
}
}
this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => {
this.loadedAccounts = accounts;
if (this.statusWrapper) this.checkStatus(accounts);
});
let account: Account;
if(this.statusWrapper) {
account = this.displayedStatus.account;

View File

@ -1 +1 @@
<div #content class="content" [class.selectable]="textIsSelectable" innerHTML="{{processedText}}" (click)="selectText()"></div>
<div #content class="content" [class.selectable]="textIsSelectable" innerHTML="{{processedText}}" (click)="selectText($event)"></div>

View File

@ -238,8 +238,10 @@ export class DatabindedTextComponent implements OnInit {
this.hashtagSelected.next(hashtag);
}
selectText() {
this.textSelected.next();
selectText(event) {
if (event.view.getSelection().toString().length === 0) {
this.textSelected.next();
}
}
}

View File

@ -1,4 +1,4 @@
<div class="status-wrapper" [class.direct-message]="isDirectMessage">
<div class="status-wrapper" [class.direct-message]="isDirectMessage" [class.status-selected]="isSelected">
<div class="reblog" *ngIf="reblog">
<a class="reblog__profile-link" href title="{{ status.account.acct }}"
(click)="openAccount(status.account)"

View File

@ -33,6 +33,13 @@
background-color: $direct-message-background;
}
.status-selected {
background-color: #0f111a;
background-color: desaturate(lighten(#0f111a, 5%), 4%);
background-color: #0a0a10;
background-color: #1e2734;
}
.status {
margin: 0;
padding: 0;

View File

@ -1,4 +1,4 @@
import { Component, OnInit, Input, Output, EventEmitter, ViewChild } from "@angular/core";
import { Component, OnInit, Input, Output, EventEmitter, ViewChild, ElementRef } from "@angular/core";
import { faStar, faRetweet, faList, faThumbtack } from "@fortawesome/free-solid-svg-icons";
import { Status, Account } from "../../../services/models/mastodon.interfaces";
@ -36,6 +36,7 @@ export class StatusComponent implements OnInit {
hasReply: boolean;
contentWarningText: string;
isDirectMessage: boolean;
isSelected: boolean;
@Output() browseAccountEvent = new EventEmitter<string>();
@Output() browseHashtagEvent = new EventEmitter<string>();
@ -49,6 +50,7 @@ export class StatusComponent implements OnInit {
private _statusWrapper: StatusWrapper;
status: Status;
@Input('statusWrapper')
set statusWrapper(value: StatusWrapper) {
this._statusWrapper = value;
@ -85,6 +87,7 @@ export class StatusComponent implements OnInit {
}
constructor(
public elem: ElementRef,
private readonly toolsService: ToolsService) { }
ngOnInit() {
@ -163,6 +166,8 @@ export class StatusComponent implements OnInit {
}
textSelected(): boolean {
if(this.isSelected) return false;
const status = this._statusWrapper.status;
const accountInfo = this._statusWrapper.provider;

View File

@ -3,7 +3,8 @@
@import "mixins";
.stream-edition {
width: 100%;
width: calc(100%);
width: $stream-column-width;
// min-height: 50px;
background-color: #222736;
border-bottom: 1px solid $color-secondary;

View File

@ -4,29 +4,49 @@
<fa-icon [icon]="faTimes"></fa-icon>
</button>
<button class="overlay__button overlay-previous" [ngClass]="{'overlay__button--focus': previousElements.length > 0 }" title="previous" (click)="previous()">
<button class="overlay__button overlay-previous"
[ngClass]="{'overlay__button--focus': hasPreviousElements }" title="previous" (click)="previous()">
<fa-icon [icon]="faAngleLeft"></fa-icon>
</button>
<button class="overlay__button overlay-refresh" [ngClass]="{'overlay__button--focus': refreshFocused }" title="refresh" (click)="refresh()">
<button class="overlay__button overlay-refresh" [ngClass]="{'overlay__button--focus': refreshFocused }"
title="refresh" (click)="refresh()">
<fa-icon [icon]="faRedoAlt"></fa-icon>
</button>
<button class="overlay__button overlay-next" [ngClass]="{'overlay__button--focus': nextElements.length > 0 }" title="next" (click)="next()">
<fa-icon [icon]="faAngleRight"></fa-icon>
</button>
<!-- <a href class="overlay-close" (click)="close()">CLOSE</a>
<a href class="overlay-previous" (click)="previous()">PREV</a>
<a href class="overlay-refresh" *ngIf="canRefresh" (click)="refresh()">REFRESH</a>
<a href class="overlay-next" *ngIf="canGoForward" (click)="next()">NEXT</a> -->
<a href title="return to top" class="overlay-gototop" (click)="goToTop()">
</a>
<button class="overlay__button overlay-next" [ngClass]="{'overlay__button--focus': hasNextElements }"
title="next" (click)="next()">
<fa-icon [icon]="faAngleRight"></fa-icon>
</button>
</div>
<app-user-profile #appUserProfile *ngIf="accountName" [currentAccount]="accountName" class="stream-overlay__content"
(browseAccountEvent)="browseAccount($event)" (browseHashtagEvent)="browseHashtag($event)"
(browseThreadEvent)="browseThread($event)"></app-user-profile>
<app-hashtag #appHashtag *ngIf="hashtagElement" [hashtagElement]="hashtagElement" class="stream-overlay__content"
(browseAccountEvent)="browseAccount($event)" (browseHashtagEvent)="browseHashtag($event)"
(browseThreadEvent)="browseThread($event)"></app-hashtag>
<app-thread #appThread *ngIf="browseThread" [currentThread]="thread" class="stream-overlay__content"
(browseAccountEvent)="browseAccount($event)" (browseHashtagEvent)="browseHashtag($event)"
(browseThreadEvent)="browseThread($event)"></app-thread>
<div *ngFor="let e of loadedElements" class="stream-overlay__content-wrapper"
[class.stream-overlay__content-wrapper--selected]="e.isVisible">
<app-user-profile #appUserProfile *ngIf="e.type === 'account'"
[currentAccount]="e.account"
[refreshEventEmitter]="e.refreshEventEmitter"
[goToTopEventEmitter]="e.goToTopEventEmitter"
class="stream-overlay__content"
(browseAccountEvent)="browseAccount($event)"
(browseHashtagEvent)="browseHashtag($event)"
(browseThreadEvent)="browseThread($event)"></app-user-profile>
<app-hashtag #appHashtag *ngIf="e.type === 'hashtag'"
[hashtagElement]="e.hashtag"
[refreshEventEmitter]="e.refreshEventEmitter"
[goToTopEventEmitter]="e.goToTopEventEmitter"
class="stream-overlay__content"
(browseAccountEvent)="browseAccount($event)"
(browseHashtagEvent)="browseHashtag($event)"
(browseThreadEvent)="browseThread($event)"></app-hashtag>
<app-thread #appThread *ngIf="e.type === 'thread'"
[currentThread]="e.thread" class="stream-overlay__content"
[refreshEventEmitter]="e.refreshEventEmitter"
[goToTopEventEmitter]="e.goToTopEventEmitter"
(browseAccountEvent)="browseAccount($event)"
(browseHashtagEvent)="browseHashtag($event)"
(browseThreadEvent)="browseThread($event)"></app-thread>
</div>
</div>

View File

@ -1,14 +1,16 @@
@import "variables";
@import "mixins";
@import "commons";
$header-content: 40px;
$header-content-height: 40px;
.stream-overlay {
// width: $stream-column-width;
// width: $stream-column-width;
height: calc(100%);
background-color: $column-color;
position: relative;
&__header {
width: calc(100%);
height: $header-content;
height: $header-content-height;
background-color: $column-header-background-color; // padding: 6px 10px 0 10px;
border-bottom: 1px solid #222736;
& a {
@ -17,10 +19,29 @@ $header-content: 40px;
font-weight: normal;
}
}
&__content-wrapper {
transition: all .2s;
position: absolute;
top: $header-content-height;
right: 0;
left: 0;
bottom: 0;
// outline: 1px solid greenyellow;
// background-color: salmon;
z-index: 1;
opacity: 0;
&--selected {
z-index: 10;
opacity: 1;
}
}
&__content {
display: block;
width: calc(100%);
height: calc(100% - #{$header-content});
height: calc(100%);
}
&__title {
width: calc(100%);
@ -42,10 +63,15 @@ $header-content: 40px;
transition: all .2s;
margin: 8px 0 0 8px;
&:hover {
color: whitesmoke;
color: #536599;
color: #7a8dc7;
}
&--focus {
color: whitesmoke;
&:hover {
color: whitesmoke;
}
}
}
&-previous {
@ -67,13 +93,21 @@ $header-content: 40px;
float: left;
font-size: 18px;
}
&-gototop {
position: absolute;
top: 0;
left: 110px;
right: 40px;
display: block;
height: $header-content-height;
}
&-close {
display: block;
float: right;
font-size: 14px;
color: white;
margin-right: 8px;
}
}
}
.not-active {

View File

@ -5,9 +5,6 @@ import { Store } from '@ngxs/store';
import { ToolsService, OpenThreadEvent } from '../../../services/tools.service';
import { StreamElement, StreamTypeEnum } from '../../../states/streams.state';
import { ThreadComponent } from '../thread/thread.component';
import { UserProfileComponent } from '../user-profile/user-profile.component';
import { HashtagComponent } from '../hashtag/hashtag.component';
import { AccountInfo } from '../../../states/accounts.state';
@ -21,18 +18,13 @@ export class StreamOverlayComponent implements OnInit, OnDestroy {
faAngleRight = faAngleRight;
faTimes = faTimes;
faRedoAlt = faRedoAlt;
refreshFocused: boolean;
previousElements: OverlayBrowsing[] = [];
nextElements: OverlayBrowsing[] = [];
private currentElement: OverlayBrowsing;
hasPreviousElements: boolean;
hasNextElements: boolean;
// canRefresh: boolean = true;
// canGoForward: boolean;
accountName: string;
thread: OpenThreadEvent;
hashtagElement: StreamElement;
loadedElements: OverlayBrowsing[] = [];
visibleElementIndex: number = -1;
@Output() closeOverlay = new EventEmitter();
@ -51,17 +43,13 @@ export class StreamOverlayComponent implements OnInit, OnDestroy {
this.browseHashtag(hashtag);
}
@ViewChild('appUserProfile') appUserProfile: UserProfileComponent;
@ViewChild('appHashtag') appHashtag: HashtagComponent;
@ViewChild('appThread') appThread: ThreadComponent;
private currentlyUsedAccount: AccountInfo;
private accounts$: Observable<AccountInfo[]>;
private accountSub: Subscription;
constructor(
private readonly store: Store,
private readonly toolsService: ToolsService) {
private readonly toolsService: ToolsService) {
this.accounts$ = this.store.select(state => state.registeredaccounts.accounts);
}
@ -87,36 +75,41 @@ export class StreamOverlayComponent implements OnInit, OnDestroy {
}
next(): boolean {
if (this.nextElements.length === 0) {
if (this.visibleElementIndex >= this.loadedElements.length - 1) {
return false;
}
}
if (this.currentElement) {
this.previousElements.push(this.currentElement);
}
this.loadedElements[this.visibleElementIndex].hide();
let newIndex = this.visibleElementIndex + 1;
this.loadedElements[newIndex].show();
this.visibleElementIndex = newIndex;
const nextElement = this.nextElements.pop();
this.loadElement(nextElement);
console.warn(`visibleElementIndex ${this.visibleElementIndex}`);
console.warn(`this.loadedElements ${this.loadedElements.length}`);
//if(this.nextElements.length === 0) this.canGoForward = false;
this.hasPreviousElements = true;
this.hasNextElements = this.visibleElementIndex < this.loadedElements.length - 1;
return false;
}
previous(): boolean {
if (this.previousElements.length === 0) {
if (this.visibleElementIndex <= 0) {
this.closeOverlay.next();
return false;
}
if (this.currentElement) {
this.nextElements.push(this.currentElement);
}
this.loadedElements[this.visibleElementIndex].hide();
let newIndex = this.visibleElementIndex - 1;
this.loadedElements[newIndex].show();
this.visibleElementIndex = newIndex;
const previousElement = this.previousElements.pop();
this.loadElement(previousElement);
console.warn(`visibleElementIndex ${this.visibleElementIndex}`);
console.warn(`this.loadedElements ${this.loadedElements.length}`);
this.hasPreviousElements = this.visibleElementIndex > 0;
this.hasNextElements = true;
//this.canGoForward = true;
return false;
}
@ -124,92 +117,92 @@ export class StreamOverlayComponent implements OnInit, OnDestroy {
this.currentlyUsedAccount = this.toolsService.getSelectedAccounts()[0];
this.refreshFocused = false;
if(this.thread){
this.appThread.refresh();
} else if(this.hashtagElement){
this.appHashtag.refresh();
} else if(this.accountName){
this.appUserProfile.refresh();
}
this.loadedElements[this.visibleElementIndex].refresh();
return false;
}
goToTop(): boolean {
this.loadedElements[this.visibleElementIndex].goToTop();
return false;
}
browseAccount(accountName: string): void {
if(!accountName) return;
if (!accountName) return;
this.nextElements.length = 0;
if (this.currentElement) {
this.previousElements.push(this.currentElement);
}
const newElement = new OverlayBrowsing(null, accountName, null);
this.loadElement(newElement);
//this.canGoForward = false;
}
browseHashtag(hashtag: string): void {
if(!hashtag) return;
this.nextElements.length = 0;
if (this.currentElement) {
this.previousElements.push(this.currentElement);
}
if (!hashtag) return;
const selectedAccount = this.toolsService.getSelectedAccounts()[0];
const hashTagElement = new StreamElement(StreamTypeEnum.tag, hashtag, selectedAccount.id, hashtag, null, null, selectedAccount.instance);
const newElement = new OverlayBrowsing(hashTagElement, null, null);
this.loadElement(newElement);
// this.canGoForward = false;
}
browseThread(openThread: OpenThreadEvent): any {
if(!openThread) return;
this.nextElements.length = 0;
if (this.currentElement) {
this.previousElements.push(this.currentElement);
}
if (!openThread) return;
const newElement = new OverlayBrowsing(null, null, openThread);
this.loadElement(newElement);
//this.canGoForward = false;
}
private loadElement(element: OverlayBrowsing) {
this.currentlyUsedAccount = this.toolsService.getSelectedAccounts()[0];
this.refreshFocused = false;
this.refreshFocused = false;
this.currentElement = element;
if (this.visibleElementIndex >= 0) {
this.loadedElements[this.visibleElementIndex].hide();
this.loadedElements = this.loadedElements.slice(0, this.visibleElementIndex + 1);
}
this.accountName = this.currentElement.account;
this.thread = this.currentElement.thread;
this.hashtagElement = this.currentElement.hashtag;
this.visibleElementIndex = this.visibleElementIndex + 1;
this.loadedElements.push(element)
this.loadedElements[this.visibleElementIndex].show();
this.hasPreviousElements = this.visibleElementIndex > 0;
this.hasNextElements = false;
}
}
class OverlayBrowsing {
class OverlayBrowsing {
refreshEventEmitter = new EventEmitter();
goToTopEventEmitter = new EventEmitter();
constructor(
public readonly hashtag: StreamElement,
public readonly account: string,
public readonly thread: OpenThreadEvent) {
if (hashtag) {
this.type = OverlayEnum.hashtag;
this.type = 'hashtag';
} else if (account) {
this.type = OverlayEnum.account;
this.type = 'account';
} else if (thread) {
this.type = OverlayEnum.thread;
this.type = 'thread';
} else {
throw Error('NotImplemented');
}
}
type: OverlayEnum;
}
show(): any {
setTimeout(() => {
this.isVisible = true;
}, 200);
}
hide(): any {
this.isVisible = false;
}
refresh(): any {
this.refreshEventEmitter.next();
}
goToTop(): any {
this.goToTopEventEmitter.next();
}
enum OverlayEnum {
unknown = 0,
hashtag = 1,
account = 2,
thread = 3
isVisible: boolean;
type: 'hashtag' | 'account' | 'thread';
}

View File

@ -175,10 +175,12 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
this.statuses.length = 2 * this.streamingService.nbStatusPerIteration;
}
const stream = this.statustream.nativeElement as HTMLElement;
stream.scrollTo({
top: 0,
behavior: 'smooth'
});
setTimeout(() => {
stream.scrollTo({
top: 0,
behavior: 'smooth'
});
}, 0);
return false;
}

View File

@ -1,30 +1,32 @@
<div class="stream-column">
<div class="sliding-column" [class.sliding-column__right-display]="overlayActive">
<app-stream-overlay class="stream-overlay" *ngIf="overlayActive"
(closeOverlay)="closeOverlay()"
[browseAccountData]="overlayAccountToBrowse"
[browseHashtagData]="overlayHashtagToBrowse"
[browseThreadData]="overlayThreadToBrowse"></app-stream-overlay>
<app-stream-overlay class="stream-overlay" *ngIf="overlayActive" (closeOverlay)="closeOverlay()"
[browseAccountData]="overlayAccountToBrowse" [browseHashtagData]="overlayHashtagToBrowse"
[browseThreadData]="overlayThreadToBrowse"></app-stream-overlay>
<!-- <div> -->
<div class="stream-column__stream-header">
<a class="stream-column__stream-selector" href title="return to top" (click)="goToTop()">
<fa-icon class="stream-column__stream-selector--icon" [icon]="columnFaIcon"></fa-icon>
<h1 class="stream-column__stream-selector--title">{{ streamElement.name.toUpperCase() }}</h1>
<span class="stream-column__stream-selector--subtitle"
*ngIf="streamElement.instance">{{ streamElement.instance.toLowerCase() }}</span>
</a>
<a class="stream-column__open-menu" href title="edit column" (click)="openEditionMenu()">
<fa-icon class="stream-column__open-menu--icon" [icon]="menuFaIcon"></fa-icon>
</a>
</div>
<div class="stream-column__stream-header">
<a class="stream-column__stream-selector" href title="return to top" (click)="goToTop()">
<fa-icon class="stream-column__stream-selector--icon" [icon]="columnFaIcon"></fa-icon>
<h1 class="stream-column__stream-selector--title">{{ streamElement.name.toUpperCase() }}</h1>
<span class="stream-column__stream-selector--subtitle"
*ngIf="streamElement.instance">{{ streamElement.instance.toLowerCase() }}</span>
</a>
<a class="stream-column__open-menu" href title="edit column" (click)="openEditionMenu()">
<fa-icon class="stream-column__open-menu--icon" [icon]="menuFaIcon"></fa-icon>
</a>
<app-stream-edition class="stream-edition" *ngIf="editionPanelIsOpen" [streamElement]="streamElement"
(closed)="streamEditionClosed()">
</app-stream-edition>
<app-stream-statuses class="stream-statuses" [streamElement]="streamElement"
[goToTop]="goToTopSubject.asObservable()" (browseAccountEvent)="browseAccount($event)"
(browseHashtagEvent)="browseHashtag($event)" (browseThreadEvent)="browseThread($event)">
</app-stream-statuses>
<!-- </div> -->
</div>
<app-stream-edition class="stream-edition" *ngIf="editionPanelIsOpen"
[streamElement]="streamElement"
(closed)="streamEditionClosed()">
</app-stream-edition>
<app-stream-statuses class="stream-statuses"
[streamElement]="streamElement"
[goToTop]="goToTopSubject.asObservable()"
(browseAccountEvent)="browseAccount($event)"
(browseHashtagEvent)="browseHashtag($event)"
(browseThreadEvent)="browseThread($event)"></app-stream-statuses>
</div>

View File

@ -1,5 +1,6 @@
@import "variables";
@import "commons";
@import "panel";
.stream-edition {
width: $stream-column-width;
@ -11,9 +12,13 @@
position: relative;
width: $stream-column-width;
height: calc(100%);
overflow: hidden;
background-color: $column-color;
margin: 0 0 0 $stream-column-separator;
&__stream-header {
position: relative;
width: $stream-column-width;
border-bottom: 1px solid #222736;
}
&__open-menu {
@ -37,7 +42,7 @@
}
&__stream-selector {
display: block;
width: calc(100%);
width: $stream-column-width;
height: $stream-header-height;
background-color: $column-header-background-color;
text-decoration: none;
@ -74,6 +79,14 @@
width: 320px;
}
// .stream-overlay {
// float: right;
// width: $stream-column-width;
// height: calc(100%);
// }
// .stream-toots {
// height: calc(100% - 30px);
// width: 320px;
@ -83,9 +96,10 @@
// border-width: 0 0 1px 0;
// }
// }
.stream-overlay {
position: absolute;
z-index: 100;
width: $stream-column-width;
height: calc(100%);
}
// .stream-overlay {
// // position: absolute;
// float: right;
// //z-index: 100;
// width: $stream-column-width;
// height: calc(100%);
// }

View File

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ViewChildren, QueryList } from '@angular/core';
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ViewChildren, QueryList, ViewChild, ElementRef } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { Subscription } from 'rxjs';
@ -28,6 +28,9 @@ export class ThreadComponent implements OnInit, OnDestroy {
@Output() browseHashtagEvent = new EventEmitter<string>();
@Output() browseThreadEvent = new EventEmitter<OpenThreadEvent>();
@Input() refreshEventEmitter: EventEmitter<any>;
@Input() goToTopEventEmitter: EventEmitter<any>;
@Input('currentThread')
set currentThread(thread: OpenThreadEvent) {
if (thread) {
@ -41,6 +44,8 @@ export class ThreadComponent implements OnInit, OnDestroy {
private newPostSub: Subscription;
private hideAccountSubscription: Subscription;
private deleteStatusSubscription: Subscription;
private refreshSubscription: Subscription;
private goToTopSubscription: Subscription;
constructor(
private readonly notificationService: NotificationService,
@ -48,26 +53,24 @@ export class ThreadComponent implements OnInit, OnDestroy {
private readonly mastodonService: MastodonService) { }
ngOnInit() {
if (this.refreshEventEmitter) {
this.refreshSubscription = this.refreshEventEmitter.subscribe(() => {
this.refresh();
})
}
if (this.goToTopEventEmitter) {
this.goToTopSubscription = this.goToTopEventEmitter.subscribe(() => {
this.goToTop();
})
}
this.newPostSub = this.notificationService.newRespondPostedStream.subscribe((replyData: NewReplyData) => {
if(replyData){
if (replyData) {
const repondingStatus = this.statuses.find(x => x.status.id === replyData.uiStatusId);
const responseStatus = replyData.response;
if(repondingStatus && this.statuses[0]){
if (repondingStatus && this.statuses[0]) {
this.statuses.push(responseStatus);
// const uiProvider = this.statuses[0].provider;
// if(uiProvider.id === responseStatus.provider.id){
// } else {
// this.toolsService.getStatusUsableByAccount(uiProvider, responseStatus)
// .then((status: Status) => {
// this.statuses.push(new StatusWrapper(status, uiProvider));
// })
// .catch((err) => {
// this.notificationService.notifyHttpError(err);
// });
// }
// this.getThread(this.statuses[0].provider, this.lastThreadEvent);
}
}
});
@ -75,7 +78,7 @@ export class ThreadComponent implements OnInit, OnDestroy {
this.hideAccountSubscription = this.notificationService.hideAccountUrlStream.subscribe((accountUrl: string) => {
if (accountUrl) {
this.statuses = this.statuses.filter(x => {
if(x.status.reblog){
if (x.status.reblog) {
return x.status.reblog.account.url != accountUrl;
} else {
return x.status.account.url != accountUrl;
@ -85,9 +88,9 @@ export class ThreadComponent implements OnInit, OnDestroy {
});
this.deleteStatusSubscription = this.notificationService.deletedStatusStream.subscribe((status: StatusWrapper) => {
if(status){
if (status) {
this.statuses = this.statuses.filter(x => {
return !(x.status.url.replace('https://','').split('/')[0] === status.provider.instance && x.status.id === status.status.id);
return !(x.status.url.replace('https://', '').split('/')[0] === status.provider.instance && x.status.id === status.status.id);
});
}
});
@ -97,6 +100,19 @@ export class ThreadComponent implements OnInit, OnDestroy {
if (this.newPostSub) this.newPostSub.unsubscribe();
if (this.hideAccountSubscription) this.hideAccountSubscription.unsubscribe();
if (this.deleteStatusSubscription) this.deleteStatusSubscription.unsubscribe();
if (this.refreshSubscription) this.refreshSubscription.unsubscribe();
if (this.goToTopSubscription) this.goToTopSubscription.unsubscribe();
}
@ViewChild('statusstream') public statustream: ElementRef;
goToTop(): any {
const stream = this.statustream.nativeElement as HTMLElement;
setTimeout(() => {
stream.scrollTo({
top: 0,
behavior: 'smooth'
});
}, 0);
}
private getThread(openThreadEvent: OpenThreadEvent) {
@ -139,6 +155,7 @@ export class ThreadComponent implements OnInit, OnDestroy {
return this.mastodonService.getStatusContext(currentAccount, status.id)
.then((context: Context) => {
let contextStatuses = [...context.ancestors, status, ...context.descendants]
const position = context.ancestors.length;
for (const s of contextStatuses) {
const wrapper = new StatusWrapper(s, currentAccount);
@ -146,9 +163,21 @@ export class ThreadComponent implements OnInit, OnDestroy {
}
this.hasContentWarnings = this.statuses.filter(x => x.status.sensitive || x.status.spoiler_text).length > 1;
return position;
});
})
.then((position: number) => {
setTimeout(() => {
const el = this.statusChildren.toArray()[position];
el.isSelected = true;
// el.elem.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });
// el.elem.nativeElement.scrollIntoView({ behavior: 'auto', block: 'start', inline: 'nearest' });
// el.elem.nativeElement.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' });
el.elem.nativeElement.scrollIntoViewIfNeeded({ behavior: 'auto', block: 'start', inline: 'nearest' });
}, 0);
})
.catch((err: HttpErrorResponse) => {
this.notificationService.notifyHttpError(err);
})

View File

@ -55,6 +55,8 @@ export class UserProfileComponent implements OnInit {
private accounts$: Observable<AccountInfo[]>;
private accountSub: Subscription;
private deleteStatusSubscription: Subscription;
private refreshSubscription: Subscription;
private goToTopSubscription: Subscription;
@ViewChild('statusstream') public statustream: ElementRef;
@ViewChild('profilestatuses') public profilestatuses: ElementRef;
@ -63,6 +65,9 @@ export class UserProfileComponent implements OnInit {
@Output() browseHashtagEvent = new EventEmitter<string>();
@Output() browseThreadEvent = new EventEmitter<OpenThreadEvent>();
@Input() refreshEventEmitter: EventEmitter<any>;
@Input() goToTopEventEmitter: EventEmitter<any>;
@Input('currentAccount')
set currentAccount(accountName: string) {
this.load(accountName);
@ -79,6 +84,18 @@ export class UserProfileComponent implements OnInit {
}
ngOnInit() {
if (this.refreshEventEmitter) {
this.refreshSubscription = this.refreshEventEmitter.subscribe(() => {
this.refresh();
})
}
if (this.goToTopEventEmitter) {
this.goToTopSubscription = this.goToTopEventEmitter.subscribe(() => {
this.goToTop();
})
}
this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => {
if (this.displayedAccount) {
const userAccount = accounts.filter(x => x.isSelected)[0];
@ -104,11 +121,23 @@ export class UserProfileComponent implements OnInit {
});
}
});
}
}
ngOnDestroy() {
this.accountSub.unsubscribe();
this.deleteStatusSubscription.unsubscribe();
if (this.accountSub) this.accountSub.unsubscribe();
if (this.deleteStatusSubscription) this.deleteStatusSubscription.unsubscribe();
if (this.refreshSubscription) this.refreshSubscription.unsubscribe();
if (this.goToTopSubscription) this.goToTopSubscription.unsubscribe();
}
goToTop(): any {
const stream = this.statustream.nativeElement as HTMLElement;
setTimeout(() => {
stream.scrollTo({
top: 0,
behavior: 'smooth'
});
}, 0);
}
private load(accountName: string) {
@ -219,6 +248,8 @@ export class UserProfileComponent implements OnInit {
}
browseAccount(accountName: string): void {
if(accountName === this.toolsService.getAccountFullHandle(this.displayedAccount)) return;
this.browseAccountEvent.next(accountName);
}
@ -323,7 +354,7 @@ export class UserProfileComponent implements OnInit {
isSwitchingSection: boolean;
switchStatusSection(section: 'status' | 'replies' | 'media'): boolean {
this.isSwitchingSection = true;
this.isSwitchingSection = true;
this.statusSection = section;
this.statuses.length = 0;

View File

@ -1,5 +1,5 @@
<div class="main-display flexcroll">
<div class="main-display__stream-column" *ngFor="let s of streamElements$ | async">
<app-stream [streamElement]="s" #stream></app-stream>
<div class="main-display__stream-column" *ngFor="let s of streamElements$ | async" #stream>
<app-stream [streamElement]="s"></app-stream>
</div>
</div>

View File

@ -6,13 +6,14 @@
overflow-y: hidden;
&__stream-column {
height: calc(100%);
width: $stream-column-width + $stream-column-separator;
width: calc(#{$stream-column-width} + #{$stream-column-separator});
display: inline-block;
overflow-x: hidden;
overflow-y: hidden;
white-space: normal;
// margin: 0 0 0 $stream-column-separator;
margin: 0;
// border: 1px solid greenyellow;
}
}

View File

@ -10,4 +10,26 @@
text-transform: uppercase;
margin: 4px 0 12px 5px;
}
}
}
.sliding-column {
transition: all .25s;
transition-timing-function: ease-out;
width: calc(2 * #{$stream-column-width});
height: calc(100%);
position: relative;
left: 0;
&__right-display {
transition-timing-function: ease-out;
position: relative;
left: -$stream-column-width;
}
}
.stream-overlay {
float: right;
width: $stream-column-width;
height: calc(100%);
}