2018-10-28 06:02:57 +01:00
|
|
|
import { Component, OnInit, Input, EventEmitter, Output, Renderer2, ViewChild, ElementRef } from '@angular/core';
|
2018-10-28 04:58:00 +01:00
|
|
|
|
2020-04-18 06:45:27 +02:00
|
|
|
import { faAngleDown } from "@fortawesome/free-solid-svg-icons";
|
|
|
|
|
2018-10-28 04:58:00 +01:00
|
|
|
@Component({
|
|
|
|
selector: 'app-databinded-text',
|
|
|
|
templateUrl: './databinded-text.component.html',
|
|
|
|
styleUrls: ['./databinded-text.component.scss']
|
|
|
|
})
|
|
|
|
export class DatabindedTextComponent implements OnInit {
|
2020-04-18 06:45:27 +02:00
|
|
|
faAngleDown = faAngleDown;
|
|
|
|
|
2018-10-28 04:58:00 +01:00
|
|
|
private accounts: string[] = [];
|
2018-10-28 05:45:32 +01:00
|
|
|
private hashtags: string[] = [];
|
2020-08-29 03:18:41 +02:00
|
|
|
private links: string[] = [];
|
2018-10-28 04:58:00 +01:00
|
|
|
|
|
|
|
processedText: string;
|
2020-04-18 06:45:27 +02:00
|
|
|
isCollapsed: boolean = false;
|
2018-10-28 04:58:00 +01:00
|
|
|
|
2018-10-28 06:02:57 +01:00
|
|
|
@ViewChild('content') contentElement: ElementRef;
|
|
|
|
|
2018-10-28 04:58:00 +01:00
|
|
|
@Output() accountSelected = new EventEmitter<string>();
|
|
|
|
@Output() hashtagSelected = new EventEmitter<string>();
|
|
|
|
@Output() textSelected = new EventEmitter();
|
|
|
|
|
2018-11-01 04:47:38 +01:00
|
|
|
@Input() textIsSelectable: boolean = true;
|
2020-04-18 06:45:27 +02:00
|
|
|
@Input() selected: boolean;
|
2018-11-01 04:47:38 +01:00
|
|
|
|
2018-10-28 04:58:00 +01:00
|
|
|
@Input('text')
|
2018-10-28 05:45:32 +01:00
|
|
|
set text(value: string) {
|
2020-08-29 04:12:30 +02:00
|
|
|
//console.log(value);
|
2020-04-18 06:45:27 +02:00
|
|
|
|
|
|
|
let parser = new DOMParser();
|
|
|
|
var dom = parser.parseFromString(value, 'text/html')
|
2020-04-22 02:00:49 +02:00
|
|
|
this.isCollapsed = [...dom.body.textContent].length > 600;
|
2019-03-02 06:35:09 +01:00
|
|
|
|
2018-10-28 06:02:57 +01:00
|
|
|
this.processedText = '';
|
2019-05-23 08:12:19 +02:00
|
|
|
|
2019-05-23 08:16:36 +02:00
|
|
|
do {
|
|
|
|
value = value.replace('@<span class="">', '<span class="">'); //Friendica sanitarization
|
|
|
|
} while (value.includes('@<span class="">'));
|
2019-05-23 08:12:19 +02:00
|
|
|
|
2019-05-23 08:24:11 +02:00
|
|
|
do {
|
|
|
|
value = value.replace('class="mention" rel="nofollow noopener" target="_blank">@', 'class="mention" rel="nofollow noopener" target="_blank">'); //Misskey sanitarization
|
|
|
|
} while (value.includes('class="mention" rel="nofollow noopener" target="_blank">@'));
|
|
|
|
|
2020-08-29 04:42:00 +02:00
|
|
|
do {
|
|
|
|
value = value.replace('@<span class="h-card">', '<span class="h-card">'); //Zap sanitarization
|
|
|
|
} while (value.includes('@<span class="h-card">'));
|
|
|
|
|
2018-10-28 06:02:57 +01:00
|
|
|
let linksSections = value.split('<a ');
|
2018-10-28 05:45:32 +01:00
|
|
|
|
|
|
|
for (let section of linksSections) {
|
|
|
|
if (!section.includes('href')) {
|
|
|
|
this.processedText += section;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2020-04-18 23:30:32 +02:00
|
|
|
if (section.includes('class="mention hashtag"') || section.includes('class="hashtag"') || section.includes('target="_blank">#') || section.includes('rel="tag">')) {
|
2018-10-30 04:56:00 +01:00
|
|
|
try {
|
|
|
|
this.processHashtag(section);
|
|
|
|
}
|
|
|
|
catch (err) {
|
2019-05-25 23:19:07 +02:00
|
|
|
console.error('error processing hashtag');
|
|
|
|
console.error(value);
|
2018-10-30 04:56:00 +01:00
|
|
|
}
|
|
|
|
|
2018-11-02 02:00:41 +01:00
|
|
|
} else if (section.includes('class="u-url mention"') || section.includes('class="mention"') || section.includes('class="mention status-link"') || section.includes('class="h-card mention')) {
|
2018-10-30 04:56:00 +01:00
|
|
|
try {
|
|
|
|
this.processUser(section);
|
|
|
|
}
|
|
|
|
catch (err) {
|
2019-05-25 23:19:07 +02:00
|
|
|
console.error('error processing mention');
|
|
|
|
console.error(value);
|
2018-10-30 04:56:00 +01:00
|
|
|
}
|
2018-10-28 20:03:57 +01:00
|
|
|
} else {
|
2018-10-30 04:56:00 +01:00
|
|
|
try {
|
|
|
|
this.processLink(section);
|
2019-05-24 06:46:26 +02:00
|
|
|
//this.processedText += `<a ${section}`;
|
2018-10-30 04:56:00 +01:00
|
|
|
}
|
|
|
|
catch (err) {
|
2019-05-25 23:19:07 +02:00
|
|
|
console.error('error processing link');
|
|
|
|
console.error(value);
|
2018-10-30 04:56:00 +01:00
|
|
|
}
|
2018-10-28 20:03:57 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-10-28 05:45:32 +01:00
|
|
|
|
2020-04-18 06:45:27 +02:00
|
|
|
expand(): boolean {
|
|
|
|
this.isCollapsed = false;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-10-28 20:03:57 +01:00
|
|
|
private processHashtag(section: string) {
|
|
|
|
let extractedLinkAndNext = section.split('</a>');
|
|
|
|
let extractedHashtag = extractedLinkAndNext[0].split('#')[1].replace('<span>', '').replace('</span>', '');
|
2020-08-29 20:30:13 +02:00
|
|
|
let extractedUrl = extractedLinkAndNext[0].split('href="')[1].split('"')[0];
|
2018-10-28 05:45:32 +01:00
|
|
|
|
2018-11-01 23:38:59 +01:00
|
|
|
let classname = this.getClassNameForHastag(extractedHashtag);
|
2020-08-29 20:30:13 +02:00
|
|
|
this.processedText += ` <a href="${extractedUrl}" class="${classname}" title="#${extractedHashtag}" target="_blank" rel="noopener noreferrer">#${extractedHashtag}</a>`;
|
2018-10-28 20:03:57 +01:00
|
|
|
if (extractedLinkAndNext[1]) this.processedText += extractedLinkAndNext[1];
|
|
|
|
this.hashtags.push(extractedHashtag);
|
|
|
|
}
|
2018-10-28 05:45:32 +01:00
|
|
|
|
2018-10-28 20:03:57 +01:00
|
|
|
private processUser(section: string) {
|
2018-11-02 01:45:18 +01:00
|
|
|
let extractedAccountAndNext: string[];
|
|
|
|
let extractedAccountName: string;
|
|
|
|
|
2019-05-23 08:16:36 +02:00
|
|
|
if (section.includes('<span class="mention">')) { //Friendica
|
2019-05-23 08:12:19 +02:00
|
|
|
extractedAccountAndNext = section.split('</a>');
|
|
|
|
extractedAccountName = extractedAccountAndNext[0].split('<span class="mention">')[1].split('</span>')[0];
|
2020-08-29 20:30:13 +02:00
|
|
|
} else if (section.includes('>@<span class="article-type">')) { //Remote status
|
2020-06-07 05:12:06 +02:00
|
|
|
extractedAccountAndNext = section.split('</a></span>');
|
|
|
|
extractedAccountName = extractedAccountAndNext[0].split('@<span class="article-type">')[1].replace('<span>', '').replace('</span>', '');
|
2020-08-29 20:30:13 +02:00
|
|
|
} else if (section.includes('class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@') && !section.includes('target="_blank">@<')) { //Misskey
|
2020-08-29 04:12:30 +02:00
|
|
|
//console.warn('misskey');
|
|
|
|
|
|
|
|
extractedAccountAndNext = section.split('</a>');
|
|
|
|
extractedAccountName = extractedAccountAndNext[0].split('class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@')[1];
|
|
|
|
|
2020-08-29 20:30:13 +02:00
|
|
|
if (extractedAccountName.includes('@'))
|
2020-08-29 04:12:30 +02:00
|
|
|
extractedAccountName = extractedAccountName.split('@')[0];
|
2019-05-23 08:12:19 +02:00
|
|
|
} else if (!section.includes('@<span>')) { //GNU social
|
2018-11-02 01:45:18 +01:00
|
|
|
extractedAccountAndNext = section.split('</a>');
|
|
|
|
extractedAccountName = extractedAccountAndNext[0].split('>')[1];
|
|
|
|
} else {
|
|
|
|
extractedAccountAndNext = section.split('</a></span>');
|
|
|
|
extractedAccountName = extractedAccountAndNext[0].split('@<span>')[1].replace('<span>', '').replace('</span>', '');
|
|
|
|
}
|
2018-10-28 20:03:57 +01:00
|
|
|
|
|
|
|
let extractedAccountLink = extractedAccountAndNext[0].split('href="https://')[1].split('"')[0].replace(' ', '').replace('@', '').split('/');
|
2018-10-31 04:27:20 +01:00
|
|
|
|
|
|
|
let domain = extractedAccountLink[0];
|
2018-11-02 01:45:18 +01:00
|
|
|
//let username = extractedAccountLink[extractedAccountLink.length - 1];
|
2018-10-31 04:27:20 +01:00
|
|
|
|
2018-11-02 01:45:18 +01:00
|
|
|
let extractedAccount = `@${extractedAccountName}@${domain}`;
|
2018-10-28 20:03:57 +01:00
|
|
|
|
|
|
|
let classname = this.getClassNameForAccount(extractedAccount);
|
2018-11-02 01:45:18 +01:00
|
|
|
this.processedText += `<a href class="${classname}" title="${extractedAccount}">@${extractedAccountName}</a>`;
|
|
|
|
|
2019-05-23 08:16:36 +02:00
|
|
|
if (extractedAccountAndNext[1])
|
2018-11-02 01:45:18 +01:00
|
|
|
this.processedText += extractedAccountAndNext[1];
|
|
|
|
|
|
|
|
//GNU Social clean up
|
2019-05-23 08:16:36 +02:00
|
|
|
if (this.processedText.includes('@<a'))
|
2018-11-02 01:45:18 +01:00
|
|
|
this.processedText = this.processedText.replace('@<a', '<a');
|
2018-10-28 20:03:57 +01:00
|
|
|
|
|
|
|
this.accounts.push(extractedAccount);
|
|
|
|
}
|
|
|
|
|
|
|
|
private processLink(section: string) {
|
2020-08-29 20:30:13 +02:00
|
|
|
if (!section.includes('</a>')) {
|
2020-05-07 00:25:33 +02:00
|
|
|
this.processedText += section;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-10-28 20:03:57 +01:00
|
|
|
let extractedLinkAndNext = section.split('</a>')
|
|
|
|
let extractedUrl = extractedLinkAndNext[0].split('"')[1];
|
|
|
|
|
|
|
|
let extractedName = '';
|
|
|
|
try {
|
|
|
|
extractedName = extractedLinkAndNext[0].split('<span class="ellipsis">')[1].split('</span>')[0];
|
2018-10-30 04:56:00 +01:00
|
|
|
} catch (err) {
|
2018-10-28 20:03:57 +01:00
|
|
|
try {
|
2019-03-02 06:35:09 +01:00
|
|
|
extractedName = extractedLinkAndNext[0].split(`<span class="">`)[1].split('</span>')[0];
|
2018-10-28 05:45:32 +01:00
|
|
|
}
|
2018-10-30 04:56:00 +01:00
|
|
|
catch (err) {
|
2019-05-24 06:46:26 +02:00
|
|
|
try {
|
|
|
|
extractedName = extractedLinkAndNext[0].split(' target="_blank">')[1].split('</span>')[0];
|
|
|
|
} catch (err) { // Pleroma
|
2019-05-25 23:19:07 +02:00
|
|
|
try {
|
|
|
|
extractedName = extractedLinkAndNext[0].split('</span><span>')[1].split('</span>')[0];
|
|
|
|
} catch (err) {
|
|
|
|
extractedName = extractedLinkAndNext[0].split('">')[1];
|
|
|
|
}
|
2019-05-24 06:46:26 +02:00
|
|
|
}
|
2018-10-28 20:03:57 +01:00
|
|
|
}
|
2018-10-30 04:56:00 +01:00
|
|
|
}
|
2018-10-28 20:03:57 +01:00
|
|
|
|
2020-08-29 03:18:41 +02:00
|
|
|
this.links.push(extractedUrl);
|
2018-10-28 20:03:57 +01:00
|
|
|
let classname = this.getClassNameForLink(extractedUrl);
|
|
|
|
|
2020-08-29 02:54:09 +02:00
|
|
|
let sanitizedLink = this.sanitizeLink(extractedUrl);
|
|
|
|
|
|
|
|
this.processedText += `<a href="${sanitizedLink}" class="${classname}" title="open link" target="_blank" rel="noopener noreferrer">${extractedName}</a>`;
|
2018-10-28 20:03:57 +01:00
|
|
|
if (extractedLinkAndNext.length > 1) this.processedText += extractedLinkAndNext[1];
|
2018-10-28 04:58:00 +01:00
|
|
|
}
|
|
|
|
|
2018-10-28 05:45:32 +01:00
|
|
|
|
|
|
|
|
|
|
|
constructor(private renderer: Renderer2) { }
|
2018-10-28 04:58:00 +01:00
|
|
|
|
|
|
|
ngOnInit() {
|
|
|
|
}
|
|
|
|
|
2018-10-28 05:45:32 +01:00
|
|
|
ngAfterViewInit() {
|
2018-10-28 06:02:57 +01:00
|
|
|
for (const hashtag of this.hashtags) {
|
2018-11-01 23:38:59 +01:00
|
|
|
let classname = this.getClassNameForHastag(hashtag);
|
2019-07-07 20:14:12 +02:00
|
|
|
let els = <Element[]>this.contentElement.nativeElement.querySelectorAll(`.${classname}`);
|
2018-10-28 06:02:57 +01:00
|
|
|
|
2019-07-07 20:14:12 +02:00
|
|
|
for (const el of els) {
|
|
|
|
this.renderer.listen(el, 'click', (event) => {
|
|
|
|
event.preventDefault();
|
|
|
|
event.stopImmediatePropagation();
|
2018-10-28 06:25:13 +01:00
|
|
|
|
2019-07-07 20:14:12 +02:00
|
|
|
this.selectHashtag(hashtag);
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
}
|
2018-10-28 06:02:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
for (const account of this.accounts) {
|
2018-10-28 20:03:57 +01:00
|
|
|
let classname = this.getClassNameForAccount(account);
|
2019-07-07 20:14:12 +02:00
|
|
|
let els = this.contentElement.nativeElement.querySelectorAll(`.${classname}`);
|
2018-10-28 06:02:57 +01:00
|
|
|
|
2019-07-07 20:14:12 +02:00
|
|
|
for (const el of els) {
|
|
|
|
this.renderer.listen(el, 'click', (event) => {
|
|
|
|
event.preventDefault();
|
|
|
|
event.stopImmediatePropagation();
|
2018-10-28 06:25:13 +01:00
|
|
|
|
2019-07-07 20:14:12 +02:00
|
|
|
this.selectAccount(account);
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
}
|
2018-10-28 06:02:57 +01:00
|
|
|
}
|
|
|
|
|
2020-08-29 03:18:41 +02:00
|
|
|
for (const link of this.links) {
|
|
|
|
let classname = this.getClassNameForLink(link);
|
|
|
|
let els = this.contentElement.nativeElement.querySelectorAll(`.${classname}`);
|
2019-05-23 08:16:36 +02:00
|
|
|
|
2020-08-29 03:18:41 +02:00
|
|
|
let sanitizedLink = this.sanitizeLink(link);
|
2019-07-07 20:14:12 +02:00
|
|
|
|
2020-08-29 03:18:41 +02:00
|
|
|
for (const el of els) {
|
|
|
|
this.renderer.listen(el, 'click', (event) => {
|
|
|
|
event.preventDefault();
|
|
|
|
event.stopImmediatePropagation();
|
2020-08-29 03:39:55 +02:00
|
|
|
window.open(sanitizedLink, '_blank', 'noopener');
|
2020-08-29 03:18:41 +02:00
|
|
|
return false;
|
|
|
|
});
|
2019-07-07 20:14:12 +02:00
|
|
|
|
2020-08-29 03:39:55 +02:00
|
|
|
// this.renderer.listen(el, 'mouseup', (event) => {
|
|
|
|
// if (event.which === 2) {
|
|
|
|
// event.preventDefault();
|
|
|
|
// event.stopImmediatePropagation();
|
|
|
|
// window.open(sanitizedLink, '_blank', 'noopener');
|
|
|
|
// return false;
|
|
|
|
// }
|
|
|
|
// });
|
2020-08-29 03:18:41 +02:00
|
|
|
}
|
|
|
|
}
|
2018-10-28 05:45:32 +01:00
|
|
|
}
|
|
|
|
|
2020-05-29 00:34:51 +02:00
|
|
|
private sanitizeLink(link: string): string {
|
|
|
|
let res = link.replace(/&/g, '&');
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
2018-11-01 23:38:59 +01:00
|
|
|
private getClassNameForHastag(value: string): string {
|
|
|
|
let res = value.replace(/[.,\/#?!@$%+\^&\*;:{}=\-_`~()]/g, "");
|
|
|
|
return `hashtag-${res}`;
|
|
|
|
}
|
|
|
|
|
2018-10-28 20:03:57 +01:00
|
|
|
private getClassNameForAccount(value: string): string {
|
2018-10-28 06:25:13 +01:00
|
|
|
let res = value;
|
2018-10-28 20:03:57 +01:00
|
|
|
while (res.includes('.')) res = res.replace('.', '-');
|
|
|
|
while (res.includes('@')) res = res.replace('@', '-');
|
|
|
|
return `account-${res}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
private getClassNameForLink(value: string): string {
|
2018-11-01 23:51:28 +01:00
|
|
|
let res = value.replace(/[.,\/#?!@°$%+\'\^&\*;:{}=\-_`~()]/g, "");
|
2018-10-28 20:03:57 +01:00
|
|
|
return `link-${res}`;
|
2018-10-28 06:02:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private selectAccount(account: string) {
|
2018-10-28 05:45:32 +01:00
|
|
|
this.accountSelected.next(account);
|
|
|
|
}
|
|
|
|
|
2018-10-28 06:02:57 +01:00
|
|
|
private selectHashtag(hashtag: string) {
|
2018-10-28 05:45:32 +01:00
|
|
|
this.hashtagSelected.next(hashtag);
|
|
|
|
}
|
|
|
|
|
2019-08-14 06:08:41 +02:00
|
|
|
selectText(event) {
|
|
|
|
if (event.view.getSelection().toString().length === 0) {
|
|
|
|
this.textSelected.next();
|
|
|
|
}
|
2018-10-28 05:45:32 +01:00
|
|
|
}
|
|
|
|
|
2018-10-28 04:58:00 +01:00
|
|
|
}
|