commit
4df59f0edb
10
README.md
10
README.md
|
@ -22,7 +22,7 @@ Sengi already supporting all the basics functionalities, but many minors enhanc
|
|||
|
||||
## Screens
|
||||
|
||||
![/docs/images/presentation_small.gif](/docs/images/presentation_small.gif)
|
||||
![https://raw.githubusercontent.com/NicolasConstant/sengi/master/docs/images/presentation_small.gif](https://raw.githubusercontent.com/NicolasConstant/sengi/master/docs/images/presentation_small.gif)
|
||||
|
||||
## Contact
|
||||
|
||||
|
@ -36,21 +36,21 @@ It's a little [elephant shrew](https://en.wikipedia.org/wiki/Elephant_shrew) fro
|
|||
|
||||
## Contribute
|
||||
|
||||
Please see the [contributing guidelines](CONTRIBUTING.md)
|
||||
Please see the [contributing guidelines](https://github.com/NicolasConstant/sengi/blob/master/CONTRIBUTING.md)
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the AGPLv3 License - see [LICENSE](LICENSE) for details
|
||||
This project is licensed under the AGPLv3 License - see [LICENSE](https://github.com/NicolasConstant/sengi/blob/master/LICENSE) for details
|
||||
|
||||
## Credits
|
||||
|
||||
See [credits](CREDITS.md)
|
||||
See [credits](https://github.com/NicolasConstant/sengi/blob/master/CREDITS.md)
|
||||
|
||||
## Dependencies
|
||||
|
||||
* [Angular 7](https://github.com/angular/angular)
|
||||
* [NGXS](https://github.com/ngxs/store)
|
||||
* [SASS](https://github.com/sass/dart-sass)
|
||||
* [Electron 4](https://github.com/electron/electron)
|
||||
* [Electron 8](https://github.com/electron/electron)
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "sengi",
|
||||
"version": "0.30.1",
|
||||
"version": "0.31.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "main-electron.js",
|
||||
"description": "A multi-account desktop client for Mastodon and Pleroma",
|
||||
|
|
|
@ -663,7 +663,7 @@ export class CreateStatusComponent implements OnInit, OnDestroy {
|
|||
|
||||
private getLinksExtraChars(status: string): number {
|
||||
let mentionExtraChars = 0;
|
||||
let links = status.split(' ').filter(x => x.startsWith('http://') || x.startsWith('https://'));
|
||||
let links = status.split(/\s+/).filter(x => x.startsWith('http://') || x.startsWith('https://'));
|
||||
for (let link of links) {
|
||||
if (link.length > 23) {
|
||||
mentionExtraChars += link.length - 23;
|
||||
|
|
|
@ -58,6 +58,11 @@ export class BookmarksComponent extends TimelineBase {
|
|||
this.mastodonService.getBookmarks(this.account)
|
||||
.then((result: BookmarkResult) => {
|
||||
this.maxId = result.max_id;
|
||||
|
||||
if(!this.maxId){
|
||||
this.lastCallReachedMax = true;
|
||||
}
|
||||
|
||||
for (const s of result.bookmarked) {
|
||||
let cwPolicy = this.toolsService.checkContentWarning(s);
|
||||
const wrapper = new StatusWrapper(cwPolicy.status, this.account, cwPolicy.applyCw, cwPolicy.hide);
|
||||
|
@ -73,6 +78,8 @@ export class BookmarksComponent extends TimelineBase {
|
|||
}
|
||||
|
||||
protected getNextStatuses(): Promise<Status[]> {
|
||||
if(this.lastCallReachedMax) return Promise.resolve([]);
|
||||
|
||||
return this.mastodonService.getBookmarks(this.account, this.maxId)
|
||||
.then((result: BookmarkResult) => {
|
||||
const statuses = result.bookmarked;
|
||||
|
|
|
@ -56,6 +56,11 @@ export class FavoritesComponent extends TimelineBase {
|
|||
this.mastodonService.getFavorites(this.account)
|
||||
.then((result: FavoriteResult) => {
|
||||
this.maxId = result.max_id;
|
||||
|
||||
if (!this.maxId) {
|
||||
this.lastCallReachedMax = true;
|
||||
}
|
||||
|
||||
for (const s of result.favorites) {
|
||||
let cwPolicy = this.toolsService.checkContentWarning(s);
|
||||
const wrapper = new StatusWrapper(cwPolicy.status, this.account, cwPolicy.applyCw, cwPolicy.hide);
|
||||
|
@ -72,12 +77,14 @@ export class FavoritesComponent extends TimelineBase {
|
|||
}
|
||||
|
||||
protected getNextStatuses(): Promise<Status[]> {
|
||||
return this.mastodonService.getFavorites(this.account, this.maxId)
|
||||
if (this.lastCallReachedMax) return Promise.resolve([]);
|
||||
|
||||
return this.mastodonService.getFavorites(this.account, this.maxId)
|
||||
.then((result: FavoriteResult) => {
|
||||
const statuses = result.favorites;
|
||||
this.maxId = result.max_id;
|
||||
|
||||
if(!this.maxId){
|
||||
if (!this.maxId) {
|
||||
this.lastCallReachedMax = true;
|
||||
}
|
||||
|
||||
|
@ -85,7 +92,7 @@ export class FavoritesComponent extends TimelineBase {
|
|||
});
|
||||
}
|
||||
|
||||
protected scrolledToTop() {}
|
||||
protected scrolledToTop() { }
|
||||
|
||||
protected statusProcessOnGoToTop(){}
|
||||
protected statusProcessOnGoToTop() { }
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<div class="card-data" *ngIf="card.type === 'link' || card.type === 'video'">
|
||||
<a *ngIf="card.type === 'link'" class="card-data__link" href="{{ card.url }}" target="_blank" title="{{ card.title }} {{ host }}">
|
||||
<a *ngIf="card.type === 'link'" class="card-data__link" href="{{ card.url }}" target="_blank" rel="noopener noreferrer" title="{{ card.title }} {{ host }}">
|
||||
<img *ngIf="card.image" class="card-data__link--image" src="{{ card.image | ensureHttps }}" alt="" />
|
||||
<div *ngIf="!card.image" class="card-data__link--image">
|
||||
<fa-icon class="card-data__link--image--logo" [icon]="faFileAlt"></fa-icon>
|
||||
|
|
|
@ -42,7 +42,7 @@ describe('DatabindedTextComponent', () => {
|
|||
const url = 'https://test.social/tags/programmers';
|
||||
const sample = `<p>bla1 <a href="${url}" class="mention hashtag" rel="nofollow noopener" target="_blank">#<span>${hashtag}</span></a> bla2</p>`;
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('<a href class="hashtag-programmers" title="#programmers">#programmers</a>');
|
||||
expect(component.processedText).toContain(`<a href="${url}" class="hashtag-programmers" title="#programmers" target="_blank" rel="noopener noreferrer">#programmers</a>`);
|
||||
expect(component.processedText).toContain('bla1');
|
||||
expect(component.processedText).toContain('bla2');
|
||||
});
|
||||
|
@ -50,7 +50,7 @@ describe('DatabindedTextComponent', () => {
|
|||
it('should parse hashtag - Pleroma 2.0.2', () => {
|
||||
const sample = `Blabla <a class="hashtag" data-tag="covid19" href="https://url.com/tag/covid19">#covid19</a> Blibli`;
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('<a href class="hashtag-covid19" title="#covid19">#covid19</a>');
|
||||
expect(component.processedText).toContain(`<a href="https://url.com/tag/covid19" class="hashtag-covid19" title="#covid19" target="_blank" rel="noopener noreferrer">#covid19</a>`);
|
||||
expect(component.processedText).toContain('Blabla');
|
||||
expect(component.processedText).toContain('Blibli');
|
||||
});
|
||||
|
@ -60,7 +60,7 @@ describe('DatabindedTextComponent', () => {
|
|||
const url = 'https://mastodon.social/@sengi_app';
|
||||
const sample = `<p>bla1 <span class="h-card"><a href="${url}" class="u-url mention" rel="nofollow noopener" target="_blank">@<span>${mention}</span></a></span> bla2</p>`;
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('<a href class="account--sengi_app-mastodon-social" title="@sengi_app@mastodon.social">@sengi_app</a>');
|
||||
expect(component.processedText).toContain(`<a href="${url}" class="account--sengi_app-mastodon-social" title="@sengi_app@mastodon.social" target="_blank" rel="noopener noreferrer">@sengi_app</a>`);
|
||||
expect(component.processedText).toContain('bla1');
|
||||
expect(component.processedText).toContain('bla2');
|
||||
});
|
||||
|
@ -69,14 +69,14 @@ describe('DatabindedTextComponent', () => {
|
|||
const sample = `<p><span class="article-type"><a href="https://domain.name/@username" class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@<span class="article-type">username</span></a></span> <br>Yes, indeed.</p>`;
|
||||
|
||||
component.text = sample;
|
||||
expect(component.processedText).toBe('<p><span class="article-type"><a href class="account--username-domain-name" title="@username@domain.name">@username</a> <br>Yes, indeed.</p>');
|
||||
expect(component.processedText).toBe('<p><span class="article-type"><a href="https://domain.name/@username" class="account--username-domain-name" title="@username@domain.name" target="_blank" rel="noopener noreferrer">@username</a> <br>Yes, indeed.</p>');
|
||||
});
|
||||
|
||||
it('should parse link', () => {
|
||||
const url = 'mydomain.co/test';
|
||||
const sample = `<p>bla1 <a href="https://${url}" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="">${url}</span><span class="invisible"></span></a> bla2</p>`;
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('<a href class="link-httpsmydomaincotest" title="open link">mydomain.co/test</a>');
|
||||
expect(component.processedText).toContain(`<a href="https://${url}" class="link-httpsmydomaincotest" title="open link" target="_blank" rel="noopener noreferrer">mydomain.co/test</a>`);
|
||||
expect(component.processedText).toContain('bla1');
|
||||
expect(component.processedText).toContain('bla2');
|
||||
});
|
||||
|
@ -85,21 +85,21 @@ describe('DatabindedTextComponent', () => {
|
|||
const url = 'bbc.com/news/magazine-34901704';
|
||||
const sample = `<p>The rise of"<br><a href="https:www//${url}" rel="nofollow noopener" target="_blank"><span class="invisible">https://www.</span><span class="">${url}</span><span class="invisible"></span></a></p>`;
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('<a href class="link-httpswwwbbccomnewsmagazine34901704" title="open link">bbc.com/news/magazine-34901704</a></p>');
|
||||
expect(component.processedText).toContain(`<a href="https:www//${url}" class="link-httpswwwbbccomnewsmagazine34901704" title="open link" target="_blank" rel="noopener noreferrer">bbc.com/news/magazine-34901704</a></p>`);
|
||||
});
|
||||
|
||||
it('should parse link - dual section', () => {
|
||||
const sample = `<p>Test.<br><a href="https://peertube.fr/videos/watch/69bb6e90-ec0f-49a3-9e28-41792f4a7c5f" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="ellipsis">peertube.fr/videos/watch/69bb6</span><span class="invisible">e90-ec0f-49a3-9e28-41792f4a7c5f</span></a></p>`;
|
||||
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('<p>Test.<br><a href class="link-httpspeertubefrvideoswatch69bb6e90ec0f49a39e2841792f4a7c5f" title="open link">peertube.fr/videos/watch/69bb6</a></p>');
|
||||
expect(component.processedText).toContain('<p>Test.<br><a href="https://peertube.fr/videos/watch/69bb6e90-ec0f-49a3-9e28-41792f4a7c5f" class="link-httpspeertubefrvideoswatch69bb6e90ec0f49a39e2841792f4a7c5f" title="open link" target="_blank" rel="noopener noreferrer">peertube.fr/videos/watch/69bb6</a></p>');
|
||||
});
|
||||
|
||||
it('should parse link with special character', () => {
|
||||
const sample = `<p>Magnitude: 2.5 Depth: 3.4 km<br>Details: 2018/09/27 06:50:17 34.968N 120.685W<br>Location: 10 km (6 mi) W of Guadalupe, CA<br>Map: <a href="https://www.google.com/maps/place/34°58'4%20N+120°41'6%20W/@34.968,-120.685,10z" rel="noopener" target="_blank" class="status-link" title="https://www.google.com/maps/place/34%C2%B058'4%20N+120%C2%B041'6%20W/@34.968,-120.685,10z"><span class="invisible">https://www.</span><span class="ellipsis">google.com/maps/place/34°58'4%</span><span class="invisible">20N+120°41'6%20W/@34.968,-120.685,10z</span></a><br><a href="https://mastodon.cloud/tags/earthquake" class="mention hashtag status-link" rel="noopener" target="_blank">#<span>EarthQuake</span></a> <a href="https://mastodon.cloud/tags/quake" class="mention hashtag status-link" rel="noopener" target="_blank">#<span>Quake</span></a> <a href="https://mastodon.cloud/tags/california" class="mention hashtag status-link" rel="noopener" target="_blank">#<span>California</span></a></p>`;
|
||||
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('<a href class="link-httpswwwgooglecommapsplace3458420N12041620W3496812068510z" title="open link">google.com/maps/place/34°58\'4%</a>');
|
||||
expect(component.processedText).toContain(`<a href="https://www.google.com/maps/place/34°58'4%20N+120°41'6%20W/@34.968,-120.685,10z" class="link-httpswwwgooglecommapsplace3458420N12041620W3496812068510z" title="open link" target="_blank" rel="noopener noreferrer">google.com/maps/place/34°58\'4%</a>`);
|
||||
});
|
||||
|
||||
it('should parse combined hashtag, mention and link', () => {
|
||||
|
@ -110,9 +110,9 @@ describe('DatabindedTextComponent', () => {
|
|||
const linkUrl = 'mydomain.co/test';
|
||||
const sample = `<p>bla1 <a href="${hashtagUrl}" class="mention hashtag" rel="nofollow noopener" target="_blank">#<span>${hashtag}</span></a> bla2 <span class="h-card"><a href="${mentionUrl}" class="u-url mention" rel="nofollow noopener" target="_blank">@<span>${mention}</span></a></span> bla3 <a href="https://${linkUrl}" rel="nofollow noopener" target="_blank"><span class="invisible">https://</span><span class="">${linkUrl}</span><span class="invisible"></span></a> bla4</p>`;
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('<a href class="hashtag-programmers" title="#programmers">#programmers</a>');
|
||||
expect(component.processedText).toContain('<a href class="account--sengi_app-mastodon-social" title="@sengi_app@mastodon.social">@sengi_app</a>');
|
||||
expect(component.processedText).toContain('<a href class="link-httpsmydomaincotest" title="open link">mydomain.co/test</a>');
|
||||
expect(component.processedText).toContain(`<a href="${hashtagUrl}" class="hashtag-programmers" title="#programmers" target="_blank" rel="noopener noreferrer">#programmers</a>`);
|
||||
expect(component.processedText).toContain(`<a href="${mentionUrl}" class="account--sengi_app-mastodon-social" title="@sengi_app@mastodon.social" target="_blank" rel="noopener noreferrer">@sengi_app</a>`);
|
||||
expect(component.processedText).toContain(`<a href="https://${linkUrl}" class="link-httpsmydomaincotest" title="open link" target="_blank" rel="noopener noreferrer">mydomain.co/test</a>`);
|
||||
expect(component.processedText).toContain('bla1');
|
||||
expect(component.processedText).toContain('bla2');
|
||||
expect(component.processedText).toContain('bla3');
|
||||
|
@ -123,7 +123,7 @@ describe('DatabindedTextComponent', () => {
|
|||
const sample = `bla1 <a href="https://www.lemonde.fr/planete.html?xtor=RSS-3208" rel="nofollow noopener" class="" target="_blank">https://social.bitcast.info/url/819438</a>`;
|
||||
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('<a href class="link-httpswwwlemondefrplanetehtmlxtorRSS3208" title="open link">https://social.bitcast.info/url/819438</a>');
|
||||
expect(component.processedText).toContain('<a href="https://www.lemonde.fr/planete.html?xtor=RSS-3208" class="link-httpswwwlemondefrplanetehtmlxtorRSS3208" title="open link" target="_blank" rel="noopener noreferrer">https://social.bitcast.info/url/819438</a>');
|
||||
expect(component.processedText).toContain('bla1');
|
||||
});
|
||||
|
||||
|
@ -131,7 +131,7 @@ describe('DatabindedTextComponent', () => {
|
|||
const sample = `<div>bla1 <br> @<a href="https://instance.club/user/1" class="h-card mention status-link" rel="noopener" target="_blank" title="https://instance.club/user/1">user</a> </div>`;
|
||||
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('<a href class="account--user-instance-club" title="@user@instance.club">@user</a>');
|
||||
expect(component.processedText).toContain('<a href="https://instance.club/user/1" class="account--user-instance-club" title="@user@instance.club" target="_blank" rel="noopener noreferrer">@user</a>');
|
||||
expect(component.processedText).toContain('bla1');
|
||||
});
|
||||
|
||||
|
@ -139,42 +139,56 @@ describe('DatabindedTextComponent', () => {
|
|||
const sample = `<div><span><a class="mention status-link" href="https://pleroma.site/users/kaniini" rel="noopener" target="_blank" title="kaniini@pleroma.site">@<span>kaniini</span></a></span> <span><a class="mention status-link" href="https://mastodon.social/@Gargron" rel="noopener" target="_blank" title="Gargron@mastodon.social">@<span>Gargron</span></a></span> bla1?</div>`;
|
||||
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('<div><span><a href class="account--kaniini-pleroma-site" title="@kaniini@pleroma.site">@kaniini</a> <span><a href class="account--Gargron-mastodon-social" title="@Gargron@mastodon.social">@Gargron</a> bla1?</div>');
|
||||
expect(component.processedText).toContain('<div><span><a href="https://pleroma.site/users/kaniini" class="account--kaniini-pleroma-site" title="@kaniini@pleroma.site" target="_blank" rel="noopener noreferrer">@kaniini</a> <span><a href="https://mastodon.social/@Gargron" class="account--Gargron-mastodon-social" title="@Gargron@mastodon.social" target="_blank" rel="noopener noreferrer">@Gargron</a> bla1?</div>');
|
||||
});
|
||||
|
||||
it('should parse mention - Friendica in Mastodon', () => {
|
||||
const sample = `@<span class=""><a href="https://m.s/me" class="u-url mention" rel="nofollow noopener" target="_blank"><span class="mention">me</span></a></span> Blablabla.`;
|
||||
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('<span class=""><a href class="account--me-m-s" title="@me@m.s">@me</a></span> Blablabla.');
|
||||
expect(component.processedText).toContain('<span class=""><a href="https://m.s/me" class="account--me-m-s" title="@me@m.s" target="_blank" rel="noopener noreferrer">@me</a></span> Blablabla.');
|
||||
});
|
||||
|
||||
it('should parse mention - Misskey in Mastodon', () => {
|
||||
const sample = `<p><a href="https://mastodon.social/users/sengi_app" class="mention" rel="nofollow noopener" target="_blank">@sengi_app@mastodon.social</a><span> Blabla</span></p>`;
|
||||
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('<p><a href class="account--sengi_app-mastodon-social-mastodon-social" title="@sengi_app@mastodon.social@mastodon.social">@sengi_app@mastodon.social</a><span> Blabla</span></p>'); //FIXME: dont let domain appear in name
|
||||
expect(component.processedText).toContain('<p><a href="https://mastodon.social/users/sengi_app" class="account--sengi_app-mastodon-social-mastodon-social" title="@sengi_app@mastodon.social@mastodon.social" target="_blank" rel="noopener noreferrer">@sengi_app@mastodon.social</a><span> Blabla</span></p>'); //FIXME: dont let domain appear in name
|
||||
});
|
||||
|
||||
it('should parse mention - Misskey in Mastodon - 2', () => {
|
||||
const sample = `<p><span>Since </span><a href="https://mastodon.technology/@test" class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@test@mastodon.technology</a><span> mentioned </span></p>`;
|
||||
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('<a href="https://mastodon.technology/@test" class="account--test-mastodon-technology" title="@test@mastodon.technology" target="_blank" rel="noopener noreferrer">@test</a>');
|
||||
});
|
||||
|
||||
it('should parse mention - Zap in Mastodon', () => {
|
||||
const sample = `test @<span class="h-card"><a class="u-url mention" href="https://mastodon.social/@test" rel="nofollow noopener noreferrer" target="_blank">test</a></span> bla"`;
|
||||
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('test <span class="h-card"><a href="https://mastodon.social/@test" class="account--test-mastodon-social" title="@test@mastodon.social" target="_blank" rel="noopener noreferrer">@test</a></span>');
|
||||
});
|
||||
|
||||
it('should parse hastag - Pleroma', () => {
|
||||
const sample = `<p>Bla <a href="https://ubuntu.social/tags/kubecon" rel="tag">#<span>KubeCon</span></a> Bla</p>`;
|
||||
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('<p>Bla <a href class="hashtag-KubeCon" title="#KubeCon">#KubeCon</a> Bla</p>');
|
||||
expect(component.processedText).toContain('<p>Bla <a href="https://ubuntu.social/tags/kubecon" class="hashtag-KubeCon" title="#KubeCon" target="_blank" rel="noopener noreferrer">#KubeCon</a> Bla</p>');
|
||||
});
|
||||
|
||||
it('should parse link - Pleroma', () => {
|
||||
const sample = `<p>Bla <a href="https://cloudblogs.microsoft.com/opensource/2019/05/21/service-mesh-interface-smi-release/"><span>https://</span><span>cloudblogs.microsoft.com/opens</span><span>ource/2019/05/21/service-mesh-interface-smi-release/</span></a></p>`;
|
||||
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('<p>Bla <a href class="link-httpscloudblogsmicrosoftcomopensource20190521servicemeshinterfacesmirelease" title="open link">cloudblogs.microsoft.com/opens</a></p>');
|
||||
expect(component.processedText).toContain('<p>Bla <a href="https://cloudblogs.microsoft.com/opensource/2019/05/21/service-mesh-interface-smi-release/" class="link-httpscloudblogsmicrosoftcomopensource20190521servicemeshinterfacesmirelease" title="open link" target="_blank" rel="noopener noreferrer">cloudblogs.microsoft.com/opens</a></p>');
|
||||
});
|
||||
|
||||
it('should parse link 2 - Pleroma', () => {
|
||||
const sample = `Bla<br /><br /><a href="https://link/">https://link/</a>`;
|
||||
|
||||
component.text = sample;
|
||||
expect(component.processedText).toContain('Bla<br /><br /><a href class="link-httpslink" title="open link">https://link/</a>');
|
||||
expect(component.processedText).toContain('Bla<br /><br /><a href="https://link/" class="link-httpslink" title="open link" target="_blank" rel="noopener noreferrer">https://link/</a>');
|
||||
});
|
||||
|
||||
it('should sanitize link', () => {
|
||||
|
|
|
@ -28,7 +28,7 @@ export class DatabindedTextComponent implements OnInit {
|
|||
|
||||
@Input('text')
|
||||
set text(value: string) {
|
||||
//console.warn(value);
|
||||
//console.log(value);
|
||||
|
||||
let parser = new DOMParser();
|
||||
var dom = parser.parseFromString(value, 'text/html')
|
||||
|
@ -44,6 +44,10 @@ export class DatabindedTextComponent implements OnInit {
|
|||
value = value.replace('class="mention" rel="nofollow noopener" target="_blank">@', 'class="mention" rel="nofollow noopener" target="_blank">'); //Misskey sanitarization
|
||||
} while (value.includes('class="mention" rel="nofollow noopener" target="_blank">@'));
|
||||
|
||||
do {
|
||||
value = value.replace('@<span class="h-card">', '<span class="h-card">'); //Zap sanitarization
|
||||
} while (value.includes('@<span class="h-card">'));
|
||||
|
||||
let linksSections = value.split('<a ');
|
||||
|
||||
for (let section of linksSections) {
|
||||
|
@ -90,9 +94,10 @@ export class DatabindedTextComponent implements OnInit {
|
|||
private processHashtag(section: string) {
|
||||
let extractedLinkAndNext = section.split('</a>');
|
||||
let extractedHashtag = extractedLinkAndNext[0].split('#')[1].replace('<span>', '').replace('</span>', '');
|
||||
let extractedUrl = extractedLinkAndNext[0].split('href="')[1].split('"')[0];
|
||||
|
||||
let classname = this.getClassNameForHastag(extractedHashtag);
|
||||
this.processedText += ` <a href class="${classname}" title="#${extractedHashtag}">#${extractedHashtag}</a>`;
|
||||
this.processedText += ` <a href="${extractedUrl}" class="${classname}" title="#${extractedHashtag}" target="_blank" rel="noopener noreferrer">#${extractedHashtag}</a>`;
|
||||
if (extractedLinkAndNext[1]) this.processedText += extractedLinkAndNext[1];
|
||||
this.hashtags.push(extractedHashtag);
|
||||
}
|
||||
|
@ -104,9 +109,17 @@ export class DatabindedTextComponent implements OnInit {
|
|||
if (section.includes('<span class="mention">')) { //Friendica
|
||||
extractedAccountAndNext = section.split('</a>');
|
||||
extractedAccountName = extractedAccountAndNext[0].split('<span class="mention">')[1].split('</span>')[0];
|
||||
} else if(section.includes('>@<span class="article-type">')){ //Remote status
|
||||
} else if (section.includes('>@<span class="article-type">')) { //Remote status
|
||||
extractedAccountAndNext = section.split('</a></span>');
|
||||
extractedAccountName = extractedAccountAndNext[0].split('@<span class="article-type">')[1].replace('<span>', '').replace('</span>', '');
|
||||
} else if (section.includes('class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@') && !section.includes('target="_blank">@<')) { //Misskey
|
||||
//console.warn('misskey');
|
||||
|
||||
extractedAccountAndNext = section.split('</a>');
|
||||
extractedAccountName = extractedAccountAndNext[0].split('class="u-url mention" rel="nofollow noopener noreferrer" target="_blank">@')[1];
|
||||
|
||||
if (extractedAccountName.includes('@'))
|
||||
extractedAccountName = extractedAccountName.split('@')[0];
|
||||
} else if (!section.includes('@<span>')) { //GNU social
|
||||
extractedAccountAndNext = section.split('</a>');
|
||||
extractedAccountName = extractedAccountAndNext[0].split('>')[1];
|
||||
|
@ -121,9 +134,10 @@ export class DatabindedTextComponent implements OnInit {
|
|||
//let username = extractedAccountLink[extractedAccountLink.length - 1];
|
||||
|
||||
let extractedAccount = `@${extractedAccountName}@${domain}`;
|
||||
let extractedUrl = section.split('href="')[1].split('"')[0];
|
||||
|
||||
let classname = this.getClassNameForAccount(extractedAccount);
|
||||
this.processedText += `<a href class="${classname}" title="${extractedAccount}">@${extractedAccountName}</a>`;
|
||||
this.processedText += `<a href="${extractedUrl}" class="${classname}" title="${extractedAccount}" target="_blank" rel="noopener noreferrer">@${extractedAccountName}</a>`;
|
||||
|
||||
if (extractedAccountAndNext[1])
|
||||
this.processedText += extractedAccountAndNext[1];
|
||||
|
@ -136,7 +150,7 @@ export class DatabindedTextComponent implements OnInit {
|
|||
}
|
||||
|
||||
private processLink(section: string) {
|
||||
if(!section.includes('</a>')){
|
||||
if (!section.includes('</a>')) {
|
||||
this.processedText += section;
|
||||
return;
|
||||
}
|
||||
|
@ -167,7 +181,9 @@ export class DatabindedTextComponent implements OnInit {
|
|||
this.links.push(extractedUrl);
|
||||
let classname = this.getClassNameForLink(extractedUrl);
|
||||
|
||||
this.processedText += `<a href class="${classname}" title="open link">${extractedName}</a>`;
|
||||
let sanitizedLink = this.sanitizeLink(extractedUrl);
|
||||
|
||||
this.processedText += `<a href="${sanitizedLink}" class="${classname}" title="open link" target="_blank" rel="noopener noreferrer">${extractedName}</a>`;
|
||||
if (extractedLinkAndNext.length > 1) this.processedText += extractedLinkAndNext[1];
|
||||
}
|
||||
|
||||
|
@ -219,20 +235,18 @@ export class DatabindedTextComponent implements OnInit {
|
|||
this.renderer.listen(el, 'click', (event) => {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
window.open(sanitizedLink, '_blank');
|
||||
window.open(sanitizedLink, '_blank', 'noopener');
|
||||
return false;
|
||||
});
|
||||
|
||||
this.renderer.listen(el, 'mouseup', (event) => {
|
||||
if (event.which === 2) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
|
||||
window.open(sanitizedLink, '_blank');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
// this.renderer.listen(el, 'mouseup', (event) => {
|
||||
// if (event.which === 2) {
|
||||
// event.preventDefault();
|
||||
// event.stopImmediatePropagation();
|
||||
// window.open(sanitizedLink, '_blank', 'noopener');
|
||||
// return false;
|
||||
// }
|
||||
// });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,4 +27,7 @@
|
|||
<a href class="poll__refresh" *ngIf="(poll.voted || poll.expired) && !pollLocked" title="refresh poll" (click)="refresh()">refresh</a>
|
||||
<div class="poll__statistics"><span *ngIf="(poll.voted || poll.expired) && !pollLocked" class="poll__separator">·</span>{{poll.votes_count}} votes<span *ngIf="!poll.expired" class="poll__separator" title="{{ poll.expires_at | timeLeft | async }}">· {{ poll.expires_at | timeLeft | async }}</span></div>
|
||||
</div>
|
||||
<div class="poll__error" *ngIf="errorOccuredWhenRetrievingPoll">
|
||||
Error occured when retrieving the poll
|
||||
</div>
|
||||
</div>
|
|
@ -27,6 +27,11 @@
|
|||
margin-top: 10px;
|
||||
}
|
||||
|
||||
&__error {
|
||||
font-size: 12px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
&__refresh {
|
||||
font-size: 0.8em;
|
||||
color: rgb(101, 121, 160);
|
||||
|
|
|
@ -22,6 +22,8 @@ export class PollComponent implements OnInit {
|
|||
choiceType: string;
|
||||
pollLocked: boolean;
|
||||
|
||||
errorOccuredWhenRetrievingPoll: boolean;
|
||||
|
||||
private pollSelection: number[] = [];
|
||||
options: PollOptionWrapper[] = [];
|
||||
|
||||
|
@ -30,7 +32,7 @@ export class PollComponent implements OnInit {
|
|||
private _poll: Poll;
|
||||
@Input('poll')
|
||||
set poll(value: Poll) {
|
||||
if(!value) return;
|
||||
if (!value) return;
|
||||
|
||||
this._poll = value;
|
||||
|
||||
|
@ -83,6 +85,7 @@ export class PollComponent implements OnInit {
|
|||
|
||||
private checkStatus(accounts: AccountInfo[]): void {
|
||||
this.pollLocked = false;
|
||||
this.errorOccuredWhenRetrievingPoll = false;
|
||||
var newSelectedAccount = accounts.find(x => x.isSelected);
|
||||
|
||||
const accountChanged = this.selectedAccount.id !== newSelectedAccount.id;
|
||||
|
@ -92,7 +95,7 @@ export class PollComponent implements OnInit {
|
|||
let statusWrapper = new StatusWrapper(this.statusWrapper.status, this.statusWrapper.provider, this.statusWrapper.applyCw, this.statusWrapper.hide);
|
||||
this.pollPerAccountId[newSelectedAccount.id] = this.toolsService.getStatusUsableByAccount(newSelectedAccount, statusWrapper)
|
||||
.then((status: Status) => {
|
||||
if(!status || !(status.poll)) return null;
|
||||
if (!status || !(status.poll)) return null;
|
||||
return this.mastodonService.getPoll(newSelectedAccount, status.poll.id);
|
||||
})
|
||||
.then((poll: Poll) => {
|
||||
|
@ -100,7 +103,9 @@ export class PollComponent implements OnInit {
|
|||
return poll;
|
||||
})
|
||||
.catch(err => {
|
||||
this.notificationService.notifyHttpError(err, newSelectedAccount);
|
||||
//this.notificationService.notifyHttpError(err, newSelectedAccount);
|
||||
this.errorOccuredWhenRetrievingPoll = true;
|
||||
this.pollPerAccountId[newSelectedAccount.id] = null;
|
||||
return null;
|
||||
});
|
||||
} else if (this.statusWrapper.status.visibility !== 'public' && this.statusWrapper.status.visibility !== 'unlisted' && this.statusWrapper.provider.id !== newSelectedAccount.id) {
|
||||
|
@ -115,8 +120,9 @@ export class PollComponent implements OnInit {
|
|||
this.selectedAccount = newSelectedAccount;
|
||||
}
|
||||
|
||||
|
||||
vote(): boolean {
|
||||
if (this.errorOccuredWhenRetrievingPoll) return false;
|
||||
|
||||
const selectedAccount = this.selectedAccount;
|
||||
const pollPromise = this.pollPerAccountId[selectedAccount.id];
|
||||
|
||||
|
@ -140,6 +146,8 @@ export class PollComponent implements OnInit {
|
|||
}
|
||||
|
||||
refresh(): boolean {
|
||||
if (this.errorOccuredWhenRetrievingPoll) return false;
|
||||
|
||||
this.setStatsAtZero();
|
||||
|
||||
const selectedAccount = this.selectedAccount;
|
||||
|
|
|
@ -224,12 +224,17 @@ export class ToolsService {
|
|||
if (!isProvider) {
|
||||
statusPromise = statusPromise
|
||||
.then((foreignStatus: Status) => {
|
||||
const statusUrl = foreignStatus.url;
|
||||
const statusUri = foreignStatus.uri;
|
||||
const statusUrl = foreignStatus.url;
|
||||
return this.getInstanceInfo(account)
|
||||
.then(instance => {
|
||||
let version: 'v1' | 'v2' = 'v1';
|
||||
if (instance.major >= 3) version = 'v2';
|
||||
return this.mastodonService.search(account, statusUrl, version, true);
|
||||
return this.mastodonService.search(account, statusUri, version, true)
|
||||
.then((results: Results) => {
|
||||
if(results && results.statuses.length > 0) return results;
|
||||
return this.mastodonService.search(account, statusUrl, version, true);
|
||||
});
|
||||
})
|
||||
.then((results: Results) => {
|
||||
return results.statuses[0];
|
||||
|
|
Loading…
Reference in New Issue