From 6d880a263d2ce066d8c2e8b1fc39bb3c542b48df Mon Sep 17 00:00:00 2001 From: Uiolee <22849383+uiolee@users.noreply.github.com> Date: Fri, 17 May 2024 17:07:16 +0800 Subject: [PATCH 1/4] feat(renderScaffold): deepMerge frontMatter of post and scaffold (#5472) * feat(renderScaffold): deepMerge frontMatter of post and scaffold * test: add test case --- lib/hexo/post.ts | 10 +++------- test/scripts/hexo/post.ts | 39 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/lib/hexo/post.ts b/lib/hexo/post.ts index 8e443b0283..2efeae4dca 100644 --- a/lib/hexo/post.ts +++ b/lib/hexo/post.ts @@ -4,7 +4,7 @@ import Promise from 'bluebird'; import { join, extname, basename } from 'path'; import { magenta } from 'picocolors'; import { load } from 'js-yaml'; -import { slugize, escapeRegExp } from 'hexo-util'; +import { slugize, escapeRegExp, deepMerge} from 'hexo-util'; import { copyDir, exists, listDir, mkdirs, readFile, rmdir, unlink, writeFile } from 'hexo-fs'; import { parse as yfmParse, split as yfmSplit, stringify as yfmStringify } from 'hexo-front-matter'; import type Hexo from './index'; @@ -306,13 +306,9 @@ class Post { const jsonMode = separator.startsWith(';'); // Parse front-matter - const obj = jsonMode ? JSON.parse(`{${frontMatter}}`) : load(frontMatter); + let obj = jsonMode ? JSON.parse(`{${frontMatter}}`) : load(frontMatter); - Object.keys(data) - .filter(key => !preservedKeys.includes(key) && obj[key] == null) - .forEach(key => { - obj[key] = data[key]; - }); + obj = deepMerge(obj, Object.fromEntries(Object.entries(data).filter(([key, value]) => !preservedKeys.includes(key) && value != null))); let content = ''; // Prepend the separator diff --git a/test/scripts/hexo/post.ts b/test/scripts/hexo/post.ts index b01e41d61f..79a4328ce7 100644 --- a/test/scripts/hexo/post.ts +++ b/test/scripts/hexo/post.ts @@ -4,7 +4,7 @@ import { readFile, mkdirs, unlink, rmdir, writeFile, exists, stat, listDir } fro import { spy, useFakeTimers } from 'sinon'; import { parse as yfm } from 'hexo-front-matter'; import { expected, content, expected_disable_nunjucks, content_for_issue_3346, expected_for_issue_3346, content_for_issue_4460 } from '../../fixtures/post_render'; -import { highlight } from 'hexo-util'; +import { highlight, deepMerge } from 'hexo-util'; import Hexo from '../../../lib/hexo'; import chai from 'chai'; const should = chai.should(); @@ -650,6 +650,43 @@ describe('Post', () => { await unlink(data.path); }); + // https:// github.com/hexojs/hexo/issues/5155 + it('publish() - merge front-matter', async () => { + const prefixTags = ['prefixTag1', 'fooo']; + const customTags = ['customTag', 'fooo']; + + await hexo.scaffold.set('customscaff', [ + '---', + 'title: {{ title }}', + 'date: {{ date }}', + `tags: ${JSON.stringify(prefixTags)}`, + 'qwe: 123', + 'zxc: zxc', + '---' + ].join('\n')); + + const path = join(hexo.source_dir, '_posts', 'fooo.md'); + const data = await post.create({ + title: 'fooo', + layout: 'draft', + tags: customTags, + qwe: 456, + asd: 'asd' + }); + const result = await post.publish({ + slug: 'fooo', + layout: 'customscaff' + }); + + const fmt = yfm(result.content); + fmt.tags.sort().should.eql(deepMerge(prefixTags, customTags).sort()); + fmt.qwe.should.eql(456); + fmt.asd.should.eql('asd'); + fmt.zxc.should.eql('zxc'); + + await unlink(path); + }); + it('render()', async () => { // TODO: validate data const beforeHook = spy(); From 069ac3827411c1f26d97caca962a175db9a2d7cc Mon Sep 17 00:00:00 2001 From: Uiolee <22849383+uiolee@users.noreply.github.com> Date: Fri, 17 May 2024 17:07:53 +0800 Subject: [PATCH 2/4] feat: add option to use slug as title of post (#5470) Signed-off-by: Uiolee <22849383+uiolee@users.noreply.github.com> Co-authored-by: Sukka --- lib/hexo/default_config.ts | 2 ++ lib/plugins/processor/post.ts | 10 ++++++++-- test/scripts/processors/post.ts | 26 ++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/lib/hexo/default_config.ts b/lib/hexo/default_config.ts index 6d1ede870c..390b8f32f5 100644 --- a/lib/hexo/default_config.ts +++ b/lib/hexo/default_config.ts @@ -58,6 +58,8 @@ export = { exclude_languages: [], strip_indent: true }, + use_filename_as_post_title: false, + // Category & Tag default_category: 'uncategorized', category_map: {}, diff --git a/lib/plugins/processor/post.ts b/lib/plugins/processor/post.ts index 2baee39021..969fad87a1 100644 --- a/lib/plugins/processor/post.ts +++ b/lib/plugins/processor/post.ts @@ -71,8 +71,8 @@ function processPost(ctx: Hexo, file: _File) { const { path } = file.params; const doc = Post.findOne({source: file.path}); const { config } = ctx; - const { timezone: timezoneCfg } = config; - const updated_option = config.updated_option; + const { timezone: timezoneCfg, updated_option, use_slug_as_post_title } = config; + let categories, tags; if (file.type === 'skip' && doc) { @@ -110,6 +110,12 @@ function processPost(ctx: Hexo, file: _File) { if (!preservedKeys[key]) data[key] = info[key]; } + // use `slug` as `title` of post when `title` is not specified. + // https://github.com/hexojs/hexo/issues/5372 + if (use_slug_as_post_title && !('title' in data)) { + data.title = info.title; + } + if (data.date) { data.date = toDate(data.date); } else if (info && info.year && (info.month || info.i_month) && (info.day || info.i_day)) { diff --git a/test/scripts/processors/post.ts b/test/scripts/processors/post.ts index c19f2f2c9c..9fa83192e2 100644 --- a/test/scripts/processors/post.ts +++ b/test/scripts/processors/post.ts @@ -805,6 +805,32 @@ describe('post', () => { ]); }); + // use `slug` as `title` of post when `title` is not specified. + // https://github.com/hexojs/hexo/issues/5372 + it('post - without title - use filename', async () => { + hexo.config.use_slug_as_post_title = true; + + const body = ''; + + const file = newFile({ + path: 'bar.md', + published: true, + type: 'create', + renderable: true + }); + + await writeFile(file.source, body); + await process(file); + const post = Post.findOne({ source: file.path }); + + post.title.should.eql('bar'); + + return Promise.all([ + post.remove(), + unlink(file.source) + ]); + }); + it('post - category is an alias for categories', async () => { const body = [ 'title: "Hello world"', From 5ccd66ea74d175f1bdc85d26d0d0ce1dc00fa97d Mon Sep 17 00:00:00 2001 From: Mimi <1119186082@qq.com> Date: Tue, 21 May 2024 11:32:26 +0800 Subject: [PATCH 3/4] Revert "refactor: backslashes on Windows (#5457)" (#5481) This reverts commit 80dafe2ab62f229e03d6af95c7e6a3abb5f257ab. --- lib/box/index.ts | 15 ++++++------ lib/plugins/processor/asset.ts | 2 +- lib/plugins/processor/post.ts | 4 ++-- lib/theme/processors/source.ts | 2 +- test/scripts/box/box.ts | 11 ++++----- test/scripts/processors/asset.ts | 12 +++++----- test/scripts/processors/post.ts | 31 +++++++++++-------------- test/scripts/theme_processors/source.ts | 10 ++++---- 8 files changed, 41 insertions(+), 46 deletions(-) diff --git a/lib/box/index.ts b/lib/box/index.ts index d2cbf7022b..4da7c69d4e 100644 --- a/lib/box/index.ts +++ b/lib/box/index.ts @@ -113,7 +113,7 @@ class Box extends EventEmitter { const src = join(this.base, path); return Cache.compareFile( - src.substring(ctx.base_dir.length), + escapeBackslash(src.substring(ctx.base_dir.length)), () => getHash(src), () => stat(src) ).then(result => ({ @@ -129,7 +129,7 @@ class Box extends EventEmitter { if (!stats.isDirectory()) return; // Check existing files in cache - const relativeBase = base.substring(ctx.base_dir.length); + const relativeBase = escapeBackslash(base.substring(ctx.base_dir.length)); const cacheFiles = Cache.filter(item => item._id.startsWith(relativeBase)).map(item => item._id.substring(relativeBase.length)); // Handle deleted files @@ -156,12 +156,11 @@ class Box extends EventEmitter { }); return BlueBirdPromise.reduce(this.processors, (count, processor) => { - // patten supports *nix style path only, replace backslashes on Windows - const params = processor.pattern.match(escapeBackslash(path)); + const params = processor.pattern.match(path); if (!params) return count; const file = new File({ - // source is used for file system path, keep backslashes on Windows + // source is used for filesystem path, keep backslashes on Windows source: join(base, path), // path is used for URL path, replace backslashes on Windows path: escapeBackslash(path), @@ -195,7 +194,7 @@ class Box extends EventEmitter { const { base } = this; function getPath(path) { - return path.substring(base.length); + return escapeBackslash(path.substring(base.length)); } return this.process().then(() => watch(base, this.options)).then(watcher => { @@ -215,7 +214,7 @@ class Box extends EventEmitter { watcher.on('addDir', path => { let prefix = getPath(path); - if (prefix) prefix += sep; + if (prefix) prefix += '/'; this._readDir(path, prefix); }); @@ -288,7 +287,7 @@ function readDirWalker(ctx: Hexo, base: string, results: any[], ignore: any, pre const prefixPath = `${prefix}${path}`; if (stats) { if (stats.isDirectory()) { - return readDirWalker(ctx, fullpath, results, ignore, prefixPath + sep); + return readDirWalker(ctx, fullpath, results, ignore, `${prefixPath}/`); } if (!isIgnoreMatch(fullpath, ignore)) { results.push(prefixPath); diff --git a/lib/plugins/processor/asset.ts b/lib/plugins/processor/asset.ts index 48ccd1401d..c56ff62052 100644 --- a/lib/plugins/processor/asset.ts +++ b/lib/plugins/processor/asset.ts @@ -109,7 +109,7 @@ function processPage(ctx: Hexo, file: _File) { } function processAsset(ctx: Hexo, file: _File) { - const id = relative(ctx.base_dir, file.source); + const id = relative(ctx.base_dir, file.source).replace(/\\/g, '/'); const Asset = ctx.model('Asset'); const doc = Asset.findById(id); diff --git a/lib/plugins/processor/post.ts b/lib/plugins/processor/post.ts index 969fad87a1..856fff5ba2 100644 --- a/lib/plugins/processor/post.ts +++ b/lib/plugins/processor/post.ts @@ -240,7 +240,7 @@ function scanAssetDir(ctx: Hexo, post) { if (err && err.code === 'ENOENT') return []; throw err; }).filter(item => !isExcludedFile(item, ctx.config)).map(item => { - const id = join(assetDir, item).substring(baseDirLength); + const id = join(assetDir, item).substring(baseDirLength).replace(/\\/g, '/'); const renderablePath = id.substring(sourceDirLength + 1); const asset = PostAsset.findById(id); @@ -274,7 +274,7 @@ function shouldSkipAsset(ctx: Hexo, post, asset) { function processAsset(ctx: Hexo, file: _File) { const PostAsset = ctx.model('PostAsset'); const Post = ctx.model('Post'); - const id = file.source.substring(ctx.base_dir.length); + const id = file.source.substring(ctx.base_dir.length).replace(/\\/g, '/'); const postAsset = PostAsset.findById(id); if (file.type === 'delete' || Post.length === 0) { diff --git a/lib/theme/processors/source.ts b/lib/theme/processors/source.ts index c82d4352c9..ac8c21a1fe 100644 --- a/lib/theme/processors/source.ts +++ b/lib/theme/processors/source.ts @@ -4,7 +4,7 @@ import type { _File } from '../../box'; function process(file: _File) { const Asset = this.model('Asset'); - const id = file.source.substring(this.base_dir.length); + const id = file.source.substring(this.base_dir.length).replace(/\\/g, '/'); const { path } = file.params; const doc = Asset.findById(id); diff --git a/test/scripts/box/box.ts b/test/scripts/box/box.ts index 55331677f2..d6d3d35b57 100644 --- a/test/scripts/box/box.ts +++ b/test/scripts/box/box.ts @@ -120,7 +120,7 @@ describe('Box', () => { const box = newBox('test'); const name = 'a.txt'; const path = join(box.base, name); - const cacheId = join('test/', name); + const cacheId = 'test/' + name; const processor = spy(); box.addProcessor(processor); @@ -144,7 +144,7 @@ describe('Box', () => { const box = newBox('test'); const name = 'a.txt'; const path = join(box.base, name); - const cacheId = join('test/', name); + const cacheId = 'test/' + name; const processor = spy(); box.addProcessor(processor); @@ -167,7 +167,7 @@ describe('Box', () => { const box = newBox('test'); const name = 'a.txt'; const path = join(box.base, name); - const cacheId = join('test/', name); + const cacheId = 'test/' + name; const processor = spy(); box.addProcessor(processor); @@ -190,7 +190,7 @@ describe('Box', () => { const box = newBox('test'); const name = 'a.txt'; const path = join(box.base, name); - const cacheId = join('test/', name); + const cacheId = 'test/' + name; const processor = spy(); box.addProcessor(processor); @@ -211,8 +211,7 @@ describe('Box', () => { it('process() - delete', async () => { const box = newBox('test'); - // join will replace backslashes on Windows - const cacheId = join('test/', 'a.txt'); + const cacheId = 'test/a.txt'; const processor = spy(); box.addProcessor(processor); diff --git a/test/scripts/processors/asset.ts b/test/scripts/processors/asset.ts index 127c9df08f..1e78bc44b1 100644 --- a/test/scripts/processors/asset.ts +++ b/test/scripts/processors/asset.ts @@ -80,7 +80,7 @@ describe('asset', () => { await writeFile(file.source, 'foo'); await process(file); - const id = join('source/', file.path); + const id = 'source/' + file.path; const asset = Asset.findById(id); asset._id.should.eql(id); @@ -103,7 +103,7 @@ describe('asset', () => { await writeFile(file.source, 'foo'); await process(file); - const id = join('../source/', 'foo.jpg'); // The id should a relative path, because the 'lib/models/assets.js' use asset path by joining base path with "_id" directly. + const id = '../source/foo.jpg'; // The id should a relative path,because the 'lib/models/assets.js' use asset path by joining base path with "_id" directly. const asset = Asset.findById(id); asset._id.should.eql(id); asset.path.should.eql(file.path); @@ -122,7 +122,7 @@ describe('asset', () => { renderable: false }); - const id = join('source/', file.path); + const id = 'source/' + file.path; await Promise.all([ writeFile(file.source, 'test'), @@ -153,7 +153,7 @@ describe('asset', () => { renderable: false }); - const id = join('source/', file.path); + const id = 'source/' + file.path; await Promise.all([ writeFile(file.source, 'test'), @@ -179,7 +179,7 @@ describe('asset', () => { renderable: false }); - const id = join('source/', file.path); + const id = 'source/' + file.path; await Asset.insert({ _id: id, @@ -197,7 +197,7 @@ describe('asset', () => { renderable: false }); - const id = join('source/', file.path); + const id = 'source/' + file.path; await process(file); should.not.exist(Asset.findById(id)); diff --git a/test/scripts/processors/post.ts b/test/scripts/processors/post.ts index 9fa83192e2..e3f1ff54c0 100644 --- a/test/scripts/processors/post.ts +++ b/test/scripts/processors/post.ts @@ -118,7 +118,7 @@ describe('post', () => { }); await process(file); - const id = join('source/', file.path); + const id = 'source/' + file.path; should.not.exist(PostAsset.findById(id)); }); @@ -141,7 +141,7 @@ describe('post', () => { const postId = doc._id; await process(file); - const id = join('source/', file.path); + const id = 'source/' + file.path; const asset = PostAsset.findById(id); asset._id.should.eql(id); @@ -165,7 +165,7 @@ describe('post', () => { renderable: false }); - const id = join('source/', file.path); + const id = 'source/' + file.path; const post = await Post.insert({ source: '_posts/foo.html', @@ -200,7 +200,7 @@ describe('post', () => { renderable: false }); - const id = join('source/', file.path); + const id = 'source/' + file.path; const post = await Post.insert({ source: '_posts/foo.html', @@ -235,7 +235,7 @@ describe('post', () => { renderable: false }); - const id = join('source/', file.path); + const id = 'source/' + file.path; const post = await Post.insert({ source: '_posts/foo.html', @@ -265,7 +265,7 @@ describe('post', () => { renderable: false }); - const id = join('source/', file.path); + const id = 'source/' + file.path; const post = await Post.insert({ source: '_posts/foo.html', @@ -290,7 +290,7 @@ describe('post', () => { renderable: false }); - const id = join('source/', file.path); + const id = 'source/' + file.path; await writeFile(file.source, 'test'); await process(file); @@ -309,7 +309,7 @@ describe('post', () => { renderable: false }); - const id = join('source/', file.path); + const id = 'source/' + file.path; await Promise.all([ writeFile(file.source, 'test'), @@ -992,7 +992,7 @@ describe('post', () => { '_fizz.jpg', '_buzz.jpg' ].map(filename => { - const id = join('source/_posts/foo/', filename); + const id = `source/_posts/foo/${filename}`; const path = join(hexo.base_dir, id); const contents = filename.replace(/\.\w+$/, ''); return { @@ -1048,8 +1048,7 @@ describe('post', () => { renderable: true }); - // join will replace backslashes on Windows - const assetId = join('source/_posts/foo/', 'bar.jpg'); + const assetId = 'source/_posts/foo/bar.jpg'; const assetPath = join(hexo.base_dir, assetId); await Promise.all([ @@ -1095,8 +1094,7 @@ describe('post', () => { renderable: true }); - // join will replace backslashes on Windows - const assetId = join('source/_posts/foo/', 'bar.jpg'); + const assetId = 'source/_posts/foo/bar.jpg'; const assetPath = join(hexo.base_dir, assetId); await Promise.all([ @@ -1134,8 +1132,7 @@ describe('post', () => { renderable: true }); - // join will replace backslashes on Windows - const assetId = join('source/_posts/foo/', 'bar.jpg'); + const assetId = 'source/_posts/foo/bar.jpg'; const assetPath = join(hexo.base_dir, assetId); await Promise.all([ @@ -1312,7 +1309,7 @@ describe('post', () => { writeFile(assetFile.source, 'test') ]); await process(file); - const id = join('source/', assetFile.path); + const id = 'source/' + assetFile.path; const post = Post.findOne({ source: file.path }); PostAsset.findById(id).renderable.should.be.true; @@ -1348,7 +1345,7 @@ describe('post', () => { writeFile(assetFile.source, 'test') ]); await process(file); - const id = join('source/', assetFile.path); + const id = 'source/' + assetFile.path; const post = Post.findOne({ source: file.path }); PostAsset.findById(id).renderable.should.be.false; diff --git a/test/scripts/theme_processors/source.ts b/test/scripts/theme_processors/source.ts index 5d9343b239..9b4f6f2979 100644 --- a/test/scripts/theme_processors/source.ts +++ b/test/scripts/theme_processors/source.ts @@ -56,7 +56,7 @@ describe('source', () => { type: 'create' }); - const id = join('themes/test/', file.path); + const id = 'themes/test/' + file.path; await writeFile(file.source, 'test'); await process(file); @@ -77,7 +77,7 @@ describe('source', () => { type: 'update' }); - const id = join('themes/test/', file.path); + const id = 'themes/test/' + file.path; await Promise.all([ writeFile(file.source, 'test'), @@ -104,7 +104,7 @@ describe('source', () => { type: 'skip' }); - const id = join('themes/test/', file.path); + const id = 'themes/test/' + file.path; await Promise.all([ writeFile(file.source, 'test'), @@ -130,7 +130,7 @@ describe('source', () => { type: 'delete' }); - const id = join('themes/test/', file.path); + const id = 'themes/test/' + file.path; await Asset.insert({ _id: id, @@ -146,7 +146,7 @@ describe('source', () => { type: 'delete' }); - const id = join('themes/test/', file.path); + const id = 'themes/test/' + file.path; await process(file); should.not.exist(Asset.findById(id)); From a3f27b85f19db17e7a6c395c3979659b627d20b2 Mon Sep 17 00:00:00 2001 From: Uiolee <22849383+uiolee@users.noreply.github.com> Date: Fri, 28 Jun 2024 10:30:15 +0800 Subject: [PATCH 4/4] build: upgrade ecmascript version (#5507) * build: upgrade ecmascript version * ci: adjust conditions of check --- .github/workflows/linter.yml | 1 - .github/workflows/tester.yml | 3 ++- tsconfig.json | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index a4f1c7cfd1..0432976eb7 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -4,7 +4,6 @@ on: push: branches: - "master" - - "v7.0.0" paths: - "lib/**" - "test/**" diff --git a/.github/workflows/tester.yml b/.github/workflows/tester.yml index 700cda5872..7c443e7418 100644 --- a/.github/workflows/tester.yml +++ b/.github/workflows/tester.yml @@ -4,17 +4,18 @@ on: push: branches: - "master" - - "v7.0.0" paths: - "lib/**" - "test/**" - "package.json" + - "tsconfig.json" - ".github/workflows/tester.yml" pull_request: paths: - "lib/**" - "test/**" - "package.json" + - "tsconfig.json" - ".github/workflows/tester.yml" permissions: diff --git a/tsconfig.json b/tsconfig.json index 78046e5376..6277f08394 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,7 @@ { "compilerOptions": { "module": "commonjs", - "target": "es6", + "target": "es2020", "sourceMap": true, "outDir": "dist", "declaration": true, @@ -17,4 +17,4 @@ "exclude": [ "node_modules" ] -} \ No newline at end of file +}