import { Component, OnInit, Input, EventEmitter, Output, Renderer2, ViewChild, ElementRef } from '@angular/core'; import { faAngleDown } from "@fortawesome/free-solid-svg-icons"; @Component({ selector: 'app-databinded-text', templateUrl: './databinded-text.component.html', styleUrls: ['./databinded-text.component.scss'] }) export class DatabindedTextComponent implements OnInit { faAngleDown = faAngleDown; private accounts: string[] = []; private hashtags: string[] = []; private links: string[] = []; processedText: string; isCollapsed: boolean = false; @ViewChild('content') contentElement: ElementRef; @Output() accountSelected = new EventEmitter(); @Output() hashtagSelected = new EventEmitter(); @Output() textSelected = new EventEmitter(); @Input() textIsSelectable: boolean = true; @Input() selected: boolean; @Input('text') set text(value: string) { //console.warn(value); let parser = new DOMParser(); var dom = parser.parseFromString(value, 'text/html') this.isCollapsed = [...dom.body.textContent].length > 600; this.processedText = ''; do { value = value.replace('@', ''); //Friendica sanitarization } while (value.includes('@')); 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">@')); let linksSections = value.split('#') || section.includes('rel="tag">')) { try { this.processHashtag(section); } catch (err) { console.error('error processing hashtag'); console.error(value); } } else if (section.includes('class="u-url mention"') || section.includes('class="mention"') || section.includes('class="mention status-link"') || section.includes('class="h-card mention')) { try { this.processUser(section); } catch (err) { console.error('error processing mention'); console.error(value); } } else { try { this.processLink(section); //this.processedText += `'); let extractedHashtag = extractedLinkAndNext[0].split('#')[1].replace('', '').replace('', ''); let classname = this.getClassNameForHastag(extractedHashtag); this.processedText += ` #${extractedHashtag}`; if (extractedLinkAndNext[1]) this.processedText += extractedLinkAndNext[1]; this.hashtags.push(extractedHashtag); } private processUser(section: string) { let extractedAccountAndNext: string[]; let extractedAccountName: string; if (section.includes('')) { //Friendica extractedAccountAndNext = section.split(''); extractedAccountName = extractedAccountAndNext[0].split('')[1].split('')[0]; } else if(section.includes('>@')){ //Remote status extractedAccountAndNext = section.split(''); extractedAccountName = extractedAccountAndNext[0].split('@')[1].replace('', '').replace('', ''); } else if (!section.includes('@')) { //GNU social extractedAccountAndNext = section.split(''); extractedAccountName = extractedAccountAndNext[0].split('>')[1]; } else { extractedAccountAndNext = section.split(''); extractedAccountName = extractedAccountAndNext[0].split('@')[1].replace('', '').replace('', ''); } let extractedAccountLink = extractedAccountAndNext[0].split('href="https://')[1].split('"')[0].replace(' ', '').replace('@', '').split('/'); let domain = extractedAccountLink[0]; //let username = extractedAccountLink[extractedAccountLink.length - 1]; let extractedAccount = `@${extractedAccountName}@${domain}`; let classname = this.getClassNameForAccount(extractedAccount); this.processedText += `@${extractedAccountName}`; if (extractedAccountAndNext[1]) this.processedText += extractedAccountAndNext[1]; //GNU Social clean up if (this.processedText.includes('@')){ this.processedText += section; return; } let extractedLinkAndNext = section.split('') let extractedUrl = extractedLinkAndNext[0].split('"')[1]; let extractedName = ''; try { extractedName = extractedLinkAndNext[0].split('')[1].split('')[0]; } catch (err) { try { extractedName = extractedLinkAndNext[0].split(``)[1].split('')[0]; } catch (err) { try { extractedName = extractedLinkAndNext[0].split(' target="_blank">')[1].split('')[0]; } catch (err) { // Pleroma try { extractedName = extractedLinkAndNext[0].split('')[1].split('')[0]; } catch (err) { extractedName = extractedLinkAndNext[0].split('">')[1]; } } } } this.links.push(extractedUrl); let classname = this.getClassNameForLink(extractedUrl); let sanitizedLink = this.sanitizeLink(extractedUrl); this.processedText += `${extractedName}`; if (extractedLinkAndNext.length > 1) this.processedText += extractedLinkAndNext[1]; } constructor(private renderer: Renderer2) { } ngOnInit() { } ngAfterViewInit() { for (const hashtag of this.hashtags) { let classname = this.getClassNameForHastag(hashtag); let els = this.contentElement.nativeElement.querySelectorAll(`.${classname}`); for (const el of els) { this.renderer.listen(el, 'click', (event) => { event.preventDefault(); event.stopImmediatePropagation(); this.selectHashtag(hashtag); return false; }); } } for (const account of this.accounts) { let classname = this.getClassNameForAccount(account); let els = this.contentElement.nativeElement.querySelectorAll(`.${classname}`); for (const el of els) { this.renderer.listen(el, 'click', (event) => { event.preventDefault(); event.stopImmediatePropagation(); this.selectAccount(account); return false; }); } } for (const link of this.links) { 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(sanitizedLink, '_blank'); return false; }); this.renderer.listen(el, 'mouseup', (event) => { if (event.which === 2) { event.preventDefault(); event.stopImmediatePropagation(); window.open(sanitizedLink, '_blank'); return false; } }); } } } 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}`; } private getClassNameForAccount(value: string): string { let res = value; while (res.includes('.')) res = res.replace('.', '-'); while (res.includes('@')) res = res.replace('@', '-'); return `account-${res}`; } private getClassNameForLink(value: string): string { let res = value.replace(/[.,\/#?!@°$%+\'\^&\*;:{}=\-_`~()]/g, ""); return `link-${res}`; } private selectAccount(account: string) { this.accountSelected.next(account); } private selectHashtag(hashtag: string) { this.hashtagSelected.next(hashtag); } selectText(event) { if (event.view.getSelection().toString().length === 0) { this.textSelected.next(); } } }