commit
9de28fad86
|
@ -38,11 +38,15 @@ function createWindow() {
|
|||
{ type: "separator" },
|
||||
{ role: "reload" },
|
||||
{ role: "forcereload" },
|
||||
{ type: 'separator' },
|
||||
{ role: 'togglefullscreen' },
|
||||
{ type: "separator" },
|
||||
{ role: "resetzoom" },
|
||||
{ role: "zoomin", accelerator: "CommandOrControl+numadd" },
|
||||
{ role: "zoomout", accelerator: "CommandOrControl+numsub" },
|
||||
{ type: "separator" },
|
||||
{ role: "togglefullscreen" },
|
||||
{ type: "separator" },
|
||||
{ role: "close" },
|
||||
{ role: 'quit' }
|
||||
{ role: "quit" }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -191,4 +195,4 @@ app.on("activate", () => {
|
|||
if (win === null) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "sengi",
|
||||
"version": "0.28.1",
|
||||
"version": "0.29.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "main-electron.js",
|
||||
"description": "A multi-account desktop client for Mastodon and Pleroma",
|
||||
"author": {
|
||||
"name": "Nicolas Constant",
|
||||
"name": "Nicolas Constant",
|
||||
"email": "github@nicolas-constant.com"
|
||||
},
|
||||
"repository": {
|
||||
|
|
|
@ -41,6 +41,7 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
faClock = faClock;
|
||||
|
||||
autoSuggestUserActionsStream = new EventEmitter<AutosuggestUserActionEnum>();
|
||||
private isRedrafting: boolean;
|
||||
|
||||
private _title: string;
|
||||
set title(value: string) {
|
||||
|
@ -54,7 +55,11 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
private _status: string = '';
|
||||
@Input('status')
|
||||
set status(value: string) {
|
||||
this.statusStateService.setStatusContent(value, this.statusReplyingToWrapper);
|
||||
if (this.isRedrafting) {
|
||||
this.statusStateService.setStatusContent(value, null);
|
||||
} else {
|
||||
this.statusStateService.setStatusContent(value, this.statusReplyingToWrapper);
|
||||
}
|
||||
this.countStatusChar(value);
|
||||
this.detectAutosuggestion(value);
|
||||
this._status = value;
|
||||
|
@ -79,31 +84,32 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
|
||||
@Input('redraftedStatus')
|
||||
set redraftedStatus(value: StatusWrapper) {
|
||||
if (value) {
|
||||
if (value) {
|
||||
this.isRedrafting = true;
|
||||
this.statusLoaded = false;
|
||||
|
||||
if(value.status && value.status.media_attachments){
|
||||
if (value.status && value.status.media_attachments) {
|
||||
for (const m of value.status.media_attachments) {
|
||||
this.mediaService.addExistingMedia(new MediaWrapper(m.id, null, m));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const newLine = String.fromCharCode(13, 10);
|
||||
let content = value.status.content;
|
||||
|
||||
content = this.tranformHtmlRepliesToReplies(content);
|
||||
|
||||
|
||||
while (content.includes('<p>') || content.includes('</p>') || content.includes('<br>') || content.includes('<br/>') || content.includes('<br />')) {
|
||||
content = content.replace('<p>', '').replace('</p>', newLine + newLine).replace('<br />', newLine).replace('<br/>', newLine).replace('<br>', newLine);
|
||||
}
|
||||
|
||||
content = this.trim(content, newLine);
|
||||
|
||||
|
||||
let parser = new DOMParser();
|
||||
var dom = parser.parseFromString(content, 'text/html')
|
||||
this.status = dom.body.textContent;
|
||||
|
||||
this.statusStateService.setStatusContent(this.status, this.statusReplyingToWrapper);
|
||||
// this.statusStateService.setStatusContent(this.status, this.statusReplyingToWrapper);
|
||||
|
||||
this.setVisibilityFromStatus(value.status);
|
||||
this.title = value.status.spoiler_text;
|
||||
|
@ -193,10 +199,13 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
public viewContainerRef: ViewContainerRef) {
|
||||
|
||||
this.accounts$ = this.store.select(state => state.registeredaccounts.accounts);
|
||||
this.status = this.statusStateService.getStatusContent(this.statusReplyingToWrapper);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
if (!this.isRedrafting) {
|
||||
this.status = this.statusStateService.getStatusContent(this.statusReplyingToWrapper);
|
||||
}
|
||||
|
||||
this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => {
|
||||
this.accountChanged(accounts);
|
||||
});
|
||||
|
@ -209,10 +218,11 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
this.statusReplyingTo = this.statusReplyingToWrapper.status;
|
||||
}
|
||||
|
||||
let state = this.statusStateService.getStatusContent(this.statusReplyingToWrapper);
|
||||
if (state && state !== '') {
|
||||
this.status = state;
|
||||
} else {
|
||||
// let state = this.statusStateService.getStatusContent(this.statusReplyingToWrapper);
|
||||
// if (state && state !== '') {
|
||||
// this.status = state;
|
||||
// } else {
|
||||
if(!this.status || this.status === '') {
|
||||
const uniqueMentions = this.getMentions(this.statusReplyingTo, this.statusReplyingToWrapper.provider);
|
||||
for (const mention of uniqueMentions) {
|
||||
this.status += `@${mention} `;
|
||||
|
@ -232,7 +242,11 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
this.innerHeight = window.innerHeight;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
ngOnDestroy() {
|
||||
if(this.isRedrafting){
|
||||
this.statusStateService.resetStatusContent(null);
|
||||
}
|
||||
|
||||
this.accountSub.unsubscribe();
|
||||
}
|
||||
|
||||
|
@ -563,7 +577,11 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
this.scheduledStatusService.statusAdded(acc);
|
||||
}
|
||||
|
||||
this.statusStateService.resetStatusContent(this.statusReplyingToWrapper);
|
||||
if(this.isRedrafting){
|
||||
this.statusStateService.resetStatusContent(null);
|
||||
} else {
|
||||
this.statusStateService.resetStatusContent(this.statusReplyingToWrapper);
|
||||
}
|
||||
})
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
this.notificationService.notifyHttpError(err, acc);
|
||||
|
@ -741,9 +759,9 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
|
||||
if (isVisible) {
|
||||
setTimeout(() => {
|
||||
try{
|
||||
try {
|
||||
this.footerElement.nativeElement.scrollIntoViewIfNeeded({ behavior: 'instant', block: 'end', inline: 'start' });
|
||||
}catch(err) {
|
||||
} catch (err) {
|
||||
this.footerElement.nativeElement.scrollIntoView({ behavior: 'instant', block: 'end', inline: 'start' });
|
||||
}
|
||||
}, 0);
|
||||
|
@ -850,11 +868,11 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
const mastodonMentionRegex = /<span class="h-card"><a href="https:\/\/([a-zA-Z0-9.]{0,255})\/[a-zA-Z0-9_@/-]{0,255}" class="u-url mention">@<span>([a-zA-Z0-9_-]{0,255})<\/span><\/a><\/span>/gmi;
|
||||
const pleromaMentionRegex = /<span class="h-card"><a data-user="[a-zA-Z0-9]{0,255}" class="u-url mention" href="https:\/\/([a-zA-Z0-9.]{0,255})\/[a-zA-Z0-9_@/-]{0,255}" rel="ugc">@<span>([a-zA-Z0-9_-]{0,255})<\/span><\/a><\/span>/gmi;
|
||||
|
||||
while(data.match(mastodonMentionRegex)){
|
||||
while (data.match(mastodonMentionRegex)) {
|
||||
data = data.replace(mastodonMentionRegex, '@$2@$1');
|
||||
}
|
||||
|
||||
while(data.match(pleromaMentionRegex)){
|
||||
while (data.match(pleromaMentionRegex)) {
|
||||
data = data.replace(pleromaMentionRegex, '@$2@$1');
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
@import "variables";
|
||||
|
||||
.stream-toots {
|
||||
background-color: $column-background;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
@import "variables";
|
||||
|
||||
.stream-toots {
|
||||
background-color: $column-background;
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
import { Component, OnInit, Input, Output, ElementRef, ViewChild, HostListener } from '@angular/core';
|
||||
import { Component, OnInit, Input, Output, ElementRef, ViewChild, HostListener, OnDestroy } from '@angular/core';
|
||||
import { SafeHtml } from '@angular/platform-browser';
|
||||
import { faTimes, faAngleLeft, faAngleRight } from "@fortawesome/free-solid-svg-icons";
|
||||
import { Subject } from 'rxjs';
|
||||
import { HotkeysService, Hotkey } from 'angular2-hotkeys';
|
||||
|
||||
import { OpenMediaEvent } from '../../models/common.model';
|
||||
import { Attachment, PleromaAttachment } from '../../services/models/mastodon.interfaces';
|
||||
|
@ -12,7 +13,7 @@ import { Attachment, PleromaAttachment } from '../../services/models/mastodon.in
|
|||
templateUrl: './media-viewer.component.html',
|
||||
styleUrls: ['./media-viewer.component.scss']
|
||||
})
|
||||
export class MediaViewerComponent implements OnInit {
|
||||
export class MediaViewerComponent implements OnInit, OnDestroy {
|
||||
private _mediaEvent: OpenMediaEvent;
|
||||
faTimes = faTimes;
|
||||
faAngleLeft = faAngleLeft;
|
||||
|
@ -64,9 +65,20 @@ export class MediaViewerComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
constructor() { }
|
||||
private escapeHotkey = new Hotkey('escape', (event: KeyboardEvent): boolean => {
|
||||
console.warn('CLOSE');
|
||||
this.close();
|
||||
return false;
|
||||
});
|
||||
|
||||
constructor(private readonly hotkeysService: HotkeysService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.hotkeysService.add(this.escapeHotkey);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.hotkeysService.remove(this.escapeHotkey);
|
||||
}
|
||||
|
||||
private setBrowsing() {
|
||||
|
|
|
@ -169,4 +169,11 @@ describe('DatabindedTextComponent', () => {
|
|||
component.text = sample;
|
||||
expect(component.processedText).toContain('Bla<br /><br /><a href class="link-httpslink" title="open link">https://link/</a>');
|
||||
});
|
||||
|
||||
it('should sanitize link', () => {
|
||||
const sample = `https://domain.fr/public.php?op=rss&id=-2&key=60c63a21c2928546b4485017876fe850c6ebcebd#tag:domain.fr,2020-05-26:/49902061`;
|
||||
|
||||
let result = (<any>component).sanitizeLink(sample);
|
||||
expect(result).toBe('https://domain.fr/public.php?op=rss&id=-2&key=60c63a21c2928546b4485017876fe850c6ebcebd#tag:domain.fr,2020-05-26:/49902061');
|
||||
});
|
||||
});
|
|
@ -210,12 +210,14 @@ export class DatabindedTextComponent implements OnInit {
|
|||
let classname = this.getClassNameForLink(link);
|
||||
let els = this.contentElement.nativeElement.querySelectorAll(`.${classname}`);
|
||||
|
||||
let sanitizedLink = this.sanitizeLink(link);
|
||||
|
||||
for (const el of els) {
|
||||
this.renderer.listen(el, 'click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
window.open(link, '_blank');
|
||||
window.open(sanitizedLink, '_blank');
|
||||
return false;
|
||||
});
|
||||
|
||||
|
@ -224,7 +226,7 @@ export class DatabindedTextComponent implements OnInit {
|
|||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
window.open(link, '_blank');
|
||||
window.open(sanitizedLink, '_blank');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
@ -232,6 +234,11 @@ export class DatabindedTextComponent implements OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
private sanitizeLink(link: string): string {
|
||||
let res = link.replace(/&/g, '&');
|
||||
return res;
|
||||
}
|
||||
|
||||
private getClassNameForHastag(value: string): string {
|
||||
let res = value.replace(/[.,\/#?!@$%+\^&\*;:{}=\-_`~()]/g, "");
|
||||
return `hashtag-${res}`;
|
||||
|
|
|
@ -107,13 +107,13 @@ export class UserProfileComponent implements OnInit {
|
|||
this.relationShipError = false;
|
||||
this.toolsService.findAccount(userAccount, this.lastAccountName)
|
||||
.then((account: Account) => {
|
||||
if(!account) throw Error(`Could not find ${this.lastAccountName}`);
|
||||
if (!account) throw Error(`Could not find ${this.lastAccountName}`);
|
||||
|
||||
return this.getFollowStatus(userAccount, account);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
this.relationShipError = true;
|
||||
this.relationShipError = true;
|
||||
})
|
||||
.then(() => {
|
||||
this.loadingRelationShip = false;
|
||||
|
@ -128,7 +128,7 @@ export class UserProfileComponent implements OnInit {
|
|||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
if (this.accountSub) this.accountSub.unsubscribe();
|
||||
|
@ -164,7 +164,7 @@ export class UserProfileComponent implements OnInit {
|
|||
this.isLoading = false;
|
||||
this.statusLoading = true;
|
||||
|
||||
if(!account) throw Error(`Could not find ${this.lastAccountName}`);
|
||||
if (!account) throw Error(`Could not find ${this.lastAccountName}`);
|
||||
|
||||
this.displayedAccount = this.fixPleromaFieldsUrl(account);
|
||||
this.hasNote = account && account.note && account.note !== '<p></p>';
|
||||
|
@ -188,9 +188,9 @@ export class UserProfileComponent implements OnInit {
|
|||
}
|
||||
|
||||
private fixPleromaFieldsUrl(acc: Account): Account {
|
||||
if(acc.fields){
|
||||
if (acc.fields) {
|
||||
acc.fields.forEach(f => {
|
||||
if(f.value.includes('<a href="') && !f.value.includes('target="_blank"')){
|
||||
if (f.value.includes('<a href="') && !f.value.includes('target="_blank"')) {
|
||||
f.value = f.value.replace('<a href="', '<a target="_blank" href="');
|
||||
}
|
||||
});
|
||||
|
@ -271,7 +271,7 @@ export class UserProfileComponent implements OnInit {
|
|||
}
|
||||
|
||||
browseAccount(accountName: string): void {
|
||||
if(accountName === this.toolsService.getAccountFullHandle(this.displayedAccount)) return;
|
||||
if (accountName === this.toolsService.getAccountFullHandle(this.displayedAccount)) return;
|
||||
|
||||
this.browseAccountEvent.next(accountName);
|
||||
}
|
||||
|
@ -330,11 +330,13 @@ export class UserProfileComponent implements OnInit {
|
|||
this.showFloatingHeader = false;
|
||||
}
|
||||
|
||||
const menuPosition = element.scrollHeight - this.profilestatuses.nativeElement.offsetHeight - 30 - 31;
|
||||
if (element.scrollTop > menuPosition) {
|
||||
this.showFloatingStatusMenu = true;
|
||||
} else {
|
||||
this.showFloatingStatusMenu = false;
|
||||
if (this.profilestatuses) {
|
||||
const menuPosition = element.scrollHeight - this.profilestatuses.nativeElement.offsetHeight - 30 - 31;
|
||||
if (element.scrollTop > menuPosition) {
|
||||
this.showFloatingStatusMenu = true;
|
||||
} else {
|
||||
this.showFloatingStatusMenu = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (atBottom) {
|
||||
|
@ -343,7 +345,7 @@ export class UserProfileComponent implements OnInit {
|
|||
}
|
||||
|
||||
private scrolledToBottom() {
|
||||
if (this.statusLoading || this.maxReached) return;
|
||||
if (this.statusLoading || this.maxReached || !this.displayedAccount) return;
|
||||
|
||||
const onlyMedia = this.statusSection === 'media';
|
||||
const excludeReplies = this.statusSection === 'status';
|
||||
|
|
Loading…
Reference in New Issue