You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
210 lines
6.3 KiB
210 lines
6.3 KiB
'use strict'; |
|
|
|
const { Schema } = require('warehouse'); |
|
const moment = require('moment'); |
|
const { extname, join, sep } = require('path'); |
|
const Promise = require('bluebird'); |
|
const Moment = require('./types/moment'); |
|
const { full_url_for } = require('hexo-util'); |
|
|
|
function pickID(data) { |
|
return data._id; |
|
} |
|
|
|
function removeEmptyTag(tags) { |
|
return tags.filter(tag => tag != null && tag !== '').map(tag => `${tag}`); |
|
} |
|
|
|
module.exports = ctx => { |
|
const Post = new Schema({ |
|
id: String, |
|
title: {type: String, default: ''}, |
|
date: { |
|
type: Moment, |
|
default: moment, |
|
language: ctx.config.languages, |
|
timezone: ctx.config.timezone |
|
}, |
|
updated: { |
|
type: Moment, |
|
language: ctx.config.languages, |
|
timezone: ctx.config.timezone |
|
}, |
|
comments: {type: Boolean, default: true}, |
|
layout: {type: String, default: 'post'}, |
|
_content: {type: String, default: ''}, |
|
source: {type: String, required: true}, |
|
slug: {type: String, required: true}, |
|
photos: [String], |
|
link: {type: String, default: ''}, |
|
raw: {type: String, default: ''}, |
|
published: {type: Boolean, default: true}, |
|
content: {type: String}, |
|
excerpt: {type: String}, |
|
more: {type: String} |
|
}); |
|
|
|
Post.virtual('path').get(function() { |
|
const path = ctx.execFilterSync('post_permalink', this, {context: ctx}); |
|
return typeof path === 'string' ? path : ''; |
|
}); |
|
|
|
Post.virtual('permalink').get(function() { |
|
return full_url_for.call(ctx, this.path); |
|
}); |
|
|
|
Post.virtual('full_source').get(function() { |
|
return join(ctx.source_dir, this.source || ''); |
|
}); |
|
|
|
Post.virtual('asset_dir').get(function() { |
|
const src = this.full_source; |
|
return src.substring(0, src.length - extname(src).length) + sep; |
|
}); |
|
|
|
Post.virtual('tags').get(function() { |
|
const PostTag = ctx.model('PostTag'); |
|
const Tag = ctx.model('Tag'); |
|
|
|
const ids = PostTag.find({post_id: this._id}, {lean: true}).map(item => item.tag_id); |
|
|
|
return Tag.find({_id: {$in: ids}}); |
|
}); |
|
|
|
Post.method('setTags', function(tags) { |
|
tags = removeEmptyTag(tags); |
|
|
|
const PostTag = ctx.model('PostTag'); |
|
const Tag = ctx.model('Tag'); |
|
const id = this._id; |
|
const existed = PostTag.find({post_id: id}, {lean: true}).map(pickID); |
|
|
|
return Promise.map(tags, tag => { |
|
// Find the tag by name |
|
const data = Tag.findOne({name: tag}, {lean: true}); |
|
if (data) return data; |
|
|
|
// Insert the tag if not exist |
|
return Tag.insert({name: tag}).catch(err => { |
|
// Try to find the tag again. Throw the error if not found |
|
const data = Tag.findOne({name: tag}, {lean: true}); |
|
|
|
if (data) return data; |
|
throw err; |
|
}); |
|
}).map(tag => { |
|
// Find the reference |
|
const ref = PostTag.findOne({post_id: id, tag_id: tag._id}, {lean: true}); |
|
if (ref) return ref; |
|
|
|
// Insert the reference if not exist |
|
return PostTag.insert({ |
|
post_id: id, |
|
tag_id: tag._id |
|
}); |
|
}).then(tags => { |
|
// Remove old tags |
|
const deleted = existed.filter(item => !tags.map(pickID).includes(item)); |
|
return deleted; |
|
}).map(tag => PostTag.removeById(tag)); |
|
}); |
|
|
|
Post.virtual('categories').get(function() { |
|
const PostCategory = ctx.model('PostCategory'); |
|
const Category = ctx.model('Category'); |
|
|
|
const ids = PostCategory.find({post_id: this._id}, {lean: true}).map(item => item.category_id); |
|
|
|
return Category.find({_id: {$in: ids}}); |
|
}); |
|
|
|
Post.method('setCategories', function(cats) { |
|
// Remove empty categories, preserving hierarchies |
|
cats = cats.filter(cat => { |
|
return Array.isArray(cat) || (cat != null && cat !== ''); |
|
}).map(cat => { |
|
return Array.isArray(cat) ? removeEmptyTag(cat) : `${cat}`; |
|
}); |
|
|
|
const PostCategory = ctx.model('PostCategory'); |
|
const Category = ctx.model('Category'); |
|
const id = this._id; |
|
const allIds = []; |
|
const existed = PostCategory.find({post_id: id}, {lean: true}).map(pickID); |
|
const hasHierarchy = cats.filter(Array.isArray).length > 0; |
|
|
|
// Add a hierarchy of categories |
|
const addHierarchy = catHierarchy => { |
|
const 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, (cat, i) => { |
|
// Find the category by name |
|
const data = Category.findOne({ |
|
name: cat, |
|
parent: i ? parentIds[i - 1] : {$exists: false} |
|
}, {lean: true}); |
|
|
|
if (data) { |
|
allIds.push(data._id); |
|
parentIds.push(data._id); |
|
return data; |
|
} |
|
|
|
// Insert the category if not exist |
|
const obj = {name: cat}; |
|
if (i) obj.parent = parentIds[i - 1]; |
|
|
|
return Category.insert(obj).catch(err => { |
|
// Try to find the category again. Throw the error if not found |
|
const data = Category.findOne({ |
|
name: cat, |
|
parent: i ? parentIds[i - 1] : {$exists: false} |
|
}, {lean: true}); |
|
|
|
if (data) return data; |
|
throw err; |
|
}).then(data => { |
|
allIds.push(data._id); |
|
parentIds.push(data._id); |
|
return data; |
|
}); |
|
}); |
|
}; |
|
|
|
return (hasHierarchy ? Promise.each(cats, addHierarchy) : Promise.resolve(addHierarchy(cats)) |
|
).then(() => allIds).map(catId => { |
|
// Find the reference |
|
const 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: catId |
|
}); |
|
}).then(postCats => // Remove old categories |
|
existed.filter(item => !postCats.map(pickID).includes(item))).map(cat => PostCategory.removeById(cat)); |
|
}); |
|
|
|
// Remove PostTag references |
|
Post.pre('remove', data => { |
|
const PostTag = ctx.model('PostTag'); |
|
return PostTag.remove({post_id: data._id}); |
|
}); |
|
|
|
// Remove PostCategory references |
|
Post.pre('remove', data => { |
|
const PostCategory = ctx.model('PostCategory'); |
|
return PostCategory.remove({post_id: data._id}); |
|
}); |
|
|
|
// Remove assets |
|
Post.pre('remove', data => { |
|
const PostAsset = ctx.model('PostAsset'); |
|
return PostAsset.remove({post: data._id}); |
|
}); |
|
|
|
return Post; |
|
};
|
|
|