const path = require('path');
nv_create_dirs('./nv-content/data/');
const db_posts = new NV_Database(path.join(process.cwd(),"./nv-content/data/posts.json"),{
columns: ['author','created_time','content','content_type','title','excerpt','status','password','slug','modified_time','parent','order','post_type','comment_status','comment_count'],
index: 'id',
references: ['content'],
});
nvdb.posts = db_posts;
global.post_exists = (arg) => {
if ( typeof(arg)=='number' ) {
var filter_func = r=>id == arg;
filter_func._nvdb_reference = true;
return db_posts.find(filter_func)[0] ? arg : false;
} else {
//arg是slug
var filter_func = r=>r.slug == arg;
filter_func._nvdb_reference = true;
var post = db_posts.find(filter_func)[0];
return post ? post.id : false;
}
}
global.nv_unique_post_slug = (slug)=>{
if (!slug) {
slug = "untitled";
}
var mySlug = slug.toLowerCase().replace(/\ /g,'-');
mySlug = encodeURIComponent(mySlug);
if ( post_exists(slug) ) {
return `${mySlug}-${ Date.now() }`;
} else {
return mySlug;
}
}
global.nv_insert_post = post=>{
if (!post.post_type) {
return new NV_Error('不存在的文章类型');
}
// 设置默认值
post = {
author: 0,
created_time:Date.now(),
title: "",
slug: "",
content: "",
content_type: "markdown",
status: "draft",
password: "",
excerpt: "",
comment_status: true,
modified_time: Date.now(),
parent: 0,
order: 0,
post_type: '',
comment_count: 0,
...post,
}
do_action('before_insert_post', post);
//markdown类型的,不是string,那么强制改成字符串
if ( ['markdown','rich-text'].includes(post.content_type) && typeof(post.content) !== 'string') {
post.content = "";
}
post.slug = nv_unique_post_slug(post.slug || post.title);
var id = db_posts.insert(post)
// 调用相关的action
do_action('insert_post', id, post);
return id;
}
global.nv_update_post = post=>{
var id = post.id;
var filter_func = r=>r.id == id;
filter_func._nvdb_reference = true;
var current_post = db_posts.find(filter_func)[0];
if (!current_post) {
return new NV_Error('文章ID不存在')
}
if (!post.post_type) {
return new NV_Error('不存在的文章类型');
}
post = {
content_type: "markdown",
...post
}
do_action('before_update_post', id, nv_remove_reference(post));
delete post.id;
if ( ['markdown','rich-text'].includes(post.content_type) && typeof(post.content) !== 'string') {
post.content = "";
}
if (!post.modified_time || current_post.status == 'draft' || post.status == 'draft') {
post.modified_time = Date.now();
}
//判定slug要不要更新
var current_slug = current_post.slug;
if (post.slug !== current_slug) {
post.slug = nv_unique_post_slug(post.slug || post.title)
}
//没有order,就设置为0
if (!post.order) {
post.order = 0;
}
db_posts.setColumn(filter_func, post);
// 调用相关的action
do_action('update_post', id, nv_remove_reference(post));
return id;
}
global.nv_delete_post = (post_id)=>{
// 调用相关的action
do_action('before_delete_post', post_id);
post_id = parseInt(post_id)
var post = get_post( post_id );
if ( is_nv_error(post) ) {
return post;
}
//如果本身是别人的parent,则将子集提升一级(子的parent变成自身的parent)
db_posts.setColumn(r=>r.parent == post.id,{
parent: post.parent || 0
})
// 读取文章所属的terms,重新对term的文章数计算
var term_ids = nvdb.term_relationships.find(r=>r.object_id == post_id).map(r=>r.term_id);
var terms = nvdb.terms.find(r=>term_ids.includes(r.id));
terms.forEach(term=>{
nvdb.terms.setColumn(r=>r.id == term.id, {
count: parseInt(term.count - 1)
})
})
delete_post_meta(post_id);
nvdb.term_relationships.drop(r=>r.object_id == post_id);
db_posts.drop(r=>r.id == post_id);
//删除该文章所有的评论、评论meta
var removed_comment_IDs = nvdb.comments.drop(r=>r.post_id == post_id);
nvdb.commentmeta.drop(r=> removed_comment_IDs.includes(r.comment_id) )
// 调用相关的action
do_action('after_delete_post', post_id);
}
var is_in_object = (keyword, object) => {
var result = false;
var deep_find = target => {
if (result) {return true}
if (!target) {return;}
if (typeof target == 'string') {
if (target.includes(keyword)) {
result = true;
return true;
}
}
else if (typeof target == 'object') {
if (Array.isArray(target)) {
for (var i = 0; i < target.length; i++) {
if ( deep_find(target[i]) ) {
return true;
}
}
} else {
var keys = Object.keys(target);
for (var i = 0; i < keys.length; i++) {
if ( deep_find(target[keys[i]]) ) {
return true;
}
}
}
}
}
deep_find(object)
return result;
}
global.query_posts = (args = {}) => {
/*
args = {
author: Number,
author_in: Array,
author_not_in: Array,
post: Number,
post_in: Array, [Number],
post_not_in: Array, [Number],
keyword: String,
parent: Number, // 0仅返回顶层
hierarchical: Boolean // 层次显示
has_password: Boolean,
post_password: String,
post_type: String / Array, [String]
status: String/Array, [String] //【没有默认值!!!】空字符串、无参数则表示任意
comment_count: {
value: Number,
compare: String, =/!=/>/>=/</<=
},
date_query: {
after: Number, timestamp
before: Number, timespamp //默认: 小于服务器当前时间
},
tax_query: {
relation: String, AND/OR
opts: [
{
taxonomy: String, //必填
terms: Array, [Number/String], //必填
operator: String, IN/NOT IN //默认 IN
}
]
},
meta_query: {
relation: String, AND /OR //默认 AND
opts: [
{
key: String, metaKey
value: String / Array, Array可以用IN /NOT IN/BETWEEN/NOT BETWEEN来compare
compare: String, =/!=/>/>=/</<=/LIKE/NOT LIKE/IN/NOT IN/BETWEEN/NOT BETWEEN
}
]
},
orderby: String, id/author/title/slug/date/parent/rand 默认:order -> order相同就modified_time
order: String, ASC/DESC, 默认:DESC
posts_per_page: Number,
current_page: Number,
}
*/
//先过滤posts表
var filter_func = r=>{
var result = [];
if (args.author && r.author !== args.author) { return false; }
if (args.author_in && !args.author_in.includes(r.author)) { return false; }
if (args.author_not_in && args.author_not_in.includes(r.author) ) { return false; }
if (args.post && r.id !== args.post) { return false; }
if (args.post_in && !args.post_in.includes(r.id)) { return false; }
if (args.post_not_in && args.post_not_in.includes(r.id)) { return false; }
if (args.post_type) {
if ( Array.isArray(args.post_type) ) {
if ( !args.post_type.includes(r.post_type) ) {return false;}
} else {
if ( r.post_type !== args.post_type) {return false;}
}
}
if (args.keyword) {
if ( !(String(r.title).includes(args.keyword) || String(r.slug).includes(args.keyword) || String(r.excerpt).includes(args.keyword) || is_in_object(args.keyword,r.content)) ) {
return false;
}
}
if (args.parent !== undefined && r.parent !== args.parent) { return false; }
if ( args.has_password !== undefined ) {
if (args.has_password && String(r.password).length == 0) { return false }
if (!args.has_password && String(r.password).length > 0) { return false }
}
if (args.post_password && r.password !== args.post_password ) { return false; }
//status 默认 publish
if (args.status) {
if ( Array.isArray(args.status) ) {
if (!args.status.includes(r.status)) {return false;}
} else {
if (r.status !== args.status) {return false;}
}
}
if (args.comment_count) {
var comment_count_result;
switch(args.comment_count.compare) {
case '!=': comment_count_result = (r.comment_count !== args.comment_count.value);
break;
case '>': comment_count_result = (r.comment_count > args.comment_count.value);
break;
case '>=': comment_count_result = (r.comment_count >= args.comment_count.value);
break;
case '<': comment_count_result = (r.comment_count < args.comment_count.value);
break;
case '<=': comment_count_result = (r.comment_count <= args.comment_count.value);
break;
default: comment_count_result = (r.comment_count == args.comment_count.value);
}
if (!comment_count_result) {return false}
}
// date_query 默认查询已发布(小于当前时间的)
if (args.date_query == undefined) {
args.date_query = {
before: Date.now()
}
}
if (args.date_query) {
var date_query_result = [];
if (args.date_query.after) {
date_query_result.push(r.modified_time >= args.date_query.after)
}
if (args.date_query.before) {
date_query_result.push(r.modified_time <= args.date_query.before)
}
//只要有一个false,就是false。全部true才是true
if ( date_query_result.includes(false) ) {return false;}
}
return true;
}
filter_func._nvdb_reference = true;
var posts = db_posts.find(filter_func);
if (args.tax_query) {
var tax_queried_post_ids = [/*post_ids*/];
//先根据opts分别获取term_id
var tax_query_result = [ /*[term_ids],[term_ids]*/ ];
args.tax_query.opts.forEach(({taxonomy,terms,operator})=>{
/*待优化:是否应该包含子级别(hierarchical)
若设为false,本身是父级,那么就找不到子分类的post
若设为true,本身是子级别,那么就找不到post*/
var get_term_arg = {taxonomy, hierarchical: true};
if (operator == "NOT IN") {
get_term_arg.exclude = terms;
} else {
get_term_arg.include = terms;
}
tax_query_result.push( get_terms(get_term_arg).map(r=>r.id) )
})
//分别根据每次获取的[term_id]来过滤得到post_id
var post_ids_in_tax_query_result = [ /*[post_ids],[post_ids]*/ ];
tax_query_result.forEach(term_ids=>{
var post_ids = nvdb.term_relationships.find( r=> term_ids.includes(r.term_id) ).map(r=>r.object_id);
post_ids_in_tax_query_result.push(post_ids);
})
//根据relation将每次获取的post_ids进行交集或并集
if (args.tax_query.relation == 'OR') {
// 取并集
tax_queried_post_ids = post_ids_in_tax_query_result.reduce( (arr1,arr2)=> arr1.concat(arr2.filter(v => !arr1.includes(v))) )
} else {
// 取交集
tax_queried_post_ids = post_ids_in_tax_query_result.reduce( (arr1,arr2)=> arr1.filter(v => arr2.includes(v)) )
}
//最后根据交集或并集的结果[post_ids]来过滤posts
var tax_post_intersections = [];
posts.forEach(post=>{
if ( tax_queried_post_ids.includes(post.id) ) {
tax_post_intersections.push(post)
}
})
posts = tax_post_intersections;
}
if (args.meta_query) {
var meta_queried_post_ids = [];
//先根据opts分别获取post_id
var meta_query_result = [ /*[post_ids],[post_ids]*/ ]
args.meta_query.opts.forEach(({key,value,compare})=>{
var post_ids = nvdb.postmeta.find(r=>{
if (r.key !== key) {return false;}
var post = posts.find(post=>post.id == r.post_id);
if (!post) {return;}
if (!post.metas) { post.metas = {} }
post.metas[key] = r.value;
try {
switch (compare) {
case '=': return r.value == value;
case '!=': return r.value !== value;
case '>': return r.value > value;
case '>=': return r.value >= value;
case '<=': return r.value <= value;
case 'LIKE': return r.value.toString().includes(value);
case 'NOT LIKE': return !r.value.toString().includes(value);
case 'IN': return value.includes(r.value);
case 'NOT IN': return !value.includes(r.value);
case 'BETWEEN': return r.value >= value[0] && r.value <=value[1];
case 'NOT BETWEEN': return r.value >= value[1] || r.value <=value[0];
default: return true;
}
} catch(e) {
console.log(e,`比较类型:${compare}`,`原始数据:`,r)
return false;
}
}).map(r=>r.post_id)
meta_query_result.push(post_ids)
})
//根据relation将每次获取的term_id进行交集或并集
if (args.meta_query.relation == 'OR') {
// 取并集
meta_queried_post_ids = meta_query_result.reduce( (arr1,arr2)=> arr1.concat(arr2.filter(v => !arr1.includes(v))) )
} else {
// 取交集
meta_queried_post_ids = meta_query_result.reduce( (arr1,arr2)=> arr1.filter(v => arr2.includes(v)) )
}
//最后根据最终交或并结果的[post_id]来过滤posts
var meta_post_intersections = [];
posts.forEach(post=>{
if ( meta_queried_post_ids.includes(post.id) ) {
meta_post_intersections.push(post)
}
})
posts = meta_post_intersections;
}
//如果是按层次显示,那么还应该循环查找该页中所有的子级
if (args.hierarchical) {
var all_child_level_ids = [];
var getChildren = (parentIDs)=>{
var childrenIDs = db_posts.find(r=>{
if (parentIDs.includes(r.parent)) {
// 层级查询也应该查询相同status的数据。否则前台会显示出未审核的文章
if (args.status) {
if ( Array.isArray(args.status) ) {
if (!args.status.includes(r.status)) {return false;}
} else {
if (r.status !== args.status) {return false;}
}
}
return true;
}
}).map(r=>r.id);
if (childrenIDs.length > 0) {
all_child_level_ids = all_child_level_ids.concat(childrenIDs)
getChildren(childrenIDs)
}
}
getChildren( posts.map(r=>r.id) );
var all_child_level_posts = db_posts.find(r=>all_child_level_ids.includes(r.id) );
posts = posts.concat( all_child_level_posts )
// 清除相同的term信息(本身query的时候,找到的结果就一个是另一个的子级,然后再找子集之后,会出现重复元素)
var putted_ids = [];
var unique_terms = [];
posts.forEach(term=>{
if (!putted_ids.includes(term.id)) {
unique_terms.push(term);
putted_ids.push(term.id)
}
})
posts = unique_terms;
}
// 排序
if (args.orderby == 'rand') {
posts.sort(() => Math.random() - 0.5);
} else {
if (!args.orderby) {
posts.sort((post1,post2)=>{
if (post1.order == post2.order) {
return post2.modified_time - post1.modified_time;
}
return post1.order - post2.order;
})
} else {
posts.sort((post1,post2)=>{
if ( typeof( post1[args.orderby] || (post1.metas||{})[args.orderby] ) == 'string' ) {
var diff = (post1[args.orderby] || (post1.metas||{})[args.orderby]).localeCompare( post2[args.orderby] || (post2.metas||{})[args.orderby] )
} else {
var diff = (post1[args.orderby] || (post1.metas||{})[args.orderby]) - (post2[args.orderby] || (post2.metas||{})[args.orderby]);
}
return args.order == "ASC" ? diff : -diff;
})
}
}
// 如果是层级显示,先装入层级,然后再分页!
if (args.hierarchical) {
var child_ids = [];
var collapser = arr => {
arr.forEach(item=>{
var {id} = item;
var children = arr.filter(item=>item.parent == id);
if (children.length) {
item.children = children;
child_ids = [...child_ids, ...children.map(item=>item.id)]
}
})
}
collapser(posts)
for (var i = posts.length - 1; i >= 0; i--) {
if ( child_ids.includes(posts[i].id) ) {
posts.splice(i,1)
}
}
}
// 分页
var current_page = parseInt(args.current_page) || 0;
if (current_page <= 0) {current_page = 1}
var posts_per_page = parseInt(args.posts_per_page) || 0;
if (posts_per_page <= 0) {posts_per_page = get_option('posts_per_page',10)}
var posts_start_index = ( current_page - 1 ) * posts_per_page;
var postsPaged = posts.slice(posts_start_index, posts_start_index + posts_per_page);
// 将tree展开,理由:方便写api接口的时候对每个user数据处理。否则循环起来不方便
var expanded = [];
var expander = arr => {
arr.forEach(item=>{
var {children} = item;
if (children) {
delete item.children;
if (children.length) {
expanded.push(item);
return expander(children)
}
}
expanded.push(item)
})
}
expander(postsPaged)
postsPaged = expanded;
return {
data: nv_remove_reference(postsPaged),
pagination: {
current_page,
posts_per_page,
total: posts.length
}
}
}
global.get_post = (arg) => {
if (!arg) { return new NV_Error('参数错误') }
if ( typeof(arg)=='number' ) {
var filter_func = r=>r.id == arg;
filter_func._nvdb_reference = true;
var post = db_posts.find(filter_func)[0];
return nv_remove_reference(post) || new NV_Error('文章ID不存在');
} else {
//arg是slug
var post_slug = encodeURIComponent(decodeURIComponent(arg));
var filter_func = r=>r.slug == post_slug;
filter_func._nvdb_reference = true;
var post = db_posts.find(filter_func)[0];
return nv_remove_reference(post) || new NV_Error('文章别名不存在');
}
}
global.get_post_permalink = (arg)=>{
// arg 可以是 post_id 或 post 对象
if ( typeof(arg) == 'number' ) {
var post_id = arg;
var post = get_post(post_id);
} else {
var post_id = ( arg || {} ).id;
var post = arg;
}
var url = "";
if ( !is_nv_error(post) && post_id ) {
// 默认:安装地址/post_slug
url = `${get_option('nv_home_url','')}/${post.slug}`;
}
return apply_filters('post_permalink', url, post, arg );
}
global.update_post_meta = (post_id,key,value) => {
var filter_func = r=>r.id == post_id;
filter_func._nvdb_reference = true;
if ( db_posts.find(filter_func)[0] ) {
return update_metadata('post',post_id,key,value);
}
}
global.get_post_meta = (post_id,key)=>{
return get_metadata('post',post_id,key);
}
global.delete_post_meta = (post_id,key) => {
return delete_metadata('post',post_id,key);
}
global.nv_set_post_fields = (post_ids, obj)=>{
var filter_func = r=>post_ids.includes(r.id);
filter_func._nvdb_reference = true;
db_posts.setColumn(filter_func,obj);
}
global.get_the_post_thumbnail_url = (post_id) => {
return get_post_meta(post_id,'_nv_thumbnail');
}
global.add_post_type_support = (post_type, feature) => {
add_filter('_handle_post_type_support', (supports,current_post_type)=>{
if (current_post_type == post_type) {
if (!Array.isArray(feature)) {
feature = [feature];
}
supports = [...supports, ...feature];
//去除重复项
const set = new Set(supports);
supports = [...set];
}
return supports;
})
}
global.remove_post_type_support = (post_type, feature) => {
add_filter('_handle_post_type_support', (supports,current_post_type)=>{
if (current_post_type == post_type) {
if (!Array.isArray(feature)) {
feature = [feature];
}
feature.map(item=>{
var index = supports.indexOf(item);
if (index >= 0) {
supports.splice(index,1);
}
})
}
return supports;
})
}
global.get_post_type_supports = (post_type) => {
return apply_filters('_handle_post_type_support', [],post_type);
}
global.register_posttype = (post_type,opts)=>{
if (!opts) {opts={}}
if (!opts.supports) {
opts.supports = []
}
// 处理 add_post_type_support 和 remove_post_type_support
var supports = apply_filters('_handle_post_type_support', opts.supports ,post_type);
var {label,labels,menu_icon} = opts;
if (!labels) {labels={}}
// labels: {all_items, add_new_item}
if (opts.show_ui) {
add_menu_page({
page_title: label || post_type,
menu_title: label || post_type,
menu_slug: `posttype-${post_type}`,
power: opts.power ? (opts.power > 6 ? opts.power : 6) : 6,
position: 10,
icon: menu_icon || "<i class='el-icon-document'></i>",
})
add_submenu_page({
parent_slug: `posttype-${post_type}`,
page_title: labels.all_items || `所有${label || post_type}`,
menu_title: labels.all_items || `所有${label || post_type}`,
menu_slug: `postlist-${post_type}`,
power: opts.power ? (opts.power > 6 ? opts.power : 6) : 6,
position: 1,
_component: "postlist"
})
if (supports.includes('editor')) {
add_submenu_page({
parent_slug: `posttype-${post_type}`,
page_title: labels.add_new_item || `添加${label || post_type}`,
menu_title: labels.add_new_item || `添加${label || post_type}`,
menu_slug: `editor-${post_type}`,
power: opts.power ? (opts.power > 6 ? opts.power : 6) : 6,
position: 5,
_component: "editor"
})
}
}
add_filter('_nv_posttypes',(posttypes)=> {
posttypes = posttypes || [];
if ( !posttypes.filter(r=>r.post_type == post_type)[0] ) {
posttypes.push({
post_type,
opts: {
...opts,
supports: apply_filters('_handle_post_type_support', opts.supports ,post_type)
}
})
}
return posttypes;
});
}
global.register_taxonomy = (taxonomy, post_type, opts)=>{
if (!opts) {opts={}}
var {labels} = opts;
if (!labels) {labels={}}
// labels: {name}
//没有这个posttype,就不注册
var posttypes = apply_filters('_nv_posttypes',[]);
if ( !posttypes.filter(r=>r.post_type == post_type)[0] ) {
return;
}
add_submenu_page({
parent_slug: `posttype-${post_type}`,
page_title: labels.name || taxonomy,
menu_title: labels.name || taxonomy,
menu_slug: `taxonomy-${post_type}-${taxonomy}`,
power: 8,
position: 10,
_component: "taxonomy"
})
add_filter('_nv_taxonomies',(taxonomies)=> {
taxonomies = taxonomies || [];
if ( !taxonomies.filter(r=>r.taxonomy == taxonomy && r.post_type == post_type)[0] ) {
taxonomies.push({taxonomy, post_type, opts})
}
return taxonomies;
});
}
global.async_get_the_content = (arg) => {
// 仅后端渲染使用!前端务必自己渲染,不要浪费服务器资源
// arg: post对象 / post_id / post_slug
async function fn() {
if ( ['number','string'].includes(typeof(arg)) ) { var post = get_post(post) }
else if(typeof(arg)=="object") { var post = arg }
else {var post = new NV_Error('Post格式错误')}
if (is_nv_error(post)) {
return await apply_async_filters("async:get_the_content","",post);
}
if (post.content_type == 'block') {
var blocks = post.content.blocks;
if ( !Array.isArray(blocks) ) {
return await apply_async_filters("async:get_the_content","",post);
}
var result_content = "";
for (var i = 0; i < blocks.length; i++) {
var block = blocks[i];
var block_data = await apply_async_filters(`async:render_block:${block.type}`,"", block, post);
result_content += block_data;
}
return await apply_async_filters("async:get_the_content",result_content,post);
}
return await apply_async_filters("async:get_the_content","",post);
}
return fn();
}
/*
global.register_editor_block = (block_id, supports, script_url) => {
add_filter('_nv_editor_blocks',(blocks)=>{
blocks.push({block_id, supports, script_url});
return blocks;
})
}
global.get_editor_blocks = (post_type)=>{
var blocks = apply_filters('_nv_editor_blocks',[]);
return blocks.filter(({supports})=> post_type ? supports.includes(post_type) : true )
}*/
global.register_blocks = script_url => {
add_filter('_nvbe_blocks', urls => {
urls.push(script_url)
return urls;
})
}
global.get_block_urls = (post_type)=>{
return apply_filters('_nvbe_blocks',[]);
}