From 2b123e386d33b05a42a6743e3a4476aad12ad006 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Mon, 29 Oct 2018 23:56:00 -0400 Subject: [PATCH 01/25] better link parsing from GNU social --- .../databinded-text.component.spec.ts | 9 ++++ .../databinded-text.component.ts | 45 +++++++++++-------- 2 files changed, 36 insertions(+), 18 deletions(-) diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts index bc0c0122..e8444801 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts @@ -75,4 +75,13 @@ describe('DatabindedTextComponent', () => { expect(component.processedText).toContain('bla3'); expect(component.processedText).toContain('bla4'); }); + + it('should parse link - GNU social', () => { + const sample = `bla1 https://social.bitcast.info/url/819438`; + + component.text = sample; + expect(component.processedText).toContain('https://social.bitcast.info/url/819438'); + expect(component.processedText).toContain('bla1'); + }); + }); diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.ts index e8a2e23d..7cba1234 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.ts +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.ts @@ -21,27 +21,39 @@ export class DatabindedTextComponent implements OnInit { @Input('text') set text(value: string) { this.processedText = ''; - let linksSections = value.split('#')) { - console.log('process hashtag'); - this.processHashtag(section); + try { + this.processHashtag(section); + } + catch (err) { + console.warn('process hashtag'); + console.warn(value); + } + } else if (section.includes('class="u-url mention"') || section.includes('class="mention"')) { - console.log('process mention'); - this.processUser(section); + try { + this.processUser(section); + } + catch (err) { + console.warn('process mention'); + console.warn(value); + } } else { - console.log('process link'); - console.log(section); - this.processLink(section); + try { + this.processLink(section); + } + catch (err) { + console.warn('process link'); + console.warn(value); + } } } } @@ -78,20 +90,17 @@ export class DatabindedTextComponent implements OnInit { let extractedLinkAndNext = section.split('') let extractedUrl = extractedLinkAndNext[0].split('"')[1]; - console.warn(extractedLinkAndNext[0]); - console.warn(extractedLinkAndNext[0].split('')); - let extractedName = ''; try { extractedName = extractedLinkAndNext[0].split('')[1].split('')[0]; - } catch (err){ + } catch (err) { try { - extractedName = extractedLinkAndNext[0].split('')[1].split('')[0]; + extractedName = extractedLinkAndNext[0].split('')[1].split('')[0]; } - catch(err){ - extractedName = extractedLinkAndNext[0].split('rel="nofollow noopener" target="_blank">')[1].split('')[0]; + catch (err) { + extractedName = extractedLinkAndNext[0].split(' target="_blank">')[1].split('')[0]; } - } + } this.links.push(extractedUrl); let classname = this.getClassNameForLink(extractedUrl); From 334f96eb84f4a500c8b1f0ceb38b956f0f95308d Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Tue, 30 Oct 2018 23:03:53 -0400 Subject: [PATCH 02/25] added + sign to class name generation --- .../stream/status/databinded-text/databinded-text.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.ts index 7cba1234..4a8d0cd2 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.ts +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.ts @@ -163,7 +163,7 @@ export class DatabindedTextComponent implements OnInit { } private getClassNameForLink(value: string): string { - let res = value.replace(/[.,\/#?!@$%\^&\*;:{}=\-_`~()]/g, ""); + let res = value.replace(/[.,\/#?!@$%\^&\*;:{}=\+-_`~()]/g, ""); return `link-${res}`; } From 8d75d8a9011f2e19f18c8f7b0b6155b6bdce64cc Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Tue, 30 Oct 2018 23:04:23 -0400 Subject: [PATCH 03/25] added test for long url parsing --- .../databinded-text.component.spec.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts index e8444801..d95297b4 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts @@ -59,6 +59,14 @@ describe('DatabindedTextComponent', () => { expect(component.processedText).toContain('bla2'); }); + it('should parse link - dual section', () => { + const sample = `

Test.
peertube.fr/videos/watch/69bb6

`; + + component.text = sample; + expect(component.processedText).toContain('

Test.
peertube.fr/videos/watch/69bb6

'); + }); + + it('should parse combined hashtag, mention and link', () => { const hashtag = 'programmers'; const hashtagUrl = 'https://test.social/tags/programmers'; @@ -83,5 +91,7 @@ describe('DatabindedTextComponent', () => { expect(component.processedText).toContain('https://social.bitcast.info/url/819438'); expect(component.processedText).toContain('bla1'); }); - + + + }); From d979abc83eb6fd9e469754c5f4c20b0e1611a72e Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Tue, 30 Oct 2018 23:27:20 -0400 Subject: [PATCH 04/25] better parsing, added test, add wirering to status comp --- .../databinded-text.component.spec.ts | 11 ++++++++--- .../databinded-text.component.ts | 19 ++++++++++--------- .../stream/status/status.component.html | 2 +- .../stream/status/status.component.ts | 18 +++++++++++++++--- 4 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts index d95297b4..95c71731 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts @@ -2,6 +2,7 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { DatabindedTextComponent } from './databinded-text.component'; import { By } from '@angular/platform-browser'; +import { isGeneratedFile } from '@angular/compiler/src/aot/util'; describe('DatabindedTextComponent', () => { let component: DatabindedTextComponent; @@ -64,8 +65,7 @@ describe('DatabindedTextComponent', () => { component.text = sample; expect(component.processedText).toContain('

Test.
peertube.fr/videos/watch/69bb6

'); - }); - + }); it('should parse combined hashtag, mention and link', () => { const hashtag = 'programmers'; @@ -84,7 +84,7 @@ describe('DatabindedTextComponent', () => { expect(component.processedText).toContain('bla4'); }); - it('should parse link - GNU social', () => { + it('should parse link - GNU social in Mastodon', () => { const sample = `bla1 https://social.bitcast.info/url/819438`; component.text = sample; @@ -92,6 +92,11 @@ describe('DatabindedTextComponent', () => { expect(component.processedText).toContain('bla1'); }); + it('shoult parse mention - Pleroma in Mastodon', () => { + const sample = `
@kaniini @Gargron bla1?
`; + component.text = sample; + expect(component.processedText).toContain('
bla1?
'); + }); }); diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.ts index 4a8d0cd2..56e475e4 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.ts +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.ts @@ -38,7 +38,7 @@ export class DatabindedTextComponent implements OnInit { console.warn(value); } - } else if (section.includes('class="u-url mention"') || section.includes('class="mention"')) { + } else if (section.includes('class="u-url mention"') || section.includes('class="mention"') || section.includes('class="mention status-link"')) { try { this.processUser(section); } @@ -68,16 +68,20 @@ export class DatabindedTextComponent implements OnInit { } private processUser(section: string) { - let mentionClass = 'class="mention"'; - if (section.includes('class="u-url mention"')) - mentionClass = 'class="u-url mention"'; + // let mentionClass = 'class="mention"'; + // if (section.includes('class="u-url mention"')) + // mentionClass = 'class="u-url mention"'; let extractedAccountAndNext = section.split(''); let extractedAccountName = extractedAccountAndNext[0].split('@')[1].replace('', '').replace('', ''); let extractedAccountLink = extractedAccountAndNext[0].split('href="https://')[1].split('"')[0].replace(' ', '').replace('@', '').split('/'); - let extractedAccount = `@${extractedAccountLink[1]}@${extractedAccountLink[0]}`; + + let domain = extractedAccountLink[0]; + let username = extractedAccountLink[extractedAccountLink.length - 1]; + + let extractedAccount = `@${username}@${domain}`; let classname = this.getClassNameForAccount(extractedAccount); this.processedText += ` @${extractedAccountName}`; @@ -163,22 +167,19 @@ export class DatabindedTextComponent implements OnInit { } private getClassNameForLink(value: string): string { - let res = value.replace(/[.,\/#?!@$%\^&\*;:{}=\+-_`~()]/g, ""); + let res = value.replace(/[.,\/#?!@$%+\^&\*;:{}=\-_`~()]/g, ""); return `link-${res}`; } private selectAccount(account: string) { - console.warn(`select ${account}`); this.accountSelected.next(account); } private selectHashtag(hashtag: string) { - console.warn(`select ${hashtag}`); this.hashtagSelected.next(hashtag); } selectText() { - console.warn(`selectText`); this.textSelected.next(); } diff --git a/src/app/components/stream/status/status.component.html b/src/app/components/stream/status/status.component.html index cdcd6638..786b4626 100644 --- a/src/app/components/stream/status/status.component.html +++ b/src/app/components/stream/status/status.component.html @@ -16,7 +16,7 @@ getCompactRelativeTime(status.created_at) }} - + diff --git a/src/app/components/stream/status/status.component.ts b/src/app/components/stream/status/status.component.ts index 17b0e2b3..1138f337 100644 --- a/src/app/components/stream/status/status.component.ts +++ b/src/app/components/stream/status/status.component.ts @@ -107,8 +107,20 @@ export class StatusComponent implements OnInit { return false; } - test(): boolean { - console.warn('heeeeyaaa!'); - return false; + // test(): boolean { + // console.warn('heeeeyaaa!'); + // return false; + // } + + accountSelected(accountName: string): void { + console.warn(`status comp: accountSelected ${accountName}`); + } + + hashtagSelected(hashtag: string): void { + console.warn(`status comp: hashtagSelected ${hashtag}`); + } + + textSelected(): void { + console.warn(`status comp: textSelected`); } } From fa4f1f235fe1ab2e38cb98c77941181f81038fef Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Wed, 31 Oct 2018 00:02:03 -0400 Subject: [PATCH 05/25] opening account from status mention is working --- .../reply-to-status.component.ts | 19 +++--- .../stream/status/status.component.ts | 12 ++-- .../stream-overlay.component.html | 2 +- .../stream-overlay.component.ts | 63 ++++++++++++++----- src/app/components/stream/stream.component.ts | 8 +-- src/app/services/tools.service.spec.ts | 15 +++++ src/app/services/tools.service.ts | 20 ++++++ 7 files changed, 104 insertions(+), 35 deletions(-) create mode 100644 src/app/services/tools.service.spec.ts create mode 100644 src/app/services/tools.service.ts diff --git a/src/app/components/stream/status/reply-to-status/reply-to-status.component.ts b/src/app/components/stream/status/reply-to-status/reply-to-status.component.ts index db9dbde0..bb30e61b 100644 --- a/src/app/components/stream/status/reply-to-status/reply-to-status.component.ts +++ b/src/app/components/stream/status/reply-to-status/reply-to-status.component.ts @@ -1,9 +1,10 @@ import { Component, OnInit, Input, Output, EventEmitter, ElementRef, ViewChild } from '@angular/core'; -import { Store } from '@ngxs/store'; +// import { Store } from '@ngxs/store'; import { MastodonService, VisibilityEnum } from '../../../../services/mastodon.service'; -import { AccountInfo } from '../../../../states/accounts.state'; +// import { AccountInfo } from '../../../../states/accounts.state'; import { StatusWrapper } from '../../stream.component'; import { Status } from '../../../../services/models/mastodon.interfaces'; +import { ToolsService } from '../../../../services/tools.service'; @Component({ selector: 'app-reply-to-status', @@ -22,7 +23,8 @@ export class ReplyToStatusComponent implements OnInit { privacyList: string[] = ['Public', 'Unlisted', 'Follows-only', 'DM']; constructor( - private readonly store: Store, + // private readonly store: Store, + private readonly toolsService: ToolsService, private readonly mastodonService: MastodonService) { } ngOnInit() { @@ -39,8 +41,7 @@ export class ReplyToStatusComponent implements OnInit { } onSubmit(): boolean { - const accounts = this.getRegisteredAccounts(); - const selectedAccounts = accounts.filter(x => x.isSelected); + const selectedAccounts = this.toolsService.getSelectedAccounts(); let visibility: VisibilityEnum = VisibilityEnum.Unknown; switch (this.selectedPrivacy) { @@ -72,10 +73,10 @@ export class ReplyToStatusComponent implements OnInit { return false; } - private getRegisteredAccounts(): AccountInfo[] { - var regAccounts = this.store.snapshot().registeredaccounts.accounts; - return regAccounts; - } + // private getRegisteredAccounts(): AccountInfo[] { + // var regAccounts = this.store.snapshot().registeredaccounts.accounts; + // return regAccounts; + // } onCtrlEnter(): boolean { this.onSubmit(); diff --git a/src/app/components/stream/status/status.component.ts b/src/app/components/stream/status/status.component.ts index 1138f337..5a000671 100644 --- a/src/app/components/stream/status/status.component.ts +++ b/src/app/components/stream/status/status.component.ts @@ -17,12 +17,10 @@ export class StatusComponent implements OnInit { hasAttachments: boolean; replyingToStatus: boolean; - @Output() browseAccount = new EventEmitter(); + @Output() browseAccount = new EventEmitter(); @Output() browseHashtag = new EventEmitter(); @Output() browseThread = new EventEmitter(); - - private _statusWrapper: StatusWrapper; status: Status; @Input('statusWrapper') @@ -74,7 +72,11 @@ export class StatusComponent implements OnInit { // } openAccount(account: Account): boolean { - this.browseAccount.next(account); + let accountName = account.acct; + if(!accountName.includes('@')) + accountName += `@${account.url.replace('https://', '').split('/')[0]}`; + + this.browseAccount.next(accountName); return false; } @@ -114,10 +116,12 @@ export class StatusComponent implements OnInit { accountSelected(accountName: string): void { console.warn(`status comp: accountSelected ${accountName}`); + this.browseAccount.next(accountName); } hashtagSelected(hashtag: string): void { console.warn(`status comp: hashtagSelected ${hashtag}`); + this.browseHashtag.next(hashtag); } textSelected(): void { diff --git a/src/app/components/stream/stream-overlay/stream-overlay.component.html b/src/app/components/stream/stream-overlay/stream-overlay.component.html index 3939df1c..61c178d8 100644 --- a/src/app/components/stream/stream-overlay/stream-overlay.component.html +++ b/src/app/components/stream/stream-overlay/stream-overlay.component.html @@ -7,7 +7,7 @@ - + \ No newline at end of file diff --git a/src/app/components/stream/stream-overlay/stream-overlay.component.ts b/src/app/components/stream/stream-overlay/stream-overlay.component.ts index 2483427f..07e216fe 100644 --- a/src/app/components/stream/stream-overlay/stream-overlay.component.ts +++ b/src/app/components/stream/stream-overlay/stream-overlay.component.ts @@ -1,5 +1,7 @@ import { Component, OnInit, Output, EventEmitter, Input } from '@angular/core'; -import { Account } from "../../../services/models/mastodon.interfaces"; +import { Account, Results } from "../../../services/models/mastodon.interfaces"; +import { MastodonService } from '../../../services/mastodon.service'; +import { ToolsService } from '../../../services/tools.service'; @Component({ selector: 'app-stream-overlay', @@ -7,37 +9,51 @@ import { Account } from "../../../services/models/mastodon.interfaces"; styleUrls: ['./stream-overlay.component.scss'] }) export class StreamOverlayComponent implements OnInit { - private account: Account; + account: Account; + private accountName: string; + private thread: string; private hashtag: string; + error: string; + @Output() closeOverlay = new EventEmitter(); - @Input('browseAccount') - set browseAccount(account: Account) { - this.account = account; - } - get browseAccount(): Account{ - return this.account; - } + @Input('browseAccount') + set browseAccount(accountName: string) { + this.accountName = accountName; + this.loadAccount(accountName); - @Input('browseThread') - set browseThread(thread: string) { - this.thread = thread; + // let selectedAccounts = this.toolsService.getSelectedAccounts(); + // if(selectedAccounts.length === 0) + // this.error = 'no user selected'; + + + // this.account = account; } - get browseThread(): string{ + // get browseAccount(): string{ + // return this.account; + // } + + @Input('browseThread') + set browseThread(thread: string) { + this.thread = thread; + } + get browseThread(): string { return this.thread; } - @Input('browseHashtag') + @Input('browseHashtag') set browseHashtag(hashtag: string) { - this.hashtag = hashtag; + this.hashtag = hashtag; } - get browseHashtag(): string{ + get browseHashtag(): string { return this.hashtag; } - constructor() { } + constructor( + private readonly toolsService: ToolsService, + private readonly mastodonService: MastodonService) { } ngOnInit() { } @@ -47,4 +63,17 @@ export class StreamOverlayComponent implements OnInit { return false; } + loadAccount(accountName: string): void { + let selectedAccounts = this.toolsService.getSelectedAccounts(); + + if (selectedAccounts.length === 0) { + this.error = 'no user selected'; + return; + } + + this.mastodonService.search(selectedAccounts[0], accountName) + .then((result: Results) => { + this.account = result.accounts[0]; + }); + } } diff --git a/src/app/components/stream/stream.component.ts b/src/app/components/stream/stream.component.ts index 82c62829..37ad7aa7 100644 --- a/src/app/components/stream/stream.component.ts +++ b/src/app/components/stream/stream.component.ts @@ -22,7 +22,7 @@ export class StreamComponent implements OnInit { private bufferWasCleared: boolean; overlayActive: boolean; - overlayAccountToBrowse: Account; + overlayAccountToBrowse: string; @Input() set streamElement(streamElement: StreamElement) { @@ -50,17 +50,17 @@ export class StreamComponent implements OnInit { ngOnInit() { } - browseAccount(account: Account): void { + browseAccount(account: string): void { this.overlayAccountToBrowse = account; this.overlayActive = true; } - browseHashtag(hashtag: any): void { + browseHashtag(hashtag: string): void { console.warn('browseHashtag'); console.warn(hashtag); } - browseThread(thread: any): void { + browseThread(thread: string): void { console.warn('browseThread'); console.warn(thread); } diff --git a/src/app/services/tools.service.spec.ts b/src/app/services/tools.service.spec.ts new file mode 100644 index 00000000..145d5f3e --- /dev/null +++ b/src/app/services/tools.service.spec.ts @@ -0,0 +1,15 @@ +import { TestBed, inject } from '@angular/core/testing'; + +import { ToolsService } from './tools.service'; + +describe('ToolsService', () => { + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [ToolsService] + }); + }); + + it('should be created', inject([ToolsService], (service: ToolsService) => { + expect(service).toBeTruthy(); + })); +}); diff --git a/src/app/services/tools.service.ts b/src/app/services/tools.service.ts new file mode 100644 index 00000000..62fb80e1 --- /dev/null +++ b/src/app/services/tools.service.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@angular/core'; +import { Store } from '@ngxs/store'; + +import { AccountInfo } from '../states/accounts.state'; + + +@Injectable({ + providedIn: 'root' +}) +export class ToolsService { + + constructor( private readonly store: Store) { } + + + getSelectedAccounts(): AccountInfo[] { + var regAccounts = this.store.snapshot().registeredaccounts.accounts; + return regAccounts.filter(x => x.isSelected); + } + +} From 4825aa53d0e5339145c52b69d56ba1c2eba6dcf5 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Wed, 31 Oct 2018 00:27:25 -0400 Subject: [PATCH 06/25] resolve remote account when displaying it --- .../stream/stream-overlay/stream-overlay.component.ts | 2 +- src/app/services/mastodon.service.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/app/components/stream/stream-overlay/stream-overlay.component.ts b/src/app/components/stream/stream-overlay/stream-overlay.component.ts index 07e216fe..55627a31 100644 --- a/src/app/components/stream/stream-overlay/stream-overlay.component.ts +++ b/src/app/components/stream/stream-overlay/stream-overlay.component.ts @@ -71,7 +71,7 @@ export class StreamOverlayComponent implements OnInit { return; } - this.mastodonService.search(selectedAccounts[0], accountName) + this.mastodonService.search(selectedAccounts[0], accountName, true) .then((result: Results) => { this.account = result.accounts[0]; }); diff --git a/src/app/services/mastodon.service.ts b/src/app/services/mastodon.service.ts index 39c82a3f..c7ee6e81 100644 --- a/src/app/services/mastodon.service.ts +++ b/src/app/services/mastodon.service.ts @@ -117,6 +117,12 @@ export class MastodonService { return this.httpClient.get(route, { headers: headers }).toPromise() } + searchAccount(account: AccountInfo, query: string, limit: number = 40, following: boolean = false): Promise{ + const route = `https://${account.instance}${this.apiRoutes.searchForAccounts}?q=${query}&limit=${limit}&following=${following}`; + const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` }); + return this.httpClient.get(route, { headers: headers }).toPromise() + } + reblog(account: AccountInfo, status: Status): Promise { const route = `https://${account.instance}${this.apiRoutes.reblogStatus}`.replace('{0}', status.id); const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` }); From 90ab3343ac035061e1fb6c693253075edf63e627 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Wed, 31 Oct 2018 00:30:41 -0400 Subject: [PATCH 07/25] always resolve remote account when searching --- src/app/components/floating-column/search/search.component.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/floating-column/search/search.component.ts b/src/app/components/floating-column/search/search.component.ts index 21f65ae6..eb8b00ec 100644 --- a/src/app/components/floating-column/search/search.component.ts +++ b/src/app/components/floating-column/search/search.component.ts @@ -46,7 +46,7 @@ export class SearchComponent implements OnInit { //First candid implementation if (enabledAccounts.length > 0) { const candid_oneAccount = enabledAccounts[0]; - this.mastodonService.search(candid_oneAccount, data) + this.mastodonService.search(candid_oneAccount, data, true) .then((results: Results) => { if (results) { console.warn(results); From 7aff805570e57edff83487eec92137ffc51af6e7 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Wed, 31 Oct 2018 23:24:18 -0400 Subject: [PATCH 08/25] added refresh link and loading animation in stream-overlay --- .../stream-overlay.component.html | 10 ++-- .../stream-overlay.component.scss | 50 +++++++++++-------- .../stream-overlay.component.ts | 25 +++++++++- src/app/services/tools.service.spec.ts | 2 +- 4 files changed, 61 insertions(+), 26 deletions(-) diff --git a/src/app/components/stream/stream-overlay/stream-overlay.component.html b/src/app/components/stream/stream-overlay/stream-overlay.component.html index 61c178d8..212131fb 100644 --- a/src/app/components/stream/stream-overlay/stream-overlay.component.html +++ b/src/app/components/stream/stream-overlay/stream-overlay.component.html @@ -1,12 +1,16 @@
- CLOSE - PREV - NEXT + CLOSE + PREV + REFRESH + NEXT
+ + + diff --git a/src/app/components/stream/stream-overlay/stream-overlay.component.scss b/src/app/components/stream/stream-overlay/stream-overlay.component.scss index 2a2b18e7..ba1ee222 100644 --- a/src/app/components/stream/stream-overlay/stream-overlay.component.scss +++ b/src/app/components/stream/stream-overlay/stream-overlay.component.scss @@ -1,14 +1,11 @@ @import "variables"; +@import "commons"; .stream-overlay { // position: absolute; - // z-index: 50; width: $stream-column-width; height: calc(100%); - background-color: $column-color; - - - // margin: 0 0 0 $stream-column-separator; + background-color: $column-color; // margin: 0 0 0 $stream-column-separator; // outline: 1px red solid; // float: left; &__header { @@ -16,14 +13,12 @@ height: 30px; background-color: $column-header-background-color; padding: 6px 10px 0 10px; - & a { + & a { color: whitesmoke; font-size: 0.8em; - font-weight: normal; - margin: 0; + font-weight: normal; // margin: 0; } } - &__title { width: calc(100%); height: 30px; @@ -34,18 +29,31 @@ } } -.overlay-previous { - display: block; - float: left; +.overlay { + margin: 0; + &-previous { + display: block; + float: left; + } + &-refresh { + display: block; + float: left; + margin-left: 65px; + } + &-next { + display: block; + float: right; + padding-right: 20px; + } + &-close { + display: block; + float: right; + } } -.overlay-next { - display: block; - float: right; - padding-right: 20px; -} - -.overlay-close { - display: block; - float: right; +.not-active { + pointer-events: none; + cursor: default; + text-decoration: none; + color: gray !important; } \ No newline at end of file diff --git a/src/app/components/stream/stream-overlay/stream-overlay.component.ts b/src/app/components/stream/stream-overlay/stream-overlay.component.ts index 55627a31..8705f67a 100644 --- a/src/app/components/stream/stream-overlay/stream-overlay.component.ts +++ b/src/app/components/stream/stream-overlay/stream-overlay.component.ts @@ -9,6 +9,9 @@ import { ToolsService } from '../../../services/tools.service'; styleUrls: ['./stream-overlay.component.scss'] }) export class StreamOverlayComponent implements OnInit { + canRefresh: boolean; + canGoForward: boolean; + account: Account; private accountName: string; @@ -16,6 +19,7 @@ export class StreamOverlayComponent implements OnInit { private hashtag: string; error: string; + isLoading: boolean; @Output() closeOverlay = new EventEmitter(); @@ -62,8 +66,20 @@ export class StreamOverlayComponent implements OnInit { this.closeOverlay.next(); return false; } + + next(): boolean { + return false; + } - loadAccount(accountName: string): void { + previous(): boolean { + return false; + } + + refresh(): boolean { + return false; + } + + private loadAccount(accountName: string): void { let selectedAccounts = this.toolsService.getSelectedAccounts(); if (selectedAccounts.length === 0) { @@ -71,9 +87,16 @@ export class StreamOverlayComponent implements OnInit { return; } + this.isLoading = true; this.mastodonService.search(selectedAccounts[0], accountName, true) .then((result: Results) => { this.account = result.accounts[0]; + }) + .catch((err) => { + this.error = 'Error when retieving account'; + }) + .then(()=>{ + this.isLoading = false; }); } } diff --git a/src/app/services/tools.service.spec.ts b/src/app/services/tools.service.spec.ts index 145d5f3e..8052b656 100644 --- a/src/app/services/tools.service.spec.ts +++ b/src/app/services/tools.service.spec.ts @@ -2,7 +2,7 @@ import { TestBed, inject } from '@angular/core/testing'; import { ToolsService } from './tools.service'; -describe('ToolsService', () => { +xdescribe('ToolsService', () => { beforeEach(() => { TestBed.configureTestingModule({ providers: [ToolsService] From 4f027592040a5a27d8f641448791a25e2ec9acd3 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Wed, 31 Oct 2018 23:47:38 -0400 Subject: [PATCH 09/25] added datebinding to presentation text in profil --- .../databinded-text/databinded-text.component.html | 2 +- .../databinded-text/databinded-text.component.scss | 2 +- .../databinded-text/databinded-text.component.ts | 2 ++ .../stream-overlay/stream-overlay.component.html | 2 +- .../stream-overlay/stream-overlay.component.ts | 11 ++++++++++- .../stream/user-profile/user-profile.component.html | 3 ++- .../stream/user-profile/user-profile.component.ts | 12 +++++++++++- 7 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.html b/src/app/components/stream/status/databinded-text/databinded-text.component.html index 96aa742f..00923173 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.html +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.html @@ -1 +1 @@ -
\ No newline at end of file +
\ No newline at end of file diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.scss b/src/app/components/stream/status/databinded-text/databinded-text.component.scss index 0df7c1c2..2c17e4cf 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.scss +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.scss @@ -1,6 +1,6 @@ @import "variables"; -.content { +.selectable { cursor: pointer; } diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.ts index 56e475e4..73d7ce8d 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.ts +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.ts @@ -18,6 +18,8 @@ export class DatabindedTextComponent implements OnInit { @Output() hashtagSelected = new EventEmitter(); @Output() textSelected = new EventEmitter(); + @Input() textIsSelectable: boolean = true; + @Input('text') set text(value: string) { this.processedText = ''; diff --git a/src/app/components/stream/stream-overlay/stream-overlay.component.html b/src/app/components/stream/stream-overlay/stream-overlay.component.html index 212131fb..5c108a57 100644 --- a/src/app/components/stream/stream-overlay/stream-overlay.component.html +++ b/src/app/components/stream/stream-overlay/stream-overlay.component.html @@ -11,7 +11,7 @@ - +
\ No newline at end of file diff --git a/src/app/components/stream/stream-overlay/stream-overlay.component.ts b/src/app/components/stream/stream-overlay/stream-overlay.component.ts index 8705f67a..099b14b8 100644 --- a/src/app/components/stream/stream-overlay/stream-overlay.component.ts +++ b/src/app/components/stream/stream-overlay/stream-overlay.component.ts @@ -25,7 +25,7 @@ export class StreamOverlayComponent implements OnInit { @Input('browseAccount') set browseAccount(accountName: string) { - this.accountName = accountName; + this.loadAccount(accountName); // let selectedAccounts = this.toolsService.getSelectedAccounts(); @@ -79,7 +79,16 @@ export class StreamOverlayComponent implements OnInit { return false; } + accountSelected(accountName: string): void { + this.loadAccount(accountName); + } + + hashtagSelected(hashtag: string): void { + } + private loadAccount(accountName: string): void { + this.account = null; + this.accountName = accountName; let selectedAccounts = this.toolsService.getSelectedAccounts(); if (selectedAccounts.length === 0) { diff --git a/src/app/components/stream/user-profile/user-profile.component.html b/src/app/components/stream/user-profile/user-profile.component.html index 20c9f289..bc773037 100644 --- a/src/app/components/stream/user-profile/user-profile.component.html +++ b/src/app/components/stream/user-profile/user-profile.component.html @@ -5,5 +5,6 @@

@{{account.acct}}

-

+ +
\ No newline at end of file diff --git a/src/app/components/stream/user-profile/user-profile.component.ts b/src/app/components/stream/user-profile/user-profile.component.ts index 0c0bf024..79397115 100644 --- a/src/app/components/stream/user-profile/user-profile.component.ts +++ b/src/app/components/stream/user-profile/user-profile.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, Input } from '@angular/core'; +import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { Account } from "../../../services/models/mastodon.interfaces"; @Component({ @@ -10,6 +10,9 @@ export class UserProfileComponent implements OnInit { account: Account; hasNote: boolean; + @Output() browseAccount = new EventEmitter(); + @Output() browseHashtag = new EventEmitter(); + @Input('currentAccount') set currentAccount(account: Account) { this.account = account; @@ -23,4 +26,11 @@ export class UserProfileComponent implements OnInit { ngOnInit() { } + accountSelected(accountName: string): void { + this.browseAccount.next(accountName); + } + + hashtagSelected(hashtag: string): void { + this.browseHashtag.next(hashtag); + } } From 8f160d71e5241472434c6f2bfec16a925c472288 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 1 Nov 2018 00:05:34 -0400 Subject: [PATCH 10/25] better display of name and user background image in profil --- .../user-profile/user-profile.component.html | 15 ++++--- .../user-profile/user-profile.component.scss | 40 +++++++++++-------- 2 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/app/components/stream/user-profile/user-profile.component.html b/src/app/components/stream/user-profile/user-profile.component.html index bc773037..4640ccb3 100644 --- a/src/app/components/stream/user-profile/user-profile.component.html +++ b/src/app/components/stream/user-profile/user-profile.component.html @@ -1,10 +1,13 @@ -
- header - header -

{{account.display_name}}

-

@{{account.acct}}

+
+
+ + header +

{{account.display_name}}

+

@{{account.acct}}

+
- +
\ No newline at end of file diff --git a/src/app/components/stream/user-profile/user-profile.component.scss b/src/app/components/stream/user-profile/user-profile.component.scss index c3d4b91a..84ef1105 100644 --- a/src/app/components/stream/user-profile/user-profile.component.scss +++ b/src/app/components/stream/user-profile/user-profile.component.scss @@ -1,23 +1,30 @@ @import "variables"; .profile-header { + background-size: cover; position: relative; - height: 140px; + // height: 140px; overflow: hidden; // background-color: black; - border-bottom: 1px solid black; + border-bottom: 1px solid black; + & h2 { font-size: $default-font-size; } - &__header { - position: absolute; - // width: calc(100%); - - width: calc(100%); - height: auto; - - float: left; - display: block; - opacity: 0.3; + &__inner{ + height: 160px; + background-color: rgba(0, 0, 0, .45); } + + // &__header { + // position: absolute; + // // width: calc(100%); + + // width: calc(100%); + // height: auto; + + // float: left; + // display: block; + // opacity: 0.3; + // } &__avatar { position: absolute; top: 15px; @@ -28,21 +35,20 @@ } &__display-name { position: absolute; - top: 45px; - left: 115px; - // font-weight: bold; + top: 105px; + left: 15px; color: white; } &__fullhandle { position: absolute; - top: 105px; + top: 130px; left: 15px; color: white; } } .profile-description { - padding: 5px 10px 0 10px; + padding: 10px 10px 15px 10px; font-size: 13px; border-bottom: 1px solid black; } \ No newline at end of file From c3fedcd1de083b8c5c37fc96850d31f85073fc54 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 1 Nov 2018 00:44:58 -0400 Subject: [PATCH 11/25] now loading status in user profile --- .../user-profile/user-profile.component.html | 7 ++++ .../user-profile/user-profile.component.scss | 2 ++ .../user-profile/user-profile.component.ts | 33 +++++++++++++++++-- src/app/services/mastodon.service.ts | 11 +++++++ 4 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/app/components/stream/user-profile/user-profile.component.html b/src/app/components/stream/user-profile/user-profile.component.html index 4640ccb3..085bd922 100644 --- a/src/app/components/stream/user-profile/user-profile.component.html +++ b/src/app/components/stream/user-profile/user-profile.component.html @@ -10,4 +10,11 @@ +
+
+ + +
+ +
\ No newline at end of file diff --git a/src/app/components/stream/user-profile/user-profile.component.scss b/src/app/components/stream/user-profile/user-profile.component.scss index 84ef1105..22f04bb1 100644 --- a/src/app/components/stream/user-profile/user-profile.component.scss +++ b/src/app/components/stream/user-profile/user-profile.component.scss @@ -1,4 +1,6 @@ @import "variables"; +@import "commons"; + .profile-header { background-size: cover; position: relative; diff --git a/src/app/components/stream/user-profile/user-profile.component.ts b/src/app/components/stream/user-profile/user-profile.component.ts index 79397115..2b9a8cbe 100644 --- a/src/app/components/stream/user-profile/user-profile.component.ts +++ b/src/app/components/stream/user-profile/user-profile.component.ts @@ -1,5 +1,8 @@ import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; -import { Account } from "../../../services/models/mastodon.interfaces"; +import { Account, Status } from "../../../services/models/mastodon.interfaces"; +import { MastodonService } from '../../../services/mastodon.service'; +import { ToolsService } from '../../../services/tools.service'; +import { StatusWrapper } from '../stream.component'; @Component({ selector: 'app-user-profile', @@ -10,6 +13,9 @@ export class UserProfileComponent implements OnInit { account: Account; hasNote: boolean; + statusLoading: boolean; + statuses: StatusWrapper[] = []; + @Output() browseAccount = new EventEmitter(); @Output() browseHashtag = new EventEmitter(); @@ -19,9 +25,12 @@ export class UserProfileComponent implements OnInit { this.hasNote = account && account.note && account.note !== '

'; console.warn('currentAccount'); console.warn(account); + this.getStatuses(account); } - constructor() { } + constructor( + private readonly mastodonService: MastodonService, + private readonly toolsService: ToolsService) { } ngOnInit() { } @@ -33,4 +42,24 @@ export class UserProfileComponent implements OnInit { hashtagSelected(hashtag: string): void { this.browseHashtag.next(hashtag); } + + private getStatuses(account: Account): void { + let selectedAccounts = this.toolsService.getSelectedAccounts(); + if (selectedAccounts.length === 0) return; + + this.statusLoading = true; + this.mastodonService.getAccountStatuses(selectedAccounts[0], account.id, false, false, true, null, null, 40) + .then((result: Status[]) => { + for (const status of result) { + const wrapper = new StatusWrapper(status,selectedAccounts[0]); + this.statuses.push(wrapper); + } + }) + .catch(err => { + + }) + .then(() => { + this.statusLoading = false; + }); + } } diff --git a/src/app/services/mastodon.service.ts b/src/app/services/mastodon.service.ts index c7ee6e81..316bc7f3 100644 --- a/src/app/services/mastodon.service.ts +++ b/src/app/services/mastodon.service.ts @@ -117,6 +117,17 @@ export class MastodonService { return this.httpClient.get(route, { headers: headers }).toPromise() } + getAccountStatuses(account: AccountInfo, targetAccountId: number, onlyMedia: boolean, onlyPinned: boolean, excludeReplies: boolean, maxId: string, sinceId: string, limit: number = 20): Promise{ + const route = `https://${account.instance}${this.apiRoutes.getAccountStatuses}`.replace('{0}', targetAccountId.toString()); + let params = `?only_media=${onlyMedia}&pinned=${onlyPinned}&exclude_replies=${excludeReplies}&limit=${limit}`; + + if(maxId) params += `&max_id=${maxId}`; + if(sinceId) params += `&since_id=${sinceId}`; + + const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` }); + return this.httpClient.get(route+params, { headers: headers }).toPromise(); + } + searchAccount(account: AccountInfo, query: string, limit: number = 40, following: boolean = false): Promise{ const route = `https://${account.instance}${this.apiRoutes.searchForAccounts}?q=${query}&limit=${limit}&following=${following}`; const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` }); From 6530ad7bdee307dab3a690ddf4a7bfb6d0d4c0dd Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 1 Nov 2018 00:55:55 -0400 Subject: [PATCH 12/25] better display of user statuses in profil --- .../components/stream/stream.component.scss | 39 ++++---- .../user-profile/user-profile.component.html | 34 ++++--- .../user-profile/user-profile.component.scss | 98 +++++++++---------- src/sass/_commons.scss | 20 ++++ 4 files changed, 107 insertions(+), 84 deletions(-) diff --git a/src/app/components/stream/stream.component.scss b/src/app/components/stream/stream.component.scss index a2b72d6e..a324fad4 100644 --- a/src/app/components/stream/stream.component.scss +++ b/src/app/components/stream/stream.component.scss @@ -1,4 +1,5 @@ @import "variables"; +@import "commons"; .stream-column { position: relative; @@ -31,25 +32,25 @@ } } -.flexcroll { - scrollbar-face-color: #08090d; - scrollbar-shadow-color: #08090d; - scrollbar-highlight-color: #08090d; - scrollbar-3dlight-color: #08090d; - 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; - // background: #08090d; - background: lighten($color-primary, 5); - // -webkit-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.5); - } -} +// .flexcroll { +// scrollbar-face-color: #08090d; +// scrollbar-shadow-color: #08090d; +// scrollbar-highlight-color: #08090d; +// scrollbar-3dlight-color: #08090d; +// 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; +// // background: #08090d; +// background: lighten($color-primary, 5); +// // -webkit-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.5); +// } +// } .stream-overlay { position: absolute; diff --git a/src/app/components/stream/user-profile/user-profile.component.html b/src/app/components/stream/user-profile/user-profile.component.html index 085bd922..252f5a55 100644 --- a/src/app/components/stream/user-profile/user-profile.component.html +++ b/src/app/components/stream/user-profile/user-profile.component.html @@ -1,20 +1,22 @@ -
-
- - header -

{{account.display_name}}

-

@{{account.acct}}

+
+
+
+ + header +

{{account.display_name}}

+

@{{account.acct}}

+
-
-
- - -
-
- +
+ + +
+
+ -
- +
+ +
\ No newline at end of file diff --git a/src/app/components/stream/user-profile/user-profile.component.scss b/src/app/components/stream/user-profile/user-profile.component.scss index 22f04bb1..96fae266 100644 --- a/src/app/components/stream/user-profile/user-profile.component.scss +++ b/src/app/components/stream/user-profile/user-profile.component.scss @@ -1,56 +1,56 @@ @import "variables"; @import "commons"; +.profile { + overflow: auto; + height: calc(100% - 30px); -.profile-header { - background-size: cover; - position: relative; - // height: 140px; - overflow: hidden; // background-color: black; - border-bottom: 1px solid black; - - & h2 { - font-size: $default-font-size; - } - &__inner{ - height: 160px; - background-color: rgba(0, 0, 0, .45); + &-header { + background-size: cover; + position: relative; // height: 140px; + overflow: hidden; // background-color: black; + border-bottom: 1px solid black; + & h2 { + font-size: $default-font-size; + } + &__inner { + height: 160px; + background-color: rgba(0, 0, 0, .45); + } + + // &__header { + // position: absolute; + // // width: calc(100%); + // width: calc(100%); + // height: auto; + // float: left; + // display: block; + // opacity: 0.3; + // } + &__avatar { + position: absolute; + top: 15px; + left: 15px; + width: 80px; + border-radius: 50%; // border: 1px solid black; + // background-color: black; + } + &__display-name { + position: absolute; + top: 105px; + left: 15px; + color: white; + } + &__fullhandle { + position: absolute; + top: 130px; + left: 15px; + color: white; + } } - // &__header { - // position: absolute; - // // width: calc(100%); - - // width: calc(100%); - // height: auto; - - // float: left; - // display: block; - // opacity: 0.3; - // } - &__avatar { - position: absolute; - top: 15px; - left: 15px; - width: 80px; - border-radius: 50%; // border: 1px solid black; - // background-color: black; + &-description { + padding: 10px 10px 15px 10px; + font-size: 13px; + border-bottom: 1px solid black; } - &__display-name { - position: absolute; - top: 105px; - left: 15px; - color: white; - } - &__fullhandle { - position: absolute; - top: 130px; - left: 15px; - color: white; - } -} - -.profile-description { - padding: 10px 10px 15px 10px; - font-size: 13px; - border-bottom: 1px solid black; } \ No newline at end of file diff --git a/src/sass/_commons.scss b/src/sass/_commons.scss index 4bd0c225..8e9eb0f2 100644 --- a/src/sass/_commons.scss +++ b/src/sass/_commons.scss @@ -2,4 +2,24 @@ width: 40px; display: block; margin: 5px auto; +} + +.flexcroll { + scrollbar-face-color: #08090d; + scrollbar-shadow-color: #08090d; + scrollbar-highlight-color: #08090d; + scrollbar-3dlight-color: #08090d; + 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; + // background: #08090d; + background: lighten($color-primary, 5); + // -webkit-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.5); + } } \ No newline at end of file From 801658bfbc4c615e5ae71a651095938b2d55fd62 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 1 Nov 2018 18:38:59 -0400 Subject: [PATCH 13/25] fix hashtag parsing --- .../databinded-text/databinded-text.component.spec.ts | 7 +++---- .../databinded-text/databinded-text.component.ts | 11 +++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts index 95c71731..7797d62e 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts @@ -36,7 +36,7 @@ describe('DatabindedTextComponent', () => { const url = 'https://test.social/tags/programmers'; const sample = `

bla1 #${hashtag} bla2

`; component.text = sample; - expect(component.processedText).toContain('#programmers'); + expect(component.processedText).toContain('#programmers'); expect(component.processedText).toContain('bla1'); expect(component.processedText).toContain('bla2'); }); @@ -75,7 +75,7 @@ describe('DatabindedTextComponent', () => { const linkUrl = 'mydomain.co/test'; const sample = `

bla1 #${hashtag} bla2 @${mention} bla3 ${linkUrl} bla4

`; component.text = sample; - expect(component.processedText).toContain('#programmers'); + expect(component.processedText).toContain('#programmers'); expect(component.processedText).toContain(''); expect(component.processedText).toContain('mydomain.co/test'); expect(component.processedText).toContain('bla1'); @@ -97,6 +97,5 @@ describe('DatabindedTextComponent', () => { component.text = sample; expect(component.processedText).toContain(''); - }); - + }); }); diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.ts index 73d7ce8d..67fb060a 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.ts +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.ts @@ -64,7 +64,8 @@ export class DatabindedTextComponent implements OnInit { let extractedLinkAndNext = section.split(''); let extractedHashtag = extractedLinkAndNext[0].split('#')[1].replace('', '').replace('', ''); - this.processedText += ` #${extractedHashtag}`; + let classname = this.getClassNameForHastag(extractedHashtag); + this.processedText += ` #${extractedHashtag}`; if (extractedLinkAndNext[1]) this.processedText += extractedLinkAndNext[1]; this.hashtags.push(extractedHashtag); } @@ -124,7 +125,8 @@ export class DatabindedTextComponent implements OnInit { ngAfterViewInit() { for (const hashtag of this.hashtags) { - let el = this.contentElement.nativeElement.querySelector(`.${hashtag}`); + let classname = this.getClassNameForHastag(hashtag); + let el = this.contentElement.nativeElement.querySelector(`.${classname}`); this.renderer.listen(el, 'click', (event) => { event.preventDefault(); @@ -161,6 +163,11 @@ export class DatabindedTextComponent implements OnInit { } } + 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('.', '-'); From 2ca7c0c226be73865988e550b81b309bc1da6ba0 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 1 Nov 2018 18:51:28 -0400 Subject: [PATCH 14/25] fix link parsing when having special characters --- .../databinded-text/databinded-text.component.spec.ts | 11 ++++++++++- .../databinded-text/databinded-text.component.ts | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts index 7797d62e..a27ebcf0 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts @@ -67,6 +67,13 @@ describe('DatabindedTextComponent', () => { expect(component.processedText).toContain('

Test.
peertube.fr/videos/watch/69bb6

'); }); + it('should parse link with special character', () => { + const sample = `

Magnitude: 2.5 Depth: 3.4 km
Details: 2018/09/27 06:50:17 34.968N 120.685W
Location: 10 km (6 mi) W of Guadalupe, CA
Map: google.com/maps/place/34°58'4%
#EarthQuake #Quake #California

`; + + component.text = sample; + expect(component.processedText).toContain('google.com/maps/place/34°58\'4%'); + }); + it('should parse combined hashtag, mention and link', () => { const hashtag = 'programmers'; const hashtagUrl = 'https://test.social/tags/programmers'; @@ -92,10 +99,12 @@ describe('DatabindedTextComponent', () => { expect(component.processedText).toContain('bla1'); }); - it('shoult parse mention - Pleroma in Mastodon', () => { + it('should parse mention - Pleroma in Mastodon', () => { const sample = ``; component.text = sample; expect(component.processedText).toContain(''); }); + + }); diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.ts index 67fb060a..3b6d6b8f 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.ts +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.ts @@ -176,7 +176,7 @@ export class DatabindedTextComponent implements OnInit { } private getClassNameForLink(value: string): string { - let res = value.replace(/[.,\/#?!@$%+\^&\*;:{}=\-_`~()]/g, ""); + let res = value.replace(/[.,\/#?!@°$%+\'\^&\*;:{}=\-_`~()]/g, ""); return `link-${res}`; } From cefb6d76fa148d7aebf8c6f2dc3444fc8162554f Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 1 Nov 2018 19:36:52 -0400 Subject: [PATCH 15/25] refactorisation of the profile loading --- .../stream/hashtag/hashtag.component.ts | 23 ++++-- .../stream-overlay.component.html | 8 +- .../stream-overlay.component.ts | 63 ++++------------ .../components/stream/stream.component.html | 6 +- src/app/components/stream/stream.component.ts | 10 ++- .../user-profile/user-profile.component.html | 6 +- .../user-profile/user-profile.component.scss | 13 +--- .../user-profile/user-profile.component.ts | 75 +++++++++++++++---- 8 files changed, 108 insertions(+), 96 deletions(-) diff --git a/src/app/components/stream/hashtag/hashtag.component.ts b/src/app/components/stream/hashtag/hashtag.component.ts index 3cb9cf38..8de00dde 100644 --- a/src/app/components/stream/hashtag/hashtag.component.ts +++ b/src/app/components/stream/hashtag/hashtag.component.ts @@ -1,15 +1,24 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Output, EventEmitter, Input } from '@angular/core'; @Component({ - selector: 'app-hashtag', - templateUrl: './hashtag.component.html', - styleUrls: ['./hashtag.component.scss'] + selector: 'app-hashtag', + templateUrl: './hashtag.component.html', + styleUrls: ['./hashtag.component.scss'] }) export class HashtagComponent implements OnInit { + hashtag: string; - constructor() { } + @Output() browseAccount = new EventEmitter(); + @Output() browseHashtag = new EventEmitter(); - ngOnInit() { - } + @Input('currentHashtag') + set currentAccount(hashtag: string) { + this.hashtag = hashtag; + } + + constructor() { } + + ngOnInit() { + } } diff --git a/src/app/components/stream/stream-overlay/stream-overlay.component.html b/src/app/components/stream/stream-overlay/stream-overlay.component.html index 5c108a57..b46a84b0 100644 --- a/src/app/components/stream/stream-overlay/stream-overlay.component.html +++ b/src/app/components/stream/stream-overlay/stream-overlay.component.html @@ -2,16 +2,14 @@ - - - +
\ No newline at end of file diff --git a/src/app/components/stream/stream-overlay/stream-overlay.component.ts b/src/app/components/stream/stream-overlay/stream-overlay.component.ts index 099b14b8..60e22363 100644 --- a/src/app/components/stream/stream-overlay/stream-overlay.component.ts +++ b/src/app/components/stream/stream-overlay/stream-overlay.component.ts @@ -12,11 +12,10 @@ export class StreamOverlayComponent implements OnInit { canRefresh: boolean; canGoForward: boolean; - account: Account; - private accountName: string; - + // account: Account; + accountName: string; private thread: string; - private hashtag: string; + hashtag: string; error: string; isLoading: boolean; @@ -24,42 +23,30 @@ export class StreamOverlayComponent implements OnInit { @Output() closeOverlay = new EventEmitter(); @Input('browseAccount') - set browseAccount(accountName: string) { - - this.loadAccount(accountName); - - // let selectedAccounts = this.toolsService.getSelectedAccounts(); - // if(selectedAccounts.length === 0) - // this.error = 'no user selected'; - - - // this.account = account; + set browseAccount(accountName: string) { + // console.warn(`browseAccount ${accountName}`); + this.accountName = accountName; + // if(accountName) this.loadAccount(accountName); } - // get browseAccount(): string{ - // return this.account; - // } @Input('browseThread') set browseThread(thread: string) { + // console.warn(`browseThread ${thread}`); this.thread = thread; } - get browseThread(): string { - return this.thread; - } @Input('browseHashtag') set browseHashtag(hashtag: string) { + // console.warn(`browseHashtag ${hashtag}`); this.hashtag = hashtag; - } - get browseHashtag(): string { - return this.hashtag; - } + } constructor( private readonly toolsService: ToolsService, private readonly mastodonService: MastodonService) { } ngOnInit() { + console.warn('ngOnInit stream-overlay'); } close(): boolean { @@ -80,32 +67,10 @@ export class StreamOverlayComponent implements OnInit { } accountSelected(accountName: string): void { - this.loadAccount(accountName); + this.accountName = accountName; + // this.loadAccount(accountName); } hashtagSelected(hashtag: string): void { - } - - private loadAccount(accountName: string): void { - this.account = null; - this.accountName = accountName; - let selectedAccounts = this.toolsService.getSelectedAccounts(); - - if (selectedAccounts.length === 0) { - this.error = 'no user selected'; - return; - } - - this.isLoading = true; - this.mastodonService.search(selectedAccounts[0], accountName, true) - .then((result: Results) => { - this.account = result.accounts[0]; - }) - .catch((err) => { - this.error = 'Error when retieving account'; - }) - .then(()=>{ - this.isLoading = false; - }); - } + } } diff --git a/src/app/components/stream/stream.component.html b/src/app/components/stream/stream.component.html index f08439cc..a9f2099e 100644 --- a/src/app/components/stream/stream.component.html +++ b/src/app/components/stream/stream.component.html @@ -1,7 +1,9 @@
+ (closeOverlay)="closeOverlay()" + [browseAccount]="overlayAccountToBrowse" + [browseHashtag]="overlayHashtagToBrowse"> \ No newline at end of file diff --git a/src/app/components/stream/stream.component.ts b/src/app/components/stream/stream.component.ts index 37ad7aa7..f7e23fe1 100644 --- a/src/app/components/stream/stream.component.ts +++ b/src/app/components/stream/stream.component.ts @@ -23,6 +23,7 @@ export class StreamComponent implements OnInit { overlayActive: boolean; overlayAccountToBrowse: string; + overlayHashtagToBrowse: string; @Input() set streamElement(streamElement: StreamElement) { @@ -52,12 +53,15 @@ export class StreamComponent implements OnInit { browseAccount(account: string): void { this.overlayAccountToBrowse = account; - this.overlayActive = true; + this.overlayHashtagToBrowse = null; + this.overlayActive = true; } browseHashtag(hashtag: string): void { - console.warn('browseHashtag'); - console.warn(hashtag); + console.warn(`browseHashtag ${hashtag}`); + this.overlayAccountToBrowse = null; + this.overlayHashtagToBrowse = hashtag; + this.overlayActive = true; } browseThread(thread: string): void { diff --git a/src/app/components/stream/user-profile/user-profile.component.html b/src/app/components/stream/user-profile/user-profile.component.html index 252f5a55..ee7816e2 100644 --- a/src/app/components/stream/user-profile/user-profile.component.html +++ b/src/app/components/stream/user-profile/user-profile.component.html @@ -1,5 +1,7 @@
-
+ + +
header @@ -7,7 +9,7 @@

@{{account.acct}}

-
+
diff --git a/src/app/components/stream/user-profile/user-profile.component.scss b/src/app/components/stream/user-profile/user-profile.component.scss index 96fae266..1035b840 100644 --- a/src/app/components/stream/user-profile/user-profile.component.scss +++ b/src/app/components/stream/user-profile/user-profile.component.scss @@ -16,23 +16,12 @@ height: 160px; background-color: rgba(0, 0, 0, .45); } - - // &__header { - // position: absolute; - // // width: calc(100%); - // width: calc(100%); - // height: auto; - // float: left; - // display: block; - // opacity: 0.3; - // } &__avatar { position: absolute; top: 15px; left: 15px; width: 80px; - border-radius: 50%; // border: 1px solid black; - // background-color: black; + border-radius: 50%; } &__display-name { position: absolute; diff --git a/src/app/components/stream/user-profile/user-profile.component.ts b/src/app/components/stream/user-profile/user-profile.component.ts index 2b9a8cbe..273d7c47 100644 --- a/src/app/components/stream/user-profile/user-profile.component.ts +++ b/src/app/components/stream/user-profile/user-profile.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; -import { Account, Status } from "../../../services/models/mastodon.interfaces"; +import { Account, Status, Results } from "../../../services/models/mastodon.interfaces"; import { MastodonService } from '../../../services/mastodon.service'; import { ToolsService } from '../../../services/tools.service'; import { StatusWrapper } from '../stream.component'; @@ -13,19 +13,37 @@ export class UserProfileComponent implements OnInit { account: Account; hasNote: boolean; + isLoading: boolean; statusLoading: boolean; - statuses: StatusWrapper[] = []; + error: string; + + statuses: StatusWrapper[] = []; + + private accountName: string; @Output() browseAccount = new EventEmitter(); @Output() browseHashtag = new EventEmitter(); @Input('currentAccount') - set currentAccount(account: Account) { - this.account = account; - this.hasNote = account && account.note && account.note !== '

'; - console.warn('currentAccount'); - console.warn(account); - this.getStatuses(account); + //set currentAccount(account: Account) { + set currentAccount(accountName: string) { + + this.loadAccount(accountName) + .then((account: Account) => { + this.account = account; + return this.getStatuses(this.account); + }) + .catch(err => { + this.error = 'Error when retieving account'; + this.isLoading = false; + this.statusLoading = false; + }); + + // this.account = account; + // this.hasNote = account && account.note && account.note !== '

'; + // console.warn('currentAccount'); + // console.warn(account); + // this.getStatuses(account); } constructor( @@ -43,23 +61,48 @@ export class UserProfileComponent implements OnInit { this.browseHashtag.next(hashtag); } - private getStatuses(account: Account): void { + private loadAccount(accountName: string): Promise { + this.account = null; + this.accountName = accountName; + let selectedAccounts = this.toolsService.getSelectedAccounts(); + + if (selectedAccounts.length === 0) { + this.error = 'no user selected'; + return; + } + + this.isLoading = true; + return this.mastodonService.search(selectedAccounts[0], accountName, true) + .then((result: Results) => { + this.isLoading = false; + return result.accounts[0]; + }); + // .catch((err) => { + // this.error = 'Error when retieving account'; + // }) + // .then(()=>{ + // this.isLoading = false; + // }); + } + + private getStatuses(account: Account): Promise { let selectedAccounts = this.toolsService.getSelectedAccounts(); if (selectedAccounts.length === 0) return; this.statusLoading = true; - this.mastodonService.getAccountStatuses(selectedAccounts[0], account.id, false, false, true, null, null, 40) + return this.mastodonService.getAccountStatuses(selectedAccounts[0], account.id, false, false, true, null, null, 40) .then((result: Status[]) => { for (const status of result) { - const wrapper = new StatusWrapper(status,selectedAccounts[0]); + const wrapper = new StatusWrapper(status, selectedAccounts[0]); this.statuses.push(wrapper); } - }) - .catch(err => { - - }) - .then(() => { this.statusLoading = false; }); + // .catch(err => { + + // }) + // .then(() => { + // this.statusLoading = false; + // }); } } From dffa15cfd9edd0d2ee18ba8c0d3c02e672c04cf4 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 1 Nov 2018 20:22:43 -0400 Subject: [PATCH 16/25] overlay navigation is now working --- .../stream-overlay.component.ts | 123 ++++++++++++++---- .../user-profile/user-profile.component.html | 2 +- .../user-profile/user-profile.component.ts | 3 + 3 files changed, 105 insertions(+), 23 deletions(-) diff --git a/src/app/components/stream/stream-overlay/stream-overlay.component.ts b/src/app/components/stream/stream-overlay/stream-overlay.component.ts index 60e22363..e607c6f8 100644 --- a/src/app/components/stream/stream-overlay/stream-overlay.component.ts +++ b/src/app/components/stream/stream-overlay/stream-overlay.component.ts @@ -9,68 +9,147 @@ import { ToolsService } from '../../../services/tools.service'; styleUrls: ['./stream-overlay.component.scss'] }) export class StreamOverlayComponent implements OnInit { + private previousElements: OverlayBrowsing[] = []; + private nextElements: OverlayBrowsing[] = []; + private currentElement: OverlayBrowsing; + canRefresh: boolean; canGoForward: boolean; - // account: Account; accountName: string; - private thread: string; + thread: string; hashtag: string; - error: string; - isLoading: boolean; - @Output() closeOverlay = new EventEmitter(); @Input('browseAccount') - set browseAccount(accountName: string) { - // console.warn(`browseAccount ${accountName}`); - this.accountName = accountName; - // if(accountName) this.loadAccount(accountName); + set browseAccount(accountName: string) { + this.accountSelected(accountName); + // this.accountName = accountName; } @Input('browseThread') set browseThread(thread: string) { - // console.warn(`browseThread ${thread}`); - this.thread = thread; + // this.thread = thread; } @Input('browseHashtag') set browseHashtag(hashtag: string) { - // console.warn(`browseHashtag ${hashtag}`); - this.hashtag = hashtag; - } + this.hashtagSelected(hashtag); + // this.hashtag = hashtag; + } - constructor( - private readonly toolsService: ToolsService, - private readonly mastodonService: MastodonService) { } + constructor() { } ngOnInit() { - console.warn('ngOnInit stream-overlay'); } close(): boolean { this.closeOverlay.next(); return false; } - + next(): boolean { + console.log('next'); + + if (this.nextElements.length === 0) { + return false; + } + + if (this.currentElement) { + this.previousElements.push(this.currentElement); + } + + const nextElement = this.nextElements.pop(); + this.loadElement(nextElement); + return false; } previous(): boolean { + console.log('previous'); + + if (this.previousElements.length === 0) { + this.closeOverlay.next(); + return false; + } + + if (this.currentElement) { + this.nextElements.push(this.currentElement); + } + + const previousElement = this.previousElements.pop(); + this.loadElement(previousElement); + + this.canGoForward = true; return false; } refresh(): boolean { + console.log('refresh'); return false; } accountSelected(accountName: string): void { - this.accountName = accountName; - // this.loadAccount(accountName); + if(!accountName) return; + + console.log('accountSelected'); + 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; } hashtagSelected(hashtag: string): void { - } + if(!hashtag) return; + + console.log('hashtagSelected'); + this.nextElements.length = 0; + if (this.currentElement) { + this.previousElements.push(this.currentElement); + } + const newElement = new OverlayBrowsing(hashtag, null, null); + this.loadElement(newElement); + this.canGoForward = false; + } + + private loadElement(element: OverlayBrowsing) { + this.currentElement = element; + + this.accountName = this.currentElement.account; + this.hashtag = this.currentElement.hashtag; + this.thread = this.currentElement.thread; + } +} + +class OverlayBrowsing { + constructor( + public readonly hashtag: string, + public readonly account: string, + public readonly thread: string) { + + console.warn(`OverlayBrowsing: ${hashtag} ${account} ${thread}`); + + if (hashtag) { + this.type = OverlayEnum.hashtag; + } else if (account) { + this.type = OverlayEnum.account; + } else if (thread) { + this.type = OverlayEnum.thread; + } else { + throw Error('NotImplemented'); + } + } + + type: OverlayEnum; +} + +enum OverlayEnum { + unknown = 0, + hashtag = 1, + account = 2, + thread = 3 } diff --git a/src/app/components/stream/user-profile/user-profile.component.html b/src/app/components/stream/user-profile/user-profile.component.html index ee7816e2..3e3770b5 100644 --- a/src/app/components/stream/user-profile/user-profile.component.html +++ b/src/app/components/stream/user-profile/user-profile.component.html @@ -18,7 +18,7 @@
- +
\ No newline at end of file diff --git a/src/app/components/stream/user-profile/user-profile.component.ts b/src/app/components/stream/user-profile/user-profile.component.ts index 273d7c47..67ee1a2e 100644 --- a/src/app/components/stream/user-profile/user-profile.component.ts +++ b/src/app/components/stream/user-profile/user-profile.component.ts @@ -27,6 +27,9 @@ export class UserProfileComponent implements OnInit { @Input('currentAccount') //set currentAccount(account: Account) { set currentAccount(accountName: string) { + this.statuses.length = 0; + this.isLoading = true; + this.statusLoading = true; this.loadAccount(accountName) .then((account: Account) => { From 6494ce59074c5b3c9131488c0289f5f31a89b8e3 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 1 Nov 2018 20:45:18 -0400 Subject: [PATCH 17/25] better parsing of mentions from GNU social in Mastodon --- .../databinded-text.component.spec.ts | 10 +++++- .../databinded-text.component.ts | 31 ++++++++++++------- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts index a27ebcf0..aceec4e8 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts @@ -99,11 +99,19 @@ describe('DatabindedTextComponent', () => { expect(component.processedText).toContain('bla1'); }); + it('should parse mention - GNU social in Mastodon', () => { + const sample = `
`; + + component.text = sample; + expect(component.processedText).toContain(''); + expect(component.processedText).toContain('bla1'); + }) + it('should parse mention - Pleroma in Mastodon', () => { const sample = ``; component.text = sample; - expect(component.processedText).toContain(''); + expect(component.processedText).toContain(''); }); diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.ts index 3b6d6b8f..b2eb73ce 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.ts +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.ts @@ -40,7 +40,7 @@ export class DatabindedTextComponent implements OnInit { console.warn(value); } - } else if (section.includes('class="u-url mention"') || section.includes('class="mention"') || section.includes('class="mention status-link"')) { + } else if (section.includes('class="u-url mention"') || section.includes('class="mention"') || section.includes('class="mention status-link"') || section.includes('class="h-card mention status-link"')) { try { this.processUser(section); } @@ -71,25 +71,34 @@ export class DatabindedTextComponent implements OnInit { } private processUser(section: string) { - // let mentionClass = 'class="mention"'; - // if (section.includes('class="u-url mention"')) - // mentionClass = 'class="u-url mention"'; + let extractedAccountAndNext: string[]; + let extractedAccountName: string; - let extractedAccountAndNext = section.split(''); - - let extractedAccountName = extractedAccountAndNext[0].split('@')[1].replace('', '').replace('', ''); + 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 username = extractedAccountLink[extractedAccountLink.length - 1]; - let extractedAccount = `@${username}@${domain}`; + let extractedAccount = `@${extractedAccountName}@${domain}`; let classname = this.getClassNameForAccount(extractedAccount); - this.processedText += ` @${extractedAccountName}`; + this.processedText += `@${extractedAccountName}`; + + if (extractedAccountAndNext[1]) + this.processedText += extractedAccountAndNext[1]; + + //GNU Social clean up + if(this.processedText.includes('@ Date: Thu, 1 Nov 2018 21:00:41 -0400 Subject: [PATCH 18/25] better mention detection in pleroma via mastodon --- .../databinded-text/databinded-text.component.spec.ts | 8 +++----- .../status/databinded-text/databinded-text.component.ts | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts index aceec4e8..5ccac522 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts @@ -99,7 +99,7 @@ describe('DatabindedTextComponent', () => { expect(component.processedText).toContain('bla1'); }); - it('should parse mention - GNU social in Mastodon', () => { + it('should parse mention - Pleroma in Mastodon', () => { const sample = `
bla1
@user 
`; component.text = sample; @@ -107,12 +107,10 @@ describe('DatabindedTextComponent', () => { expect(component.processedText).toContain('bla1'); }) - it('should parse mention - Pleroma in Mastodon', () => { + it('should parse mention - Pleroma in Mastodon - 2', () => { const sample = ``; component.text = sample; expect(component.processedText).toContain(''); - }); - - + }); }); diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.ts index b2eb73ce..4e7977d3 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.ts +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.ts @@ -40,7 +40,7 @@ export class DatabindedTextComponent implements OnInit { console.warn(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 status-link"')) { + } 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); } From 6af023fbbd2a3e5e34d22a1849b16b106b6ccfb3 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 1 Nov 2018 22:34:26 -0400 Subject: [PATCH 19/25] creation of a pipe for displaying the time --- src/app/app.module.ts | 4 +- .../stream/status/status.component.html | 2 +- .../user-profile/user-profile.component.ts | 3 +- src/app/pipes/mastodon-time.pipe.spec.ts | 8 ++++ src/app/pipes/mastodon-time.pipe.ts | 48 +++++++++++++++++++ 5 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 src/app/pipes/mastodon-time.pipe.spec.ts create mode 100644 src/app/pipes/mastodon-time.pipe.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8bc070c6..1cb8f4ab 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -40,6 +40,7 @@ import { ThreadComponent } from './components/stream/thread/thread.component'; import { HashtagComponent } from './components/stream/hashtag/hashtag.component'; import { StreamOverlayComponent } from './components/stream/stream-overlay/stream-overlay.component'; import { DatabindedTextComponent } from './components/stream/status/databinded-text/databinded-text.component'; +import { MastodonTimePipe } from './pipes/mastodon-time.pipe'; const routes: Routes = [ { path: "", redirectTo: "home", pathMatch: "full" }, @@ -72,7 +73,8 @@ const routes: Routes = [ ThreadComponent, HashtagComponent, StreamOverlayComponent, - DatabindedTextComponent + DatabindedTextComponent, + MastodonTimePipe ], imports: [ BrowserModule, diff --git a/src/app/components/stream/status/status.component.html b/src/app/components/stream/status/status.component.html index 786b4626..c3a9e341 100644 --- a/src/app/components/stream/status/status.component.html +++ b/src/app/components/stream/status/status.component.html @@ -13,7 +13,7 @@
{{ - getCompactRelativeTime(status.created_at) }}
+ status.created_at | mastodontime }}
diff --git a/src/app/components/stream/user-profile/user-profile.component.ts b/src/app/components/stream/user-profile/user-profile.component.ts index 67ee1a2e..64fb26a5 100644 --- a/src/app/components/stream/user-profile/user-profile.component.ts +++ b/src/app/components/stream/user-profile/user-profile.component.ts @@ -28,8 +28,7 @@ export class UserProfileComponent implements OnInit { //set currentAccount(account: Account) { set currentAccount(accountName: string) { this.statuses.length = 0; - this.isLoading = true; - this.statusLoading = true; + this.isLoading = true; this.loadAccount(accountName) .then((account: Account) => { diff --git a/src/app/pipes/mastodon-time.pipe.spec.ts b/src/app/pipes/mastodon-time.pipe.spec.ts new file mode 100644 index 00000000..312c4165 --- /dev/null +++ b/src/app/pipes/mastodon-time.pipe.spec.ts @@ -0,0 +1,8 @@ +import { MastodonTimePipe } from './mastodon-time.pipe'; + +xdescribe('MastodonTimePipe', () => { +// it('create an instance', () => { +// const pipe = new MastodonTimePipe(); +// expect(pipe).toBeTruthy(); +// }); +}); diff --git a/src/app/pipes/mastodon-time.pipe.ts b/src/app/pipes/mastodon-time.pipe.ts new file mode 100644 index 00000000..44c7b90d --- /dev/null +++ b/src/app/pipes/mastodon-time.pipe.ts @@ -0,0 +1,48 @@ +import { Pipe, PipeTransform, Inject, LOCALE_ID } from '@angular/core'; +import { formatDate } from '@angular/common'; + +@Pipe({ + name: 'mastodontime', + pure: false +}) +export class MastodonTimePipe implements PipeTransform { + + constructor(@Inject(LOCALE_ID) private locale: string) { } + + // private cachedDict: { [id:string] : string } = {}; + // private cached: string; + // private resultCached: string; + + transform(value: string): string { + // if (value == this.cached && this.resultCached) { + // return this.resultCached; + // } + + // if(this.cachedDict[value]) { + // return this.cachedDict[value]; + // } + + const date = (new Date(value)).getTime(); + const now = Date.now(); + const timeDelta = (now - date) / (1000); + + if (timeDelta < 60) { + return `${timeDelta | 0}s`; + } else if (timeDelta < 60 * 60) { + return `${timeDelta / 60 | 0}m`; + } else if (timeDelta < 60 * 60 * 24) { + return `${timeDelta / (60 * 60) | 0}h`; + } else if (timeDelta < 60 * 60 * 24 * 31) { + return `${timeDelta / (60 * 60 * 24) | 0}d`; + } + + return formatDate(date, 'MM/dd', this.locale); + + // this.cachedDict[value] = formatDate(date, 'MM/dd', this.locale); + + // // this.cached = value; + // // this.resultCached = formatDate(date, 'MM/dd', this.locale); + + // return this.cachedDict[value]; + } +} From b876728dbec98cb6f4f712862ec0624b5694e571 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 1 Nov 2018 22:35:07 -0400 Subject: [PATCH 20/25] clean up since the pipe is now handling that --- .../stream/status/status.component.ts | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/app/components/stream/status/status.component.ts b/src/app/components/stream/status/status.component.ts index 5a000671..47c99fc2 100644 --- a/src/app/components/stream/status/status.component.ts +++ b/src/app/components/stream/status/status.component.ts @@ -80,23 +80,23 @@ export class StatusComponent implements OnInit { return false; } - getCompactRelativeTime(d: string): string { - const date = (new Date(d)).getTime(); - const now = Date.now(); - const timeDelta = (now - date) / (1000); + // getCompactRelativeTime(d: string): string { + // const date = (new Date(d)).getTime(); + // const now = Date.now(); + // const timeDelta = (now - date) / (1000); - if (timeDelta < 60) { - return `${timeDelta | 0}s`; - } else if (timeDelta < 60 * 60) { - return `${timeDelta / 60 | 0}m`; - } else if (timeDelta < 60 * 60 * 24) { - return `${timeDelta / (60 * 60) | 0}h`; - } else if (timeDelta < 60 * 60 * 24 * 31) { - return `${timeDelta / (60 * 60 * 24) | 0}d`; - } + // if (timeDelta < 60) { + // return `${timeDelta | 0}s`; + // } else if (timeDelta < 60 * 60) { + // return `${timeDelta / 60 | 0}m`; + // } else if (timeDelta < 60 * 60 * 24) { + // return `${timeDelta / (60 * 60) | 0}h`; + // } else if (timeDelta < 60 * 60 * 24 * 31) { + // return `${timeDelta / (60 * 60 * 24) | 0}d`; + // } - return formatDate(date, 'MM/dd', this.locale); - } + // return formatDate(date, 'MM/dd', this.locale); + // } openReply(): boolean { this.replyingToStatus = !this.replyingToStatus; From 7cb5b3f226407346f943a5f18566ce4a50e476dc Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 1 Nov 2018 22:58:43 -0400 Subject: [PATCH 21/25] better time pipe --- src/app/app.module.ts | 4 +- .../stream/status/status.component.html | 2 +- src/app/pipes/time-ago.pipe.spec.ts | 8 ++ src/app/pipes/time-ago.pipe.ts | 104 ++++++++++++++++++ 4 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 src/app/pipes/time-ago.pipe.spec.ts create mode 100644 src/app/pipes/time-ago.pipe.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 1cb8f4ab..0995eaa6 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -41,6 +41,7 @@ import { HashtagComponent } from './components/stream/hashtag/hashtag.component' import { StreamOverlayComponent } from './components/stream/stream-overlay/stream-overlay.component'; import { DatabindedTextComponent } from './components/stream/status/databinded-text/databinded-text.component'; import { MastodonTimePipe } from './pipes/mastodon-time.pipe'; +import { TimeAgoPipe } from './pipes/time-ago.pipe'; const routes: Routes = [ { path: "", redirectTo: "home", pathMatch: "full" }, @@ -74,7 +75,8 @@ const routes: Routes = [ HashtagComponent, StreamOverlayComponent, DatabindedTextComponent, - MastodonTimePipe + MastodonTimePipe, + TimeAgoPipe ], imports: [ BrowserModule, diff --git a/src/app/components/stream/status/status.component.html b/src/app/components/stream/status/status.component.html index c3a9e341..9161e98c 100644 --- a/src/app/components/stream/status/status.component.html +++ b/src/app/components/stream/status/status.component.html @@ -13,7 +13,7 @@
{{ - status.created_at | mastodontime }}
+ status.created_at | timeAgo | async }}
diff --git a/src/app/pipes/time-ago.pipe.spec.ts b/src/app/pipes/time-ago.pipe.spec.ts new file mode 100644 index 00000000..c3d7df28 --- /dev/null +++ b/src/app/pipes/time-ago.pipe.spec.ts @@ -0,0 +1,8 @@ +import { TimeAgoPipe } from './time-ago.pipe'; + +describe('TimeAgoPipe', () => { + it('create an instance', () => { + const pipe = new TimeAgoPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/src/app/pipes/time-ago.pipe.ts b/src/app/pipes/time-ago.pipe.ts new file mode 100644 index 00000000..bb7f0a9d --- /dev/null +++ b/src/app/pipes/time-ago.pipe.ts @@ -0,0 +1,104 @@ +//https://github.com/AndrewPoyntz/time-ago-pipe/issues/6#issuecomment-313726956 + +import { Pipe, PipeTransform, NgZone } from "@angular/core"; +import { Observable, Observer } from 'rxjs'; + +interface processOutput { + text: string; // Convert timestamp to string + timeToUpdate: number; // Time until update in milliseconds +} + +@Pipe({ + name: 'timeAgo', + pure: true +}) +export class TimeAgoPipe implements PipeTransform { + + constructor(private ngZone: NgZone) { } + + private process = (timestamp: number): processOutput => { + let text: string; + let timeToUpdate: number; + + const now = new Date(); + + // Time ago in milliseconds + const timeAgo: number = now.getTime() - timestamp; + + const seconds = timeAgo / 1000; + const minutes = seconds / 60; + const hours = minutes / 60; + const days = hours / 24; + // const months = days / 30.416; + // const years = days / 365; + + if (seconds <= 60) { + text = Math.round(seconds) + 's'; + } else if (minutes <= 90) { + text = Math.round(minutes) + 'm'; + } else if (hours <= 24) { + text = Math.round(hours) + 'h'; + } else { + text = Math.round(days) + 'd'; + } + + if (minutes < 1) { + // update every 2 secs + timeToUpdate = 2 * 1000; + } else if (hours < 1) { + // update every 30 secs + timeToUpdate = 30 * 1000; + } else if (days < 1) { + // update every 5 mins + timeToUpdate = 300 * 1000; + } else { + // update every hour + timeToUpdate = 3600 * 1000; + } + + return { + text, + timeToUpdate + }; + } + + public transform = (value: string | Date): Observable => { + let d: Date; + if (value instanceof Date) { + d = value; + } + else { + d = new Date(value); + } + // time value in milliseconds + const timestamp = d.getTime(); + + let timeoutID: any; + + return Observable.create((observer: Observer) => { + let latestText = ''; + + // Repeatedly set new timeouts for new update checks. + const registerUpdate = () => { + const processOutput = this.process(timestamp); + if (processOutput.text !== latestText) { + latestText = processOutput.text; + this.ngZone.run(() => { + observer.next(latestText); + }); + } + timeoutID = setTimeout(registerUpdate, processOutput.timeToUpdate); + }; + + this.ngZone.runOutsideAngular(registerUpdate); + + // Return teardown function + const teardownFunction = () => { + if (timeoutID) { + clearTimeout(timeoutID); + } + }; + return teardownFunction; + }); + } +} \ No newline at end of file From 24be608434d9fbce88f88afb5fa030490b0a5ac5 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 1 Nov 2018 22:59:44 -0400 Subject: [PATCH 22/25] clean up --- src/app/app.module.ts | 2 - src/app/pipes/mastodon-time.pipe.spec.ts | 8 ---- src/app/pipes/mastodon-time.pipe.ts | 48 ------------------------ 3 files changed, 58 deletions(-) delete mode 100644 src/app/pipes/mastodon-time.pipe.spec.ts delete mode 100644 src/app/pipes/mastodon-time.pipe.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 0995eaa6..50e84682 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -40,7 +40,6 @@ import { ThreadComponent } from './components/stream/thread/thread.component'; import { HashtagComponent } from './components/stream/hashtag/hashtag.component'; import { StreamOverlayComponent } from './components/stream/stream-overlay/stream-overlay.component'; import { DatabindedTextComponent } from './components/stream/status/databinded-text/databinded-text.component'; -import { MastodonTimePipe } from './pipes/mastodon-time.pipe'; import { TimeAgoPipe } from './pipes/time-ago.pipe'; const routes: Routes = [ @@ -75,7 +74,6 @@ const routes: Routes = [ HashtagComponent, StreamOverlayComponent, DatabindedTextComponent, - MastodonTimePipe, TimeAgoPipe ], imports: [ diff --git a/src/app/pipes/mastodon-time.pipe.spec.ts b/src/app/pipes/mastodon-time.pipe.spec.ts deleted file mode 100644 index 312c4165..00000000 --- a/src/app/pipes/mastodon-time.pipe.spec.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { MastodonTimePipe } from './mastodon-time.pipe'; - -xdescribe('MastodonTimePipe', () => { -// it('create an instance', () => { -// const pipe = new MastodonTimePipe(); -// expect(pipe).toBeTruthy(); -// }); -}); diff --git a/src/app/pipes/mastodon-time.pipe.ts b/src/app/pipes/mastodon-time.pipe.ts deleted file mode 100644 index 44c7b90d..00000000 --- a/src/app/pipes/mastodon-time.pipe.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { Pipe, PipeTransform, Inject, LOCALE_ID } from '@angular/core'; -import { formatDate } from '@angular/common'; - -@Pipe({ - name: 'mastodontime', - pure: false -}) -export class MastodonTimePipe implements PipeTransform { - - constructor(@Inject(LOCALE_ID) private locale: string) { } - - // private cachedDict: { [id:string] : string } = {}; - // private cached: string; - // private resultCached: string; - - transform(value: string): string { - // if (value == this.cached && this.resultCached) { - // return this.resultCached; - // } - - // if(this.cachedDict[value]) { - // return this.cachedDict[value]; - // } - - const date = (new Date(value)).getTime(); - const now = Date.now(); - const timeDelta = (now - date) / (1000); - - if (timeDelta < 60) { - return `${timeDelta | 0}s`; - } else if (timeDelta < 60 * 60) { - return `${timeDelta / 60 | 0}m`; - } else if (timeDelta < 60 * 60 * 24) { - return `${timeDelta / (60 * 60) | 0}h`; - } else if (timeDelta < 60 * 60 * 24 * 31) { - return `${timeDelta / (60 * 60 * 24) | 0}d`; - } - - return formatDate(date, 'MM/dd', this.locale); - - // this.cachedDict[value] = formatDate(date, 'MM/dd', this.locale); - - // // this.cached = value; - // // this.resultCached = formatDate(date, 'MM/dd', this.locale); - - // return this.cachedDict[value]; - } -} From 6628bd5e6f453408ec21aadae1170431ef6948cd Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 1 Nov 2018 23:49:00 -0400 Subject: [PATCH 23/25] disable (temporary) retrying websocket when failed --- .../user-profile/user-profile.component.ts | 15 +++---- src/app/services/streaming.service.ts | 42 +++++++++++++++---- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/src/app/components/stream/user-profile/user-profile.component.ts b/src/app/components/stream/user-profile/user-profile.component.ts index 64fb26a5..70d07d8a 100644 --- a/src/app/components/stream/user-profile/user-profile.component.ts +++ b/src/app/components/stream/user-profile/user-profile.component.ts @@ -28,7 +28,7 @@ export class UserProfileComponent implements OnInit { //set currentAccount(account: Account) { set currentAccount(accountName: string) { this.statuses.length = 0; - this.isLoading = true; + this.isLoading = true; this.loadAccount(accountName) .then((account: Account) => { @@ -36,9 +36,10 @@ export class UserProfileComponent implements OnInit { return this.getStatuses(this.account); }) .catch(err => { - this.error = 'Error when retieving account'; + this.error = 'Error when retrieving account'; this.isLoading = false; this.statusLoading = false; + console.warn(this.error); }); // this.account = account; @@ -70,21 +71,17 @@ export class UserProfileComponent implements OnInit { if (selectedAccounts.length === 0) { this.error = 'no user selected'; - return; + console.error(this.error); + return Promise.resolve(null); } this.isLoading = true; return this.mastodonService.search(selectedAccounts[0], accountName, true) .then((result: Results) => { + console.warn(result); this.isLoading = false; return result.accounts[0]; }); - // .catch((err) => { - // this.error = 'Error when retieving account'; - // }) - // .then(()=>{ - // this.isLoading = false; - // }); } private getStatuses(account: Account): Promise { diff --git a/src/app/services/streaming.service.ts b/src/app/services/streaming.service.ts index 697a0b30..ebcfbf25 100644 --- a/src/app/services/streaming.service.ts +++ b/src/app/services/streaming.service.ts @@ -39,11 +39,12 @@ export class StreamingWrapper { this.eventSource.onmessage = x => this.statusParsing(JSON.parse(x.data)); this.eventSource.onerror = x => this.webSocketGotError(x); this.eventSource.onopen = x => console.log(x); - this.eventSource.onclose = x => this.webSocketClosed(route, x); + this.eventSource.onclose = x => this.webSocketClosed(route, x); } private errorClosing: boolean; private webSocketGotError(x: Event) { + console.log(x); this.errorClosing = true; } @@ -52,7 +53,36 @@ export class StreamingWrapper { console.log(x); if (this.errorClosing) { - this.mastodonService.getTimeline(this.accountInfo, this.streamType, null, this.since_id) + + this.pullNewStatuses(domain); + + // this.mastodonService.getTimeline(this.accountInfo, this.streamType, null, this.since_id) + // .then((status: Status[]) => { + // // status = status.sort((n1, n2) => { return (n1.id) < (n2.id); }); + // status = status.sort((a, b) => a.id.localeCompare(b.id)); + // for (const s of status) { + // const update = new StatusUpdate(); + // update.status = s; + // update.type = EventEnum.update; + // this.since_id = update.status.id; + // this.statusUpdateSubjet.next(update); + // } + // }) + // .catch(err => { + // console.error(err); + // }) + // .then(() => { + // setTimeout(() => { this.start(domain) }, 20 * 1000); + // }); + + this.errorClosing = false; + } else { + setTimeout(() => { this.start(domain) }, 5000); + } + } + + private pullNewStatuses(domain){ + this.mastodonService.getTimeline(this.accountInfo, this.streamType, null, this.since_id) .then((status: Status[]) => { // status = status.sort((n1, n2) => { return (n1.id) < (n2.id); }); status = status.sort((a, b) => a.id.localeCompare(b.id)); @@ -68,13 +98,9 @@ export class StreamingWrapper { console.error(err); }) .then(() => { - setTimeout(() => { this.start(domain) }, 20 * 1000); + // setTimeout(() => { this.start(domain) }, 20 * 1000); + setTimeout(() => { this.pullNewStatuses(domain) }, 15 * 1000); }); - - this.errorClosing = false; - } else { - setTimeout(() => { this.start(domain) }, 5000); - } } private statusParsing(event: WebSocketEvent) { From c928fe9acb44e2256e0695c50e08141bae8263be Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 2 Nov 2018 00:02:49 -0400 Subject: [PATCH 24/25] added link to user page in profile and no statuses found notification --- .../stream/user-profile/user-profile.component.html | 6 +++++- .../stream/user-profile/user-profile.component.scss | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/app/components/stream/user-profile/user-profile.component.html b/src/app/components/stream/user-profile/user-profile.component.html index 3e3770b5..b369e6f0 100644 --- a/src/app/components/stream/user-profile/user-profile.component.html +++ b/src/app/components/stream/user-profile/user-profile.component.html @@ -6,7 +6,7 @@ header

{{account.display_name}}

-

@{{account.acct}}

+

@{{account.acct}}

@@ -17,6 +17,10 @@
+
+ no toots found +
+
diff --git a/src/app/components/stream/user-profile/user-profile.component.scss b/src/app/components/stream/user-profile/user-profile.component.scss index 1035b840..d1dc7549 100644 --- a/src/app/components/stream/user-profile/user-profile.component.scss +++ b/src/app/components/stream/user-profile/user-profile.component.scss @@ -29,7 +29,7 @@ left: 15px; color: white; } - &__fullhandle { + &__fullhandle a { position: absolute; top: 130px; left: 15px; @@ -42,4 +42,10 @@ font-size: 13px; border-bottom: 1px solid black; } + + &-no-toots { + text-align: center; + margin: 15px; + border: 2px solid whitesmoke; + } } \ No newline at end of file From 5677adc2aa1579b2c9de01fbf56b8d44a117b725 Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Fri, 2 Nov 2018 00:11:17 -0400 Subject: [PATCH 25/25] fix background image not being correctly darken --- .../components/stream/user-profile/user-profile.component.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/components/stream/user-profile/user-profile.component.scss b/src/app/components/stream/user-profile/user-profile.component.scss index d1dc7549..ceb6ec22 100644 --- a/src/app/components/stream/user-profile/user-profile.component.scss +++ b/src/app/components/stream/user-profile/user-profile.component.scss @@ -13,6 +13,7 @@ font-size: $default-font-size; } &__inner { + overflow: auto; height: 160px; background-color: rgba(0, 0, 0, .45); }