Skip to content

Commit a98eb5d

Browse files
committed
Merge remote-tracking branch 'origin/master' into en
2 parents 5801264 + cf35afb commit a98eb5d

File tree

8 files changed

+184
-37
lines changed

8 files changed

+184
-37
lines changed

.eslintrc.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -736,8 +736,8 @@ module.exports = {
736736
'no-underscore-dangle': [2, { 'allow': [
737737
'_id', '__get__', '__set__', '__RewireAPI__', '__Rewire__', '__ResetDependency__', '__GetDependency__',
738738
] }],
739-
// Max assertions is 10 and warning rather than error.
740-
'jest/max-expects': [1, { 'max': 10 }],
739+
// Max assertions is 20 and warning rather than error.
740+
'jest/max-expects': [1, { 'max': 20 }],
741741
// We are not using TypeScript
742742
'jest/no-untyped-mock-factory': 0,
743743
},

controllers/__tests__/comment.test.js

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* Copyright: The PastVu contributors.
3+
* GNU Affero General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/agpl.txt)
4+
*/
5+
6+
import { CommentN } from '../../models/Comment';
7+
import admin from '../admin';
8+
import comment from '../comment';
9+
import testHelpers from '../../tests/testHelpers';
10+
11+
describe('comment', () => {
12+
beforeEach(async () => {
13+
// Mock non-registerd user handshake.
14+
admin.handshake = { 'usObj': { 'isAdmin': true } };
15+
});
16+
17+
afterEach(() => {
18+
// Delete handshake.
19+
delete admin.handshake;
20+
});
21+
22+
describe('create for news', () => {
23+
let news;
24+
25+
beforeEach(async () => {
26+
const data = { pdate: new Date(), 'title': 'Test news', 'txt': 'Test news content' };
27+
28+
({ news } = await admin.saveOrCreateNews(data));
29+
30+
const user = await testHelpers.createUser({ login: 'user1', pass: 'pass1' });
31+
32+
// Mock non-registered user handshake.
33+
comment.handshake = { 'usObj': { 'isAdmin': true, 'registered': true, user } };
34+
});
35+
36+
afterEach(() => {
37+
// Delete handshake.
38+
delete comment.handshake;
39+
});
40+
41+
it('create', async () => {
42+
expect.assertions(3);
43+
44+
const data = { txt: 'news comment', type: 'news', obj: news.cid };
45+
46+
// Create two comments.
47+
const result = await comment.create(data);
48+
49+
expect(result.comment.txt).toMatch(data.txt);
50+
expect(result.comment.user).toMatch('user1');
51+
52+
await expect(CommentN.count({ obj: news })).resolves.toBe(1);
53+
});
54+
});
55+
56+
describe('retrive', () => {
57+
let news;
58+
59+
beforeEach(async () => {
60+
const data = { pdate: new Date(), 'title': 'Test news', 'txt': 'Test news content' };
61+
62+
({ news } = await admin.saveOrCreateNews(data));
63+
64+
const user = await testHelpers.createUser({ login: 'user1', pass: 'pass1' });
65+
66+
// Mock non-registered user handshake.
67+
comment.handshake = { 'usObj': { 'isAdmin': true, 'registered': true, user } };
68+
});
69+
70+
afterEach(() => {
71+
// Delete handshake.
72+
delete comment.handshake;
73+
});
74+
75+
it('give news comments for user', async () => {
76+
expect.assertions(17);
77+
78+
const data = { txt: 'news comment', type: 'news', obj: news.cid };
79+
80+
// Create 4 comments.
81+
const comment0 = await comment.create(data);
82+
const comment1 = await comment.create(data);
83+
84+
data.parent = comment1.comment.cid;
85+
data.level = comment1.comment.level + 1;
86+
87+
const comment2 = await comment.create(data);
88+
89+
data.parent = comment2.comment.cid;
90+
data.level = comment2.comment.level + 1;
91+
92+
const comment3 = await comment.create(data);
93+
94+
// Sanity check.
95+
await expect(CommentN.count({ obj: news })).resolves.toBe(4);
96+
97+
const comments = await comment.giveForUser({ login: 'user1', type: 'news' });
98+
99+
expect(comments.type).toMatch('news');
100+
expect(comments.countActive).toBe(4);
101+
expect(comments.objs[news.cid].cid).toStrictEqual(news.cid);
102+
expect(comments.objs[news.cid].ccount).toBe(4);
103+
// Comment 0 - no child, waits answer.
104+
expect(comments.comments[3].cid).toStrictEqual(comment0.comment.cid);
105+
expect(comments.comments[3].hasChild).toBeFalsy();
106+
expect(comments.comments[3].waitsAnswer).toBeTruthy();
107+
// Comment 1 - has child, does not wait answer.
108+
expect(comments.comments[2].cid).toStrictEqual(comment1.comment.cid);
109+
expect(comments.comments[2].hasChild).toBeTruthy();
110+
expect(comments.comments[2].waitsAnswer).toBeFalsy();
111+
// Comment 2 - has child, does not wait answer.
112+
expect(comments.comments[1].cid).toStrictEqual(comment2.comment.cid);
113+
expect(comments.comments[1].hasChild).toBeTruthy();
114+
expect(comments.comments[1].waitsAnswer).toBeFalsy();
115+
// Comment 3 - no child, waits answer.
116+
expect(comments.comments[0].cid).toStrictEqual(comment3.comment.cid);
117+
expect(comments.comments[0].hasChild).toBeFalsy();
118+
expect(comments.comments[0].waitsAnswer).toBeTruthy();
119+
});
120+
});
121+
});

controllers/comment.js

+36-20
Original file line numberDiff line numberDiff line change
@@ -722,9 +722,36 @@ async function giveForUser({ login, page = 1, type = 'photo', active = true, del
722722
}
723723

724724
const fields = { _id: 0, lastChanged: 1, cid: 1, obj: 1, stamp: 1, txt: 1, 'del.origin': 1 };
725-
const options = { lean: true, sort: { stamp: -1 }, skip: page * commentsUserPerPage, limit: commentsUserPerPage };
725+
const options = { sort: { stamp: -1 }, skip: page * commentsUserPerPage, limit: commentsUserPerPage };
726726

727-
comments = await commentModel.find(query, fields, options).exec();
727+
if (!iAm.registered) {
728+
comments = await commentModel.find(query, fields, options).lean().exec();
729+
} else {
730+
fields.hasChild = 1;
731+
comments = await commentModel.aggregate([
732+
{
733+
'$match': query,
734+
},
735+
{
736+
'$lookup': {
737+
'from': commentModel.collection.collectionName,
738+
'localField': 'cid',
739+
'foreignField': 'parent',
740+
'as': 'children',
741+
},
742+
},
743+
{
744+
'$addFields': {
745+
'hasChild': {
746+
$gt: [{ $size: '$children' }, 0],
747+
},
748+
},
749+
},
750+
{
751+
'$project': fields,
752+
},
753+
]).sort(options.sort).skip(options.skip).limit(options.limit).exec();
754+
}
728755
}
729756

730757
if (_.isEmpty(comments)) {
@@ -757,18 +784,6 @@ async function giveForUser({ login, page = 1, type = 'photo', active = true, del
757784

758785
if (type === 'photo' && iAm.registered) {
759786
await this.call('photo.fillPhotosProtection', { photos: objs, setMyFlag: true });
760-
761-
for (const obj of objs) {
762-
objFormattedHashCid[obj.cid] = objFormattedHashId[obj._id] = obj;
763-
obj._id = undefined;
764-
obj.user = undefined;
765-
obj.mime = undefined;
766-
}
767-
} else {
768-
for (const obj of objs) {
769-
objFormattedHashCid[obj.cid] = objFormattedHashId[obj._id] = obj;
770-
obj._id = undefined;
771-
}
772787
}
773788

774789
for (const obj of objs) {
@@ -780,6 +795,9 @@ async function giveForUser({ login, page = 1, type = 'photo', active = true, del
780795

781796
// For each comment check object exists and assign to comment its cid
782797
for (const comment of comments) {
798+
// Mark those awaiting response.
799+
comment.waitsAnswer = comment.hasChild !== undefined && !comment.hasChild;
800+
783801
const obj = objFormattedHashId[comment.obj];
784802

785803
if (obj !== undefined) {
@@ -926,7 +944,7 @@ async function create(data) {
926944
throw obj.nocomments ? new NoticeError(constantsError.COMMENT_NOT_ALLOWED) : new AuthorizationError();
927945
}
928946

929-
if (data.parent && (!parent || parent.del || parent.level >= 9 || data.level !== (parent.level || 0) + 1)) {
947+
if (data.parent && (!parent || parent.del || parent.level >= 9 || data.level !== parent.level + 1)) {
930948
throw new NoticeError(constantsError.COMMENT_WRONG_PARENT);
931949
}
932950

@@ -952,9 +970,11 @@ async function create(data) {
952970
}
953971
}
954972

973+
comment.level = data.level ?? 0;
974+
955975
if (data.parent) {
956976
comment.parent = data.parent;
957-
comment.level = data.level;
977+
comment.level = data.level ?? parent.level + 1;
958978
}
959979

960980
if (fragAdded) {
@@ -998,10 +1018,6 @@ async function create(data) {
9981018
comment.obj = objCid;
9991019
comment.can = {};
10001020

1001-
if (comment.level === undefined) {
1002-
comment.level = 0;
1003-
}
1004-
10051021
session.emitUser({ usObj: iAm, excludeSocket: socket });
10061022
subscrController.commentAdded(obj._id, iAm.user, stamp);
10071023

public/js/lib/leaflet/extends/L.Google.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@
44
*/
55

66
// Google layer plugin wrapper. Loads Google Maps library with API key included.
7-
define(['Params', 'leaflet', 'leaflet-plugins/lru', 'leaflet-plugins/Leaflet.GoogleMutant'], function (P, L) {
7+
define(['Params', 'leaflet', 'leaflet-plugins/lru', 'leaflet-plugins/Leaflet.GoogleMutant'], function (P, L, lru) {
88
const keyParam = P.settings.publicApiKeys.googleMaps.length ? '&key=' + P.settings.publicApiKeys.googleMaps : '';
99
const url = 'https://maps.googleapis.com/maps/api/js?v=weekly&region=RU' + keyParam;
1010

1111
// Load Google Maps API library asynchronously.
12-
require(['async!' + url]);
13-
12+
L.GridLayer.GoogleMutant.addInitHook(function() {
13+
require(['async!' + url]);
14+
});
1415
return function (options) {
1516
options = options || {};
17+
options.lru = new lru.LRUMap(100); // Tile LRU cache.
1618

1719
return new L.GridLayer.GoogleMutant(options);
1820
};

public/js/lib/leaflet/plugins/Leaflet.GoogleMutant.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ this stuff is worth it, you can buy me a beer in return.
1111
*/
1212

1313
//import { LRUMap } from "./lru_map.js";
14-
// We have to use requirejs to load module, ES6 syntax won't work here.
15-
const { LRUMap } = require('leaflet-plugins/lru');
14+
// LRU is passed in options.
1615

1716
function waitForAPI(callback, context) {
1817
let checkCounter = 0,
@@ -47,7 +46,7 @@ L.GridLayer.GoogleMutant = L.GridLayer.extend({
4746

4847
// Couple data structures indexed by tile key
4948
this._tileCallbacks = {}; // Callbacks for promises for tiles that are expected
50-
this._lru = new LRUMap(100); // Tile LRU cache
49+
this._lru = options.lru; // Tile LRU cache
5150

5251
this._imagesPerTile = this.options.type === "hybrid" ? 2 : 1;
5352

public/style/comment/comments.less

-8
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
@import '../_vars.less';
22
@import '../bs/variables.less';
33
@import '../bs/mixins.less';
4-
@import '../bs/badges.less';
54

65
@headColor: #677A8F;
76
@headColorHover: @MainBlueColor;
@@ -136,13 +135,6 @@
136135

137136
.levelLooping(0, 58);
138137

139-
.badge {
140-
font-size: 11px;
141-
font-weight: normal;
142-
color: #f2f2f2;
143-
background-color: rgba(85, 85, 85, 80%);
144-
}
145-
146138
&.isnew {
147139
padding-left: 5px;
148140
border-width: 0 0 0 1px;

public/style/common.less

+9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@import '_vars.less';
22
@import 'fonts/fontU.less';
33
@import 'bs/bootstrap.less';
4+
@import 'bs/badges.less';
45

56
@-webkit-keyframes fadeIn {
67
0% { opacity: 0; }
@@ -480,6 +481,14 @@ body {
480481
}
481482
}
482483

484+
// Badges
485+
.badge-latest {
486+
font-size: 11px;
487+
font-weight: normal;
488+
color: #f2f2f2;
489+
background-color: rgba(85, 85, 85, 80%);
490+
}
491+
483492
// Tooltip
484493
.tltp {
485494
position: absolute;

views/module/user/comments.pug

+9-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@
4545
.dotDelimeter ·
4646
.commentChanged(title="Show history of changes", data-bind="text: ($data.del ? 'Removed at ' : 'Changed at ') + moment($data.lastChanged).calendar().toLowerCase(), click: function () {$parent.showHistory($data.obj.cid, $data.cid)}")
4747
// /ko
48+
//ko if: $data.waitsAnswer
49+
.dotDelimeter ·
50+
.badge.badge-latest Awaiting answer
51+
// /ko
4852
a.commentText(data-bind="attr: {href: $data.link}, html: $data.txt")
4953
| </script>
5054

@@ -58,6 +62,10 @@
5862
.dotDelimeter ·
5963
.commentChanged(title="Show history of changes", data-bind="text: ($data.del ? 'Removed at ' : 'Changed at ') + moment($data.lastChanged).calendar().toLowerCase(), click: function () {$parent.showHistory($data.obj.cid, $data.cid)}")
6064
// /ko
65+
//ko if: $data.waitsAnswer
66+
.dotDelimeter ·
67+
.badge.badge-latest Awaiting answer
68+
// /ko
6169
a.commentText(style="margin-left:29px", data-bind="attr: {href: $data.link}, html: $data.txt")
6270
| </script>
6371

@@ -70,4 +78,4 @@
7078
// /ko
7179
li.edge(data-bind="css: {disabled: !pageHasNext()}"): a(data-bind="attr: {href: pageUrl() + '/' + (page() + 1) + pageQuery()}", title="Next page") &raquo;
7280
li.edge(data-bind="css: {disabled: page() === pageLast()}"): a(data-bind="attr: {href: pageUrl() + '/' + pageLast() + pageQuery()}", title="Last page") &raquo;&raquo;
73-
| </script>
81+
| </script>

0 commit comments

Comments
 (0)