diff --git a/lib/models/post.js b/lib/models/post.js
index cf83a33e5c..d53e875d87 100644
--- a/lib/models/post.js
+++ b/lib/models/post.js
@@ -133,63 +133,76 @@ module.exports = function(ctx) {
});
Post.method('setCategories', function(cats) {
- cats = removeEmptyTag(cats);
+ // Remove empty categories, preserving hierarchies
+ cats = cats.filter(function(cat) {
+ return Array.isArray(cat) ? true : cat != null && cat !== '';
+ }).map(function(cat) {
+ return Array.isArray(cat) ? removeEmptyTag(cat) : cat + '';
+ });
var PostCategory = ctx.model('PostCategory');
var Category = ctx.model('Category');
var id = this._id;
- var arr = [];
+ var allIds = [];
var existed = PostCategory.find({post_id: id}, {lean: true}).map(pickID);
-
- // Don't use "Promise.map". It doesn't run in series.
- // MUST USE "Promise.each".
- return Promise.each(cats, function(cat, i) {
- // Find the category by name
- var data = Category.findOne({
- name: cat,
- parent: i ? arr[i - 1] : {$exists: false}
- }, {lean: true});
-
- if (data) {
- arr.push(data._id);
- return data;
- }
-
- // Insert the category if not exist
- var obj = {name: cat};
- if (i) obj.parent = arr[i - 1];
-
- return Category.insert(obj).catch(function(err) {
- // Try to find the category again. Throw the error if not found
+ var hasHierarchy = cats.filter(Array.isArray).length > 0;
+
+ // Add a hierarchy of categories
+ var addHierarchy = function(catHierarchy) {
+ var parentIds = [];
+ if (!Array.isArray(catHierarchy)) catHierarchy = [catHierarchy];
+ // Don't use "Promise.map". It doesn't run in series.
+ // MUST USE "Promise.each".
+ return Promise.each(catHierarchy, function(cat, i) {
+ // Find the category by name
var data = Category.findOne({
name: cat,
- parent: i ? arr[i - 1] : {$exists: false}
+ parent: i ? parentIds[i - 1] : {$exists: false}
}, {lean: true});
- if (data) return data;
- throw err;
- }).then(function(data) {
- arr.push(data._id);
- return data;
+ if (data) {
+ allIds.push(data._id);
+ parentIds.push(data._id);
+ return data;
+ }
+
+ // Insert the category if not exist
+ var obj = {name: cat};
+ if (i) obj.parent = parentIds[i - 1];
+
+ return Category.insert(obj).catch(function(err) {
+ // Try to find the category again. Throw the error if not found
+ var data = Category.findOne({
+ name: cat,
+ parent: i ? parentIds[i - 1] : {$exists: false}
+ }, {lean: true});
+
+ if (data) return data;
+ throw err;
+ }).then(function(data) {
+ allIds.push(data._id);
+ parentIds.push(data._id);
+ return data;
+ });
});
- }).map(function() {
- // Get the index from the second argument
- // and get the category id from arr.
- var cat = arr[arguments[1]];
+ };
+ return (hasHierarchy ? Promise.each(cats, addHierarchy) : Promise.resolve(addHierarchy(cats))
+ ).then(function() {
+ return allIds;
+ }).map(function(catId) {
// Find the reference
- var ref = PostCategory.findOne({post_id: id, category_id: cat}, {lean: true});
+ var ref = PostCategory.findOne({post_id: id, category_id: catId}, {lean: true});
if (ref) return ref;
// Insert the reference if not exist
return PostCategory.insert({
post_id: id,
- category_id: cat
+ category_id: catId
});
- }).then(function(cats) {
+ }).then(function(postCats) {
// Remove old categories
- var deleted = _.difference(existed, cats.map(pickID));
- return deleted;
+ return _.difference(existed, postCats.map(pickID));
}).map(function(cat) {
return PostCategory.removeById(cat);
});
diff --git a/test/scripts/helpers/list_categories.js b/test/scripts/helpers/list_categories.js
index 976a0831f9..353b362a45 100644
--- a/test/scripts/helpers/list_categories.js
+++ b/test/scripts/helpers/list_categories.js
@@ -19,12 +19,14 @@ describe('list_categories', () => {
{source: 'foo', slug: 'foo'},
{source: 'bar', slug: 'bar'},
{source: 'baz', slug: 'baz'},
- {source: 'boo', slug: 'boo'}
+ {source: 'boo', slug: 'boo'},
+ {source: 'bat', slug: 'bat'}
])).then(posts => Promise.each([
['baz'],
['baz', 'bar'],
['foo'],
- ['baz']
+ ['baz'],
+ ['bat', ['baz', 'bar']]
], (cats, i) => posts[i].setCategories(cats))).then(() => {
hexo.locals.invalidate();
ctx.site = hexo.locals.toObject();
@@ -37,10 +39,13 @@ describe('list_categories', () => {
result.should.eql([
'
',
'- ',
- 'baz3',
+ 'bat1',
+ '
',
+ '- ',
+ 'baz4',
'
',
'- ',
- 'bar1',
+ 'bar2',
'
',
'
',
' ',
@@ -59,7 +64,10 @@ describe('list_categories', () => {
result.should.eql([
'',
'- ',
- 'baz3',
+ 'bat1',
+ '
',
+ '- ',
+ 'baz4',
'
',
'- ',
'foo1',
@@ -74,8 +82,9 @@ describe('list_categories', () => {
});
result.should.eql([
- 'baz3',
- 'bar1',
+ 'bat1',
+ 'baz4',
+ 'bar2',
'foo1'
].join(', '));
});
@@ -87,6 +96,9 @@ describe('list_categories', () => {
result.should.eql([
'
',
+ '- ',
+ 'bat',
+ '
',
'- ',
'baz',
'
',
@@ -110,10 +122,13 @@ describe('list_categories', () => {
result.should.eql([
'',
'- ',
- 'baz3',
+ 'bat1',
+ '
',
+ '- ',
+ 'baz4',
'
',
'- ',
- 'bar1',
+ 'bar2',
'
',
'
',
' ',
@@ -132,7 +147,10 @@ describe('list_categories', () => {
result.should.eql([
'',
'- ',
- 'baz3',
+ 'bat1',
+ '
',
+ '- ',
+ 'baz4',
'
',
'- ',
'foo1',
@@ -152,10 +170,13 @@ describe('list_categories', () => {
'foo1',
'
',
'- ',
- 'baz3',
+ 'bat1',
+ '
',
+ '- ',
+ 'baz4',
'
',
'- ',
- 'bar1',
+ 'bar2',
'
',
'
',
' ',
@@ -174,13 +195,16 @@ describe('list_categories', () => {
'foo1',
'
',
'- ',
- 'baz3',
+ 'baz4',
'
',
'- ',
- 'bar1',
+ 'bar2',
'
',
'
',
' ',
+ '- ',
+ 'bat1',
+ '
',
'
'
].join(''));
});
@@ -195,10 +219,13 @@ describe('list_categories', () => {
result.should.eql([
'',
'- ',
- 'BAZ3',
+ 'BAT1',
+ '
',
+ '- ',
+ 'BAZ4',
'
',
'- ',
- 'BAR1',
+ 'BAR2',
'
',
'
',
' ',
@@ -209,15 +236,30 @@ describe('list_categories', () => {
].join(''));
});
- it('separator', () => {
+ it('separator (blank)', () => {
var result = listCategories({
style: false,
separator: ''
});
result.should.eql([
- 'baz3',
- 'bar1',
+ 'bat1',
+ 'baz4',
+ 'bar2',
+ 'foo1'
+ ].join(''));
+ });
+
+ it('separator (non-blank)', () => {
+ var result = listCategories({
+ style: false,
+ separator: '|'
+ });
+
+ result.should.eql([
+ 'bat1|',
+ 'baz4|',
+ 'bar2|',
'foo1'
].join(''));
});
@@ -229,11 +271,14 @@ describe('list_categories', () => {
result.should.eql([
'',
+ '- ',
+ 'bat1',
+ '
',
'- ',
- 'baz3',
+ 'baz4',
'
',
'- ',
- 'bar1',
+ 'bar2',
'
',
'
',
' ',
@@ -252,10 +297,13 @@ describe('list_categories', () => {
result.should.eql([
'',
'- ',
- 'baz3',
+ 'bat1',
+ '
',
+ '- ',
+ 'baz4',
'
',
'- ',
- 'bar1',
+ 'bar2',
'
',
'
',
' ',
diff --git a/test/scripts/models/post.js b/test/scripts/models/post.js
index 4599db273a..e0dd933d2c 100644
--- a/test/scripts/models/post.js
+++ b/test/scripts/models/post.js
@@ -313,6 +313,44 @@ describe('Post', () => {
}).finally(() => Post.removeById(id));
});
+ it('setCategories() - multiple hierarchies', () => Post.insert({
+ source: 'foo.md',
+ slug: 'bar'
+ }).then(post => post.setCategories([['foo', '', 'bar'], '', 'baz'])
+ .thenReturn(Post.findById(post._id))).then(post => {
+ var cats = post.categories.toArray();
+
+ // There should have been 3 categories set; blanks eliminated
+ cats.should.have.lengthOf(3);
+
+ // Category 1 should be foo, no parent
+ cats[0].name.should.eql('foo');
+ should.not.exist(cats[0].parent);
+
+ // Category 2 should be bar, foo as parent
+ cats[1].name.should.eql('bar');
+ cats[1].parent.should.eql(cats[0]._id);
+
+ // Category 3 should be baz, no parent
+ cats[2].name.should.eql('baz');
+ should.not.exist(cats[2].parent);
+
+ return Post.removeById(post._id);
+ }));
+
+ it('setCategories() - multiple hierarchies (dedupes repeated parent)', () => Post.insert({
+ source: 'foo.md',
+ slug: 'bar'
+ }).then(post => post.setCategories([['foo', 'bar'], ['foo', 'baz']])
+ .thenReturn(Post.findById(post._id))).then(post => {
+ var cats = post.categories.toArray();
+
+ // There should have been 3 categories set (foo is dupe)
+ cats.should.have.lengthOf(3);
+
+ return Post.removeById(post._id);
+ }));
+
it('remove PostTag references when a post is removed', () => Post.insert({
source: 'foo.md',
slug: 'bar'
diff --git a/test/scripts/processors/post.js b/test/scripts/processors/post.js
index 7a7dbeb9a5..7895a9ba99 100644
--- a/test/scripts/processors/post.js
+++ b/test/scripts/processors/post.js
@@ -663,6 +663,31 @@ describe('post', () => {
}).finally(() => fs.unlink(file.source));
});
+ it('post - categories (multiple hierarchies)', () => {
+ var body = [
+ 'title: "Hello world"',
+ 'categories:',
+ '- foo',
+ '- [bar, baz]',
+ '---'
+ ].join('\n');
+
+ var file = newFile({
+ path: 'foo.html',
+ published: true,
+ type: 'create',
+ renderable: true
+ });
+
+ return fs.writeFile(file.source, body).then(() => process(file)).then(() => {
+ var post = Post.findOne({source: file.path});
+
+ post.categories.map(item => item.name).should.eql(['foo', 'bar', 'baz']);
+
+ return post.remove();
+ }).finally(() => fs.unlink(file.source));
+ });
+
it('post - tag is an alias for tags', () => {
var body = [
'title: "Hello world"',