1 | var async = require('async');
|
2 | var _ = require('lodash');
|
3 | var fancyPage = require('apostrophe-fancy-page');
|
4 | var RSS = require('rss');
|
5 | var url = require('url');
|
6 | var absolution = require('absolution');
|
7 | var moment = require('moment');
|
8 | var util = require('util');
|
9 |
|
10 | module.exports = blog2;
|
11 |
|
12 | function blog2(options, callback) {
|
13 | return new blog2.Blog2(options, callback);
|
14 | }
|
15 |
|
16 | blog2.Blog2 = function(options, callback) {
|
17 | var self = this;
|
18 |
|
19 | options.modules = (options.modules || []).concat([ { dir: __dirname, name: 'blog-2' } ]);
|
20 |
|
21 | self.name = options.name || 'Blog2';
|
22 | self._apos = options.apos;
|
23 | self._action = '/apos-' + self._apos.cssName(self.name);
|
24 | self._app = options.app;
|
25 | self._pages = options.pages;
|
26 | self._schemas = options.schemas;
|
27 | self._options = options;
|
28 | self._perPage = options.perPage || 10;
|
29 | self._dateInSlug = (options.dateInSlug === undefined) ? true : options.dateInSlug;
|
30 | self.pieceName = options.pieceName || 'blogPost';
|
31 | self.pieceLabel = options.pieceLabel || 'Blog Post';
|
32 | self.pluralPieceLabel = options.pluralPieceLabel || self.pieceLabel + 's';
|
33 | self.indexName = options.indexName || 'blog';
|
34 | self.indexLabel = options.indexLabel || 'Blog';
|
35 | self.pluralIndexLabel = options.pluralIndexLabel || self.indexLabel + 's';
|
36 |
|
37 | self._browser = options.browser || {};
|
38 |
|
39 |
|
40 | self._apos.mixinModuleAssets(self, 'blog-2', __dirname, options);
|
41 |
|
42 |
|
43 |
|
44 | if (self._options.feed === undefined) {
|
45 | self._options.feed = {};
|
46 | }
|
47 | if (self._options.feed) {
|
48 | var defaultPrefix;
|
49 |
|
50 |
|
51 | if (self._options.site && self._options.site.title) {
|
52 |
|
53 | defaultPrefix = self._options.site.title + (self._options.feed.titleSeparator || ' – ');
|
54 | } else {
|
55 | defaultPrefix = '';
|
56 | }
|
57 | _.defaults(self._options.feed, {
|
58 |
|
59 | thumbnail: true,
|
60 |
|
61 |
|
62 | alternateThumbnail: true,
|
63 | titlePrefix: defaultPrefix
|
64 | });
|
65 | }
|
66 |
|
67 | self.setupIndexes = function() {
|
68 | self.indexes = {};
|
69 |
|
70 | var indexesOptions = options.indexes || {};
|
71 | self.indexes.options = indexesOptions;
|
72 |
|
73 | _.defaults(indexesOptions, {
|
74 | name: self.indexName,
|
75 | label: self.indexLabel,
|
76 | pluralLabel: self.pluralIndexLabel,
|
77 | apos: options.apos,
|
78 | app: options.app,
|
79 | pages: options.pages,
|
80 | schemas: options.schemas,
|
81 | modules: options.modules,
|
82 |
|
83 | greedy: true,
|
84 | browser: {
|
85 | baseConstruct: 'AposFancyPage',
|
86 |
|
87 |
|
88 | afterYield: true,
|
89 | options: {}
|
90 | },
|
91 | pageSettingsTemplate: 'indexPageSettings',
|
92 |
|
93 |
|
94 |
|
95 |
|
96 |
|
97 | });
|
98 | indexesOptions.addFields = [
|
99 | {
|
100 | name: '_andFromPages',
|
101 | label: 'And From These Blogs',
|
102 | type: 'joinByArray',
|
103 | idsField: 'andFromPagesIds',
|
104 | relationship: [
|
105 | {
|
106 | name: 'tag',
|
107 | label: 'With this tag (optional)',
|
108 | type: 'string',
|
109 | }
|
110 | ],
|
111 | relationshipsField: 'andFromPagesRelationships',
|
112 | withType: 'blog'
|
113 | }
|
114 | ].concat(indexesOptions.addFields || []);
|
115 |
|
116 | _.defaults(indexesOptions, {
|
117 |
|
118 |
|
119 | contextMenu: [
|
120 | {
|
121 | name: 'new-' + self._apos.cssName(self.pieceName),
|
122 | label: 'New ' + self.pieceLabel
|
123 | },
|
124 | ].concat(options.allowSubpagesOfIndex ?
|
125 | [
|
126 | {
|
127 | name: 'new-page',
|
128 | label: 'New Page'
|
129 | }
|
130 | ] :
|
131 | []
|
132 | ).concat([
|
133 | {
|
134 | name: 'edit-page',
|
135 | label: self.indexLabel + ' Settings'
|
136 | },
|
137 | {
|
138 | name: 'versions-page',
|
139 | label: 'Page Versions'
|
140 | },
|
141 | {
|
142 | name: 'rescue-' + self._apos.cssName(self.pieceName),
|
143 | label: 'Browse Trash'
|
144 | },
|
145 | {
|
146 | name: 'delete-page',
|
147 | label: 'Move Entire ' + self.indexLabel + ' to Trash'
|
148 | }
|
149 |
|
150 |
|
151 | ])
|
152 | });
|
153 | fancyPage.FancyPage.call(self.indexes, indexesOptions, null);
|
154 |
|
155 |
|
156 |
|
157 | self.indexes.dispatch = function(req, callback) {
|
158 | var criteria = {};
|
159 | var options = {};
|
160 | var results;
|
161 | self.addPager(req, options);
|
162 | self.addCriteria(req, criteria, options);
|
163 | return async.series({
|
164 | get: function(callback) {
|
165 | return self.pieces.get(req, criteria, options, function(err, _results) {
|
166 | if (err) {
|
167 | return callback(err);
|
168 | }
|
169 | results = _results;
|
170 |
|
171 | return callback(null);
|
172 | });
|
173 | },
|
174 | tags: function(callback) {
|
175 | if (self.pieces.options.tags === false) {
|
176 | return setImmediate(callback);
|
177 | }
|
178 | var distinctOptions = _.cloneDeep(options);
|
179 | distinctOptions.getDistinct = 'tags';
|
180 | return self.pieces.get(req, criteria, distinctOptions, function(err, tags) {
|
181 | if (err) {
|
182 | return callback(err);
|
183 | }
|
184 |
|
185 |
|
186 |
|
187 | req.extras.piecesTags = tags;
|
188 | return callback(null);
|
189 | });
|
190 | }
|
191 | }, function(err) {
|
192 | if (err) {
|
193 | return callback(err);
|
194 | }
|
195 | self.setPagerTotal(req, results.total);
|
196 | return self.index(req, results.pages, callback);
|
197 | });
|
198 | };
|
199 | };
|
200 |
|
201 |
|
202 |
|
203 |
|
204 |
|
205 |
|
206 | self.addPager = function(req, options) {
|
207 | var pageNumber = self._apos.sanitizeInteger(req.query.page, 1, 1);
|
208 | req.extras.pager = {
|
209 | page: pageNumber
|
210 | };
|
211 | if (req.query.feed) {
|
212 |
|
213 |
|
214 |
|
215 |
|
216 | options.skip = 0;
|
217 | options.limit = self._options.feed.limit || 50;
|
218 | return;
|
219 | }
|
220 | options.skip = self._perPage * (pageNumber - 1);
|
221 | options.limit = self._perPage;
|
222 | };
|
223 |
|
224 | self.setPagerTotal = function(req, total) {
|
225 | req.extras.pager.total = Math.ceil(total / self._perPage);
|
226 | if (req.extras.pager.total < 1) {
|
227 | req.extras.pager.total = 1;
|
228 | }
|
229 | };
|
230 |
|
231 |
|
232 |
|
233 |
|
234 | self.setIndexTemplate = function(req) {
|
235 | if (req.query.feed && self._options.feed) {
|
236 |
|
237 | req.decorate = false;
|
238 | req.contentType = self.feedContentType(req.query.feed);
|
239 | req.template = self.renderFeed;
|
240 | } else {
|
241 | if ((req.xhr || req.query.xhr) && (!req.query.apos_refresh)) {
|
242 | req.template = self.renderer('indexAjax');
|
243 | } else {
|
244 | req.template = self.renderer('index');
|
245 | }
|
246 | }
|
247 | };
|
248 |
|
249 |
|
250 |
|
251 | self.index = function(req, pieces, callback) {
|
252 |
|
253 |
|
254 |
|
255 | if (req.xhr && (req.query.page > 1) && (!pieces.length)) {
|
256 | req.notfound = true;
|
257 | return callback(null);
|
258 | }
|
259 | self.setIndexTemplate(req);
|
260 |
|
261 | req.extras.pieces = pieces;
|
262 | return self.beforeIndex(req, pieces, callback);
|
263 | };
|
264 |
|
265 |
|
266 |
|
267 |
|
268 |
|
269 | self.beforeIndex = function(req, pieces, callback) {
|
270 | return callback(null);
|
271 | };
|
272 |
|
273 |
|
274 |
|
275 |
|
276 | self.feedContentType = function(feed) {
|
277 | return 'application/rss+xml';
|
278 | };
|
279 |
|
280 |
|
281 |
|
282 |
|
283 | self.renderFeed = function(data, req) {
|
284 |
|
285 | var feedOptions = {
|
286 | title: self._options.feed.title || ((self._options.feed.titlePrefix || '') + data.page.title),
|
287 | description: self._options.feed.description,
|
288 | generator: self._options.feed.generator || 'Apostrophe 2',
|
289 | feed_url: req.absoluteUrl,
|
290 |
|
291 | site_url: self._apos.build(req.absoluteUrl, { feed: null }),
|
292 | image_url: self._options.feed.imageUrl
|
293 | };
|
294 | _.defaults(feedOptions, {
|
295 | description: feedOptions.title
|
296 | });
|
297 | var feed = new RSS(feedOptions);
|
298 | _.each(data.pieces, function(piece) {
|
299 | feed.item(self.renderFeedPiece(piece, req));
|
300 | });
|
301 | return feed.xml(' ');
|
302 | };
|
303 |
|
304 |
|
305 | self.renderFeedPiece = function(piece, req) {
|
306 | var feedPiece = {
|
307 | title: piece.title,
|
308 | description: self.renderFeedPieceDescription(piece, req),
|
309 |
|
310 | url: url.resolve(req.absoluteUrl, piece.url),
|
311 | guid: piece._id,
|
312 | author: piece.author || piece._author || undefined,
|
313 |
|
314 |
|
315 | date: piece.publishedAt || piece.start || piece.createdAt
|
316 | };
|
317 | return feedPiece;
|
318 | };
|
319 |
|
320 | |
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 | self.renderFeedPieceDescription = function(piece, req) {
|
330 |
|
331 |
|
332 | var result = self.renderer('feedPiece')({
|
333 | page: req.page,
|
334 | piece: piece,
|
335 | url: req.absoluteUrl,
|
336 | options: self._options.feed
|
337 | });
|
338 |
|
339 |
|
340 | result = absolution(result, req.absoluteUrl).trim();
|
341 | return result;
|
342 | };
|
343 |
|
344 |
|
345 |
|
346 |
|
347 |
|
348 | self.addCriteria = function(req, criteria, options) {
|
349 | if (req.query.tag) {
|
350 | options.tags = self._apos.sanitizeTags([ req.query.tag ]);
|
351 | }
|
352 | if (req.query.tags) {
|
353 | options.tags = self._apos.sanitizeTags(req.query.tags) ;
|
354 | }
|
355 |
|
356 | self.addDateCriteria(req, criteria, options);
|
357 |
|
358 | options.fromPages = [ req.page ];
|
359 |
|
360 | if (req.query.search) {
|
361 | options.search = self._apos.sanitizeString(req.query.search);
|
362 | }
|
363 |
|
364 |
|
365 |
|
366 |
|
367 | };
|
368 |
|
369 |
|
370 |
|
371 |
|
372 | self.addDateCriteria = function(req, criteria, options) {
|
373 | if (req.remainder.length) {
|
374 |
|
375 | matches = req.remainder.match(/^\/(\d+)\/(\d+)$/);
|
376 |
|
377 |
|
378 |
|
379 |
|
380 |
|
381 |
|
382 |
|
383 |
|
384 |
|
385 | if (matches) {
|
386 |
|
387 | req.extras.activeYear = matches[1];
|
388 | req.extras.activeMonth = matches[2];
|
389 |
|
390 | var nextYear = req.extras.activeYear;
|
391 |
|
392 | var nextMonth = req.extras.activeMonth - 0 + 1;
|
393 | if (nextMonth > 12) {
|
394 | nextMonth = 1;
|
395 | nextYear = req.extras.activeYear - 0 + 1;
|
396 | }
|
397 | nextMonth = pad(nextMonth, 2);
|
398 | req.extras.nextYear = nextYear;
|
399 | req.extras.nextMonth = nextMonth;
|
400 |
|
401 | var prevYear = req.extras.activeYear;
|
402 | var prevMonth = req.extras.activeMonth - 0 - 1;
|
403 | if (prevMonth < 1) {
|
404 | prevMonth = 12;
|
405 | prevYear = req.extras.activeYear - 0 - 1;
|
406 | }
|
407 | prevMonth = pad(prevMonth, 2);
|
408 | req.extras.prevYear = prevYear;
|
409 | req.extras.prevMonth = prevMonth;
|
410 |
|
411 | req.remainder = '';
|
412 | }
|
413 | } else {
|
414 |
|
415 | req.extras.defaultView = true;
|
416 |
|
417 | var now = moment(new Date());
|
418 | req.extras.thisYear = now.format('YYYY');
|
419 | req.extras.thisMonth = now.format('MM');
|
420 | }
|
421 | if (req.extras.activeYear) {
|
422 |
|
423 | var year = req.extras.activeYear - 0;
|
424 |
|
425 | var month = req.extras.activeMonth - 1;
|
426 |
|
427 | criteria.publishedAt = { $gte: new Date(year, month, 1), $lt: new Date(year, month + 1, 1) };
|
428 |
|
429 | options.sort = { publishedAt: 1 };
|
430 | }
|
431 | function pad(s, n) {
|
432 | return self._apos.padInteger(s, n);
|
433 | }
|
434 | };
|
435 |
|
436 |
|
437 |
|
438 |
|
439 |
|
440 | self.beforeShow = function(req, page, callback) {
|
441 | return callback(null);
|
442 | };
|
443 |
|
444 | self.setupPieces = function() {
|
445 | self.pieces = {};
|
446 |
|
447 | var piecesOptions = options.pieces || {};
|
448 | self.pieces.options = piecesOptions;
|
449 |
|
450 | _.defaults(piecesOptions, {
|
451 | name: self.pieceName,
|
452 | label: self.pieceLabel,
|
453 | pluralLabel: self.pluralPieceLabel,
|
454 | apos: options.apos,
|
455 | app: options.app,
|
456 | pages: options.pages,
|
457 | schemas: options.schemas,
|
458 | modules: options.modules,
|
459 |
|
460 | orphan: true,
|
461 | browser: {
|
462 | baseConstruct: 'AposFancyPage',
|
463 |
|
464 |
|
465 | afterYield: true
|
466 | },
|
467 | pageSettingsTemplate: 'piecePageSettings',
|
468 |
|
469 |
|
470 |
|
471 |
|
472 |
|
473 | });
|
474 |
|
475 | piecesOptions.addFields = [
|
476 | {
|
477 |
|
478 | after: 'published',
|
479 | name: 'publicationDate',
|
480 | label: 'Publication Date',
|
481 | type: 'date'
|
482 | },
|
483 | {
|
484 | name: 'publicationTime',
|
485 | label: 'Publication Time',
|
486 | type: 'time'
|
487 | },
|
488 | {
|
489 | name: 'body',
|
490 | type: 'area',
|
491 | label: 'Body',
|
492 |
|
493 |
|
494 | contextual: true
|
495 | },
|
496 |
|
497 |
|
498 |
|
499 |
|
500 | {
|
501 | name: '_parent',
|
502 | type: 'joinByOne',
|
503 | label: 'Move to Another Blog',
|
504 | placeholder: 'Type the name of the blog',
|
505 | withType: self.indexName,
|
506 | idField: '_newParentId',
|
507 | getOptions: {
|
508 | editable: true
|
509 | }
|
510 | }
|
511 | ].concat(piecesOptions.addFields || []);
|
512 |
|
513 | _.defaults(piecesOptions, {
|
514 |
|
515 |
|
516 |
|
517 | contextMenu: [
|
518 | {
|
519 | name: 'new-' + self._apos.cssName(piecesOptions.name),
|
520 | label: 'New ' + piecesOptions.label
|
521 | },
|
522 | {
|
523 | name: 'edit-page',
|
524 | label: piecesOptions.label + ' Settings'
|
525 | },
|
526 | {
|
527 | name: 'versions-page',
|
528 | label: piecesOptions.label + ' Versions'
|
529 | },
|
530 | {
|
531 | name: 'delete-' + self._apos.cssName(self.pieceName),
|
532 | label: 'Move to Trash'
|
533 | },
|
534 | {
|
535 | name: 'rescue-' + self._apos.cssName(self.pieceName),
|
536 | label: 'Rescue ' + self.pieceLabel + ' From Trash'
|
537 | }
|
538 | ]
|
539 | });
|
540 | fancyPage.FancyPage.call(self.pieces, piecesOptions, null);
|
541 |
|
542 |
|
543 |
|
544 |
|
545 |
|
546 |
|
547 | self.aggregateCriteria = function(req, _pages, callback) {
|
548 |
|
549 |
|
550 | pages = _.clone(_pages);
|
551 |
|
552 | var done = false;
|
553 |
|
554 | var fetched = {};
|
555 | var tagsByPageId = {};
|
556 | var recursed = {};
|
557 |
|
558 | _.each(pages, function(page) {
|
559 | fetched[page._id] = true;
|
560 | if (page._andFromPages) {
|
561 | _.each(page._andFromPages || [], function(pair) {
|
562 | pages.push(pair.item);
|
563 | fetched[pair.item._id] = true;
|
564 | });
|
565 | recursed[page._id] = true;
|
566 | }
|
567 | });
|
568 |
|
569 | return async.whilst(
|
570 | function() { return !done; },
|
571 | function(callback) {
|
572 | var ids = [];
|
573 | _.each(pages, function(page) {
|
574 | if (_.has(recursed, page._id)) {
|
575 | return;
|
576 | }
|
577 | recursed[page._id] = true;
|
578 | _.each(page.andFromPagesIds || [], function(id) {
|
579 | if (_.has(fetched, id)) {
|
580 | return;
|
581 | }
|
582 | ids.push(id);
|
583 | fetched[id] = true;
|
584 | });
|
585 | });
|
586 | if (!ids.length) {
|
587 | done = true;
|
588 | return setImmediate(callback);
|
589 | }
|
590 | return self.indexes.get(req, { _id: { $in: ids } }, { fields: { title: 1, slug: 1, path: 1, level: 1, rank: 1, andFromPagesIds: 1, andFromPagesRelationships: 1 }, withJoins: false }, function(err, results) {
|
591 | if (err) {
|
592 | return callback(err);
|
593 | }
|
594 | pages = pages.concat(results.pages);
|
595 | return callback(null);
|
596 | });
|
597 | },
|
598 | function(err) {
|
599 | if (err) {
|
600 | return callback(err);
|
601 | }
|
602 |
|
603 |
|
604 |
|
605 |
|
606 |
|
607 |
|
608 |
|
609 |
|
610 |
|
611 | var pagesById = {};
|
612 | var visited = {};
|
613 | _.each(pages, function(page) {
|
614 | pagesById[page._id] = page;
|
615 | });
|
616 |
|
617 |
|
618 |
|
619 | _.each(_pages, function(page) {
|
620 | recurseTags(page, [], []);
|
621 | });
|
622 |
|
623 | function recurseTags(page, tags, antecedents) {
|
624 | if (_.contains(antecedents, page._id)) {
|
625 |
|
626 |
|
627 |
|
628 |
|
629 |
|
630 | return;
|
631 | }
|
632 | _.each(page.andFromPagesIds || [], function(id) {
|
633 | var _page = pagesById[id];
|
634 | if (_page) {
|
635 | var rel = ((page.andFromPagesRelationships || {})[id]) || {};
|
636 | var newTags;
|
637 | if (rel.tag) {
|
638 |
|
639 |
|
640 |
|
641 |
|
642 | newTags = [ self._apos.filterTag(rel.tag) ];
|
643 | } else {
|
644 | newTags = [];
|
645 | }
|
646 | recurseTags(pagesById[id], tags.concat(newTags), antecedents.concat(page._id));
|
647 | }
|
648 | });
|
649 | if (!tagsByPageId[page._id]) {
|
650 | tagsByPageId[page._id] = [];
|
651 | }
|
652 | tagsByPageId[page._id].push(tags);
|
653 | }
|
654 |
|
655 |
|
656 |
|
657 | var clauses = [];
|
658 | _.each(pages, function(page) {
|
659 | var clause = {
|
660 | path: new RegExp('^' + RegExp.quote(page.path + '/')),
|
661 | level: page.level + 1
|
662 | };
|
663 | var tagPaths = tagsByPageId[page._id];
|
664 | if (tagPaths) {
|
665 | if (_.find(tagPaths, function(tagPath) {
|
666 | return !tagPath.length;
|
667 | })) {
|
668 |
|
669 |
|
670 | } else {
|
671 |
|
672 |
|
673 |
|
674 |
|
675 | _.each(tagPaths, function(tagPath) {
|
676 | clause.tags = { $all: tagPath };
|
677 | });
|
678 | }
|
679 | }
|
680 | clauses.push(clause);
|
681 | });
|
682 | return callback(null, clauses);
|
683 | }
|
684 | );
|
685 | };
|
686 |
|
687 | var superPiecesGet = self.pieces.get;
|
688 |
|
689 |
|
690 |
|
691 |
|
692 |
|
693 |
|
694 |
|
695 |
|
696 |
|
697 |
|
698 |
|
699 |
|
700 |
|
701 |
|
702 |
|
703 |
|
704 |
|
705 |
|
706 |
|
707 | self.pieces.get = function(req, userCriteria, options, callback) {
|
708 | var criteria;
|
709 | var filterCriteria = {};
|
710 |
|
711 | if (options.fromPageIds) {
|
712 |
|
713 |
|
714 |
|
715 |
|
716 | var page = {};
|
717 | if (!options.fromPageIds.length) {
|
718 | filterCriteria = { _never: true };
|
719 | } else {
|
720 | var ids = options.fromPageIds;
|
721 | if (typeof(ids[0]) === 'object') {
|
722 | page.andFromPagesIds = _.pluck(ids, 'value');
|
723 | var relationships = {};
|
724 | page.andFromPagesRelationships = relationships;
|
725 | _.each(ids, function(id) {
|
726 | relationships[id.value] = { tag: id.tag };
|
727 | });
|
728 | } else {
|
729 | page.andFromPagesIds = ids;
|
730 | page.andFromPagesRelationships = {};
|
731 | }
|
732 | options.fromPages = [ page ];
|
733 | }
|
734 | }
|
735 |
|
736 | var results;
|
737 |
|
738 | return async.series({
|
739 | fromPages: function(callback) {
|
740 | if (!options.fromPages) {
|
741 | return setImmediate(callback);
|
742 | }
|
743 | return self.aggregateCriteria(req, options.fromPages, function(err, clauses) {
|
744 | if (err) {
|
745 | return callback(err);
|
746 | }
|
747 | if (clauses.length) {
|
748 | filterCriteria.$or = clauses;
|
749 | }
|
750 | return callback(null);
|
751 | });
|
752 | },
|
753 | get: function(callback) {
|
754 |
|
755 |
|
756 |
|
757 | if (options.publishedAt === 'any') {
|
758 |
|
759 |
|
760 | } else {
|
761 | filterCriteria.publishedAt = { $lte: new Date() };
|
762 | }
|
763 |
|
764 | if (!options.sort) {
|
765 | options.sort = { publishedAt: -1 };
|
766 | }
|
767 |
|
768 | criteria = {
|
769 | $and: [
|
770 | userCriteria,
|
771 | filterCriteria
|
772 | ]
|
773 | };
|
774 | return superPiecesGet(req, criteria, options, function(err, _results) {
|
775 | if (err) {
|
776 | return callback(err);
|
777 | }
|
778 | results = _results;
|
779 | return callback(null);
|
780 | });
|
781 | }
|
782 | }, function(err) {
|
783 | if (err) {
|
784 | return callback(err);
|
785 | }
|
786 | return callback(null, results);
|
787 | });
|
788 | };
|
789 |
|
790 | self.pieces.dispatch = function(req, callback) {
|
791 | req.template = self.renderer('show');
|
792 | return callback(null);
|
793 | };
|
794 |
|
795 |
|
796 |
|
797 |
|
798 |
|
799 |
|
800 | self.pieces.beforePutOne = function(req, slug, options, piece, callback) {
|
801 |
|
802 |
|
803 |
|
804 |
|
805 | piece.orphan = true;
|
806 |
|
807 |
|
808 |
|
809 |
|
810 | piece.reorganize = false;
|
811 |
|
812 | if (piece.publicationTime === null) {
|
813 |
|
814 |
|
815 | piece.publishedAt = new Date(piece.publicationDate + ' 00:00:00');
|
816 | } else {
|
817 | piece.publishedAt = new Date(piece.publicationDate + ' ' + piece.publicationTime);
|
818 | }
|
819 |
|
820 | if (self._dateInSlug) {
|
821 | var datePath = piece.publicationDate.replace(/-/g, '/');
|
822 | var dateSlugRegex = /\/(\d\d\d\d\/\d\d\/\d\d\/)?([^\/]+)$/;
|
823 | var matches = piece.slug.match(dateSlugRegex);
|
824 | if (!matches) {
|
825 |
|
826 | console.error("I don't understand how to add a date to this slug: " + piece.slug);
|
827 | } else {
|
828 | piece.slug = piece.slug.replace(dateSlugRegex, '/' + datePath + '/' + matches[2]);
|
829 | }
|
830 | }
|
831 |
|
832 | return callback(null);
|
833 | };
|
834 |
|
835 |
|
836 |
|
837 | self.pieces.afterPutOne = function(req, slug, options, piece, callback) {
|
838 | var newParent;
|
839 | return async.series({
|
840 | getNewParent: function(callback) {
|
841 | if (!piece._newParentId) {
|
842 | return callback(null);
|
843 | }
|
844 | return self.indexes.getOne(req, { _id: piece._newParentId }, {}, function(err, page) {
|
845 | if (err) {
|
846 | return callback(err);
|
847 | }
|
848 | newParent = page;
|
849 | return callback(null);
|
850 | });
|
851 | },
|
852 | move: function(callback) {
|
853 | if (!newParent) {
|
854 | return callback(null);
|
855 | }
|
856 | return self._pages.move(req, piece, newParent, 'inside', callback);
|
857 | }
|
858 | }, callback);
|
859 | };
|
860 | };
|
861 |
|
862 |
|
863 | self.loader = function(req, callback) {
|
864 | return async.series({
|
865 | indexes: function(callback) {
|
866 | return self.indexes.loader(req, function(err) {
|
867 | return callback(err);
|
868 | });
|
869 | },
|
870 | pieces: function(callback) {
|
871 | return self.pieces.loader(req, function(err) {
|
872 | return callback(err);
|
873 | });
|
874 | }
|
875 | }, callback);
|
876 | };
|
877 |
|
878 | self.setupIndexes();
|
879 | self.setupPieces();
|
880 |
|
881 |
|
882 | var widgetOptions = {};
|
883 | if (self._options.widget === false) {
|
884 | widgetOptions = false;
|
885 | } else if (typeof(self._options.widget) === 'object') {
|
886 | widgetOptions = self._options.widget;
|
887 | }
|
888 |
|
889 |
|
890 | var args = {
|
891 | name: self.name,
|
892 | pieceName: self.pieceName,
|
893 | pieceLabel: self.pieceLabel,
|
894 | indexName: self.indexName,
|
895 | indexLabel: self.indexLabel,
|
896 | action: self._action,
|
897 | widget: widgetOptions
|
898 | };
|
899 |
|
900 |
|
901 |
|
902 |
|
903 | self._apos.pushGlobalCallWhen('user', 'AposBlog2.subclassIfNeeded(?, ?, ?)', getBrowserConstructor(), getBaseBrowserConstructor(), args);
|
904 | self._apos.pushGlobalCallWhen('user', '@ = new @(?)', getBrowserInstance(), getBrowserConstructor(), args);
|
905 |
|
906 | function getBrowserInstance() {
|
907 | if (self._browser.instance) {
|
908 | return self._browser.instance;
|
909 | }
|
910 | var c = getBrowserConstructor();
|
911 | return c.charAt(0).toLowerCase() + c.substr(1);
|
912 | }
|
913 |
|
914 | function getBrowserConstructor() {
|
915 | return self._browser.construct || 'Apos' + self.name.charAt(0).toUpperCase() + self.name.substr(1);
|
916 | }
|
917 |
|
918 |
|
919 |
|
920 | function getBaseBrowserConstructor() {
|
921 | return self._browser.baseConstruct || 'AposBlogManager';
|
922 | }
|
923 |
|
924 | if (widgetOptions) {
|
925 |
|
926 |
|
927 | var widget = {
|
928 | _manager: self
|
929 | };
|
930 | (function(options) {
|
931 | var self = widget;
|
932 | self._apos = self._manager._apos;
|
933 | self.icon = options.icon || 'icon-blog';
|
934 | self.name = options.name || self._manager.indexes.name;
|
935 | self.label = options.label || self._manager.indexes.label;
|
936 | self.widget = true;
|
937 | self.css = self._apos.cssName(self.name);
|
938 |
|
939 |
|
940 | var titleField = _.find(self._manager.pieces.schema, function(field) {
|
941 | return field.name === 'title';
|
942 | }) || { label: 'Title' };
|
943 |
|
944 | var widgetData = {
|
945 | widgetEditorClass: 'apos-' + self.css + '-widget-editor',
|
946 | pieceLabel: self._manager.pieces.label,
|
947 | pluralPieceLabel: self._manager.pieces.pluralLabel,
|
948 | indexLabel: self._manager.indexes.label,
|
949 | pluralIndexLabel: self._manager.indexes.pluralLabel,
|
950 | titleLabel: titleField.label
|
951 | };
|
952 |
|
953 |
|
954 | self._manager.pushAsset('template', 'widgetEditor', {
|
955 | when: 'user',
|
956 | data: widgetData
|
957 | });
|
958 |
|
959 |
|
960 |
|
961 |
|
962 |
|
963 | self.addCriteria = function(item, criteria, options) {
|
964 | if (item.by === 'tag') {
|
965 | if (item.tags && item.tags.length) {
|
966 | criteria.tags = { $in: item.tags };
|
967 | }
|
968 | if (item.limitByTag) {
|
969 | options.limit = item.limitByTag;
|
970 | } else {
|
971 | options.limit = 5;
|
972 | }
|
973 | } else if (item.by === 'id') {
|
974 |
|
975 | criteria._id = { $in: item.ids || []};
|
976 | } else if (item.by === 'fromPageIds') {
|
977 | if (item.limitFromPageIds) {
|
978 | options.limit = item.limitFromPageIds;
|
979 | } else {
|
980 | options.limit = 5;
|
981 | }
|
982 | if (item.fromPageIds && item.fromPageIds.length) {
|
983 | options.fromPageIds = item.fromPageIds;
|
984 | }
|
985 | }
|
986 | };
|
987 |
|
988 | self.sanitize = function(item) {
|
989 | item.by = self._apos.sanitizeSelect(item.by, [ 'id', 'tag', 'fromPageIds' ], 'fromPageIds');
|
990 | item.tags = self._apos.sanitizeTags(item.tags);
|
991 | item.ids = self._apos.sanitizeIds(item.ids);
|
992 | var fromPageIds = [];
|
993 | if (Array.isArray(item.fromPageIds)) {
|
994 | _.each(item.fromPageIds, function(pageId) {
|
995 | if (typeof(pageId) === 'object') {
|
996 | fromPageIds.push(_.pick(pageId, [ 'value', 'tag' ]));
|
997 | } else {
|
998 | fromPageIds.push(apos.sanitizeId(pageid));
|
999 | }
|
1000 | });
|
1001 | item.fromPageIds = fromPageIds;
|
1002 | }
|
1003 | item.limit = self._apos.sanitizeInteger(item.limit, 5, 1, 1000);
|
1004 | };
|
1005 |
|
1006 | self.renderWidget = function(data) {
|
1007 | return self._manager.render('widget', data);
|
1008 | };
|
1009 |
|
1010 | self.addDiffLines = function(item, lines) {
|
1011 | if (item.by === 'id') {
|
1012 | lines.push(self.label + ': items selected: ' + ((item.ids && item.ids.length) || 0));
|
1013 | } else if (item.by === 'tag') {
|
1014 | lines.push(self.label + ': tags selected: ' + item.tags.join(', '));
|
1015 | } else if (item.by === 'fromPageIds') {
|
1016 | lines.push(self.label + ': sources selected: ' + ((item.fromPageIds && item.fromPageIds.length) || 0));
|
1017 | }
|
1018 | };
|
1019 |
|
1020 |
|
1021 |
|
1022 |
|
1023 |
|
1024 | self.load = function(req, item, callback) {
|
1025 | var criteria = {};
|
1026 | var options = {};
|
1027 |
|
1028 | self.addCriteria(item, criteria, options);
|
1029 |
|
1030 | return self._manager.pieces.get(req, criteria, options, function(err, results) {
|
1031 | if (err) {
|
1032 | item._pieces = [];
|
1033 | console.error(err);
|
1034 | return callback(err);
|
1035 | }
|
1036 | var pieces = results.pages;
|
1037 | if (item.by === 'id') {
|
1038 | pieces = self._apos.orderById(item.ids, pieces);
|
1039 | }
|
1040 | item._pieces = pieces;
|
1041 | return callback(null);
|
1042 | });
|
1043 | };
|
1044 |
|
1045 | self.empty = function(item) {
|
1046 | return (!item._pieces) || (!item._pieces.length);
|
1047 | };
|
1048 |
|
1049 |
|
1050 | })(widgetOptions);
|
1051 |
|
1052 |
|
1053 |
|
1054 | self._apos.defaultControls.push(widget.name);
|
1055 | self._apos.addWidgetType(widget.name, widget);
|
1056 |
|
1057 |
|
1058 |
|
1059 | self.extendWidget = function(widget) {
|
1060 | };
|
1061 |
|
1062 |
|
1063 |
|
1064 | process.nextTick(function() {
|
1065 | self.extendWidget(widget);
|
1066 | });
|
1067 | }
|
1068 |
|
1069 | self._apos.on('beforeEndAssets', function() {
|
1070 | self.pushAllAssets();
|
1071 | });
|
1072 |
|
1073 | self.pushAllAssets = function() {
|
1074 | self.pushAsset('script', 'manager', {
|
1075 | when: 'user',
|
1076 | data: {
|
1077 | pieceName: self.pieceName,
|
1078 | pieceLabel: self.pieceLabel
|
1079 | }
|
1080 | });
|
1081 | self.pushAsset('template', 'browseTrash', {
|
1082 | when: 'user',
|
1083 | data: {
|
1084 | browseTrashClass: 'apos-browse-trash-' + self._apos.cssName(self.pieceName),
|
1085 | pluralLabel: self.pieces.pluralLabel
|
1086 | }
|
1087 | });
|
1088 | };
|
1089 |
|
1090 |
|
1091 |
|
1092 |
|
1093 |
|
1094 | self._app.get(self._action + '/get', function(req, res) {
|
1095 | var criteria = {};
|
1096 | var options = {};
|
1097 | self.addApiCriteria(req.query, criteria, options);
|
1098 | self.pieces.get(req, criteria, options, function(err, results) {
|
1099 | if (err) {
|
1100 | console.error(err);
|
1101 | return res.send({ status: 'error' });
|
1102 | }
|
1103 | results.status = 'ok';
|
1104 | return res.send(results);
|
1105 | });
|
1106 | });
|
1107 |
|
1108 | self.addApiCriteria = function(queryArg, criteria, options) {
|
1109 |
|
1110 |
|
1111 |
|
1112 |
|
1113 |
|
1114 | var query = _.cloneDeep(queryArg);
|
1115 |
|
1116 | var slug = self._apos.sanitizeString(query.slug);
|
1117 | if (slug.length) {
|
1118 | criteria.slug = query.slug;
|
1119 |
|
1120 | delete query.slug;
|
1121 | }
|
1122 |
|
1123 | var _id = self._apos.sanitizeString(query._id);
|
1124 | if (_id.length) {
|
1125 | criteria._id = query._id;
|
1126 |
|
1127 | delete query._id;
|
1128 | }
|
1129 |
|
1130 |
|
1131 | _.extend(options, query);
|
1132 |
|
1133 |
|
1134 | if (options.skip !== undefined) {
|
1135 | options.skip = self._apos.sanitizeInteger(options.skip);
|
1136 | }
|
1137 | if (options.limit !== undefined) {
|
1138 | options.limit = self._apos.sanitizeInteger(options.limit);
|
1139 | }
|
1140 | options.editable = true;
|
1141 | };
|
1142 |
|
1143 |
|
1144 |
|
1145 |
|
1146 |
|
1147 |
|
1148 |
|
1149 |
|
1150 | self._app.post(self._action + '/delete', function(req, res) {
|
1151 | var piece;
|
1152 | var parent;
|
1153 | return async.series({
|
1154 | get: function(callback) {
|
1155 | return self.pieces.getOne(req, { type: self.pieceName, slug: self._apos.sanitizeString(req.body.slug) }, { trash: 'any' }, function(err, _piece) {
|
1156 | piece = _piece;
|
1157 | if (!piece) {
|
1158 | return res.send({ status: 'notfound' });
|
1159 | }
|
1160 | return callback(err);
|
1161 | });
|
1162 | },
|
1163 | update: function(callback) {
|
1164 | var trash = self._apos.sanitizeBoolean(req.body.trash);
|
1165 | var oldSlug = piece.slug;
|
1166 | if (trash) {
|
1167 | if (piece.trash) {
|
1168 | return callback(null);
|
1169 | }
|
1170 | piece.trash = true;
|
1171 |
|
1172 |
|
1173 |
|
1174 | piece.slug = piece.slug.replace(/\/[^\/]+$/, function(match) {
|
1175 | return match.replace(/^\//, '/♻');
|
1176 | });
|
1177 | } else {
|
1178 | if (!piece.trash) {
|
1179 | return callback(null);
|
1180 | }
|
1181 | piece.slug = piece.slug.replace(/\/♻[^\/]+$/, function(match) {
|
1182 | return match.replace(/^\/♻/, '/');
|
1183 | });
|
1184 | delete piece.trash;
|
1185 | }
|
1186 | return self.pieces.putOne(req, oldSlug, {}, piece, callback);
|
1187 | },
|
1188 | findParent: function(callback) {
|
1189 | self._pages.getParent(req, piece, function(err, _parent) {
|
1190 | if (err || (!_parent)) {
|
1191 | return callback(err || new Error('No parent'));
|
1192 | }
|
1193 | parent = _parent;
|
1194 | return callback(null);
|
1195 | });
|
1196 | }
|
1197 | }, function(err) {
|
1198 | if (err) {
|
1199 | console.error(err);
|
1200 | return res.send({ status: 'error' });
|
1201 | }
|
1202 | return res.send({ status: 'ok', parent: parent.slug, slug: piece.slug });
|
1203 | });
|
1204 | });
|
1205 |
|
1206 | self._apos.addMigration('blog2AddReorganizeFlag', function(callback) {
|
1207 | var needed = false;
|
1208 | return self._apos.forEachPage({ type: 'blogPost', reorganize: { $ne: false } }, function(page, callback) {
|
1209 | if (!needed) {
|
1210 | needed = true;
|
1211 | console.log('Hiding blog posts from reorganize');
|
1212 | }
|
1213 | return self._apos.pages.update({ _id: page._id }, { $set: { reorganize: false } }, callback);
|
1214 | }, callback);
|
1215 | });
|
1216 |
|
1217 |
|
1218 | self._apos.on('tasks:register', function(taskGroups) {
|
1219 |
|
1220 |
|
1221 | var taskName = self._apos.camelName('generate ' + self.pieces.pluralLabel);
|
1222 | taskGroups.apostrophe[taskName] = function(apos, argv, callback) {
|
1223 | if (argv._.length !== 2) {
|
1224 | return callback('Usage: node app apostrophe:' + taskName + ' /slug/of/parent/blog');
|
1225 | }
|
1226 | var req = self._apos.getTaskReq();
|
1227 | var parentSlug = argv._[1];
|
1228 | var parent;
|
1229 |
|
1230 | return async.series({
|
1231 | getParent: function(callback) {
|
1232 | return self.indexes.getOne(req, { slug: parentSlug }, {}, function(err, _parent) {
|
1233 | if (err) {
|
1234 | return callback(err);
|
1235 | }
|
1236 | if (!_parent) {
|
1237 | return callback('No such parent blog page found');
|
1238 | }
|
1239 | parent = _parent;
|
1240 | return callback(null);
|
1241 | });
|
1242 | },
|
1243 | posts: function(callback) {
|
1244 | var randomWords = require('random-words');
|
1245 | var i;
|
1246 | var posts = [];
|
1247 | for (i = 0; (i < 100); i++) {
|
1248 | var title = randomWords({ min: 5, max: 10, join: ' ' });
|
1249 | var at = new Date();
|
1250 |
|
1251 |
|
1252 | at.setTime(at.getTime() + (10 - 90 * Math.random()) * 86400 * 1000);
|
1253 | var post = {
|
1254 | type: 'blogPost',
|
1255 | title: title,
|
1256 | publishedAt: at,
|
1257 | publicationDate: moment(at).format('YYYY-MM-DD'),
|
1258 | publicationTime: moment(at).format('HH:MM'),
|
1259 | body: {
|
1260 | type: 'area',
|
1261 | items: [
|
1262 | {
|
1263 | type: 'richText',
|
1264 | content: randomWords({ min: 50, max: 200, join: ' ' })
|
1265 | }
|
1266 | ]
|
1267 | }
|
1268 | };
|
1269 | if (Math.random() > 0.2) {
|
1270 | post.published = true;
|
1271 | }
|
1272 | posts.push(post);
|
1273 | }
|
1274 | return async.eachSeries(posts, function(post, callback) {
|
1275 | return self.pieces.putOne(req,
|
1276 | undefined,
|
1277 | { parent: parent },
|
1278 | post,
|
1279 | callback);
|
1280 | }, callback);
|
1281 | }
|
1282 | }, callback);
|
1283 | };
|
1284 | });
|
1285 |
|
1286 | self._apos.on('addSearchFilters', function(searchFilters) {
|
1287 | searchFilters.push({
|
1288 | name: self.pieceName,
|
1289 | label: self.pieces.pluralLabel
|
1290 | });
|
1291 | });
|
1292 |
|
1293 | if (callback) {
|
1294 | process.nextTick(function() {
|
1295 | return callback();
|
1296 | });
|
1297 | }
|
1298 | };
|