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 | self.show = function(req, callback) {
|
439 | req.template = self.renderer('show');
|
440 | return self.beforeShow(req, req.page, callback);
|
441 | };
|
442 |
|
443 |
|
444 |
|
445 |
|
446 |
|
447 | self.beforeShow = function(req, page, callback) {
|
448 | return callback(null);
|
449 | };
|
450 |
|
451 | self.setupPieces = function() {
|
452 | self.pieces = {};
|
453 |
|
454 | var piecesOptions = options.pieces || {};
|
455 | self.pieces.options = piecesOptions;
|
456 |
|
457 | _.defaults(piecesOptions, {
|
458 | name: self.pieceName,
|
459 | label: self.pieceLabel,
|
460 | pluralLabel: self.pluralPieceLabel,
|
461 | apos: options.apos,
|
462 | app: options.app,
|
463 | pages: options.pages,
|
464 | schemas: options.schemas,
|
465 | modules: options.modules,
|
466 |
|
467 | orphan: true,
|
468 | browser: {
|
469 | baseConstruct: 'AposFancyPage',
|
470 |
|
471 |
|
472 | afterYield: true
|
473 | },
|
474 | pageSettingsTemplate: 'piecePageSettings',
|
475 |
|
476 |
|
477 |
|
478 |
|
479 |
|
480 | });
|
481 |
|
482 | piecesOptions.addFields = [
|
483 | {
|
484 |
|
485 | after: 'published',
|
486 | name: 'publicationDate',
|
487 | label: 'Publication Date',
|
488 | type: 'date'
|
489 | },
|
490 | {
|
491 | name: 'publicationTime',
|
492 | label: 'Publication Time',
|
493 | type: 'time'
|
494 | },
|
495 | {
|
496 | name: 'body',
|
497 | type: 'area',
|
498 | label: 'Body',
|
499 |
|
500 |
|
501 | contextual: true
|
502 | },
|
503 |
|
504 |
|
505 |
|
506 |
|
507 | {
|
508 | name: '_parent',
|
509 | type: 'joinByOne',
|
510 | label: 'Move to Another Blog',
|
511 | placeholder: 'Type the name of the blog',
|
512 | withType: self.indexName,
|
513 | idField: '_newParentId',
|
514 | getOptions: {
|
515 | editable: true
|
516 | }
|
517 | }
|
518 | ].concat(piecesOptions.addFields || []);
|
519 |
|
520 | _.defaults(piecesOptions, {
|
521 |
|
522 |
|
523 |
|
524 | contextMenu: [
|
525 | {
|
526 | name: 'new-' + self._apos.cssName(piecesOptions.name),
|
527 | label: 'New ' + piecesOptions.label
|
528 | },
|
529 | {
|
530 | name: 'edit-page',
|
531 | label: piecesOptions.label + ' Settings'
|
532 | },
|
533 | {
|
534 | name: 'versions-page',
|
535 | label: piecesOptions.label + ' Versions'
|
536 | },
|
537 | {
|
538 | name: 'delete-' + self._apos.cssName(self.pieceName),
|
539 | label: 'Move to Trash'
|
540 | },
|
541 | {
|
542 | name: 'rescue-' + self._apos.cssName(self.pieceName),
|
543 | label: 'Rescue ' + self.pieceLabel + ' From Trash'
|
544 | }
|
545 | ]
|
546 | });
|
547 | fancyPage.FancyPage.call(self.pieces, piecesOptions, null);
|
548 |
|
549 |
|
550 |
|
551 |
|
552 |
|
553 |
|
554 | self.aggregateCriteria = function(req, _pages, callback) {
|
555 |
|
556 |
|
557 | pages = _.clone(_pages);
|
558 |
|
559 | var done = false;
|
560 |
|
561 | var fetched = {};
|
562 | var tagsByPageId = {};
|
563 | var recursed = {};
|
564 |
|
565 | _.each(pages, function(page) {
|
566 | fetched[page._id] = true;
|
567 | if (page._andFromPages) {
|
568 | _.each(page._andFromPages || [], function(pair) {
|
569 | pages.push(pair.item);
|
570 | fetched[pair.item._id] = true;
|
571 | });
|
572 | recursed[page._id] = true;
|
573 | }
|
574 | });
|
575 |
|
576 | return async.whilst(
|
577 | function() { return !done; },
|
578 | function(callback) {
|
579 | var ids = [];
|
580 | _.each(pages, function(page) {
|
581 | if (_.has(recursed, page._id)) {
|
582 | return;
|
583 | }
|
584 | recursed[page._id] = true;
|
585 | _.each(page.andFromPagesIds || [], function(id) {
|
586 | if (_.has(fetched, id)) {
|
587 | return;
|
588 | }
|
589 | ids.push(id);
|
590 | fetched[id] = true;
|
591 | });
|
592 | });
|
593 | if (!ids.length) {
|
594 | done = true;
|
595 | return setImmediate(callback);
|
596 | }
|
597 | 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) {
|
598 | if (err) {
|
599 | return callback(err);
|
600 | }
|
601 | pages = pages.concat(results.pages);
|
602 | return callback(null);
|
603 | });
|
604 | },
|
605 | function(err) {
|
606 | if (err) {
|
607 | return callback(err);
|
608 | }
|
609 |
|
610 |
|
611 |
|
612 |
|
613 |
|
614 |
|
615 |
|
616 |
|
617 |
|
618 | var pagesById = {};
|
619 | var visited = {};
|
620 | _.each(pages, function(page) {
|
621 | pagesById[page._id] = page;
|
622 | });
|
623 |
|
624 |
|
625 |
|
626 | _.each(_pages, function(page) {
|
627 | recurseTags(page, [], []);
|
628 | });
|
629 |
|
630 | function recurseTags(page, tags, antecedents) {
|
631 | if (_.contains(antecedents, page._id)) {
|
632 |
|
633 |
|
634 |
|
635 |
|
636 |
|
637 | return;
|
638 | }
|
639 | _.each(page.andFromPagesIds || [], function(id) {
|
640 | var _page = pagesById[id];
|
641 | if (_page) {
|
642 | var rel = ((page.andFromPagesRelationships || {})[id]) || {};
|
643 | var newTags;
|
644 | if (rel.tag) {
|
645 |
|
646 |
|
647 |
|
648 |
|
649 | newTags = [ self._apos.filterTag(rel.tag) ];
|
650 | } else {
|
651 | newTags = [];
|
652 | }
|
653 | recurseTags(pagesById[id], tags.concat(newTags), antecedents.concat(page._id));
|
654 | }
|
655 | });
|
656 | if (!tagsByPageId[page._id]) {
|
657 | tagsByPageId[page._id] = [];
|
658 | }
|
659 | tagsByPageId[page._id].push(tags);
|
660 | }
|
661 |
|
662 |
|
663 |
|
664 | var clauses = [];
|
665 | _.each(pages, function(page) {
|
666 | var clause = {
|
667 | path: new RegExp('^' + RegExp.quote(page.path + '/')),
|
668 | level: page.level + 1
|
669 | };
|
670 | var tagPaths = tagsByPageId[page._id];
|
671 | if (tagPaths) {
|
672 | if (_.find(tagPaths, function(tagPath) {
|
673 | return !tagPath.length;
|
674 | })) {
|
675 |
|
676 |
|
677 | } else {
|
678 |
|
679 |
|
680 |
|
681 |
|
682 | _.each(tagPaths, function(tagPath) {
|
683 | clause.tags = { $all: tagPath };
|
684 | });
|
685 | }
|
686 | }
|
687 | clauses.push(clause);
|
688 | });
|
689 | return callback(null, clauses);
|
690 | }
|
691 | );
|
692 | };
|
693 |
|
694 | var superPiecesGet = self.pieces.get;
|
695 |
|
696 |
|
697 |
|
698 |
|
699 |
|
700 |
|
701 |
|
702 |
|
703 |
|
704 |
|
705 |
|
706 |
|
707 |
|
708 |
|
709 |
|
710 |
|
711 |
|
712 |
|
713 |
|
714 | self.pieces.get = function(req, userCriteria, options, callback) {
|
715 | var criteria;
|
716 | var filterCriteria = {};
|
717 |
|
718 | if (options.fromPageIds) {
|
719 |
|
720 |
|
721 |
|
722 |
|
723 | var page = {};
|
724 | if (!options.fromPageIds.length) {
|
725 | filterCriteria = { _never: true };
|
726 | } else {
|
727 | var ids = options.fromPageIds;
|
728 | if (typeof(ids[0]) === 'object') {
|
729 | page.andFromPagesIds = _.pluck(ids, 'value');
|
730 | var relationships = {};
|
731 | page.andFromPagesRelationships = relationships;
|
732 | _.each(ids, function(id) {
|
733 | relationships[id.value] = { tag: id.tag };
|
734 | });
|
735 | } else {
|
736 | page.andFromPagesIds = ids;
|
737 | page.andFromPagesRelationships = {};
|
738 | }
|
739 | options.fromPages = [ page ];
|
740 | }
|
741 | }
|
742 |
|
743 | var results;
|
744 |
|
745 | return async.series({
|
746 | fromPages: function(callback) {
|
747 | if (!options.fromPages) {
|
748 | return setImmediate(callback);
|
749 | }
|
750 | return self.aggregateCriteria(req, options.fromPages, function(err, clauses) {
|
751 | if (err) {
|
752 | return callback(err);
|
753 | }
|
754 | if (clauses.length) {
|
755 | filterCriteria.$or = clauses;
|
756 | }
|
757 | return callback(null);
|
758 | });
|
759 | },
|
760 | get: function(callback) {
|
761 |
|
762 |
|
763 |
|
764 | if (options.publishedAt === 'any') {
|
765 |
|
766 |
|
767 | } else {
|
768 | filterCriteria.publishedAt = { $lte: new Date() };
|
769 | }
|
770 |
|
771 | if (!options.sort) {
|
772 | options.sort = { publishedAt: -1 };
|
773 | }
|
774 |
|
775 | criteria = {
|
776 | $and: [
|
777 | userCriteria,
|
778 | filterCriteria
|
779 | ]
|
780 | };
|
781 | return superPiecesGet(req, criteria, options, function(err, _results) {
|
782 | if (err) {
|
783 | return callback(err);
|
784 | }
|
785 | results = _results;
|
786 | return callback(null);
|
787 | });
|
788 | }
|
789 | }, function(err) {
|
790 | if (err) {
|
791 | return callback(err);
|
792 | }
|
793 | return callback(null, results);
|
794 | });
|
795 | };
|
796 |
|
797 | self.pieces.dispatch = function(req, callback) {
|
798 | return self.show(req, callback);
|
799 | };
|
800 |
|
801 |
|
802 |
|
803 |
|
804 |
|
805 |
|
806 | self.pieces.beforePutOne = function(req, slug, options, piece, callback) {
|
807 |
|
808 |
|
809 |
|
810 |
|
811 | piece.orphan = true;
|
812 |
|
813 |
|
814 |
|
815 |
|
816 | piece.reorganize = false;
|
817 |
|
818 | if (piece.publicationTime === null) {
|
819 |
|
820 |
|
821 | piece.publishedAt = new Date(piece.publicationDate + ' 00:00:00');
|
822 | } else {
|
823 | piece.publishedAt = new Date(piece.publicationDate + ' ' + piece.publicationTime);
|
824 | }
|
825 |
|
826 | if (self._dateInSlug) {
|
827 | var datePath = piece.publicationDate.replace(/-/g, '/');
|
828 | var dateSlugRegex = /\/(\d\d\d\d\/\d\d\/\d\d\/)?([^\/]+)$/;
|
829 | var matches = piece.slug.match(dateSlugRegex);
|
830 | if (!matches) {
|
831 |
|
832 | console.error("I don't understand how to add a date to this slug: " + piece.slug);
|
833 | } else {
|
834 | piece.slug = piece.slug.replace(dateSlugRegex, '/' + datePath + '/' + matches[2]);
|
835 | }
|
836 | }
|
837 |
|
838 | return callback(null);
|
839 | };
|
840 |
|
841 |
|
842 |
|
843 | self.pieces.afterPutOne = function(req, slug, options, piece, callback) {
|
844 | var newParent;
|
845 | return async.series({
|
846 | getNewParent: function(callback) {
|
847 | if (!piece._newParentId) {
|
848 | return callback(null);
|
849 | }
|
850 | return self.indexes.getOne(req, { _id: piece._newParentId }, {}, function(err, page) {
|
851 | if (err) {
|
852 | return callback(err);
|
853 | }
|
854 | newParent = page;
|
855 | return callback(null);
|
856 | });
|
857 | },
|
858 | move: function(callback) {
|
859 | if (!newParent) {
|
860 | return callback(null);
|
861 | }
|
862 | return self._pages.move(req, piece, newParent, 'inside', callback);
|
863 | }
|
864 | }, callback);
|
865 | };
|
866 | };
|
867 |
|
868 |
|
869 | self.loader = function(req, callback) {
|
870 | return async.series({
|
871 | indexes: function(callback) {
|
872 | return self.indexes.loader(req, function(err) {
|
873 | return callback(err);
|
874 | });
|
875 | },
|
876 | pieces: function(callback) {
|
877 | return self.pieces.loader(req, function(err) {
|
878 | return callback(err);
|
879 | });
|
880 | }
|
881 | }, callback);
|
882 | };
|
883 |
|
884 | self.setupIndexes();
|
885 | self.setupPieces();
|
886 |
|
887 |
|
888 | var widgetOptions = {};
|
889 | if (self._options.widget === false) {
|
890 | widgetOptions = false;
|
891 | } else if (typeof(self._options.widget) === 'object') {
|
892 | widgetOptions = self._options.widget;
|
893 | }
|
894 |
|
895 |
|
896 | var args = {
|
897 | name: self.name,
|
898 | pieceName: self.pieceName,
|
899 | pieceLabel: self.pieceLabel,
|
900 | indexName: self.indexName,
|
901 | indexLabel: self.indexLabel,
|
902 | action: self._action,
|
903 | widget: widgetOptions
|
904 | };
|
905 |
|
906 |
|
907 |
|
908 |
|
909 | self._apos.pushGlobalCallWhen('user', 'AposBlog2.subclassIfNeeded(?, ?, ?)', getBrowserConstructor(), getBaseBrowserConstructor(), args);
|
910 | self._apos.pushGlobalCallWhen('user', '@ = new @(?)', getBrowserInstance(), getBrowserConstructor(), args);
|
911 |
|
912 | function getBrowserInstance() {
|
913 | if (self._browser.instance) {
|
914 | return self._browser.instance;
|
915 | }
|
916 | var c = getBrowserConstructor();
|
917 | return c.charAt(0).toLowerCase() + c.substr(1);
|
918 | }
|
919 |
|
920 | function getBrowserConstructor() {
|
921 | return self._browser.construct || 'Apos' + self.name.charAt(0).toUpperCase() + self.name.substr(1);
|
922 | }
|
923 |
|
924 |
|
925 |
|
926 | function getBaseBrowserConstructor() {
|
927 | return self._browser.baseConstruct || 'AposBlogManager';
|
928 | }
|
929 |
|
930 | if (widgetOptions) {
|
931 |
|
932 |
|
933 | var widget = {
|
934 | _manager: self
|
935 | };
|
936 | (function(options) {
|
937 | var self = widget;
|
938 | self._apos = self._manager._apos;
|
939 | self.icon = options.icon || 'icon-blog';
|
940 | self.name = options.name || self._manager.indexes.name;
|
941 | self.label = options.label || self._manager.indexes.label;
|
942 | self.widget = true;
|
943 | self.css = self._apos.cssName(self.name);
|
944 |
|
945 |
|
946 | var titleField = _.find(self._manager.pieces.schema, function(field) {
|
947 | return field.name === 'title';
|
948 | }) || { label: 'Title' };
|
949 |
|
950 | var widgetData = {
|
951 | widgetEditorClass: 'apos-' + self.css + '-widget-editor',
|
952 | pieceLabel: self._manager.pieces.label,
|
953 | pluralPieceLabel: self._manager.pieces.pluralLabel,
|
954 | indexLabel: self._manager.indexes.label,
|
955 | pluralIndexLabel: self._manager.indexes.pluralLabel,
|
956 | titleLabel: titleField.label
|
957 | };
|
958 |
|
959 |
|
960 | self._manager.pushAsset('template', 'widgetEditor', {
|
961 | when: 'user',
|
962 | data: widgetData
|
963 | });
|
964 |
|
965 |
|
966 |
|
967 |
|
968 |
|
969 | self.addCriteria = function(item, criteria, options) {
|
970 | if (item.by === 'tag') {
|
971 | if (item.tags && item.tags.length) {
|
972 | criteria.tags = { $in: item.tags };
|
973 | }
|
974 | if (item.limitByTag) {
|
975 | options.limit = item.limitByTag;
|
976 | } else {
|
977 | options.limit = 5;
|
978 | }
|
979 | } else if (item.by === 'id') {
|
980 |
|
981 | criteria._id = { $in: item.ids || []};
|
982 | } else if (item.by === 'fromPageIds') {
|
983 | if (item.limitFromPageIds) {
|
984 | options.limit = item.limitFromPageIds;
|
985 | } else {
|
986 | options.limit = 5;
|
987 | }
|
988 | if (item.fromPageIds && item.fromPageIds.length) {
|
989 | options.fromPageIds = item.fromPageIds;
|
990 | }
|
991 | }
|
992 | };
|
993 |
|
994 | self.sanitize = function(item) {
|
995 | item.by = self._apos.sanitizeSelect(item.by, [ 'id', 'tag', 'fromPageIds' ], 'fromPageIds');
|
996 | item.tags = self._apos.sanitizeTags(item.tags);
|
997 | item.ids = self._apos.sanitizeIds(item.ids);
|
998 | var fromPageIds = [];
|
999 | if (Array.isArray(item.fromPageIds)) {
|
1000 | _.each(item.fromPageIds, function(pageId) {
|
1001 | if (typeof(pageId) === 'object') {
|
1002 | fromPageIds.push(_.pick(pageId, [ 'value', 'tag' ]));
|
1003 | } else {
|
1004 | fromPageIds.push(apos.sanitizeId(pageid));
|
1005 | }
|
1006 | });
|
1007 | item.fromPageIds = fromPageIds;
|
1008 | }
|
1009 | item.limit = self._apos.sanitizeInteger(item.limit, 5, 1, 1000);
|
1010 | };
|
1011 |
|
1012 | self.renderWidget = function(data) {
|
1013 | return self._manager.render('widget', data);
|
1014 | };
|
1015 |
|
1016 | self.addDiffLines = function(item, lines) {
|
1017 | if (item.by === 'id') {
|
1018 | lines.push(self.label + ': items selected: ' + ((item.ids && item.ids.length) || 0));
|
1019 | } else if (item.by === 'tag') {
|
1020 | lines.push(self.label + ': tags selected: ' + item.tags.join(', '));
|
1021 | } else if (item.by === 'fromPageIds') {
|
1022 | lines.push(self.label + ': sources selected: ' + ((item.fromPageIds && item.fromPageIds.length) || 0));
|
1023 | }
|
1024 | };
|
1025 |
|
1026 |
|
1027 |
|
1028 |
|
1029 |
|
1030 | self.load = function(req, item, callback) {
|
1031 | var criteria = {};
|
1032 | var options = {};
|
1033 |
|
1034 | self.addCriteria(item, criteria, options);
|
1035 |
|
1036 | return self._manager.pieces.get(req, criteria, options, function(err, results) {
|
1037 | if (err) {
|
1038 | item._pieces = [];
|
1039 | console.error(err);
|
1040 | return callback(err);
|
1041 | }
|
1042 | var pieces = results.pages;
|
1043 | if (item.by === 'id') {
|
1044 | pieces = self._apos.orderById(item.ids, pieces);
|
1045 | }
|
1046 | item._pieces = pieces;
|
1047 | return callback(null);
|
1048 | });
|
1049 | };
|
1050 |
|
1051 | self.empty = function(item) {
|
1052 | return (!item._pieces) || (!item._pieces.length);
|
1053 | };
|
1054 |
|
1055 |
|
1056 | })(widgetOptions);
|
1057 |
|
1058 |
|
1059 |
|
1060 | self._apos.defaultControls.push(widget.name);
|
1061 | self._apos.addWidgetType(widget.name, widget);
|
1062 |
|
1063 |
|
1064 |
|
1065 | self.extendWidget = function(widget) {
|
1066 | };
|
1067 |
|
1068 |
|
1069 |
|
1070 | process.nextTick(function() {
|
1071 | self.extendWidget(widget);
|
1072 | });
|
1073 | }
|
1074 |
|
1075 | self._apos.on('beforeEndAssets', function() {
|
1076 | self.pushAllAssets();
|
1077 | });
|
1078 |
|
1079 | self.pushAllAssets = function() {
|
1080 | self.pushAsset('script', 'manager', {
|
1081 | when: 'user',
|
1082 | data: {
|
1083 | pieceName: self.pieceName,
|
1084 | pieceLabel: self.pieceLabel
|
1085 | }
|
1086 | });
|
1087 | self.pushAsset('template', 'browseTrash', {
|
1088 | when: 'user',
|
1089 | data: {
|
1090 | browseTrashClass: 'apos-browse-trash-' + self._apos.cssName(self.pieceName),
|
1091 | pluralLabel: self.pieces.pluralLabel
|
1092 | }
|
1093 | });
|
1094 | };
|
1095 |
|
1096 |
|
1097 |
|
1098 |
|
1099 |
|
1100 | self._app.get(self._action + '/get', function(req, res) {
|
1101 | var criteria = {};
|
1102 | var options = {};
|
1103 | self.addApiCriteria(req.query, criteria, options);
|
1104 | self.pieces.get(req, criteria, options, function(err, results) {
|
1105 | if (err) {
|
1106 | console.error(err);
|
1107 | return res.send({ status: 'error' });
|
1108 | }
|
1109 | results.status = 'ok';
|
1110 | return res.send(results);
|
1111 | });
|
1112 | });
|
1113 |
|
1114 | self.addApiCriteria = function(queryArg, criteria, options) {
|
1115 |
|
1116 |
|
1117 |
|
1118 |
|
1119 |
|
1120 | var query = _.cloneDeep(queryArg);
|
1121 |
|
1122 | var slug = self._apos.sanitizeString(query.slug);
|
1123 | if (slug.length) {
|
1124 | criteria.slug = query.slug;
|
1125 |
|
1126 | delete query.slug;
|
1127 | }
|
1128 |
|
1129 | var _id = self._apos.sanitizeString(query._id);
|
1130 | if (_id.length) {
|
1131 | criteria._id = query._id;
|
1132 |
|
1133 | delete query._id;
|
1134 | }
|
1135 |
|
1136 |
|
1137 | _.extend(options, query);
|
1138 |
|
1139 |
|
1140 | if (options.skip !== undefined) {
|
1141 | options.skip = self._apos.sanitizeInteger(options.skip);
|
1142 | }
|
1143 | if (options.limit !== undefined) {
|
1144 | options.limit = self._apos.sanitizeInteger(options.limit);
|
1145 | }
|
1146 | options.editable = true;
|
1147 | };
|
1148 |
|
1149 |
|
1150 |
|
1151 |
|
1152 |
|
1153 |
|
1154 |
|
1155 |
|
1156 | self._app.post(self._action + '/delete', function(req, res) {
|
1157 | var piece;
|
1158 | var parent;
|
1159 | return async.series({
|
1160 | get: function(callback) {
|
1161 | return self.pieces.getOne(req, { type: self.pieceName, slug: self._apos.sanitizeString(req.body.slug) }, { trash: 'any' }, function(err, _piece) {
|
1162 | piece = _piece;
|
1163 | if (!piece) {
|
1164 | return res.send({ status: 'notfound' });
|
1165 | }
|
1166 | return callback(err);
|
1167 | });
|
1168 | },
|
1169 | update: function(callback) {
|
1170 | var trash = self._apos.sanitizeBoolean(req.body.trash);
|
1171 | var oldSlug = piece.slug;
|
1172 | if (trash) {
|
1173 | if (piece.trash) {
|
1174 | return callback(null);
|
1175 | }
|
1176 | piece.trash = true;
|
1177 |
|
1178 |
|
1179 |
|
1180 | piece.slug = piece.slug.replace(/\/[^\/]+$/, function(match) {
|
1181 | return match.replace(/^\//, '/♻');
|
1182 | });
|
1183 | } else {
|
1184 | if (!piece.trash) {
|
1185 | return callback(null);
|
1186 | }
|
1187 | piece.slug = piece.slug.replace(/\/♻[^\/]+$/, function(match) {
|
1188 | return match.replace(/^\/♻/, '/');
|
1189 | });
|
1190 | delete piece.trash;
|
1191 | }
|
1192 | return self.pieces.putOne(req, oldSlug, {}, piece, callback);
|
1193 | },
|
1194 | findParent: function(callback) {
|
1195 | self._pages.getParent(req, piece, function(err, _parent) {
|
1196 | if (err || (!_parent)) {
|
1197 | return callback(err || new Error('No parent'));
|
1198 | }
|
1199 | parent = _parent;
|
1200 | return callback(null);
|
1201 | });
|
1202 | }
|
1203 | }, function(err) {
|
1204 | if (err) {
|
1205 | console.error(err);
|
1206 | return res.send({ status: 'error' });
|
1207 | }
|
1208 | return res.send({ status: 'ok', parent: parent.slug, slug: piece.slug });
|
1209 | });
|
1210 | });
|
1211 |
|
1212 | self._apos.addMigration('blog2AddReorganizeFlag', function(callback) {
|
1213 | var needed = false;
|
1214 | return self._apos.forEachPage({ type: 'blogPost', reorganize: { $ne: false } }, function(page, callback) {
|
1215 | if (!needed) {
|
1216 | needed = true;
|
1217 | console.log('Hiding blog posts from reorganize');
|
1218 | }
|
1219 | return self._apos.pages.update({ _id: page._id }, { $set: { reorganize: false } }, callback);
|
1220 | }, callback);
|
1221 | });
|
1222 |
|
1223 |
|
1224 | self._apos.on('tasks:register', function(taskGroups) {
|
1225 |
|
1226 |
|
1227 | var taskName = self._apos.camelName('generate ' + self.pieces.pluralLabel);
|
1228 | taskGroups.apostrophe[taskName] = function(apos, argv, callback) {
|
1229 | if (argv._.length !== 2) {
|
1230 | return callback('Usage: node app apostrophe:' + taskName + ' /slug/of/parent/blog');
|
1231 | }
|
1232 | var req = self._apos.getTaskReq();
|
1233 | var parentSlug = argv._[1];
|
1234 | var parent;
|
1235 |
|
1236 | return async.series({
|
1237 | getParent: function(callback) {
|
1238 | return self.indexes.getOne(req, { slug: parentSlug }, {}, function(err, _parent) {
|
1239 | if (err) {
|
1240 | return callback(err);
|
1241 | }
|
1242 | if (!_parent) {
|
1243 | return callback('No such parent blog page found');
|
1244 | }
|
1245 | parent = _parent;
|
1246 | return callback(null);
|
1247 | });
|
1248 | },
|
1249 | posts: function(callback) {
|
1250 | var randomWords = require('random-words');
|
1251 | var i;
|
1252 | var posts = [];
|
1253 | for (i = 0; (i < 100); i++) {
|
1254 | var title = randomWords({ min: 5, max: 10, join: ' ' });
|
1255 | var at = new Date();
|
1256 |
|
1257 |
|
1258 | at.setTime(at.getTime() + (10 - 90 * Math.random()) * 86400 * 1000);
|
1259 | var post = {
|
1260 | type: 'blogPost',
|
1261 | title: title,
|
1262 | publishedAt: at,
|
1263 | publicationDate: moment(at).format('YYYY-MM-DD'),
|
1264 | publicationTime: moment(at).format('HH:MM'),
|
1265 | body: {
|
1266 | type: 'area',
|
1267 | items: [
|
1268 | {
|
1269 | type: 'richText',
|
1270 | content: randomWords({ min: 50, max: 200, join: ' ' })
|
1271 | }
|
1272 | ]
|
1273 | }
|
1274 | };
|
1275 | if (Math.random() > 0.2) {
|
1276 | post.published = true;
|
1277 | }
|
1278 | posts.push(post);
|
1279 | }
|
1280 | return async.eachSeries(posts, function(post, callback) {
|
1281 | return self.pieces.putOne(req,
|
1282 | undefined,
|
1283 | { parent: parent },
|
1284 | post,
|
1285 | callback);
|
1286 | }, callback);
|
1287 | }
|
1288 | }, callback);
|
1289 | };
|
1290 | taskName = self._apos.camelName('import wordpress ' + self.pieces.pluralLabel);
|
1291 | taskGroups.apostrophe[taskName] = function(apos, argv, callback) {
|
1292 | return require('./wordpress.js')(self, argv, callback);
|
1293 | };
|
1294 | });
|
1295 |
|
1296 | self._apos.on('addSearchFilters', function(searchFilters) {
|
1297 | searchFilters.push({
|
1298 | name: self.pieceName,
|
1299 | label: self.pieces.pluralLabel
|
1300 | });
|
1301 | });
|
1302 |
|
1303 | if (callback) {
|
1304 | process.nextTick(function() {
|
1305 | return callback();
|
1306 | });
|
1307 | }
|
1308 | };
|