• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hexojs / hexo / 5549650752

pending completion
5549650752

push

github

stevenjoezhang
feat(post): remove front-matter property `link`

2282 of 2372 branches covered (96.21%)

8870 of 8912 relevant lines covered (99.53%)

54.84 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.94
/lib/plugins/processor/post.ts
1
import { toDate, timezone, isExcludedFile, isTmpFile, isHiddenFile, isMatch } from './common';
1✔
2
import Promise from 'bluebird';
1✔
3
import { parse as yfm } from 'hexo-front-matter';
1✔
4
import { extname, join } from 'path';
1✔
5
import { stat, listDir } from 'hexo-fs';
1✔
6
import { slugize, Pattern, Permalink } from 'hexo-util';
1✔
7
import { magenta } from 'picocolors';
1✔
8

1✔
9
const postDir = '_posts/';
1✔
10
const draftDir = '_drafts/';
1✔
11
let permalink;
1✔
12

1✔
13
const preservedKeys = {
1✔
14
  title: true,
1✔
15
  year: true,
1✔
16
  month: true,
1✔
17
  day: true,
1✔
18
  i_month: true,
1✔
19
  i_day: true,
1✔
20
  hash: true
1✔
21
};
1✔
22

1✔
23
export = ctx => {
1✔
24
  return {
69✔
25
    pattern: new Pattern(path => {
69✔
26
      if (isTmpFile(path)) return;
41✔
27

39✔
28
      let result;
39✔
29

39✔
30
      if (path.startsWith(postDir)) {
41✔
31
        result = {
13✔
32
          published: true,
13✔
33
          path: path.substring(postDir.length)
13✔
34
        };
13✔
35
      } else if (path.startsWith(draftDir)) {
41✔
36
        result = {
2✔
37
          published: false,
2✔
38
          path: path.substring(draftDir.length)
2✔
39
        };
2✔
40
      }
2✔
41

39✔
42
      if (!result || isHiddenFile(result.path)) return;
41✔
43

11✔
44
      // checks only if there is a renderer for the file type or if is included in skip_render
11✔
45
      result.renderable = ctx.render.isRenderable(path) && !isMatch(path, ctx.config.skip_render);
41✔
46

41✔
47
      // if post_asset_folder is set, restrict renderable files to default file extension
41✔
48
      if (result.renderable && ctx.config.post_asset_folder) {
41✔
49
        result.renderable = (extname(ctx.config.new_post_name) === extname(path));
3✔
50
      }
3✔
51

11✔
52
      return result;
11✔
53
    }),
69✔
54

69✔
55
    process: function postProcessor(file) {
69✔
56
      if (file.params.renderable) {
43✔
57
        return processPost(ctx, file);
35✔
58
      } else if (ctx.config.post_asset_folder) {
43✔
59
        return processAsset(ctx, file);
7✔
60
      }
7✔
61
    }
43✔
62
  };
69✔
63
};
69✔
64

1✔
65
function processPost(ctx, file) {
35✔
66
  const Post = ctx.model('Post');
35✔
67
  const { path } = file.params;
35✔
68
  const doc = Post.findOne({source: file.path});
35✔
69
  const { config } = ctx;
35✔
70
  const { timezone: timezoneCfg } = config;
35✔
71
  const updated_option = config.updated_option;
35✔
72
  let categories, tags;
35✔
73

35✔
74
  if (file.type === 'skip' && doc) {
35✔
75
    return;
1✔
76
  }
1✔
77

34✔
78
  if (file.type === 'delete') {
35✔
79
    if (doc) {
3✔
80
      return doc.remove();
1✔
81
    }
1✔
82

2✔
83
    return;
2✔
84
  }
2✔
85

31✔
86
  return Promise.all([
31✔
87
    file.stat(),
31✔
88
    file.read()
31✔
89
  ]).spread((stats, content) => {
31✔
90
    const data = yfm(content);
31✔
91
    const info = parseFilename(config.new_post_name, path);
31✔
92
    const keys = Object.keys(info);
31✔
93

31✔
94
    data.source = file.path;
31✔
95
    data.raw = content;
31✔
96
    data.slug = info.title;
31✔
97

31✔
98
    if (file.params.published) {
31✔
99
      if (!Object.prototype.hasOwnProperty.call(data, 'published')) data.published = true;
30✔
100
    } else {
31✔
101
      data.published = false;
1✔
102
    }
1✔
103

31✔
104
    for (let i = 0, len = keys.length; i < len; i++) {
31✔
105
      const key = keys[i];
41✔
106
      if (!preservedKeys[key]) data[key] = info[key];
41✔
107
    }
41✔
108

31✔
109
    if (data.date) {
31✔
110
      data.date = toDate(data.date);
6✔
111
    } else if (info && info.year && (info.month || info.i_month) && (info.day || info.i_day)) {
31!
112
      data.date = new Date(
3✔
113
        info.year,
3✔
114
        parseInt(info.month || info.i_month, 10) - 1,
3!
115
        parseInt(info.day || info.i_day, 10)
3!
116
      );
3✔
117
    }
3✔
118

31✔
119
    if (data.date) {
31✔
120
      if (timezoneCfg) data.date = timezone(data.date, timezoneCfg);
8✔
121
    } else {
31✔
122
      data.date = stats.birthtime;
23✔
123
    }
23✔
124

31✔
125
    data.updated = toDate(data.updated);
31✔
126

31✔
127
    if (data.updated) {
31✔
128
      if (timezoneCfg) data.updated = timezone(data.updated, timezoneCfg);
3✔
129
    } else if (updated_option === 'date') {
31✔
130
      data.updated = data.date;
1✔
131
    } else if (updated_option === 'empty') {
28✔
132
      data.updated = undefined;
1✔
133
    } else {
27✔
134
      data.updated = stats.mtime;
26✔
135
    }
26✔
136

31✔
137
    if (data.category && !data.categories) {
31✔
138
      data.categories = data.category;
1✔
139
      data.category = undefined;
1✔
140
    }
1✔
141

31✔
142
    if (data.tag && !data.tags) {
31✔
143
      data.tags = data.tag;
1✔
144
      data.tag = undefined;
1✔
145
    }
1✔
146

31✔
147
    categories = data.categories || [];
31✔
148
    tags = data.tags || [];
31✔
149

31✔
150
    if (!Array.isArray(categories)) categories = [categories];
31✔
151
    if (!Array.isArray(tags)) tags = [tags];
31✔
152

31✔
153
    if (data.photo && !data.photos) {
31✔
154
      data.photos = data.photo;
1✔
155
      data.photo = undefined;
1✔
156
    }
1✔
157

31✔
158
    if (data.photos && !Array.isArray(data.photos)) {
31✔
159
      data.photos = [data.photos];
1✔
160
    }
1✔
161

31✔
162
    if (data.permalink) {
31✔
163
      data.__permalink = data.permalink;
1✔
164
      data.permalink = undefined;
1✔
165
    }
1✔
166

31✔
167
    if (doc) {
31✔
168
      if (file.type !== 'update') {
3✔
169
        ctx.log.warn(`Trying to "create" ${magenta(file.path)}, but the file already exists!`);
2✔
170
      }
2✔
171
      return doc.replace(data);
3✔
172
    }
3✔
173

28✔
174
    return Post.insert(data);
28✔
175
  }).then(doc => Promise.all([
31✔
176
    doc.setCategories(categories),
31✔
177
    doc.setTags(tags),
31✔
178
    scanAssetDir(ctx, doc)
31✔
179
  ]));
31✔
180
}
31✔
181

1✔
182
function parseFilename(config, path) {
31✔
183
  config = config.substring(0, config.length - extname(config).length);
31✔
184
  path = path.substring(0, path.length - extname(path).length);
31✔
185

31✔
186
  if (!permalink || permalink.rule !== config) {
31✔
187
    permalink = new Permalink(config, {
8✔
188
      segments: {
8✔
189
        year: /(\d{4})/,
8✔
190
        month: /(\d{2})/,
8✔
191
        day: /(\d{2})/,
8✔
192
        i_month: /(\d{1,2})/,
8✔
193
        i_day: /(\d{1,2})/,
8✔
194
        hash: /([0-9a-f]{12})/
8✔
195
      }
8✔
196
    });
8✔
197
  }
8✔
198

31✔
199
  const data = permalink.parse(path);
31✔
200

31✔
201
  if (data) {
31✔
202
    if (data.title !== undefined) {
30✔
203
      return data;
29✔
204
    }
29✔
205
    return Object.assign(data, {
1✔
206
      title: slugize(path)
1✔
207
    });
1✔
208
  }
1✔
209

1✔
210
  return {
1✔
211
    title: slugize(path)
1✔
212
  };
1✔
213
}
1✔
214

1✔
215
function scanAssetDir(ctx, post) {
31✔
216
  if (!ctx.config.post_asset_folder) return;
31✔
217

5✔
218
  const assetDir = post.asset_dir;
5✔
219
  const baseDir = ctx.base_dir;
5✔
220
  const baseDirLength = baseDir.length;
5✔
221
  const PostAsset = ctx.model('PostAsset');
5✔
222

5✔
223
  return stat(assetDir).then(stats => {
5✔
224
    if (!stats.isDirectory()) return [];
5!
225

5✔
226
    return listDir(assetDir);
5✔
227
  }).catch(err => {
5✔
228
    if (err && err.code === 'ENOENT') return [];
×
229
    throw err;
×
230
  }).filter(item => !isExcludedFile(item, ctx.config)).map(item => {
5✔
231
    const id = join(assetDir, item).substring(baseDirLength).replace(/\\/g, '/');
6✔
232
    const asset = PostAsset.findById(id);
6✔
233

6✔
234
    if (shouldSkipAsset(ctx, post, asset)) return undefined;
6✔
235

3✔
236
    return PostAsset.save({
3✔
237
      _id: id,
3✔
238
      post: post._id,
3✔
239
      slug: item,
3✔
240
      modified: true
3✔
241
    });
3✔
242
  });
5✔
243
}
5✔
244

1✔
245
function shouldSkipAsset(ctx, post, asset) {
6✔
246
  if (!ctx._showDrafts()) {
6✔
247
    if (post.published === false && asset) {
5✔
248
      // delete existing draft assets if draft posts are hidden
1✔
249
      asset.remove();
1✔
250
    }
1✔
251
    if (post.published === false) {
5✔
252
      // skip draft assets if draft posts are hidden
3✔
253
      return true;
3✔
254
    }
3✔
255
  }
5✔
256

3✔
257
  return asset !== undefined; // skip already existing assets
3✔
258
}
3✔
259

1✔
260
function processAsset(ctx, file) {
7✔
261
  const PostAsset = ctx.model('PostAsset');
7✔
262
  const Post = ctx.model('Post');
7✔
263
  const id = file.source.substring(ctx.base_dir.length).replace(/\\/g, '/');
7✔
264
  const doc = PostAsset.findById(id);
7✔
265

7✔
266
  if (file.type === 'delete') {
7✔
267
    if (doc) {
2✔
268
      return doc.remove();
1✔
269
    }
1✔
270

1✔
271
    return;
1✔
272
  }
1✔
273

5✔
274
  // TODO: Better post searching
5✔
275
  const post = Post.toArray().find(post => file.source.startsWith(post.asset_dir));
5✔
276
  if (post != null && (post.published || ctx._showDrafts())) {
7!
277
    return PostAsset.save({
3✔
278
      _id: id,
3✔
279
      slug: file.source.substring(post.asset_dir.length),
3✔
280
      post: post._id,
3✔
281
      modified: file.type !== 'skip',
3✔
282
      renderable: file.params.renderable
3✔
283
    });
3✔
284
  }
3✔
285

2✔
286
  if (doc) {
7✔
287
    return doc.remove();
1✔
288
  }
1✔
289
}
7✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc