Skip to content

Commit 0956a07

Browse files
Mutugiiidogi
andauthored
community: smoother voices (fixes #9185) (#9186)
Co-authored-by: dogi <[email protected]>
1 parent ea1be27 commit 0956a07

File tree

5 files changed

+107
-41
lines changed

5 files changed

+107
-41
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "planet",
33
"license": "AGPL-3.0",
4-
"version": "0.20.46",
4+
"version": "0.20.47",
55
"myplanet": {
66
"latest": "v0.34.30",
77
"min": "v0.33.30"

src/app/community/community.component.ts

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ActivatedRoute, ParamMap, Router } from '@angular/router';
33
import { MatLegacyDialog as MatDialog } from '@angular/material/legacy-dialog';
44
import { UntypedFormBuilder } from '@angular/forms';
55
import { Subject, forkJoin, iif, of, throwError } from 'rxjs';
6-
import { takeUntil, finalize, switchMap, map, catchError, tap, debounceTime, distinctUntilChanged } from 'rxjs/operators';
6+
import { takeUntil, finalize, switchMap, map, catchError, tap, debounceTime, distinctUntilChanged, take } from 'rxjs/operators';
77
import { StateService } from '../shared/state.service';
88
import { NewsService } from '../news/news.service';
99
import { DialogsFormService } from '../shared/dialogs/dialogs-form.service';
@@ -69,6 +69,7 @@ export class CommunityComponent implements OnInit, OnDestroy {
6969
availableLabels: string[] = [];
7070
selectedLabel = '';
7171
pinned = false;
72+
attachmentMap: Record<string, any> = {};
7273

7374
get leadersTabLabel(): string {
7475
return this.configuration.planetType === 'nation' ? $localize`Nation Leaders` : $localize`Community Leaders`;
@@ -108,7 +109,6 @@ export class CommunityComponent implements OnInit, OnDestroy {
108109
const newsSortValue = (item: any) => item.sharedDate || item.doc.time;
109110
this.newsService.newsUpdated$.pipe(takeUntil(this.onDestroy$)).subscribe(news => {
110111
this.news = news.sort((a, b) => newsSortValue(b) - newsSortValue(a));
111-
this.news.forEach(item => item.doc.messageLower = item.doc.message?.toLowerCase() || '');
112112
this.filteredNews = this.news;
113113
this.availableLabels = this.getAvailableLabels(this.news);
114114
this.isLoading = false;
@@ -291,9 +291,13 @@ export class CommunityComponent implements OnInit, OnDestroy {
291291

292292
getLinks(planetCode?) {
293293
return this.teamsService.getTeamMembers(this.team || this.teamObject(planetCode), true).pipe(map((docs) => {
294-
const { link: links, transaction: finances, report: reports } = docs.reduce((docObject, doc) => ({
295-
...docObject, [doc.docType]: [ ...(docObject[doc.docType] || []), doc ]
296-
}), { link: [], transaction: [] });
294+
const { link: links, transaction: finances, report: reports } = docs.reduce((docObject, doc) => {
295+
if (!docObject[doc.docType]) {
296+
docObject[doc.docType] = [];
297+
}
298+
docObject[doc.docType].push(doc);
299+
return docObject;
300+
}, { link: [], transaction: [], report: [] });
297301
return { links, finances, reports };
298302
}));
299303
}
@@ -312,21 +316,40 @@ export class CommunityComponent implements OnInit, OnDestroy {
312316

313317
setCouncillors(users) {
314318
const planetCode = this.planetCode ? this.planetCode : this.stateService.configuration.code;
315-
this.couchService.findAll('attachments').subscribe((attachments: any[]) => {
316-
this.councillors = users
317-
.filter(user => planetCode === user.doc.planetCode && (user.doc.isUserAdmin || user.doc.roles.indexOf('leader')) !== -1)
318-
.map(user => {
319-
const { _id: userId, planetCode: userPlanetCode, name } = user.doc;
320-
const attachmentId = `org.couchdb.user:${name}@${userPlanetCode}`;
321-
const attachmentDoc: any = attachments.find(attachment => attachment._id === attachmentId);
322-
const avatar = attachmentDoc ?
323-
`${environment.couchAddress}/attachments/${attachmentId}/${Object.keys(attachmentDoc._attachments)[0]}` :
324-
(user.imageSrc || 'assets/image.png');
325-
return { avatar, userDoc: user, userId, name: user.doc.name, userPlanetCode: user.doc.planetCode, ...user };
326-
});
319+
const councillorUsers = users
320+
.filter(user => planetCode === user.doc.planetCode && (user.doc.isUserAdmin || user.doc.roles.indexOf('leader')) !== -1);
321+
const attachmentIds = councillorUsers
322+
.map(user => `org.couchdb.user:${user.doc.name}@${user.doc.planetCode}`)
323+
.filter(id => !!id);
324+
325+
this.fetchMissingAttachments(attachmentIds).pipe(take(1)).subscribe(() => {
326+
this.councillors = councillorUsers.map(user => {
327+
const { _id: userId, planetCode: userPlanetCode, name } = user.doc;
328+
const attachmentId = `org.couchdb.user:${name}@${userPlanetCode}`;
329+
const attachmentDoc: any = this.attachmentMap[attachmentId];
330+
const avatar = attachmentDoc ?
331+
`${environment.couchAddress}/attachments/${attachmentId}/${Object.keys(attachmentDoc._attachments)[0]}` :
332+
(user.imageSrc || 'assets/image.png');
333+
return { avatar, userDoc: user, userId, name: user.doc.name, userPlanetCode: user.doc.planetCode, ...user };
334+
});
327335
});
328336
}
329337

338+
private fetchMissingAttachments(ids: string[]) {
339+
const missing = ids.filter(id => !this.attachmentMap[id]);
340+
if (missing.length === 0) {
341+
return of(undefined);
342+
}
343+
return this.couchService.findAttachmentsByIds(missing).pipe(
344+
tap((attachments: any[]) => {
345+
attachments.forEach(attachment => {
346+
this.attachmentMap[attachment._id] = attachment;
347+
});
348+
}),
349+
map(() => undefined)
350+
);
351+
}
352+
330353
openAddLinkDialog() {
331354
this.dialog.open(CommunityLinkDialogComponent, {
332355
width: '50vw',
@@ -498,7 +521,12 @@ export class CommunityComponent implements OnInit, OnDestroy {
498521
}
499522
if (this.voiceSearch) {
500523
const lower = this.voiceSearch.toLowerCase();
501-
filtered = filtered.filter(item => item.doc.messageLower.includes(lower));
524+
filtered = filtered.filter(item => {
525+
if (typeof item.doc.messageLower !== 'string') {
526+
item.doc.messageLower = (item.doc.message || '').toLowerCase();
527+
}
528+
return item.doc.messageLower.includes(lower);
529+
});
502530
}
503531
this.filteredNews = filtered;
504532
}

src/app/news/news-list.component.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ export class NewsListComponent implements OnInit, OnChanges, AfterViewInit, OnDe
5959
) {}
6060

6161
ngOnInit() {
62-
6362
this.router.events.subscribe(() => {
6463
this.initNews();
6564
});
@@ -71,7 +70,11 @@ export class NewsListComponent implements OnInit, OnChanges, AfterViewInit, OnDe
7170
let isLatest = true;
7271
this.replyObject = {};
7372
this.items.forEach(item => {
74-
this.replyObject[item.doc.replyTo || 'root'] = [ ...(this.replyObject[item.doc.replyTo || 'root'] || []), item ];
73+
const key = item.doc.replyTo || 'root';
74+
if (!this.replyObject[key]) {
75+
this.replyObject[key] = [];
76+
}
77+
this.replyObject[key].push(item);
7578
if (!item.doc.replyTo && isLatest) {
7679
item.latestMessage = true;
7780
isLatest = false;

src/app/news/news.service.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Injectable } from '@angular/core';
2-
import { Subject, forkJoin, of } from 'rxjs';
3-
import { map } from 'rxjs/operators';
2+
import { Subject, of } from 'rxjs';
3+
import { map, switchMap } from 'rxjs/operators';
44
import { CouchService } from '../shared/couchdb.service';
55
import { StateService } from '../shared/state.service';
66
import { UserService } from '../shared/user.service';
@@ -29,19 +29,25 @@ export class NewsService {
2929

3030
requestNews({ selectors, viewId } = this.currentOptions) {
3131
this.currentOptions = { selectors, viewId };
32-
forkJoin([
33-
this.couchService.findAll(this.dbName, findDocuments(selectors, 0, [ { 'time': 'desc' } ])),
34-
this.couchService.findAll('attachments')
35-
]).subscribe(([ newsItems, avatars ]) => {
32+
this.couchService.findAll(this.dbName, findDocuments(selectors, 0, [ { 'time': 'desc' } ])).pipe(
33+
switchMap((newsItems: any[]) =>
34+
this.couchService.findAttachmentsByIds(this.collectAttachmentIds(newsItems)).pipe(
35+
map((attachments: any[]) => ({
36+
newsItems,
37+
avatarMap: new Map<string, any>(attachments.map((attachment: any) => [ attachment._id, attachment ]))
38+
}))
39+
)
40+
)
41+
).subscribe(({ newsItems, avatarMap }) => {
3642
this.newsUpdated$.next(newsItems.map((item: any) => (
37-
{ doc: item, sharedDate: this.findShareDate(item, viewId), avatar: this.findAvatar(item.user, avatars), _id: item._id }
43+
{ doc: item, sharedDate: this.findShareDate(item, viewId), avatar: this.findAvatar(item.user, avatarMap), _id: item._id }
3844
)));
3945
});
4046
}
4147

42-
findAvatar(user: any, attachments: any[]) {
48+
findAvatar(user: any, attachments: Map<string, any>) {
4349
const attachmentId = `${user._id}@${user.planetCode}`;
44-
const attachment = attachments.find(avatar => avatar._id === attachmentId);
50+
const attachment = attachments.get(attachmentId);
4551
const extractFilename = (object) => Object.keys(object._attachments)[0];
4652
return attachment ?
4753
`${this.imgUrlPrefix}/attachments/${attachmentId}/${extractFilename(attachment)}` :
@@ -54,6 +60,17 @@ export class NewsService {
5460
return ((item.viewIn || []).find(view => view._id === viewId) || {}).sharedDate;
5561
}
5662

63+
private collectAttachmentIds(newsItems: any[]): string[] {
64+
const ids = new Set<string>();
65+
newsItems.forEach((item: any) => {
66+
const user = item?.user;
67+
if (user && user._id && user.planetCode) {
68+
ids.add(`${user._id}@${user.planetCode}`);
69+
}
70+
});
71+
return Array.from(ids);
72+
}
73+
5774
postNews(post, successMessage = $localize`Thank you for submitting your message`, isMessageEdit = true) {
5875
const { configuration } = this.stateService;
5976
const message = typeof post.message === 'string' ? post.message : post.message.text;

src/app/shared/couchdb.service.ts

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Injectable } from '@angular/core';
22
import { HttpHeaders, HttpClient, HttpRequest } from '@angular/common/http';
33
import { environment } from '../../environments/environment';
4-
import { Observable, of, empty, throwError } from 'rxjs';
5-
import { catchError, map, expand, toArray, flatMap, switchMap } from 'rxjs/operators';
4+
import { Observable, of, empty, throwError, forkJoin } from 'rxjs';
5+
import { catchError, map, expand, toArray, flatMap, switchMap } from 'rxjs/operators';
66
import { debug } from '../debug-operator';
77
import { PlanetMessageService } from './planet-message.service';
88
import { findDocuments } from './mangoQueries';
@@ -97,15 +97,33 @@ export class CouchService {
9797
}));
9898
}
9999

100-
findAll(db: string, query: any = { 'selector': { '_id': { '$gt': null } }, 'limit': 1000 }, opts?: any) {
101-
return this.findAllRequest(db, query, opts).pipe(flatMap(({ docs }) => docs), toArray());
102-
}
103-
104-
findAllStream(db: string, query: any = { 'selector': { '_id': { '$gt': null } }, 'limit': 1000 }, opts?: any) {
105-
return this.findAllRequest(db, query, opts).pipe(map(({ docs }) => docs));
106-
}
107-
108-
private findAllRequest(db: string, query: any, opts: any) {
100+
findAll(db: string, query: any = { 'selector': { '_id': { '$gt': null } }, 'limit': 1000 }, opts?: any) {
101+
return this.findAllRequest(db, query, opts).pipe(flatMap(({ docs }) => docs), toArray());
102+
}
103+
104+
findAllStream(db: string, query: any = { 'selector': { '_id': { '$gt': null } }, 'limit': 1000 }, opts?: any) {
105+
return this.findAllRequest(db, query, opts).pipe(map(({ docs }) => docs));
106+
}
107+
108+
findAttachmentsByIds(ids: string[], opts?: any) {
109+
const uniqueIds = Array.from(new Set((ids || []).filter(id => !!id)));
110+
if (uniqueIds.length === 0) {
111+
return of([]);
112+
}
113+
const chunkSize = 50;
114+
const queries = [];
115+
for (let i = 0; i < uniqueIds.length; i += chunkSize) {
116+
const chunk = uniqueIds.slice(i, i + chunkSize);
117+
queries.push(
118+
this.findAll('attachments', findDocuments({ '_id': { '$in': chunk } }, 0, 0, chunk.length), opts)
119+
);
120+
}
121+
return forkJoin(queries).pipe(
122+
map((results: any[]) => results.reduce((acc: any[], docs: any[]) => acc.concat(docs), []))
123+
);
124+
}
125+
126+
private findAllRequest(db: string, query: any, opts: any) {
109127
return this.post(db + '/_find', query, opts).pipe(
110128
catchError(() => {
111129
return of({ docs: [], rows: [] });

0 commit comments

Comments
 (0)