diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 353fae2aca..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -name: Bug report -about: Something isn't working as expected -title: '' -labels: '' -assignees: '' - ---- - - - -## Check List - -Please check followings before submitting a new issue. - -- [ ] I have already read [Docs page](https://hexo.io/docs/) & [Troubleshooting page](https://hexo.io/docs/troubleshooting) -- [ ] I have already searched existing issues and they are not help to me -- [ ] I examined error or warning messages and it's difficult to solve -- [ ] Using [the latest](https://github.com/hexojs/hexo/releases) version of Hexo (run `hexo version` to check) -- [ ] Node.js is higher than [minimum required version](https://hexo.io/docs/#Minimum-required-Node-js-version) - -## Expected behavior - -## Actual behavior - -## How to reproduce? - -* Step1 -* Step2 -* etc... - -## Is the problem still there under "Safe mode"? - - - -## Environment & Settings - -**Node.js & npm version(`node -v && npm -v`)** - - - -``` -``` - -**Your site `_config.yml`** (Optional) - - - -```yaml -``` - -**Hexo and Plugin version(`npm ls --depth 0`)** - - - -``` -``` - -**Your package.json `package.json`** - - - -``` -``` - -## Others - - diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000000..4c1e219d45 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,148 @@ +name: Bug report +description: Something isn't working as expected. +# title: "" +# labels: [] +# assignees: [] + +body: + - type: markdown + attributes: + value: | + ## Tips + + - 给简体中文用户的提示: + + - 在提交 issue 时请按照下面的模板提供相关信息,这将有助于我们发现问题。 + - 请尽量使用英语描述你遇到的问题,这可以让更多的人帮助到你。 + + - A good bug report should have your configuration and build environment information, which are essential for us to investigate the problem. We've provided the following steps on how to attach the necessary information. + + - If you find that markdown files are not rendered as expected, please go to https://marked.js.org/demo/ to see if it can be reproduced there. If it can be reproduced, please file a bug to https://github.com/markedjs/marked. + + - If you want help on your bug, please also send us the git repository (GitHub, GitLab, Bitbucket, etc.) where your hexo code is stored. It would greatly help. If you prefer not to have your hexo code out in public, please upload to a private GitHub repository and grant read-only access to `hexojs/core`. + + - Please take extra precaution not to attach any secret or personal information. (likes personal privacy, password, GitHub Personal Access Token, etc.) + + ------ + + - type: checkboxes + validations: + required: true + attributes: + label: Check List + description: Please check followings before submitting a new issue. + options: + - label: I have already read [Docs page](https://hexo.io/docs/) & [Troubleshooting page](https://hexo.io/docs/troubleshooting). + - label: I have already searched existing issues and they are not help to me. + - label: I examined error or warning messages and it's difficult to solve. + - label: I am using the [latest](https://github.com/hexojs/hexo/releases) version of Hexo. (run `hexo version` to check) + - label: My Node.js is matched [the required version](https://hexo.io/docs/#Required-Node-js-version). + + - type: textarea + validations: + required: true + attributes: + label: Expected behavior + # description: + placeholder: Descripe what you expected to happen. + # value: + # render: + + - type: textarea + validations: + required: true + attributes: + label: Actual behavior + # description: + placeholder: Descripe what actually happen. + # value: + # render: + + - type: textarea + validations: + required: true + attributes: + label: How to reproduce? + description: How do you trigger this bug? Please walk us through it step by step. + placeholder: | + 1. Step1 + 2. Step2 + 3. etc. + ... + # value: + # render: + + - type: input + validations: + required: true + attributes: + label: Is the problem still there under `Safe mode`? + description: | + https://hexo.io/docs/commands#Safe-mode + + "Safe mode" will disable all the plugins and scripts. + If your problem disappear under "Safe mode" means the problem is probably at your newly installed plugins, not at hexo. + # placeholder: + # value: | + # render: + + - type: markdown + attributes: + value: | + ------ + + ## Environment & Settings + + - type: textarea + validations: + required: false + attributes: + label: Your Node.js & npm version + description: | + Please run `node -v && npm -v` + and paste the output here. + placeholder: node -v && npm -v + # value: | + render: text + + - type: textarea + validations: + required: false + attributes: + label: Your Hexo and Plugin version + description: | + Please run `npm ls --depth 0` + and paste the output here. + placeholder: npm ls --depth 0 + # value: + render: text + + - type: textarea + validations: + required: false + attributes: + label: Your `package.json` + description: Please paste the content of `package.json` here. + placeholder: package.json + # value: + render: json + + - type: textarea + validations: + required: false + attributes: + label: Your site's `_config.yml` (Optional) + description: Please paste the content of your `_config.yml` here. + placeholder: _config.yml + # value: | + render: yaml + + - type: textarea + validations: + required: false + attributes: + label: Others + description: If you have other information. Please write here. + # placeholder: + # value: + # render: diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3c6bdca6bd..5548b4bc8c 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,4 +2,4 @@ blank_issues_enabled: false contact_links: - name: Ask a Question, Help, Discuss url: https://github.com/hexojs/hexo/discussions - about: I have a question, help for hexo (e.g. Customize) \ No newline at end of file + about: I have a question, help for hexo (e.g. Customize) diff --git a/.github/ISSUE_TEMPLATE/feature-request-improvement.md b/.github/ISSUE_TEMPLATE/feature-request-improvement.md deleted file mode 100644 index 46f8ad6691..0000000000 --- a/.github/ISSUE_TEMPLATE/feature-request-improvement.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -name: Feature request / Improvement -about: I have a feature request, suggestion, improvement etc... -title: '' -labels: '' -assignees: '' - ---- - -## Check List - -Please check followings before submitting a new feature request. - -- [ ] I have already read [Docs page](https://hexo.io/docs/) -- [ ] I have already searched existing issues - -## Feature Request - - - -## Others - - diff --git a/.github/ISSUE_TEMPLATE/feature-request-improvement.yml b/.github/ISSUE_TEMPLATE/feature-request-improvement.yml new file mode 100644 index 0000000000..32db67e4e2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request-improvement.yml @@ -0,0 +1,36 @@ +name: Feature request / Improvement +description: I have a feature request, suggestion, improvement etc... +# title: "" +# labels: [] +# assignees: [] + +body: + - type: checkboxes + validations: + required: true + attributes: + label: Check List + description: Please check followings before submitting a new feature request. + options: + - label: I have already read [Docs page](https://hexo.io/docs/). + - label: I have already searched existing issues. + + - type: textarea + validations: + required: true + attributes: + label: Feature Request + description: Descripe the feature and why it is needed. + # placeholder: + # value: + # render: + + - type: textarea + validations: + required: false + attributes: + label: Others + description: If you have other information. Please write here. + # placeholder: + # value: + # render: diff --git a/.github/ISSUE_TEMPLATE/other.md b/.github/ISSUE_TEMPLATE/other.md deleted file mode 100644 index 6d0d667285..0000000000 --- a/.github/ISSUE_TEMPLATE/other.md +++ /dev/null @@ -1,58 +0,0 @@ ---- -name: Other -about: Not a question, feature-request, bug -title: '' -labels: '' -assignees: '' - ---- - - - -## Check List - -Please check followings before submitting a new issue. - -- [ ] I have already confirmed other issue template and they are not different my want -- [ ] Not a question, feature-request, bug - - Please submit to [discussion](https://github.com/hexojs/hexo/discussions) if your issue is a question. - -## Information - - - -## Environment & Settings - -**Node.js & npm version** - -``` -``` - -**Your site `_config.yml`** (Optional) - -``` -``` - -**Hexo and Plugin version(`npm ls --depth 0`)** - -``` -``` - -**Your package.json `package.json`** - -``` -``` - -## Others - - diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index b6c1a4ec00..5d27c7401b 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -3,12 +3,12 @@ name: Benchmark on: push: paths: - - 'lib/**' + - "lib/**" pull_request: branches: - master paths-ignore: - - 'test/scripts/**' + - "test/scripts/**" jobs: benchmark: @@ -16,12 +16,12 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node-version: ['14', '16', '18'] + node-version: ["14", "16", "18"] fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Install dependencies @@ -30,15 +30,17 @@ jobs: run: node test/benchmark.js --benchmark profiling: runs-on: ${{ matrix.os }} + permissions: + pull-requests: write # for marocchino/sticky-pull-request-comment to create or update PR comment strategy: matrix: os: [ubuntu-latest] - node-version: ['14', '16', '18'] + node-version: ["14", "16", "18"] fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Install dependencies diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index d3d9678043..a4f1c7cfd1 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -1,6 +1,19 @@ name: Linter -on: [push, pull_request] +on: + push: + branches: + - "master" + - "v7.0.0" + paths: + - "lib/**" + - "test/**" + - ".github/workflows/linter.yml" + pull_request: + paths: + - "lib/**" + - "test/**" + - ".github/workflows/linter.yml" permissions: contents: read @@ -9,11 +22,11 @@ jobs: linter: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js 14.x - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: '14.x' + node-version: "14.x" - name: Install Dependencies run: npm install - name: Lint diff --git a/.github/workflows/tester.yml b/.github/workflows/tester.yml index a28cf0a77d..700cda5872 100644 --- a/.github/workflows/tester.yml +++ b/.github/workflows/tester.yml @@ -1,6 +1,21 @@ name: Tester -on: [push, pull_request] +on: + push: + branches: + - "master" + - "v7.0.0" + paths: + - "lib/**" + - "test/**" + - "package.json" + - ".github/workflows/tester.yml" + pull_request: + paths: + - "lib/**" + - "test/**" + - "package.json" + - ".github/workflows/tester.yml" permissions: contents: read @@ -11,12 +26,12 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - node-version: ['14.x', '16.x', '18.x'] + node-version: ["14.x", "16.x", "18.x"] fail-fast: false steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Install Dependencies @@ -27,17 +42,17 @@ jobs: CI: true coverage: permissions: - checks: write # for coverallsapp/github-action to create new checks - contents: read # for actions/checkout to fetch code + checks: write # for coverallsapp/github-action to create new checks + contents: read # for actions/checkout to fetch code runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest] - node-version: ['14.x'] + node-version: ["14.x"] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Install Dependencies diff --git a/.lintstagedrc b/.lintstagedrc deleted file mode 100644 index 66886867d5..0000000000 --- a/.lintstagedrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "*.js": "git-exec-and-restage eslint --fix --" -} diff --git a/.lintstagedrc.json b/.lintstagedrc.json new file mode 100644 index 0000000000..6953073375 --- /dev/null +++ b/.lintstagedrc.json @@ -0,0 +1,4 @@ +{ + "*.js": "eslint --fix", + "*.ts": "eslint --fix" +} diff --git a/lib/box/file.ts b/lib/box/file.ts index 6171e430d6..99bb509ddd 100644 --- a/lib/box/file.ts +++ b/lib/box/file.ts @@ -1,10 +1,12 @@ -import { readFile, readFileSync, stat, statSync } from 'hexo-fs'; +import type Promise from 'bluebird'; +import { readFile, readFileSync, stat, statSync, type ReadFileOptions } from 'hexo-fs'; +import type fs from 'fs'; class File { - public source: any; - public path: any; + public source: string; + public path: string; public params: any; - public type: any; + public type: string; static TYPE_CREATE: 'create'; static TYPE_UPDATE: 'update'; static TYPE_SKIP: 'skip'; @@ -17,19 +19,19 @@ class File { this.type = type; } - read(options) { + read(options?: ReadFileOptions): Promise { return readFile(this.source, options); } - readSync(options) { + readSync(options?: ReadFileOptions): string | Buffer { return readFileSync(this.source, options); } - stat(options) { + stat(): Promise { return stat(this.source); } - statSync(options) { + statSync(): fs.Stats { return statSync(this.source); } } diff --git a/lib/box/index.ts b/lib/box/index.ts index a61e08e54e..3222b55880 100644 --- a/lib/box/index.ts +++ b/lib/box/index.ts @@ -6,6 +6,7 @@ import { createReadStream, readdir, stat, watch } from 'hexo-fs'; import { magenta } from 'picocolors'; import { EventEmitter } from 'events'; import { isMatch, makeRe } from 'micromatch'; +import type Hexo from '../hexo'; const defaultPattern = new Pattern(() => ({})); @@ -16,20 +17,18 @@ interface Processor { class Box extends EventEmitter { public options: any; - public context: any; - public base: any; + public context: Hexo; + public base: string; public processors: Processor[]; public _processingFiles: any; public watcher: any; public Cache: any; // TODO: replace runtime class _File public File: any; - public ignore: any; + public ignore: any[]; public source: any; - public emit: any; - public ctx: any; - constructor(ctx, base, options?: object) { + constructor(ctx: Hexo, base: string, options?: object) { super(); this.options = Object.assign({ @@ -64,13 +63,13 @@ class Box extends EventEmitter { class _File extends File { public box: Box; - render(options) { + render(options?: object) { return ctx.render.render({ path: this.source }, options); } - renderSync(options) { + renderSync(options?: object) { return ctx.render.renderSync({ path: this.source }, options); @@ -82,7 +81,9 @@ class Box extends EventEmitter { return _File; } - addProcessor(pattern, fn) { + addProcessor(pattern: (...args: any[]) => any): void; + addProcessor(pattern: string | RegExp | Pattern | ((...args: any[]) => any), fn: (...args: any[]) => any): void; + addProcessor(pattern: string | RegExp | Pattern | ((...args: any[]) => any), fn?: (...args: any[]) => any): void { if (!fn && typeof pattern === 'function') { fn = pattern; pattern = defaultPattern; @@ -97,7 +98,7 @@ class Box extends EventEmitter { }); } - _readDir(base, prefix = '') { + _readDir(base: string, prefix = ''): BlueBirdPromise { const { context: ctx } = this; const results = []; return readDirWalker(ctx, base, results, this.ignore, prefix) @@ -106,7 +107,7 @@ class Box extends EventEmitter { .map(file => this._processFile(file.type, file.path).return(file.path)); } - _checkFileStatus(path) { + _checkFileStatus(path: string) { const { Cache, context: ctx } = this; const src = join(this.base, path); @@ -120,7 +121,7 @@ class Box extends EventEmitter { })); } - process(callback?) { + process(callback?: NodeJSLikeCallback): BlueBirdPromise { const { base, Cache, context: ctx } = this; return stat(base).then(stats => { @@ -132,14 +133,14 @@ class Box extends EventEmitter { // Handle deleted files return this._readDir(base) - .then(files => cacheFiles.filter(path => !files.includes(path))) - .map(path => this._processFile(File.TYPE_DELETE, path)); + .then((files: string[]) => cacheFiles.filter((path: string) => !files.includes(path))) + .map((path: string) => this._processFile(File.TYPE_DELETE, path) as PromiseLike); }).catch(err => { if (err && err.code !== 'ENOENT') throw err; }).asCallback(callback); } - _processFile(type, path) { + _processFile(type: string, path: string): BlueBirdPromise | BlueBirdPromise { if (this._processingFiles[path]) { return BlueBirdPromise.resolve(); } @@ -182,7 +183,7 @@ class Box extends EventEmitter { }).thenReturn(path); } - watch(callback?) { + watch(callback?: NodeJSLikeCallback): BlueBirdPromise { if (this.isWatching()) { return BlueBirdPromise.reject(new Error('Watcher has already started.')).asCallback(callback); } @@ -217,24 +218,24 @@ class Box extends EventEmitter { }).asCallback(callback); } - unwatch() { + unwatch(): void { if (!this.isWatching()) return; this.watcher.close(); this.watcher = null; } - isWatching() { + isWatching(): boolean { return Boolean(this.watcher); } } -function escapeBackslash(path) { +function escapeBackslash(path: string): string { // Replace backslashes on Windows return path.replace(/\\/g, '/'); } -function getHash(path) { +function getHash(path: string): BlueBirdPromise { const src = createReadStream(path); const hasher = createSha1Hash(); @@ -248,7 +249,7 @@ function getHash(path) { return finishedPromise.then(() => hasher.digest('hex')); } -function toRegExp(ctx, arg) { +function toRegExp(ctx: Hexo, arg: string): RegExp | null { if (!arg) return null; if (typeof arg !== 'string') { ctx.log.warn('A value of "ignore:" section in "_config.yml" is not invalid (not a string)'); @@ -262,11 +263,11 @@ function toRegExp(ctx, arg) { return result; } -function isIgnoreMatch(path, ignore) { +function isIgnoreMatch(path: string, ignore: string | any[]): boolean { return path && ignore && ignore.length && isMatch(path, ignore); } -function readDirWalker(ctx, base, results, ignore, prefix) { +function readDirWalker(ctx: Hexo, base: string, results: any[], ignore: any, prefix: string): BlueBirdPromise { if (isIgnoreMatch(base, ignore)) return BlueBirdPromise.resolve(); return BlueBirdPromise.map(readdir(base).catch(err => { @@ -292,4 +293,10 @@ function readDirWalker(ctx, base, results, ignore, prefix) { }); } -export = Box; +export interface _File extends File { + box: Box; + render(options?: any): any; + renderSync(options?: any): any; +} + +export default Box; diff --git a/lib/extend/console.ts b/lib/extend/console.ts index c4af6322d7..a5b7868af9 100644 --- a/lib/extend/console.ts +++ b/lib/extend/console.ts @@ -51,7 +51,7 @@ class Console { return this.store[this.alias[name]]; } - list() { + list(): Store { return this.store; } @@ -66,7 +66,7 @@ class Console { register(name: string, desc: string, fn: AnyFn): void register(name: string, options: Option, fn: AnyFn): void register(name: string, desc: string, options: Option, fn: AnyFn): void - register(name: string, desc: string | Option | AnyFn, options?: Option | AnyFn, fn?: AnyFn) { + register(name: string, desc: string | Option | AnyFn, options?: Option | AnyFn, fn?: AnyFn): void { if (!name) throw new TypeError('name is required'); if (!fn) { diff --git a/lib/extend/deployer.ts b/lib/extend/deployer.ts index d42d3d854c..ae88664cdc 100644 --- a/lib/extend/deployer.ts +++ b/lib/extend/deployer.ts @@ -17,15 +17,15 @@ class Deployer { this.store = {}; } - list() { + list(): Store { return this.store; } - get(name: string) { + get(name: string): StoreFunction { return this.store[name]; } - register(name: string, fn: StoreFunction) { + register(name: string, fn: StoreFunction): void { if (!name) throw new TypeError('name is required'); if (typeof fn !== 'function') throw new TypeError('fn must be a function'); diff --git a/lib/extend/filter.ts b/lib/extend/filter.ts index 78113d01c2..2af06fae87 100644 --- a/lib/extend/filter.ts +++ b/lib/extend/filter.ts @@ -38,7 +38,7 @@ class Filter { register(fn: StoreFunction, priority: number): void register(type: string, fn: StoreFunction): void register(type: string, fn: StoreFunction, priority: number): void - register(type: string | StoreFunction, fn?: StoreFunction | number, priority?: number) { + register(type: string | StoreFunction, fn?: StoreFunction | number, priority?: number): void { if (!priority) { if (typeof type === 'function') { priority = fn as number; @@ -61,7 +61,7 @@ class Filter { store.sort((a, b) => a.priority - b.priority); } - unregister(type: string, fn: StoreFunction) { + unregister(type: string, fn: StoreFunction): void { if (!type) throw new TypeError('type is required'); if (typeof fn !== 'function') throw new TypeError('fn must be a function'); @@ -75,7 +75,7 @@ class Filter { if (index !== -1) list.splice(index, 1); } - exec(type: string, data: any[], options: FilterOptions = {}) { + exec(type: string, data: any[], options: FilterOptions = {}): Promise { const filters = this.list(type); if (filters.length === 0) return Promise.resolve(data); diff --git a/lib/extend/generator.ts b/lib/extend/generator.ts index 6360d1eb2c..2854f8f869 100644 --- a/lib/extend/generator.ts +++ b/lib/extend/generator.ts @@ -31,17 +31,17 @@ class Generator { this.store = {}; } - list() { + list(): Store { return this.store; } - get(name: string) { + get(name: string): StoreFunction { return this.store[name]; } register(fn: GeneratorFunction): void register(name: string, fn: GeneratorFunction): void - register(name: string | GeneratorFunction, fn?: GeneratorFunction) { + register(name: string | GeneratorFunction, fn?: GeneratorFunction): void { if (!fn) { if (typeof name === 'function') { // fn fn = name; diff --git a/lib/extend/helper.ts b/lib/extend/helper.ts index 7903557c97..f167282371 100644 --- a/lib/extend/helper.ts +++ b/lib/extend/helper.ts @@ -35,7 +35,7 @@ class Helper { * @param {String} name - The name of the helper plugin * @param {StoreFunction} fn - The helper plugin function */ - register(name: string, fn: StoreFunction) { + register(name: string, fn: StoreFunction): void { if (!name) throw new TypeError('name is required'); if (typeof fn !== 'function') throw new TypeError('fn must be a function'); diff --git a/lib/extend/injector.ts b/lib/extend/injector.ts index fd1d09acdd..edb4a45094 100644 --- a/lib/extend/injector.ts +++ b/lib/extend/injector.ts @@ -24,15 +24,15 @@ class Injector { this.cache = new Cache(); } - list() { + list(): Store { return this.store; } - get(entry: Entry, to = 'default') { + get(entry: Entry, to = 'default'): any[] { return Array.from(this.store[entry][to] || []); } - getText(entry: Entry, to = 'default') { + getText(entry: Entry, to = 'default'): string { const arr = this.get(entry, to); if (!arr || !arr.length) return ''; return arr.join(''); @@ -42,7 +42,7 @@ class Injector { return this.cache.apply(`${entry}-size`, Object.keys(this.store[entry]).length); } - register(entry: Entry, value: string | (() => string), to = 'default') { + register(entry: Entry, value: string | (() => string), to = 'default'): void { if (!entry) throw new TypeError('entry is required'); if (typeof value === 'function') value = value(); @@ -52,7 +52,7 @@ class Injector { entryMap[to] = valueSet; } - _getPageType(pageLocals) { + _getPageType(pageLocals): string { let currentType = 'default'; if (pageLocals.__index) currentType = 'home'; if (pageLocals.__post) currentType = 'post'; @@ -65,7 +65,7 @@ class Injector { return currentType; } - _injector(input, pattern, flag, isBegin = true, currentType) { + _injector(input: string, pattern: string | RegExp, flag: Entry, isBegin = true, currentType: string): string { if (input.includes(`hexo injector ${flag}`)) return input; const code = this.cache.apply(`${flag}-${currentType}-code`, () => { @@ -81,7 +81,7 @@ class Injector { return input.replace(pattern, str => { return isBegin ? str + code : code + str; }); } - exec(data, locals = { page: {} }) { + exec(data: string, locals = { page: {} }): string { const { page } = locals; const currentType = this._getPageType(page); diff --git a/lib/extend/migrator.ts b/lib/extend/migrator.ts index 2a149f191a..5ab0f7063e 100644 --- a/lib/extend/migrator.ts +++ b/lib/extend/migrator.ts @@ -14,15 +14,15 @@ class Migrator { this.store = {}; } - list() { + list(): Store { return this.store; } - get(name: string) { + get(name: string): StoreFunction { return this.store[name]; } - register(name: string, fn: StoreFunction) { + register(name: string, fn: StoreFunction): void { if (!name) throw new TypeError('name is required'); if (typeof fn !== 'function') throw new TypeError('fn must be a function'); diff --git a/lib/extend/processor.ts b/lib/extend/processor.ts index 0af6998494..ce73da1031 100644 --- a/lib/extend/processor.ts +++ b/lib/extend/processor.ts @@ -19,13 +19,13 @@ class Processor { this.store = []; } - list() { + list(): Store { return this.store; } register(fn: StoreFunction): void; register(pattern: patternType, fn: StoreFunction): void; - register(pattern: patternType | StoreFunction, fn?: StoreFunction) { + register(pattern: patternType | StoreFunction, fn?: StoreFunction): void { if (!fn) { if (typeof pattern === 'function') { fn = pattern; diff --git a/lib/extend/renderer.ts b/lib/extend/renderer.ts index c70e054f18..15f90c6de6 100644 --- a/lib/extend/renderer.ts +++ b/lib/extend/renderer.ts @@ -8,36 +8,36 @@ const getExtname = (str: string) => { return ext.startsWith('.') ? ext.slice(1) : ext; }; -interface StoreSyncFunction { +export interface StoreFunctionData { + path?: any; + text?: string; + engine?: string; + toString?: any; + onRenderEnd?: any; +} + +export interface StoreSyncFunction { + [x: string]: any; ( - data: { - path?: string; - text: string; - }, + data: StoreFunctionData, options: object, - // callback: (err: Error, value: string) => any + // callback: NodeJSLikeCallback ): any; output?: string; - compile?: (local: object) => string; + compile?: (local: object) => any; } -interface StoreFunction { +export interface StoreFunction { ( - data: { - path?: string; - text: string; - }, + data: StoreFunctionData, options: object, ): Promise; ( - data: { - path?: string; - text: string; - }, + data: StoreFunctionData, options: object, - callback: (err: Error, value: string) => any + callback: NodeJSLikeCallback ): void; output?: string; - compile?: (local: object) => string; + compile?: (local: object) => any; disableNunjucks?: boolean; } @@ -57,25 +57,25 @@ class Renderer { this.storeSync = {}; } - list(sync: boolean) { + list(sync: boolean): Store | SyncStore { return sync ? this.storeSync : this.store; } - get(name: string, sync?: boolean) { + get(name: string, sync?: boolean): StoreSyncFunction | StoreFunction { const store = this[sync ? 'storeSync' : 'store']; return store[getExtname(name)] || store[name]; } - isRenderable(path: string) { + isRenderable(path: string): boolean { return Boolean(this.get(path)); } - isRenderableSync(path: string) { + isRenderableSync(path: string): boolean { return Boolean(this.get(path, true)); } - getOutput(path: string) { + getOutput(path: string): string { const renderer = this.get(path); return renderer ? renderer.output : ''; } @@ -109,4 +109,4 @@ class Renderer { } } -export = Renderer; +export default Renderer; diff --git a/lib/extend/syntax_highlight.ts b/lib/extend/syntax_highlight.ts index 419a950f24..c5cf28a95f 100644 --- a/lib/extend/syntax_highlight.ts +++ b/lib/extend/syntax_highlight.ts @@ -3,7 +3,7 @@ import type Hexo from '../hexo'; export interface HighlightOptions { lang: string | undefined, caption: string | undefined, - lines_length: number, + lines_length?: number | undefined, // plugins/filter/before_post_render/backtick_code_block firstLineNumber?: string | number @@ -39,13 +39,13 @@ class SyntaxHighlight { this.store = {}; } - register(name: string, fn: StoreFunction) { + register(name: string, fn: StoreFunction): void { if (typeof fn !== 'function') throw new TypeError('fn must be a function'); this.store[name] = fn; } - query(name: string) { + query(name: string): StoreFunction { return name && this.store[name]; } diff --git a/lib/extend/tag.ts b/lib/extend/tag.ts index f2fcc3d325..d44b5dd993 100644 --- a/lib/extend/tag.ts +++ b/lib/extend/tag.ts @@ -4,7 +4,7 @@ import { Environment } from 'nunjucks'; import Promise from 'bluebird'; const rSwigRawFullBlock = /{% *raw *%}/; const rCodeTag = /]*>[\s\S]+?<\/code>/g; -const escapeSwigTag = str => str.replace(/{/g, '{').replace(/}/g, '}'); +const escapeSwigTag = (str: string) => str.replace(/{/g, '{').replace(/}/g, '}'); interface TagFunction { (args: any[], content: string): string; @@ -66,7 +66,7 @@ class NunjucksTag { } } -const trimBody = body => { +const trimBody = (body: () => any) => { return stripIndent(body()).replace(/^\n?|\n?$/g, ''); }; @@ -126,7 +126,7 @@ class NunjucksAsyncBlock extends NunjucksBlock { } } -const getContextLineNums = (min, max, center, amplitude) => { +const getContextLineNums = (min: number, max: number, center: number, amplitude: number) => { const result = []; let lbound = Math.max(min, center - amplitude); const hbound = Math.min(max, center + amplitude); @@ -136,7 +136,7 @@ const getContextLineNums = (min, max, center, amplitude) => { const LINES_OF_CONTEXT = 5; -const getContext = (lines, errLine, location, type) => { +const getContext = (lines: string[], errLine: number, location: string, type: string) => { const message = [ location + ' ' + red(type), cyan(' ===== Context Dump ====='), @@ -173,7 +173,7 @@ class NunjucksError extends Error { * @param {string} str string input for Nunjucks * @return {Error} New error object with embedded context */ -const formatNunjucksError = (err, input, source = '') => { +const formatNunjucksError = (err: Error, input: string, source = ''): Error => { err.message = err.message.replace('(unknown path)', source ? magenta(source) : ''); const match = err.message.match(/Line (\d+), Column \d+/); @@ -199,8 +199,8 @@ type RegisterOptions = { } class Tag { - public env: any; - public source: any; + public env: Environment; + public source: string; constructor() { this.env = new Environment(null, { @@ -211,7 +211,7 @@ class Tag { register(name: string, fn: TagFunction): void register(name: string, fn: TagFunction, ends: boolean): void register(name: string, fn: TagFunction, options: RegisterOptions): void - register(name: string, fn: TagFunction, options?: RegisterOptions | boolean) { + register(name: string, fn: TagFunction, options?: RegisterOptions | boolean):void { if (!name) throw new TypeError('name is required'); if (typeof fn !== 'function') throw new TypeError('fn must be a function'); @@ -243,7 +243,7 @@ class Tag { this.env.addExtension(name, tag); } - unregister(name) { + unregister(name: string): void { if (!name) throw new TypeError('name is required'); const { env } = this; @@ -251,7 +251,7 @@ class Tag { if (env.hasExtension(name)) env.removeExtension(name); } - render(str, options: { source?: string } = {}, callback) { + render(str: string, options: { source?: string } = {}, callback?: NodeJSLikeCallback): Promise { if (!callback && typeof options === 'function') { callback = options; options = {}; diff --git a/lib/hexo/index.ts b/lib/hexo/index.ts index e8c06b83d4..c248b7b675 100644 --- a/lib/hexo/index.ts +++ b/lib/hexo/index.ts @@ -36,16 +36,17 @@ import defaultConfig from './default_config'; import loadDatabase from './load_database'; import multiConfigPath from './multi_config_path'; import { deepMerge, full_url_for } from 'hexo-util'; +import type Box from '../box'; let resolveSync; // = require('resolve'); const libDir = dirname(__dirname); const dbVersion = 1; -const stopWatcher = box => { if (box.isWatching()) box.unwatch(); }; +const stopWatcher = (box: Box) => { if (box.isWatching()) box.unwatch(); }; const routeCache = new WeakMap(); -const castArray = obj => { return Array.isArray(obj) ? obj : [obj]; }; +const castArray = (obj: any) => { return Array.isArray(obj) ? obj : [obj]; }; const mergeCtxThemeConfig = ctx => { // Merge hexo.config.theme_config into hexo.theme.config before post rendering & generating @@ -335,7 +336,7 @@ class Hexo extends EventEmitter { }); } - call(name, args, callback) { + call(name: string, args: any, callback?: NodeJSLikeCallback) { if (!callback && typeof args === 'function') { callback = args; args = {}; @@ -348,11 +349,11 @@ class Hexo extends EventEmitter { return Promise.reject(new Error(`Console \`${name}\` has not been registered yet!`)); } - model(name, schema) { + model(name: string, schema?: any) { return this.database.model(name, schema); } - resolvePlugin(name, basedir) { + resolvePlugin(name: string, basedir: string) { try { // Try to resolve the plugin with the Node.js's built-in require.resolve. return require.resolve(name, { paths: [basedir] }); @@ -370,18 +371,18 @@ class Hexo extends EventEmitter { } } - loadPlugin(path: string, callback: (...args: any[]) => any) { + loadPlugin(path: string, callback?: NodeJSLikeCallback) { return readFile(path).then(script => { // Based on: https://github.com/joyent/node/blob/v0.10.33/src/node.js#L516 const module = new Module(path); module.filename = path; module.paths = Module._nodeModulePaths(path); - function req(path) { + function req(path: string) { return module.require(path); } - req.resolve = request => Module._resolveFilename(request, module); + req.resolve = (request: string) => Module._resolveFilename(request, module); req.main = require.main; req.extensions = Module._extensions; @@ -400,7 +401,7 @@ class Hexo extends EventEmitter { return args.draft || args.drafts || this.config.render_drafts; } - load(callback) { + load(callback?: NodeJSLikeCallback) { return loadDatabase(this).then(() => { this.log.info('Start processing'); @@ -414,7 +415,7 @@ class Hexo extends EventEmitter { }).asCallback(callback); } - watch(callback) { + watch(callback?: NodeJSLikeCallback) { let useCache = false; const { cache } = Object.assign({ cache: false @@ -478,7 +479,7 @@ class Hexo extends EventEmitter { site: object; cache?: boolean; - constructor(path, locals) { + constructor(path: string, locals) { this.page = { ...locals }; if (this.page.path == null) this.page.path = path; this.path = path; @@ -512,7 +513,7 @@ class Hexo extends EventEmitter { }, []); } - _routerRefresh(runningGenerators, useCache) { + _routerRefresh(runningGenerators: Promise, useCache: boolean) { const { route } = this; const routeList = route.list(); const Locals = this._generateLocals(); @@ -555,7 +556,7 @@ class Hexo extends EventEmitter { this.emit('generateBefore'); // Run before_generate filters - return this.execFilter('before_generate', this.locals.get('data'), { context: this }) + return this.execFilter('before_generate', null, { context: this }) .then(() => this._routerRefresh(this._runGenerators(), useCache)).then(() => { this.emit('generateAfter'); @@ -580,11 +581,11 @@ class Hexo extends EventEmitter { }); } - execFilter(type, data, options) { + execFilter(type: string, data: any, options) { return this.extend.filter.exec(type, data, options); } - execFilterSync(type, data, options) { + execFilterSync(type: string, data: any, options) { return this.extend.filter.execSync(type, data, options); } } @@ -598,4 +599,11 @@ Hexo.prototype.core_dir = Hexo.core_dir; Hexo.version = version; Hexo.prototype.version = Hexo.version; +// define global variable +// this useful for plugin written in typescript +declare global { + // eslint-disable-next-line one-var + const hexo: Hexo; +} + export = Hexo; diff --git a/lib/hexo/load_config.ts b/lib/hexo/load_config.ts index 231e285f02..489f4e03ff 100644 --- a/lib/hexo/load_config.ts +++ b/lib/hexo/load_config.ts @@ -6,8 +6,9 @@ import { exists, readdir } from 'hexo-fs'; import { magenta } from 'picocolors'; import { deepMerge } from 'hexo-util'; import validateConfig from './validate_config'; +import type Hexo from './index'; -export = async ctx => { +export = async (ctx: Hexo) => { if (!ctx.env.init) return; const baseDir = ctx.base_dir; @@ -65,7 +66,7 @@ export = async ctx => { }; -async function findConfigPath(path) { +async function findConfigPath(path: string) { const { dir, name } = parse(path); const files = await readdir(dir); diff --git a/lib/hexo/load_database.ts b/lib/hexo/load_database.ts index c64719c929..f0ac5decb0 100644 --- a/lib/hexo/load_database.ts +++ b/lib/hexo/load_database.ts @@ -1,7 +1,8 @@ import { exists, unlink } from 'hexo-fs'; import Promise from 'bluebird'; +import type Hexo from './index'; -export = ctx => { +export = (ctx: Hexo) => { if (ctx._dbLoaded) return Promise.resolve(); const db = ctx.database; diff --git a/lib/hexo/load_plugins.ts b/lib/hexo/load_plugins.ts index c1dcb29067..289474f00f 100644 --- a/lib/hexo/load_plugins.ts +++ b/lib/hexo/load_plugins.ts @@ -2,14 +2,15 @@ import { join } from 'path'; import { exists, readFile, listDir } from 'hexo-fs'; import Promise from 'bluebird'; import { magenta } from 'picocolors'; +import type Hexo from './index'; -export = ctx => { +export = (ctx: Hexo) => { if (!ctx.env.init || ctx.env.safe) return; return loadModules(ctx).then(() => loadScripts(ctx)); }; -function loadModuleList(ctx, basedir) { +function loadModuleList(ctx: Hexo, basedir: string) { const packagePath = join(basedir, 'package.json'); // Make sure package.json exists @@ -42,14 +43,14 @@ function loadModuleList(ctx, basedir) { }); } -function loadModules(ctx) { +function loadModules(ctx: Hexo) { return Promise.map([ctx.base_dir, ctx.theme_dir], basedir => loadModuleList(ctx, basedir)) .then(([hexoModuleList, themeModuleList]) => { return Object.entries(Object.assign(themeModuleList, hexoModuleList)); }) .map(([name, path]) => { // Load plugins - return ctx.loadPlugin(path).then(() => { + return ctx.loadPlugin(path as string).then(() => { ctx.log.debug('Plugin loaded: %s', magenta(name)); }).catch(err => { ctx.log.error({err}, 'Plugin load failed: %s', magenta(name)); @@ -57,7 +58,7 @@ function loadModules(ctx) { }); } -function loadScripts(ctx) { +function loadScripts(ctx: Hexo) { const baseDirLength = ctx.base_dir.length; return Promise.filter([ @@ -76,6 +77,6 @@ function loadScripts(ctx) { })); } -function displayPath(path, baseDirLength) { +function displayPath(path: string, baseDirLength: number) { return magenta(path.substring(baseDirLength)); } diff --git a/lib/hexo/load_theme_config.ts b/lib/hexo/load_theme_config.ts index 57a28adb41..54c700960a 100644 --- a/lib/hexo/load_theme_config.ts +++ b/lib/hexo/load_theme_config.ts @@ -3,8 +3,9 @@ import tildify from 'tildify'; import { exists, readdir } from 'hexo-fs'; import { magenta } from 'picocolors'; import { deepMerge } from 'hexo-util'; +import type Hexo from './index'; -export = ctx => { +export = (ctx: Hexo) => { if (!ctx.env.init) return; if (!ctx.config.theme) return; @@ -30,7 +31,7 @@ export = ctx => { }); }; -function findConfigPath(path) { +function findConfigPath(path: string) { const { dir, name } = parse(path); return readdir(dir).then(files => { diff --git a/lib/hexo/locals.ts b/lib/hexo/locals.ts index 468dabe26f..7153cb0a48 100644 --- a/lib/hexo/locals.ts +++ b/lib/hexo/locals.ts @@ -9,7 +9,7 @@ class Locals { this.getters = {}; } - get(name) { + get(name: string) { if (typeof name !== 'string') throw new TypeError('name must be a string!'); return this.cache.apply(name, () => { @@ -20,7 +20,7 @@ class Locals { }); } - set(name, value) { + set(name: string, value: any) { if (typeof name !== 'string') throw new TypeError('name must be a string!'); if (value == null) throw new TypeError('value is required!'); @@ -32,7 +32,7 @@ class Locals { return this; } - remove(name) { + remove(name: string) { if (typeof name !== 'string') throw new TypeError('name must be a string!'); this.getters[name] = null; diff --git a/lib/hexo/multi_config_path.ts b/lib/hexo/multi_config_path.ts index 8d140d3569..7402657e90 100644 --- a/lib/hexo/multi_config_path.ts +++ b/lib/hexo/multi_config_path.ts @@ -2,8 +2,9 @@ import { isAbsolute, resolve, join, extname } from 'path'; import { existsSync, readFileSync, writeFileSync } from 'hexo-fs'; import yml from 'js-yaml'; import { deepMerge } from 'hexo-util'; +import type Hexo from './index'; -export = ctx => function multiConfigPath(base, configPaths, outputDir) { +export = (ctx: Hexo) => function multiConfigPath(base: string, configPaths: string, outputDir: string) { const { log } = ctx; const defaultPath = join(base, '_config.yml'); @@ -12,7 +13,7 @@ export = ctx => function multiConfigPath(base, configPaths, outputDir) { return join(base, '_config.yml'); } - let paths; + let paths: string[]; // determine if comma or space separated if (configPaths.includes(',')) { paths = configPaths.replace(' ', '').split(','); @@ -61,7 +62,7 @@ export = ctx => function multiConfigPath(base, configPaths, outputDir) { return defaultPath; } - log.i('Config based on', count, 'files'); + log.i('Config based on', count.toString(), 'files'); const multiconfigRoot = outputDir || base; const outputPath = join(multiconfigRoot, '_multiconfig.yml'); diff --git a/lib/hexo/post.ts b/lib/hexo/post.ts index 748d73fd94..f9269484e2 100644 --- a/lib/hexo/post.ts +++ b/lib/hexo/post.ts @@ -7,6 +7,7 @@ import { load } from 'js-yaml'; import { slugize, escapeRegExp } 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'; const preservedKeys = ['title', 'slug', 'path', 'layout', 'date', 'content']; const rHexoPostRenderEscape = /([\s\S]+?)<\/hexoPostRenderCodeBlock>/g; @@ -20,7 +21,7 @@ const STATE_SWIG_COMMENT = Symbol('swig_comment'); const STATE_SWIG_TAG = Symbol('swig_tag'); const STATE_SWIG_FULL_TAG = Symbol('swig_full_tag'); -const isNonWhiteSpaceChar = char => char !== '\r' +const isNonWhiteSpaceChar = (char: string) => char !== '\r' && char !== '\n' && char !== '\t' && char !== '\f' @@ -28,18 +29,18 @@ const isNonWhiteSpaceChar = char => char !== '\r' && char !== ' '; class PostRenderEscape { - public stored: any; - public length: any; + public stored: any[]; + public length: number; constructor() { this.stored = []; } - static escapeContent(cache, flag, str) { + static escapeContent(cache: any[], flag: string, str: string) { return ``; } - static restoreContent(cache) { + static restoreContent(cache: any[]) { return (_, index) => { assert(cache[index]); const value = cache[index]; @@ -48,16 +49,16 @@ class PostRenderEscape { }; } - restoreAllSwigTags(str) { + restoreAllSwigTags(str: string) { const restored = str.replace(rSwigPlaceHolder, PostRenderEscape.restoreContent(this.stored)); return restored; } - restoreCodeBlocks(str) { + restoreCodeBlocks(str: string) { return str.replace(rCodeBlockPlaceHolder, PostRenderEscape.restoreContent(this.stored)); } - escapeCodeBlocks(str) { + escapeCodeBlocks(str: string) { return str.replace(rHexoPostRenderEscape, (_, content) => PostRenderEscape.escapeContent(this.stored, 'code', content)); } @@ -65,7 +66,7 @@ class PostRenderEscape { * @param {string} str * @returns string */ - escapeAllSwigTags(str) { + escapeAllSwigTags(str: string) { let state = STATE_PLAINTEXT; let buffer = ''; let output = ''; @@ -185,7 +186,7 @@ class PostRenderEscape { } } -const prepareFrontMatter = (data, jsonMode) => { +const prepareFrontMatter = (data: any, jsonMode: boolean) => { for (const [key, item] of Object.entries(data)) { if (moment.isMoment(item)) { data[key] = item.utc().format('YYYY-MM-DD HH:mm:ss'); @@ -202,11 +203,11 @@ const prepareFrontMatter = (data, jsonMode) => { }; -const removeExtname = str => { +const removeExtname = (str: string) => { return str.substring(0, str.length - extname(str).length); }; -const createAssetFolder = (path, assetFolder) => { +const createAssetFolder = (path: string, assetFolder: boolean) => { if (!assetFolder) return Promise.resolve(); const target = removeExtname(path); @@ -231,17 +232,24 @@ interface Data { source?: string; } +interface PostData { + title?: string; + layout?: string; + slug?: string; + path?: string; + [prop: string]: any; +} + class Post { - public context: any; - public config: any; - public tag: any; - public separator: any; + public context: Hexo; - constructor(context) { + constructor(context: Hexo) { this.context = context; } - create(data, replace, callback?) { + create(data: PostData, callback?: NodeJSLikeCallback); + create(data: PostData, replace: boolean, callback?: NodeJSLikeCallback); + create(data: PostData, replace: boolean | (NodeJSLikeCallback), callback?: NodeJSLikeCallback) { if (!callback && typeof replace === 'function') { callback = replace; replace = false; @@ -276,7 +284,7 @@ class Post { }).asCallback(callback); } - _getScaffold(layout) { + _getScaffold(layout: string) { const ctx = this.context; return ctx.scaffold.get(layout).then(result => { @@ -285,7 +293,7 @@ class Post { }); } - _renderScaffold(data) { + _renderScaffold(data: PostData) { const { tag } = this.context.extend; let splitted; @@ -327,7 +335,7 @@ class Post { }); } - publish(data, replace, callback) { + publish(data: PostData, replace: boolean, callback?: NodeJSLikeCallback) { if (!callback && typeof replace === 'function') { callback = replace; replace = false; @@ -354,7 +362,7 @@ class Post { // Read the content src = join(draftDir, item); return readFile(src); - }).then(content => { + }).then((content: string) => { // Create post Object.assign(data, yfmParse(content)); data.content = data._content; @@ -380,7 +388,7 @@ class Post { }).thenReturn(result).asCallback(callback); } - render(source, data: Data = {}, callback) { + render(source: string, data: Data = {}, callback?: NodeJSLikeCallback) { const ctx = this.context; const { config } = ctx; const { tag } = ctx.extend; diff --git a/lib/hexo/register_models.ts b/lib/hexo/register_models.ts index 864e4e3631..88a73e6081 100644 --- a/lib/hexo/register_models.ts +++ b/lib/hexo/register_models.ts @@ -1,6 +1,7 @@ import * as models from '../models'; +import type Hexo from './index'; -export = ctx => { +export = (ctx: Hexo) => { const db = ctx.database; const keys = Object.keys(models); diff --git a/lib/hexo/render.ts b/lib/hexo/render.ts index a25d152314..4d1ebda922 100644 --- a/lib/hexo/render.ts +++ b/lib/hexo/render.ts @@ -1,8 +1,11 @@ import { extname } from 'path'; import Promise from 'bluebird'; import { readFile, readFileSync } from 'hexo-fs'; +import type Hexo from './index'; +import type { Renderer } from '../extend'; +import type { StoreFunctionData } from '../extend/renderer'; -const getExtname = str => { +const getExtname = (str: string) => { if (typeof str !== 'string') return ''; const ext = extname(str); @@ -24,35 +27,35 @@ const toString = (result, options) => { }; class Render { - public context: any; - public renderer: any; + public context: Hexo; + public renderer: Renderer; - constructor(ctx) { + constructor(ctx: Hexo) { this.context = ctx; this.renderer = ctx.extend.renderer; } - isRenderable(path) { + isRenderable(path: string) { return this.renderer.isRenderable(path); } - isRenderableSync(path) { + isRenderableSync(path: string) { return this.renderer.isRenderableSync(path); } - getOutput(path) { + getOutput(path: string) { return this.renderer.getOutput(path); } - getRenderer(ext, sync?) { + getRenderer(ext: string, sync?: boolean) { return this.renderer.get(ext, sync); } - getRendererSync(ext) { + getRendererSync(ext: string) { return this.getRenderer(ext, true); } - render(data, options, callback) { + render(data: StoreFunctionData, options?: { highlight?: boolean; }, callback?: undefined) { if (!callback && typeof options === 'function') { callback = options; options = {}; @@ -96,14 +99,14 @@ class Render { }).asCallback(callback); } - renderSync(data, options = {}) { + renderSync(data: StoreFunctionData, options = {}) { if (!data) throw new TypeError('No input file or string!'); const ctx = this.context; if (data.text == null) { if (!data.path) throw new TypeError('No input file or string!'); - data.text = readFileSync(data.path); + data.text = readFileSync(data.path) as string; } if (data.text == null) throw new TypeError('No input file or string!'); diff --git a/lib/hexo/router.ts b/lib/hexo/router.ts index f00824d381..33edb61478 100644 --- a/lib/hexo/router.ts +++ b/lib/hexo/router.ts @@ -18,8 +18,6 @@ class RouteStream extends Readable { public _data: any; public _ended: boolean; public modified: any; - public push: any; - public emit: any; constructor(data: Data) { super({ objectMode: true }); @@ -133,14 +131,14 @@ class Router extends EventEmitter { return new RouteStream(data); } - isModified(path) { + isModified(path: string) { if (typeof path !== 'string') throw new TypeError('path must be a string!'); const data = this.routes[this.format(path)]; return data ? data.modified : false; } - set(path, data) { + set(path: string, data: any) { if (typeof path !== 'string') throw new TypeError('path must be a string!'); if (data == null) throw new TypeError('data is required!'); @@ -175,7 +173,7 @@ class Router extends EventEmitter { return this; } - remove(path) { + remove(path: string) { if (typeof path !== 'string') throw new TypeError('path must be a string!'); path = this.format(path); diff --git a/lib/hexo/scaffold.ts b/lib/hexo/scaffold.ts index 18a1a5ff41..37b6e65e86 100644 --- a/lib/hexo/scaffold.ts +++ b/lib/hexo/scaffold.ts @@ -1,12 +1,12 @@ import { extname, join } from 'path'; import { exists, listDir, readFile, unlink, writeFile } from 'hexo-fs'; - +import type Hexo from './index'; class Scaffold { - public context: any; - public scaffoldDir: any; + public context: Hexo; + public scaffoldDir: string; public defaults: any; - constructor(context) { + constructor(context: Hexo) { this.context = context; this.scaffoldDir = context.scaffold_dir; this.defaults = { @@ -36,11 +36,11 @@ class Scaffold { })); } - _getScaffold(name) { + _getScaffold(name: string) { return this._listDir().then(list => list.find(item => item.name === name)); } - get(name, callback) { + get(name: string, callback?: NodeJSLikeCallback) { return this._getScaffold(name).then(item => { if (item) { return readFile(item.path); @@ -50,7 +50,7 @@ class Scaffold { }).asCallback(callback); } - set(name, content, callback) { + set(name: string, content: any, callback: NodeJSLikeCallback) { const { scaffoldDir } = this; return this._getScaffold(name).then(item => { @@ -61,7 +61,7 @@ class Scaffold { }).asCallback(callback); } - remove(name, callback) { + remove(name: string, callback: NodeJSLikeCallback) { return this._getScaffold(name).then(item => { if (!item) return; diff --git a/lib/hexo/source.ts b/lib/hexo/source.ts index ab4d26bc45..736a41037f 100644 --- a/lib/hexo/source.ts +++ b/lib/hexo/source.ts @@ -1,7 +1,8 @@ import Box from '../box'; +import type Hexo from './index'; class Source extends Box { - constructor(ctx) { + constructor(ctx: Hexo) { super(ctx, ctx.source_dir); this.processors = ctx.extend.processor.list(); diff --git a/lib/hexo/update_package.ts b/lib/hexo/update_package.ts index 03319913e6..4b8f3593bd 100644 --- a/lib/hexo/update_package.ts +++ b/lib/hexo/update_package.ts @@ -1,7 +1,8 @@ import { join } from 'path'; import { writeFile, exists, readFile } from 'hexo-fs'; +import type Hexo from './index'; -export = ctx => { +export = (ctx: Hexo) => { const pkgPath = join(ctx.base_dir, 'package.json'); return readPkg(pkgPath).then(pkg => { @@ -18,7 +19,7 @@ export = ctx => { }); }; -function readPkg(path) { +function readPkg(path: string) { return exists(path).then(exist => { if (!exist) return; diff --git a/lib/hexo/validate_config.ts b/lib/hexo/validate_config.ts index 8dfcb389e9..66af8ceeba 100644 --- a/lib/hexo/validate_config.ts +++ b/lib/hexo/validate_config.ts @@ -1,4 +1,6 @@ -export = ctx => { +import type Hexo from './index'; + +export = (ctx: Hexo) => { const { config, log } = ctx; log.info('Validating config'); diff --git a/lib/models/asset.ts b/lib/models/asset.ts index 006391d3b5..5a761192da 100644 --- a/lib/models/asset.ts +++ b/lib/models/asset.ts @@ -1,7 +1,8 @@ import warehouse from 'warehouse'; import { join } from 'path'; +import type Hexo from '../hexo'; -export = ctx => { +export = (ctx: Hexo) => { const Asset = new warehouse.Schema({ _id: {type: String, required: true}, path: {type: String, required: true}, diff --git a/lib/models/cache.ts b/lib/models/cache.ts index d4b43f4722..60db5ce1c4 100644 --- a/lib/models/cache.ts +++ b/lib/models/cache.ts @@ -1,7 +1,8 @@ import warehouse from 'warehouse'; import Promise from 'bluebird'; +import type Hexo from '../hexo'; -export = ctx => { +export = (ctx: Hexo) => { const Cache = new warehouse.Schema({ _id: {type: String, required: true}, hash: {type: String, default: ''}, diff --git a/lib/models/category.ts b/lib/models/category.ts index 740bc67015..2fdbfb58df 100644 --- a/lib/models/category.ts +++ b/lib/models/category.ts @@ -1,7 +1,8 @@ import warehouse from 'warehouse'; import { slugize, full_url_for } from 'hexo-util'; +import type Hexo from '../hexo'; -export = ctx => { +export = (ctx: Hexo) => { const Category = new warehouse.Schema({ name: {type: String, required: true}, parent: { type: warehouse.Schema.Types.CUID, ref: 'Category'} diff --git a/lib/models/data.ts b/lib/models/data.ts index 0cdefe318a..89832d3cf0 100644 --- a/lib/models/data.ts +++ b/lib/models/data.ts @@ -1,6 +1,7 @@ import warehouse from 'warehouse'; +import type Hexo from '../hexo'; -export = ctx => { +export = (ctx: Hexo) => { const Data = new warehouse.Schema({ _id: {type: String, required: true}, data: Object diff --git a/lib/models/page.ts b/lib/models/page.ts index bbaa606422..f1a96922d7 100644 --- a/lib/models/page.ts +++ b/lib/models/page.ts @@ -3,8 +3,9 @@ import { join } from 'path'; import Moment from './types/moment'; import moment from 'moment'; import { full_url_for } from 'hexo-util'; +import type Hexo from '../hexo'; -export = ctx => { +export = (ctx: Hexo) => { const Page = new warehouse.Schema({ title: {type: String, default: ''}, date: { diff --git a/lib/models/post.ts b/lib/models/post.ts index 60695323ec..208e8ea477 100644 --- a/lib/models/post.ts +++ b/lib/models/post.ts @@ -3,7 +3,8 @@ import moment from 'moment'; import { extname, join, sep } from 'path'; import Promise from 'bluebird'; import Moment from './types/moment'; -import { full_url_for } from 'hexo-util'; +import { full_url_for, Cache } from 'hexo-util'; +import type Hexo from '../hexo'; function pickID(data) { return data._id; @@ -13,7 +14,9 @@ function removeEmptyTag(tags) { return tags.filter(tag => tag != null && tag !== '').map(tag => `${tag}`); } -export = ctx => { +const tagsGetterCache = new Cache(); + +export = (ctx: Hexo) => { const Post = new warehouse.Schema({ id: String, title: {type: String, default: ''}, @@ -34,7 +37,6 @@ export = ctx => { 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}, @@ -61,12 +63,14 @@ export = ctx => { }); Post.virtual('tags').get(function() { - const PostTag = ctx.model('PostTag'); - const Tag = ctx.model('Tag'); + return tagsGetterCache.apply(this._id, () => { + 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); + const ids = PostTag.find({post_id: this._id}, {lean: true}).map(item => item.tag_id); - return Tag.find({_id: {$in: ids}}); + return Tag.find({_id: {$in: ids}}); + }); }); Post.method('notPublished', function() { @@ -80,6 +84,7 @@ export = ctx => { // If the post is unpublished then the tag needs to be removed, thus the function cannot be returned early here tags = []; } + tagsGetterCache.flush(); tags = removeEmptyTag(tags); const PostTag = ctx.model('PostTag'); diff --git a/lib/models/post_asset.ts b/lib/models/post_asset.ts index 72ec5f90f1..fe73a02a47 100644 --- a/lib/models/post_asset.ts +++ b/lib/models/post_asset.ts @@ -1,7 +1,8 @@ import warehouse from 'warehouse'; import { join } from 'path'; +import type Hexo from '../hexo'; -export = ctx => { +export = (ctx: Hexo) => { const PostAsset = new warehouse.Schema({ _id: {type: String, required: true}, slug: {type: String, required: true}, diff --git a/lib/models/post_category.ts b/lib/models/post_category.ts index f3c813f8be..8b8feb0c0a 100644 --- a/lib/models/post_category.ts +++ b/lib/models/post_category.ts @@ -1,6 +1,7 @@ import warehouse from 'warehouse'; +import type Hexo from '../hexo'; -export = ctx => { +export = (ctx: Hexo) => { const PostCategory = new warehouse.Schema({ post_id: {type: warehouse.Schema.Types.CUID, ref: 'Post'}, category_id: {type: warehouse.Schema.Types.CUID, ref: 'Category'} diff --git a/lib/models/post_tag.ts b/lib/models/post_tag.ts index e727ca5e3e..b55533c49a 100644 --- a/lib/models/post_tag.ts +++ b/lib/models/post_tag.ts @@ -1,6 +1,7 @@ import warehouse from 'warehouse'; +import type Hexo from '../hexo'; -export = ctx => { +export = (ctx: Hexo) => { const PostTag = new warehouse.Schema({ post_id: {type: warehouse.Schema.Types.CUID, ref: 'Post'}, tag_id: {type: warehouse.Schema.Types.CUID, ref: 'Tag'} diff --git a/lib/models/tag.ts b/lib/models/tag.ts index 9c5b10a55e..84a118aa25 100644 --- a/lib/models/tag.ts +++ b/lib/models/tag.ts @@ -1,8 +1,9 @@ import warehouse from 'warehouse'; import { slugize, full_url_for } from 'hexo-util'; const { hasOwnProperty: hasOwn } = Object.prototype; +import type Hexo from '../hexo'; -export = ctx => { +export = (ctx: Hexo) => { const Tag = new warehouse.Schema({ name: {type: String, required: true} }); diff --git a/lib/plugins/console/clean.ts b/lib/plugins/console/clean.ts index fb799039d8..eea456a757 100644 --- a/lib/plugins/console/clean.ts +++ b/lib/plugins/console/clean.ts @@ -1,7 +1,8 @@ import Promise from 'bluebird'; import { exists, unlink, rmdir } from 'hexo-fs'; +import type Hexo from '../../hexo'; -function cleanConsole(args) { +function cleanConsole(this: Hexo): Promise<[void, void, any]> { return Promise.all([ deleteDatabase(this), deletePublicDir(this), @@ -9,7 +10,7 @@ function cleanConsole(args) { ]); } -function deleteDatabase(ctx) { +function deleteDatabase(ctx: Hexo): Promise { const dbPath = ctx.database.options.path; return exists(dbPath).then(exist => { @@ -21,7 +22,7 @@ function deleteDatabase(ctx) { }); } -function deletePublicDir(ctx) { +function deletePublicDir(ctx: Hexo): Promise { const publicDir = ctx.public_dir; return exists(publicDir).then(exist => { diff --git a/lib/plugins/console/config.ts b/lib/plugins/console/config.ts index 841c241c23..777bc4e1d0 100644 --- a/lib/plugins/console/config.ts +++ b/lib/plugins/console/config.ts @@ -2,8 +2,9 @@ import yaml from 'js-yaml'; import { exists, writeFile } from 'hexo-fs'; import { extname } from 'path'; import Promise from 'bluebird'; +import type Hexo from '../../hexo'; -function configConsole(args) { +function configConsole(this: Hexo, args): Promise { const key = args._[0]; let value = args._[1]; @@ -35,7 +36,7 @@ function configConsole(args) { }); } -function getProperty(obj, key) { +function getProperty(obj: object, key: string): any { const split = key.split('.'); let result = obj[split[0]]; @@ -46,7 +47,7 @@ function getProperty(obj, key) { return result; } -function setProperty(obj, key, value) { +function setProperty(obj: object, key: string, value: any): void { const split = key.split('.'); let cursor = obj; const lastKey = split.pop(); @@ -60,7 +61,7 @@ function setProperty(obj, key, value) { cursor[lastKey] = value; } -function castValue(value) { +function castValue(value: string): any { switch (value) { case 'true': return true; diff --git a/lib/plugins/console/deploy.ts b/lib/plugins/console/deploy.ts index 6da32c2e76..1da54819d4 100644 --- a/lib/plugins/console/deploy.ts +++ b/lib/plugins/console/deploy.ts @@ -1,7 +1,8 @@ import { exists } from 'hexo-fs'; import { underline, magenta } from 'picocolors'; +import type Hexo from '../../hexo'; -function deployConsole(args) { +function deployConsole(this: Hexo, args) { let config = this.config.deploy; const deployers = this.extend.deployer.list(); diff --git a/lib/plugins/console/generate.ts b/lib/plugins/console/generate.ts index ccae2b3d94..c91b45e2d3 100644 --- a/lib/plugins/console/generate.ts +++ b/lib/plugins/console/generate.ts @@ -6,21 +6,22 @@ import { cyan, magenta } from 'picocolors'; import tildify from 'tildify'; import { PassThrough } from 'stream'; import { createSha1Hash } from 'hexo-util'; +import type Hexo from '../../hexo'; class Generater { - public context: any; + public context: Hexo; public force: any; public bail: any; public concurrency: any; public watch: any; public deploy: any; - public generatingFiles: any; - public start: any; + public generatingFiles: Set; + public start: [number, number]; public args: any; public route: any; public log: any; - constructor(ctx, args) { + constructor(ctx: Hexo, args) { this.context = ctx; this.force = args.f || args.force; this.bail = args.b || args.bail; @@ -31,7 +32,7 @@ class Generater { this.start = process.hrtime(); this.args = args; } - generateFile(path) { + generateFile(path: string) { const publicDir = this.context.public_dir; const { generatingFiles } = this; const { route } = this.context; @@ -58,7 +59,7 @@ class Generater { generatingFiles.delete(path); }); } - writeFile(path, force?) { + writeFile(path: string, force?: boolean): Promise { const { route, log } = this.context; const publicDir = this.context.public_dir; const Cache = this.context.model('Cache'); @@ -99,7 +100,7 @@ class Generater { }); }); } - deleteFile(path) { + deleteFile(path: string): Promise { const { log } = this.context; const publicDir = this.context.public_dir; const dest = join(publicDir, path); @@ -126,7 +127,7 @@ class Generater { return dataStream.pipe(new PassThrough()); } - firstGenerate() { + firstGenerate(): Promise { const { concurrency } = this; const { route, log } = this.context; const publicDir = this.context.public_dir; @@ -168,10 +169,10 @@ class Generater { const interval = prettyHrtime(process.hrtime(this.start)); const count = result.filter(Boolean).length; - log.info('%d files generated in %s', count, cyan(interval)); + log.info('%d files generated in %s', count.toString(), cyan(interval)); }); } - execWatch() { + execWatch(): Promise { const { route, log } = this.context; return this.context.watch().then(() => this.firstGenerate()).then(() => { log.info('Hexo is watching for file changes. Press Ctrl+C to exit.'); diff --git a/lib/plugins/console/list/category.ts b/lib/plugins/console/list/category.ts index 0044ec3a0e..a1c6527c80 100644 --- a/lib/plugins/console/list/category.ts +++ b/lib/plugins/console/list/category.ts @@ -1,8 +1,9 @@ import { underline } from 'picocolors'; import table from 'text-table'; import { stringLength } from './common'; +import type Hexo from '../../../hexo'; -function listCategory() { +function listCategory(this: Hexo): void { const categories = this.model('Category'); const data = categories.sort({name: 1}).map(cate => [cate.name, String(cate.length)]); diff --git a/lib/plugins/console/list/common.ts b/lib/plugins/console/list/common.ts index 5adcb8022c..cc18685f5c 100644 --- a/lib/plugins/console/list/common.ts +++ b/lib/plugins/console/list/common.ts @@ -1,6 +1,6 @@ import strip from 'strip-ansi'; -export function stringLength(str) { +export function stringLength(str: string): number { str = strip(str); const len = str.length; diff --git a/lib/plugins/console/list/index.ts b/lib/plugins/console/list/index.ts index 7c3f084138..cd091ca272 100644 --- a/lib/plugins/console/list/index.ts +++ b/lib/plugins/console/list/index.ts @@ -4,6 +4,7 @@ import post from './post'; import route from './route'; import tag from './tag'; import category from './category'; +import type Hexo from '../../../hexo'; const store = { page, post, route, tag, category @@ -11,7 +12,7 @@ const store = { const alias = abbrev(Object.keys(store)); -function listConsole(args) { +function listConsole(this: Hexo, args) { const type = args._.shift(); // Display help message if user didn't input any arguments diff --git a/lib/plugins/console/list/page.ts b/lib/plugins/console/list/page.ts index d132231450..8b9de1bc09 100644 --- a/lib/plugins/console/list/page.ts +++ b/lib/plugins/console/list/page.ts @@ -1,8 +1,9 @@ import { magenta, underline, gray } from 'picocolors'; import table from 'text-table'; import { stringLength } from './common'; +import type Hexo from '../../../hexo'; -function listPage() { +function listPage(this: Hexo): void { const Page = this.model('Page'); const data = Page.sort({date: 1}).map(page => { diff --git a/lib/plugins/console/list/post.ts b/lib/plugins/console/list/post.ts index e4dca1a578..280b339df8 100644 --- a/lib/plugins/console/list/post.ts +++ b/lib/plugins/console/list/post.ts @@ -1,12 +1,13 @@ import { gray, magenta, underline } from 'picocolors'; import table from 'text-table'; import { stringLength } from './common'; +import type Hexo from '../../../hexo'; function mapName(item) { return item.name; } -function listPost() { +function listPost(this: Hexo): void { const Post = this.model('Post'); const data = Post.sort({published: -1, date: 1}).map(post => { diff --git a/lib/plugins/console/list/route.ts b/lib/plugins/console/list/route.ts index a29e4e4c54..2c15e217cc 100644 --- a/lib/plugins/console/list/route.ts +++ b/lib/plugins/console/list/route.ts @@ -1,6 +1,7 @@ import archy from 'archy'; +import type Hexo from '../../../hexo'; -function listRoute() { +function listRoute(this: Hexo): void { const routes = this.route.list().sort(); const tree = buildTree(routes); const nodes = buildNodes(tree); diff --git a/lib/plugins/console/list/tag.ts b/lib/plugins/console/list/tag.ts index 09c0a148df..4ce37595d1 100644 --- a/lib/plugins/console/list/tag.ts +++ b/lib/plugins/console/list/tag.ts @@ -1,8 +1,9 @@ import { magenta, underline } from 'picocolors'; import table from 'text-table'; import { stringLength } from './common'; +import type Hexo from '../../../hexo'; -function listTag() { +function listTag(this: Hexo): void { const Tag = this.model('Tag'); const data = Tag.sort({name: 1}).map(tag => [tag.name, String(tag.length), magenta(tag.path)]); diff --git a/lib/plugins/console/migrate.ts b/lib/plugins/console/migrate.ts index df341f6ed4..afe5dfb537 100644 --- a/lib/plugins/console/migrate.ts +++ b/lib/plugins/console/migrate.ts @@ -1,6 +1,7 @@ import { underline, magenta } from 'picocolors'; +import type Hexo from '../../hexo'; -function migrateConsole(args) { +function migrateConsole(this: Hexo, args) { // Display help message if user didn't input any arguments if (!args._.length) { return this.call('help', {_: ['migrate']}); diff --git a/lib/plugins/console/new.ts b/lib/plugins/console/new.ts index becbe1c625..64e3d83bd1 100644 --- a/lib/plugins/console/new.ts +++ b/lib/plugins/console/new.ts @@ -1,6 +1,7 @@ import tildify from 'tildify'; import { magenta } from 'picocolors'; import { basename } from 'path'; +import Hexo from '../../hexo'; const reservedKeys = { _: true, @@ -19,7 +20,7 @@ const reservedKeys = { silent: true }; -function newConsole(args) { +function newConsole(this: Hexo, args) { const path = args.p || args.path; let title; if (args._.length) { diff --git a/lib/plugins/console/publish.ts b/lib/plugins/console/publish.ts index f73d87ec55..6f97f9d175 100644 --- a/lib/plugins/console/publish.ts +++ b/lib/plugins/console/publish.ts @@ -1,7 +1,8 @@ import tildify from 'tildify'; import { magenta } from 'picocolors'; +import type Hexo from '../../hexo'; -function publishConsole(args) { +function publishConsole(this: Hexo, args) { // Display help message if user didn't input any arguments if (!args._.length) { return this.call('help', {_: ['publish']}); diff --git a/lib/plugins/console/render.ts b/lib/plugins/console/render.ts index 01cb57c581..c40230f728 100644 --- a/lib/plugins/console/render.ts +++ b/lib/plugins/console/render.ts @@ -3,8 +3,9 @@ import tildify from 'tildify'; import prettyHrtime from 'pretty-hrtime'; import { writeFile } from 'hexo-fs'; import { cyan, magenta } from 'picocolors'; +import type Hexo from '../../hexo'; -function renderConsole(args) { +function renderConsole(this: Hexo, args) { // Display help message if user didn't input any arguments if (!args._.length) { return this.call('help', {_: 'render'}); diff --git a/lib/plugins/filter/after_post_render/excerpt.ts b/lib/plugins/filter/after_post_render/excerpt.ts index ef1746a352..5694ce5dec 100644 --- a/lib/plugins/filter/after_post_render/excerpt.ts +++ b/lib/plugins/filter/after_post_render/excerpt.ts @@ -1,6 +1,6 @@ const rExcerpt = //i; -function excerptFilter(data) { +function excerptFilter(data): void { const { content } = data; if (typeof data.excerpt !== 'undefined') { diff --git a/lib/plugins/filter/after_post_render/external_link.ts b/lib/plugins/filter/after_post_render/external_link.ts index 0b116de8b4..dab35b3a9d 100644 --- a/lib/plugins/filter/after_post_render/external_link.ts +++ b/lib/plugins/filter/after_post_render/external_link.ts @@ -1,11 +1,12 @@ import { isExternalLink } from 'hexo-util'; +import type Hexo from '../../../hexo'; let EXTERNAL_LINK_POST_ENABLED = true; const rATag = /]+?\s+?)href=["']((?:https?:|\/\/)[^<>"']+)["'][^<>]*>/gi; const rTargetAttr = /target=/i; const rRelAttr = /rel=/i; const rRelStrAttr = /rel=["']([^<>"']*)["']/i; -function externalLinkFilter(data) { +function externalLinkFilter(this: Hexo, data): void { if (!EXTERNAL_LINK_POST_ENABLED) return; const { external_link, url } = this.config; @@ -16,7 +17,7 @@ function externalLinkFilter(data) { } data.content = data.content.replace(rATag, (str, href) => { - if (!isExternalLink(href, url, external_link.exclude) || rTargetAttr.test(str)) return str; + if (!isExternalLink(href, url, external_link.exclude as any) || rTargetAttr.test(str)) return str; if (rRelAttr.test(str)) { str = str.replace(rRelStrAttr, (relStr, rel) => { diff --git a/lib/plugins/filter/after_post_render/index.ts b/lib/plugins/filter/after_post_render/index.ts index 87bb8b433c..014eb0b6d0 100644 --- a/lib/plugins/filter/after_post_render/index.ts +++ b/lib/plugins/filter/after_post_render/index.ts @@ -1,4 +1,6 @@ -export = ctx => { +import type Hexo from '../../../hexo'; + +export = (ctx: Hexo) => { const { filter } = ctx.extend; filter.register('after_post_render', require('./external_link')); diff --git a/lib/plugins/filter/after_render/external_link.ts b/lib/plugins/filter/after_render/external_link.ts index a5d7218133..7a0168d842 100644 --- a/lib/plugins/filter/after_render/external_link.ts +++ b/lib/plugins/filter/after_render/external_link.ts @@ -1,4 +1,5 @@ import { isExternalLink } from 'hexo-util'; +import Hexo from '../../../hexo'; let EXTERNAL_LINK_SITE_ENABLED = true; const rATag = /]+?\s+?)href=["']((?:https?:|\/\/)[^<>"']+)["'][^<>]*>/gi; @@ -6,7 +7,7 @@ const rTargetAttr = /target=/i; const rRelAttr = /rel=/i; const rRelStrAttr = /rel=["']([^<>"']*)["']/i; -function externalLinkFilter(data) { +function externalLinkFilter(this: Hexo, data: string): string { if (!EXTERNAL_LINK_SITE_ENABLED) return; const { external_link, url } = this.config; @@ -17,7 +18,7 @@ function externalLinkFilter(data) { } return data.replace(rATag, (str, href) => { - if (!isExternalLink(href, url, external_link.exclude) || rTargetAttr.test(str)) return str; + if (!isExternalLink(href, url, external_link.exclude as any) || rTargetAttr.test(str)) return str; if (rRelAttr.test(str)) { str = str.replace(rRelStrAttr, (relStr, rel) => { diff --git a/lib/plugins/filter/after_render/index.ts b/lib/plugins/filter/after_render/index.ts index 509e95a0f8..a1eb862d5a 100644 --- a/lib/plugins/filter/after_render/index.ts +++ b/lib/plugins/filter/after_render/index.ts @@ -1,4 +1,6 @@ -export = ctx => { +import type Hexo from '../../../hexo'; + +export = (ctx: Hexo) => { const { filter } = ctx.extend; filter.register('after_render:html', require('./external_link')); diff --git a/lib/plugins/filter/after_render/meta_generator.ts b/lib/plugins/filter/after_render/meta_generator.ts index 4c04daee3a..7848f435ee 100644 --- a/lib/plugins/filter/after_render/meta_generator.ts +++ b/lib/plugins/filter/after_render/meta_generator.ts @@ -1,7 +1,9 @@ +import type Hexo from '../../../hexo'; + let NEED_INJECT = true; let META_GENERATOR_TAG; -function hexoMetaGeneratorInject(data) { +function hexoMetaGeneratorInject(this: Hexo, data: string): string { if (!NEED_INJECT) return; if (!this.config.meta_generator diff --git a/lib/plugins/filter/before_exit/index.ts b/lib/plugins/filter/before_exit/index.ts index ba3312749e..1443eed5cb 100644 --- a/lib/plugins/filter/before_exit/index.ts +++ b/lib/plugins/filter/before_exit/index.ts @@ -1,4 +1,6 @@ -export = ctx => { +import type Hexo from '../../../hexo'; + +export = (ctx: Hexo) => { const { filter } = ctx.extend; filter.register('before_exit', require('./save_database')); diff --git a/lib/plugins/filter/before_exit/save_database.ts b/lib/plugins/filter/before_exit/save_database.ts index 9aadce2ee8..9a853eb93e 100644 --- a/lib/plugins/filter/before_exit/save_database.ts +++ b/lib/plugins/filter/before_exit/save_database.ts @@ -1,4 +1,6 @@ -function saveDatabaseFilter() { +import type Hexo from '../../../hexo'; + +function saveDatabaseFilter(this: Hexo) { if (!this.env.init || !this._dbLoaded) return; return this.database.save().then(() => { diff --git a/lib/plugins/filter/before_generate/index.ts b/lib/plugins/filter/before_generate/index.ts index 288bb1651a..9c9565b9f4 100644 --- a/lib/plugins/filter/before_generate/index.ts +++ b/lib/plugins/filter/before_generate/index.ts @@ -1,4 +1,6 @@ -export = ctx => { +import type Hexo from '../../../hexo'; + +export = (ctx: Hexo) => { const { filter } = ctx.extend; filter.register('before_generate', require('./render_post')); diff --git a/lib/plugins/filter/before_generate/render_post.ts b/lib/plugins/filter/before_generate/render_post.ts index ae202e9477..0a6cbe78d8 100644 --- a/lib/plugins/filter/before_generate/render_post.ts +++ b/lib/plugins/filter/before_generate/render_post.ts @@ -1,12 +1,12 @@ import Promise from 'bluebird'; +import type Hexo from '../../../hexo'; -function renderPostFilter(data) { +function renderPostFilter(this: Hexo): Promise<[any[], any[]]> { const renderPosts = model => { const posts = model.toArray().filter(post => post.content == null); return Promise.map(posts, (post: any) => { post.content = post._content; - post.site = {data}; return this.post.render(post.full_source, post).then(() => post.save()); }); diff --git a/lib/plugins/filter/before_post_render/backtick_code_block.ts b/lib/plugins/filter/before_post_render/backtick_code_block.ts index 802ed7edd3..7952169edd 100644 --- a/lib/plugins/filter/before_post_render/backtick_code_block.ts +++ b/lib/plugins/filter/before_post_render/backtick_code_block.ts @@ -1,4 +1,6 @@ -const rBacktick = /^((?:[^\S\r\n]*>){0,3}[^\S\r\n]*)(`{3,}|~{3,})[^\S\r\n]*((?:.*?[^`\s])?)[^\S\r\n]*\n((?:[\s\S]*?\n)*?)(?:(?:[^\S\r\n]*>){0,3}[^\S\r\n]*)\2[^\S\r\n]?(\n+|$)/gm; +import type Hexo from '../../../hexo'; + +const rBacktick = /^((?:[^\S\r\n]*>){0,3}[^\S\r\n]*)(`{3,}|~{3,})[^\S\r\n]*((?:.*?[^`\s])?)[^\S\r\n]*\n((?:[\s\S]*?\n)?)(?:(?:[^\S\r\n]*>){0,3}[^\S\r\n]*)\2[^\S\r\n]?(\n+|$)/gm; const rAllOptions = /([^\s]+)\s+(.+?)\s+(https?:\/\/\S+|\/\S+)\s*(.+)?/; const rLangCaption = /([^\s]+)\s*(.+)?/; @@ -11,8 +13,8 @@ interface Options { firstLineNumber?: string | number } -export = ctx => { - return function backtickCodeBlock(data) { +export = (ctx: Hexo) => { + return function backtickCodeBlock(data): void { const dataContent = data.content; if ((!dataContent.includes('```') && !dataContent.includes('~~~')) || !ctx.extend.highlight.query(ctx.config.syntax_highlighter)) return; diff --git a/lib/plugins/filter/before_post_render/index.ts b/lib/plugins/filter/before_post_render/index.ts index 07bde83da3..501b483df7 100644 --- a/lib/plugins/filter/before_post_render/index.ts +++ b/lib/plugins/filter/before_post_render/index.ts @@ -1,4 +1,6 @@ -export = ctx => { +import type Hexo from '../../../hexo'; + +export = (ctx: Hexo) => { const { filter } = ctx.extend; filter.register('before_post_render', require('./backtick_code_block')(ctx)); diff --git a/lib/plugins/filter/before_post_render/titlecase.ts b/lib/plugins/filter/before_post_render/titlecase.ts index 24e596ace8..bd149d3823 100644 --- a/lib/plugins/filter/before_post_render/titlecase.ts +++ b/lib/plugins/filter/before_post_render/titlecase.ts @@ -1,6 +1,6 @@ let titlecase; -function titlecaseFilter(data) { +function titlecaseFilter(data): void { if (!(typeof data.titlecase !== 'undefined' ? data.titlecase : this.config.titlecase) || !data.title) return; if (!titlecase) titlecase = require('titlecase'); diff --git a/lib/plugins/filter/new_post_path.ts b/lib/plugins/filter/new_post_path.ts index c06b946230..1eaa58c9f2 100644 --- a/lib/plugins/filter/new_post_path.ts +++ b/lib/plugins/filter/new_post_path.ts @@ -3,6 +3,7 @@ import moment from 'moment'; import Promise from 'bluebird'; import { createSha1Hash, Permalink } from 'hexo-util'; import { ensurePath } from 'hexo-fs'; +import type Hexo from '../../hexo'; let permalink; const reservedKeys = { @@ -22,7 +23,7 @@ interface Data { date?: Date; } -function newPostPathFilter(data: Data = {}, replace) { +function newPostPathFilter(this: Hexo, data: Data = {}, replace): Promise { const sourceDir = this.source_dir; const draftDir = join(sourceDir, '_drafts'); const postDir = join(sourceDir, '_posts'); diff --git a/lib/plugins/filter/post_permalink.ts b/lib/plugins/filter/post_permalink.ts index 753ab02ea2..5c92f7fc87 100644 --- a/lib/plugins/filter/post_permalink.ts +++ b/lib/plugins/filter/post_permalink.ts @@ -1,8 +1,9 @@ import { createSha1Hash, Permalink, slugize } from 'hexo-util'; import { basename } from 'path'; +import type Hexo from '../../hexo'; let permalink; -function postPermalinkFilter(data) { +function postPermalinkFilter(this: Hexo, data) { const { config } = this; const { id, _id, slug, title, date } = data; let { __permalink } = data; diff --git a/lib/plugins/filter/template_locals/i18n.ts b/lib/plugins/filter/template_locals/i18n.ts index 73367d8df0..a709fe8934 100644 --- a/lib/plugins/filter/template_locals/i18n.ts +++ b/lib/plugins/filter/template_locals/i18n.ts @@ -1,6 +1,7 @@ import { Pattern } from 'hexo-util'; +import type Hexo from '../../../hexo'; -function i18nLocalsFilter(locals) { +function i18nLocalsFilter(this: Hexo, locals): void { const { i18n } = this.theme; const { config } = this; const i18nDir = config.i18n_dir; diff --git a/lib/plugins/filter/template_locals/index.ts b/lib/plugins/filter/template_locals/index.ts index 663cb8ab3f..27dbf0caa4 100644 --- a/lib/plugins/filter/template_locals/index.ts +++ b/lib/plugins/filter/template_locals/index.ts @@ -1,4 +1,6 @@ -export = ctx => { +import type Hexo from '../../../hexo'; + +export = (ctx: Hexo) => { const { filter } = ctx.extend; filter.register('template_locals', require('./i18n')); diff --git a/lib/plugins/generator/asset.ts b/lib/plugins/generator/asset.ts index 722807c52e..074f497620 100644 --- a/lib/plugins/generator/asset.ts +++ b/lib/plugins/generator/asset.ts @@ -4,13 +4,14 @@ import Promise from 'bluebird'; import { extname } from 'path'; import { magenta } from 'picocolors'; import type warehouse from 'warehouse'; +import type Hexo from '../../hexo'; interface Data { modified: boolean; data?: () => any; } -const process = (name, ctx) => { +const process = (name: string, ctx: Hexo) => { // @ts-expect-error return Promise.filter(ctx.model(name).toArray(), (asset: warehouse['Schema']) => exists(asset.source).tap(exist => { // @ts-expect-error @@ -46,7 +47,7 @@ const process = (name, ctx) => { }); }; -function assetGenerator() { +function assetGenerator(this: Hexo): Promise { return Promise.all([ process('Asset', this), process('PostAsset', this) diff --git a/lib/plugins/helper/css.ts b/lib/plugins/helper/css.ts index 74ca0ba357..9dac2e3588 100644 --- a/lib/plugins/helper/css.ts +++ b/lib/plugins/helper/css.ts @@ -2,7 +2,7 @@ import { htmlTag, url_for } from 'hexo-util'; import moize from 'moize'; let relative_link = true; -function cssHelper(...args) { +function cssHelper(...args: any[]) { let result = '\n'; relative_link = this.config.relative_link; diff --git a/lib/plugins/helper/date.ts b/lib/plugins/helper/date.ts index b347770dc8..8d45b27147 100644 --- a/lib/plugins/helper/date.ts +++ b/lib/plugins/helper/date.ts @@ -1,13 +1,14 @@ import moment from 'moment-timezone'; const { isMoment } = moment; import moize from 'moize'; +import type Hexo from '../../hexo'; -const isDate = value => +const isDate = (value: moment.MomentInput | moment.Moment): boolean => typeof value === 'object' && value instanceof Date && !isNaN(value.getTime()); -function getMoment(date, lang, timezone) { +function getMoment(date: moment.MomentInput | moment.Moment, lang: string, timezone: string): moment.Moment { if (date == null) date = moment(); - if (!isMoment(date)) date = moment(isDate(date) ? date : new Date(date)); + if (!isMoment(date)) date = moment(isDate(date) ? date : new Date(date)); lang = _toMomentLocale(lang); if (lang) date = date.locale(lang); @@ -16,7 +17,7 @@ function getMoment(date, lang, timezone) { return date; } -function toISOString(date) { +function toISOString(date: string | number | Date | moment.Moment) { if (date == null) { return new Date().toISOString(); } @@ -25,22 +26,22 @@ function toISOString(date) { return date.toISOString(); } - return new Date(date).toISOString(); + return new Date(date as (string | number)).toISOString(); } -function dateHelper(date, format) { +function dateHelper(date: moment.Moment | moment.MomentInput, format: string) { const { config } = this; const moment = getMoment(date, getLanguage(this), config.timezone); return moment.format(format || config.date_format); } -function timeHelper(date, format) { +function timeHelper(date: moment.Moment | moment.MomentInput, format: string) { const { config } = this; const moment = getMoment(date, getLanguage(this), config.timezone); return moment.format(format || config.time_format); } -function fullDateHelper(date, format) { +function fullDateHelper(date: moment.Moment | moment.MomentInput, format: string) { if (format) { const moment = getMoment(date, getLanguage(this), this.config.timezone); return moment.format(format); @@ -49,13 +50,13 @@ function fullDateHelper(date, format) { return `${this.date(date)} ${this.time(date)}`; } -function relativeDateHelper(date) { +function relativeDateHelper(date: moment.Moment | moment.MomentInput) { const { config } = this; const moment = getMoment(date, getLanguage(this), config.timezone); return moment.fromNow(); } -function timeTagHelper(date, format) { +function timeTagHelper(date: string | number | Date | moment.Moment, format: string) { const { config } = this; return ``; } @@ -72,7 +73,7 @@ function getLanguage(ctx) { * * Moment defined locales: https://github.com/moment/moment/tree/master/locale */ -function _toMomentLocale(lang) { +function _toMomentLocale(lang: string) { if (lang === undefined) { return undefined; } diff --git a/lib/plugins/helper/debug.ts b/lib/plugins/helper/debug.ts index ba4a96983f..a097223bb9 100644 --- a/lib/plugins/helper/debug.ts +++ b/lib/plugins/helper/debug.ts @@ -1,12 +1,12 @@ import { inspect } from 'util'; // this format object as string, resolves circular reference -function inspectObject(object, options) { +function inspectObject(object: any, options: boolean) { return inspect(object, options); } // wrapper to log to console -function log(...args) { +function log(...args: any[]) { return Reflect.apply(console.log, null, args); } diff --git a/lib/plugins/helper/favicon_tag.ts b/lib/plugins/helper/favicon_tag.ts index 579c04379a..3c00b5dbd5 100644 --- a/lib/plugins/helper/favicon_tag.ts +++ b/lib/plugins/helper/favicon_tag.ts @@ -1,6 +1,6 @@ import { url_for } from 'hexo-util'; -function faviconTagHelper(path) { +function faviconTagHelper(path: string) { return ``; } diff --git a/lib/plugins/helper/feed_tag.ts b/lib/plugins/helper/feed_tag.ts index 8fccfc7738..bb8bca13ab 100644 --- a/lib/plugins/helper/feed_tag.ts +++ b/lib/plugins/helper/feed_tag.ts @@ -11,7 +11,7 @@ interface Options { type?: string; } -function makeFeedTag(path, options: Options = {}, configFeed?, configTitle?) { +function makeFeedTag(path: string, options: Options = {}, configFeed?: any, configTitle?: string) { const title = options.title || configTitle; if (path) { @@ -46,7 +46,7 @@ function makeFeedTag(path, options: Options = {}, configFeed?, configTitle?) { return ''; } -function feedTagHelper(path, options = {}) { +function feedTagHelper(path: string, options = {}) { const { config } = this; return moize.deep(makeFeedTag.bind(this))(path, options, config.feed, config.title); } diff --git a/lib/plugins/helper/format.ts b/lib/plugins/helper/format.ts index c9372b507e..745e9901ff 100644 --- a/lib/plugins/helper/format.ts +++ b/lib/plugins/helper/format.ts @@ -3,7 +3,7 @@ import titlecase from 'titlecase'; export {stripHTML as strip_html}; export {stripHTML}; -export function trim(str) { +export function trim(str: string) { return str.trim(); } diff --git a/lib/plugins/helper/fragment_cache.ts b/lib/plugins/helper/fragment_cache.ts index 1c38229e0c..91926b919f 100644 --- a/lib/plugins/helper/fragment_cache.ts +++ b/lib/plugins/helper/fragment_cache.ts @@ -1,7 +1,7 @@ - import { Cache } from 'hexo-util'; +import type Hexo from '../../hexo'; -export = ctx => { +export = (ctx: Hexo) => { const cache = new Cache(); // reset cache for watch mode diff --git a/lib/plugins/helper/full_url_for.ts b/lib/plugins/helper/full_url_for.ts index 4e77338082..9484f0e537 100644 --- a/lib/plugins/helper/full_url_for.ts +++ b/lib/plugins/helper/full_url_for.ts @@ -1,6 +1,6 @@ import { full_url_for } from 'hexo-util'; -export = function(path) { +export = function(path: string) { return full_url_for.call(this, path); } diff --git a/lib/plugins/helper/js.ts b/lib/plugins/helper/js.ts index 24ca0fca3d..8ca44e5c52 100644 --- a/lib/plugins/helper/js.ts +++ b/lib/plugins/helper/js.ts @@ -2,7 +2,7 @@ import { htmlTag, url_for } from 'hexo-util'; import moize from 'moize'; let relative_link = true; -function jsHelper(...args) { +function jsHelper(...args: any[]) { let result = '\n'; relative_link = this.config.relative_link; diff --git a/lib/plugins/helper/list_categories.ts b/lib/plugins/helper/list_categories.ts index ff248d6bf3..b2c1fd4e58 100644 --- a/lib/plugins/helper/list_categories.ts +++ b/lib/plugins/helper/list_categories.ts @@ -33,7 +33,7 @@ function listCategoriesHelper(categories, options) { const hierarchicalList = (level: number, parent?: any) => { let result = ''; - prepareQuery(parent).forEach((cat, i) => { + prepareQuery(parent).forEach(cat => { let child; if (!depth || level + 1 < depth) { child = hierarchicalList(level + 1, cat._id); diff --git a/lib/plugins/helper/mail_to.ts b/lib/plugins/helper/mail_to.ts index 90f9ef88c9..be102ef516 100644 --- a/lib/plugins/helper/mail_to.ts +++ b/lib/plugins/helper/mail_to.ts @@ -14,7 +14,7 @@ interface Attrs { [key: string]: string | boolean | null | undefined; } -function mailToHelper(path, text, options: Options = {}) { +function mailToHelper(path: string, text: string, options: Options = {}) { if (Array.isArray(path)) path = path.join(','); if (!text) text = path; diff --git a/lib/plugins/helper/markdown.ts b/lib/plugins/helper/markdown.ts index 956425f072..9465bbb671 100644 --- a/lib/plugins/helper/markdown.ts +++ b/lib/plugins/helper/markdown.ts @@ -1,4 +1,4 @@ -function markdownHelper(text, options) { +function markdownHelper(text: string, options) { return this.render(text, 'markdown', options); } diff --git a/lib/plugins/helper/open_graph.ts b/lib/plugins/helper/open_graph.ts index 9209eeda63..27d142f8bc 100644 --- a/lib/plugins/helper/open_graph.ts +++ b/lib/plugins/helper/open_graph.ts @@ -34,7 +34,7 @@ const localeToTerritory = moize.shallow(str => { } }); -const meta = (name, content, escape?: boolean) => { +const meta = (name: string, content: string | URL, escape?: boolean) => { if (escape !== false && typeof content === 'string') { content = escapeHTML(content); } @@ -43,7 +43,7 @@ const meta = (name, content, escape?: boolean) => { return `\n`; }; -const og = (name, content?: string, escape?: boolean) => { +const og = (name: string, content?: string, escape?: boolean) => { if (escape !== false && typeof content === 'string') { content = escapeHTML(content); } diff --git a/lib/plugins/helper/paginator.ts b/lib/plugins/helper/paginator.ts index 8d37c80739..429aff8b4d 100644 --- a/lib/plugins/helper/paginator.ts +++ b/lib/plugins/helper/paginator.ts @@ -1,12 +1,35 @@ import { htmlTag, url_for } from 'hexo-util'; +import type Hexo from '../../hexo'; -const createLink = (options, ctx) => { +interface Options { + base?: string; + current?: number; + format?: string; + total?: number; + end_size?: number; + mid_size?: number; + space?: string; + next_text?: string; + prev_text?: string; + prev_next?: boolean; + escape?: boolean; + page_class?: string; + current_class?: string; + space_class?: string; + prev_class?: string; + next_class?: string; + force_prev_next?: boolean; + show_all?: boolean; + transform?: (i: number) => any; +} + +const createLink = (options: Options, ctx: Hexo) => { const { base, format } = options; - return i => url_for.call(ctx, i === 1 ? base : base + format.replace('%d', i)); + return (i: number) => url_for.call(ctx, i === 1 ? base : base + format.replace('%d', String(i))); }; -const createPageTag = (options, ctx) => { +const createPageTag = (options: Options, ctx: Hexo) => { const link = createLink(options, ctx); const { current, @@ -16,7 +39,7 @@ const createPageTag = (options, ctx) => { current_class: currentClass } = options; - return i => { + return (i: number) => { if (i === current) { return htmlTag('span', { class: pageClass + ' ' + currentClass }, transform ? transform(i) : i, escape); } @@ -24,7 +47,7 @@ const createPageTag = (options, ctx) => { }; }; -const showAll = (tags, options, ctx) => { +const showAll = (tags: string[], options: Options, ctx: Hexo) => { const { total } = options; const pageLink = createPageTag(options, ctx); @@ -34,7 +57,7 @@ const showAll = (tags, options, ctx) => { } }; -const paginationPartShow = (tags, options, ctx) => { +const paginationPartShow = (tags, options, ctx: Hexo) => { const { current, total, @@ -86,27 +109,6 @@ const paginationPartShow = (tags, options, ctx) => { } }; -interface Options { - base?: string; - current?: number; - format?: string; - total?: number; - end_size?: number; - mid_size?: number; - space?: string; - next_text?: string; - prev_text?: string; - prev_next?: boolean; - escape?: boolean; - page_class?: string; - current_class?: string; - space_class?: string; - prev_class?: string; - next_class?: string; - force_prev_next?: boolean; - show_all?: boolean; -} - function paginatorHelper(options: Options = {}) { options = Object.assign({ base: this.page.base || '', diff --git a/lib/plugins/helper/partial.ts b/lib/plugins/helper/partial.ts index d548a58ba9..fac560117d 100644 --- a/lib/plugins/helper/partial.ts +++ b/lib/plugins/helper/partial.ts @@ -1,11 +1,12 @@ import { dirname, join } from 'path'; +import type Hexo from '../../hexo'; interface Options { cache?: boolean | string; only?: boolean; } -export = ctx => function partial(name, locals, options: Options = {}) { +export = (ctx: Hexo) => function partial(name: string, locals: any, options: Options = {}) { if (typeof name !== 'string') throw new TypeError('name must be a string!'); const { cache } = options; diff --git a/lib/plugins/helper/relative_url.ts b/lib/plugins/helper/relative_url.ts index 631a9946fd..62ebaa16e2 100644 --- a/lib/plugins/helper/relative_url.ts +++ b/lib/plugins/helper/relative_url.ts @@ -1,5 +1,5 @@ import { relative_url } from 'hexo-util'; -export = function(from, to) { +export = function(from: string, to: string) { return relative_url(from, to); } diff --git a/lib/plugins/helper/render.ts b/lib/plugins/helper/render.ts index 9f8473efb3..4d2d7414b3 100644 --- a/lib/plugins/helper/render.ts +++ b/lib/plugins/helper/render.ts @@ -1,4 +1,6 @@ -export = ctx => function render(text, engine, options) { +import type Hexo from '../../hexo'; + +export = (ctx: Hexo) => function render(text: string, engine: string, options:object = {}) { return ctx.render.renderSync({ text, engine diff --git a/lib/plugins/helper/toc.ts b/lib/plugins/helper/toc.ts index 4abec4d30b..02a4b1ad7d 100644 --- a/lib/plugins/helper/toc.ts +++ b/lib/plugins/helper/toc.ts @@ -13,7 +13,7 @@ interface Options { list_number?: boolean; } -function tocHelper(str, options: Options = {}) { +function tocHelper(str: string, options: Options = {}) { options = Object.assign({ min_depth: 1, max_depth: 6, diff --git a/lib/plugins/helper/url_for.ts b/lib/plugins/helper/url_for.ts index 49e7a5f124..e7c58dd05d 100644 --- a/lib/plugins/helper/url_for.ts +++ b/lib/plugins/helper/url_for.ts @@ -1,5 +1,9 @@ import { url_for } from 'hexo-util'; -export = function(path, options) { +interface Options { + relative?: boolean +} + +export = function(path: string, options: Options = {}) { return url_for.call(this, path, options); } diff --git a/lib/plugins/processor/asset.ts b/lib/plugins/processor/asset.ts index 9693bbaeb5..c56ff62052 100644 --- a/lib/plugins/processor/asset.ts +++ b/lib/plugins/processor/asset.ts @@ -4,8 +4,11 @@ import { parse as yfm } from 'hexo-front-matter'; import { extname, relative } from 'path'; import { Pattern } from 'hexo-util'; import { magenta } from 'picocolors'; +import type { _File } from '../../box'; +import type Hexo from '../../hexo'; +import type { Stats } from 'fs'; -export = ctx => { +export = (ctx: Hexo) => { return { pattern: new Pattern(path => { if (isExcludedFile(path, ctx.config)) return; @@ -15,7 +18,7 @@ export = ctx => { }; }), - process: function assetProcessor(file) { + process: function assetProcessor(file: _File) { if (file.params.renderable) { return processPage(ctx, file); } @@ -25,7 +28,7 @@ export = ctx => { }; }; -function processPage(ctx, file) { +function processPage(ctx: Hexo, file: _File) { const Page = ctx.model('Page'); const { path } = file; const doc = Page.findOne({source: path}); @@ -48,7 +51,7 @@ function processPage(ctx, file) { return Promise.all([ file.stat(), file.read() - ]).spread((stats, content) => { + ]).spread((stats: Stats, content: string) => { const data = yfm(content); const output = ctx.render.getOutput(path); @@ -105,7 +108,7 @@ function processPage(ctx, file) { }); } -function processAsset(ctx, file) { +function processAsset(ctx: Hexo, file: _File) { 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/common.ts b/lib/plugins/processor/common.ts index c981536598..fa5202b633 100644 --- a/lib/plugins/processor/common.ts +++ b/lib/plugins/processor/common.ts @@ -4,21 +4,21 @@ import micromatch from 'micromatch'; const DURATION_MINUTE = 1000 * 60; -function isMatch(path, patterns) { +function isMatch(path: string, patterns: string| string[]) { if (!patterns) return false; return micromatch.isMatch(path, patterns); } -function isTmpFile(path) { +function isTmpFile(path: string) { return path.endsWith('%') || path.endsWith('~'); } -function isHiddenFile(path) { +function isHiddenFile(path: string) { return /(^|\/)[_.]/.test(path); } -function isExcludedFile(path, config) { +function isExcludedFile(path: string, config) { if (isTmpFile(path)) return true; if (isMatch(path, config.exclude)) return true; if (isHiddenFile(path) && !isMatch(path, config.include)) return true; @@ -34,7 +34,7 @@ export {isTmpFile}; export {isHiddenFile}; export {isExcludedFile}; -export function toDate(date) { +export function toDate(date: string | number | Date) { if (!date || moment.isMoment(date)) return date; if (!(date instanceof Date)) { @@ -46,7 +46,7 @@ export function toDate(date) { return date; } -export function timezone(date, timezone) { +export function timezone(date: Date, timezone: string) { if (moment.isMoment(date)) date = date.toDate(); const offset = date.getTimezoneOffset(); diff --git a/lib/plugins/processor/data.ts b/lib/plugins/processor/data.ts index 7757e7c99c..6db47b3097 100644 --- a/lib/plugins/processor/data.ts +++ b/lib/plugins/processor/data.ts @@ -1,10 +1,12 @@ import { Pattern } from 'hexo-util'; import { extname } from 'path'; +import type Hexo from '../../hexo'; +import type { _File } from '../../box'; -export = ctx => ({ +export = (ctx: Hexo) => ({ pattern: new Pattern('_data/*path'), - process: function dataProcessor(file) { + process: function dataProcessor(file: _File) { const Data = ctx.model('Data'); const { path } = file.params; const id = path.substring(0, path.length - extname(path).length); diff --git a/lib/plugins/processor/post.ts b/lib/plugins/processor/post.ts index dca6306e62..c9ec3a1a86 100644 --- a/lib/plugins/processor/post.ts +++ b/lib/plugins/processor/post.ts @@ -5,6 +5,9 @@ import { extname, join } from 'path'; import { stat, listDir } from 'hexo-fs'; import { slugize, Pattern, Permalink } from 'hexo-util'; import { magenta } from 'picocolors'; +import type { _File } from '../../box'; +import type Hexo from '../../hexo'; +import type { Stats } from 'fs'; const postDir = '_posts/'; const draftDir = '_drafts/'; @@ -20,7 +23,7 @@ const preservedKeys = { hash: true }; -export = ctx => { +export = (ctx: Hexo) => { return { pattern: new Pattern(path => { if (isTmpFile(path)) return; @@ -62,7 +65,7 @@ export = ctx => { }; }; -function processPost(ctx, file) { +function processPost(ctx: Hexo, file: _File) { const Post = ctx.model('Post'); const { path } = file.params; const doc = Post.findOne({source: file.path}); @@ -86,7 +89,7 @@ function processPost(ctx, file) { return Promise.all([ file.stat(), file.read() - ]).spread((stats, content) => { + ]).spread((stats: Stats, content: string) => { const data = yfm(content); const info = parseFilename(config.new_post_name, path); const keys = Object.keys(info); @@ -159,10 +162,6 @@ function processPost(ctx, file) { data.photos = [data.photos]; } - if (data.link && !data.title) { - data.title = data.link.replace(/^https?:\/\/|\/$/g, ''); - } - if (data.permalink) { data.__permalink = data.permalink; data.permalink = undefined; @@ -183,7 +182,7 @@ function processPost(ctx, file) { ])); } -function parseFilename(config, path) { +function parseFilename(config: string, path: string) { config = config.substring(0, config.length - extname(config).length); path = path.substring(0, path.length - extname(path).length); @@ -216,12 +215,14 @@ function parseFilename(config, path) { }; } -function scanAssetDir(ctx, post) { +function scanAssetDir(ctx: Hexo, post) { if (!ctx.config.post_asset_folder) return; const assetDir = post.asset_dir; const baseDir = ctx.base_dir; + const sourceDir = ctx.config.source_dir; const baseDirLength = baseDir.length; + const sourceDirLength = sourceDir.length; const PostAsset = ctx.model('PostAsset'); return stat(assetDir).then(stats => { @@ -233,6 +234,7 @@ function scanAssetDir(ctx, post) { throw err; }).filter(item => !isExcludedFile(item, ctx.config)).map(item => { const id = join(assetDir, item).substring(baseDirLength).replace(/\\/g, '/'); + const renderablePath = id.substring(sourceDirLength + 1); const asset = PostAsset.findById(id); if (shouldSkipAsset(ctx, post, asset)) return undefined; @@ -241,12 +243,13 @@ function scanAssetDir(ctx, post) { _id: id, post: post._id, slug: item, - modified: true + modified: true, + renderable: ctx.render.isRenderable(renderablePath) && !isMatch(renderablePath, ctx.config.skip_render) }); }); } -function shouldSkipAsset(ctx, post, asset) { +function shouldSkipAsset(ctx: Hexo, post, asset) { if (!ctx._showDrafts()) { if (post.published === false && asset) { // delete existing draft assets if draft posts are hidden @@ -261,7 +264,7 @@ function shouldSkipAsset(ctx, post, asset) { return asset !== undefined; // skip already existing assets } -function processAsset(ctx, file) { +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).replace(/\\/g, '/'); diff --git a/lib/plugins/renderer/json.ts b/lib/plugins/renderer/json.ts index 7c87cf6ecb..c5d02377f9 100644 --- a/lib/plugins/renderer/json.ts +++ b/lib/plugins/renderer/json.ts @@ -1,4 +1,6 @@ -function jsonRenderer(data) { +import type { StoreFunctionData } from '../../extend/renderer'; + +function jsonRenderer(data: StoreFunctionData) { return JSON.parse(data.text); } diff --git a/lib/plugins/renderer/nunjucks.ts b/lib/plugins/renderer/nunjucks.ts index 1ba4ba0205..ccb0281346 100644 --- a/lib/plugins/renderer/nunjucks.ts +++ b/lib/plugins/renderer/nunjucks.ts @@ -1,6 +1,7 @@ -import nunjucks from 'nunjucks'; +import nunjucks, { Environment } from 'nunjucks'; import { readFileSync } from 'hexo-fs'; import { dirname } from 'path'; +import type { StoreFunctionData } from '../../extend/renderer'; function toArray(value) { if (Array.isArray(value)) { @@ -36,13 +37,13 @@ const nunjucksCfg = { lstripBlocks: false }; -const nunjucksAddFilter = env => { +const nunjucksAddFilter = (env: Environment) => { env.addFilter('toarray', toArray); env.addFilter('safedump', safeJsonStringify); }; -function njkCompile(data) { - let env; +function njkCompile(data: StoreFunctionData) { + let env: Environment; if (data.path) { env = nunjucks.configure(dirname(data.path), nunjucksCfg); } else { @@ -52,14 +53,14 @@ function njkCompile(data) { const text = 'text' in data ? data.text : readFileSync(data.path); - return nunjucks.compile(text, env, data.path); + return nunjucks.compile(text as string, env, data.path); } -function njkRenderer(data, locals) { +function njkRenderer(data: StoreFunctionData, locals: object) { return njkCompile(data).render(locals); } -njkRenderer.compile = data => { +njkRenderer.compile = (data: StoreFunctionData) => { // Need a closure to keep the compiled template. return locals => njkCompile(data).render(locals); }; diff --git a/lib/plugins/renderer/plain.ts b/lib/plugins/renderer/plain.ts index e1aa47f2df..99c35a1c1c 100644 --- a/lib/plugins/renderer/plain.ts +++ b/lib/plugins/renderer/plain.ts @@ -1,4 +1,6 @@ -function plainRenderer(data) { +import type { StoreFunctionData } from '../../extend/renderer'; + +function plainRenderer(data: StoreFunctionData) { return data.text; } diff --git a/lib/plugins/renderer/yaml.ts b/lib/plugins/renderer/yaml.ts index a8f0af4a34..fd460d14a0 100644 --- a/lib/plugins/renderer/yaml.ts +++ b/lib/plugins/renderer/yaml.ts @@ -1,6 +1,7 @@ import yaml from 'js-yaml'; import { escape } from 'hexo-front-matter'; import logger from 'hexo-log'; +import type { StoreFunctionData } from '../../extend/renderer'; let schema = {}; // FIXME: workaround for https://github.com/hexojs/hexo/issues/4917 @@ -14,7 +15,7 @@ try { } } -function yamlHelper(data) { +function yamlHelper(data: StoreFunctionData) { return yaml.load(escape(data.text), { schema }); } diff --git a/lib/plugins/tag/asset_img.ts b/lib/plugins/tag/asset_img.ts index e608b499ff..8a9941b74a 100644 --- a/lib/plugins/tag/asset_img.ts +++ b/lib/plugins/tag/asset_img.ts @@ -1,5 +1,6 @@ import img from './img'; import { encodeURL } from 'hexo-util'; +import type Hexo from '../../hexo'; /** * Asset image tag @@ -7,10 +8,10 @@ import { encodeURL } from 'hexo-util'; * Syntax: * {% asset_img [class names] slug [width] [height] [title text [alt text]]%} */ -export = ctx => { +export = (ctx: Hexo) => { const PostAsset = ctx.model('PostAsset'); - return function assetImgTag(args) { + return function assetImgTag(args: string[]) { const len = args.length; // Find image URL diff --git a/lib/plugins/tag/asset_link.ts b/lib/plugins/tag/asset_link.ts index eb07087625..f132730db2 100644 --- a/lib/plugins/tag/asset_link.ts +++ b/lib/plugins/tag/asset_link.ts @@ -1,4 +1,5 @@ import { encodeURL, escapeHTML } from 'hexo-util'; +import type Hexo from '../../hexo'; /** * Asset link tag @@ -6,10 +7,10 @@ import { encodeURL, escapeHTML } from 'hexo-util'; * Syntax: * {% asset_link slug [title] [escape] %} */ -export = ctx => { +export = (ctx: Hexo) => { const PostAsset = ctx.model('PostAsset'); - return function assetLinkTag(args) { + return function assetLinkTag(args: string[]) { const slug = args.shift(); if (!slug) return; diff --git a/lib/plugins/tag/asset_path.ts b/lib/plugins/tag/asset_path.ts index 7242e3ce73..60e4c31746 100644 --- a/lib/plugins/tag/asset_path.ts +++ b/lib/plugins/tag/asset_path.ts @@ -1,4 +1,5 @@ import { encodeURL } from 'hexo-util'; +import type Hexo from '../../hexo'; /** * Asset path tag @@ -6,10 +7,10 @@ import { encodeURL } from 'hexo-util'; * Syntax: * {% asset_path slug %} */ -export = ctx => { +export = (ctx: Hexo) => { const PostAsset = ctx.model('PostAsset'); - return function assetPathTag(args) { + return function assetPathTag(args: string[]) { const slug = args.shift(); if (!slug) return; diff --git a/lib/plugins/tag/blockquote.ts b/lib/plugins/tag/blockquote.ts index 1f99e67c1a..d523e6b994 100644 --- a/lib/plugins/tag/blockquote.ts +++ b/lib/plugins/tag/blockquote.ts @@ -1,6 +1,7 @@ // Based on: https://raw.github.com/imathis/octopress/master/plugins/blockquote.rb import titlecase from 'titlecase'; +import type Hexo from '../../hexo'; const rFullCiteWithTitle = /(\S.*)\s+(https?:\/\/\S+)\s+(.+)/i; const rFullCite = /(\S.*)\s+(https?:\/\/\S+)/i; @@ -10,7 +11,7 @@ const rAuthorTitle = /([^,]+),\s*([^,]+)/; * @param {string[]} args * @param {Hexo} ctx */ -const parseFooter = (args, ctx) => { +const parseFooter = (args: string[], ctx: Hexo) => { const str = args.join(' '); if (!str) return ''; @@ -55,7 +56,7 @@ const parseFooter = (args, ctx) => { * {% endblockquote %} */ -export = ctx => function blockquoteTag(args, content) { +export = (ctx: Hexo) => function blockquoteTag(args: string[], content: string) { const footer = parseFooter(args, ctx); let result = '
'; diff --git a/lib/plugins/tag/code.ts b/lib/plugins/tag/code.ts index 266bda70e2..e56e2122c4 100644 --- a/lib/plugins/tag/code.ts +++ b/lib/plugins/tag/code.ts @@ -1,23 +1,13 @@ // Based on: https://raw.github.com/imathis/octopress/master/plugins/code_block.rb import { escapeHTML } from 'hexo-util'; +import type Hexo from '../../hexo'; +import type { HighlightOptions } from '../../extend/syntax_highlight'; const rCaptionUrlTitle = /(\S[\S\s]*)\s+(https?:\/\/\S+)\s+(.+)/i; const rCaptionUrl = /(\S[\S\s]*)\s+(https?:\/\/\S+)/i; const rCaption = /\S[\S\s]*/; -interface Options { - lang: string; - language_attr: boolean; - firstLine: number; - caption: string; - line_number: boolean; - line_threshold: number; - mark: number[]; - wrap: boolean; - lines_length?: number; -} - /** * Code block tag * Syntax: @@ -37,11 +27,11 @@ interface Options { * @returns {String} Code snippet with code highlighting */ -function parseArgs(args): Options { +function parseArgs(args: string[]): HighlightOptions { const _else = []; const len = args.length; - let lang, language_attr, - line_number, line_threshold, wrap; + let lang: string, language_attr: boolean, + line_number: boolean, line_threshold: number, wrap: boolean; let firstLine = 1; const mark = []; for (let i = 0; i < len; i++) { @@ -63,10 +53,10 @@ function parseArgs(args): Options { line_number = value === 'true'; break; case 'line_threshold': - if (!isNaN(value)) line_threshold = +value; + if (!isNaN(Number(value))) line_threshold = +value; break; case 'first_line': - if (!isNaN(value)) firstLine = +value; + if (!isNaN(Number(value))) firstLine = +value; break; case 'wrap': wrap = value === 'true'; @@ -88,7 +78,7 @@ function parseArgs(args): Options { mark.push(a); } } - if (!isNaN(cur)) mark.push(+cur); + if (!isNaN(Number(cur))) mark.push(+cur); } break; } @@ -126,14 +116,14 @@ function parseArgs(args): Options { }; } -export = ctx => function codeTag(args, content) { +export = (ctx: Hexo) => function codeTag(args: string[], content: string) { // If neither highlight.js nor prism.js is enabled, return escaped code directly if (!ctx.extend.highlight.query(ctx.config.syntax_highlighter)) { return `
${escapeHTML(content)}
`; } - let index; + let index: number; let enableHighlight = true; if ((index = args.findIndex(item => item.startsWith('highlight:'))) !== -1) { diff --git a/lib/plugins/tag/full_url_for.ts b/lib/plugins/tag/full_url_for.ts index 6dd2ff76b8..887b2f07c7 100644 --- a/lib/plugins/tag/full_url_for.ts +++ b/lib/plugins/tag/full_url_for.ts @@ -1,4 +1,5 @@ import { full_url_for, htmlTag } from 'hexo-util'; +import type Hexo from '../../hexo'; /** * Full url for tag @@ -6,7 +7,7 @@ import { full_url_for, htmlTag } from 'hexo-util'; * Syntax: * {% full_url_for text path %} */ -export = ctx => { +export = (ctx: Hexo) => { return function fullUrlForTag([text, path]) { const url = full_url_for.call(ctx, path); const attrs = { diff --git a/lib/plugins/tag/iframe.ts b/lib/plugins/tag/iframe.ts index f1e6b2663d..9597a809a7 100644 --- a/lib/plugins/tag/iframe.ts +++ b/lib/plugins/tag/iframe.ts @@ -7,7 +7,7 @@ import { htmlTag } from 'hexo-util'; * {% iframe url [width] [height] %} */ -function iframeTag(args) { +function iframeTag(args: string[]) { const src = args[0]; const width = args[1] && args[1] !== 'default' ? args[1] : '100%'; const height = args[2] && args[2] !== 'default' ? args[2] : '300'; diff --git a/lib/plugins/tag/img.ts b/lib/plugins/tag/img.ts index 22e4970020..2e8b1c8d29 100644 --- a/lib/plugins/tag/img.ts +++ b/lib/plugins/tag/img.ts @@ -1,4 +1,5 @@ import { htmlTag, url_for } from 'hexo-util'; +import type Hexo from '../../hexo'; const rUrl = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[.!/\\w]*))?)/; const rMetaDoubleQuote = /"?([^"]+)?"?/; @@ -10,9 +11,9 @@ const rMetaSingleQuote = /'?([^']+)?'?/; * Syntax: * {% img [class names] /path/to/image [width] [height] [title text [alt text]] %} */ -export = ctx => { +export = (ctx: Hexo) => { - return function imgTag(args) { + return function imgTag(args: string[]) { const classes = []; let src, width, height, title, alt; diff --git a/lib/plugins/tag/include_code.ts b/lib/plugins/tag/include_code.ts index 8457c75294..78edbe1bdb 100644 --- a/lib/plugins/tag/include_code.ts +++ b/lib/plugins/tag/include_code.ts @@ -1,5 +1,6 @@ import { exists, readFile } from 'hexo-fs'; import { basename, extname, join, posix } from 'path'; +import type Hexo from '../../hexo'; const rCaptionTitleFile = /(.*)?(?:\s+|^)(\/*\S+)/; const rLang = /\s*lang:(\w+)/i; @@ -13,7 +14,7 @@ const rTo = /\s*to:(\d+)/i; * {% include_code [title] [lang:language] path/to/file %} */ -export = ctx => function includeCodeTag(args) { +export = (ctx: Hexo) => function includeCodeTag(args: string[]) { let codeDir = ctx.config.code_dir; let arg = args.join(' '); diff --git a/lib/plugins/tag/index.ts b/lib/plugins/tag/index.ts index b2306d36e4..8f3d303db2 100644 --- a/lib/plugins/tag/index.ts +++ b/lib/plugins/tag/index.ts @@ -52,7 +52,7 @@ export default (ctx: Hexo) => { // Use WeakMap to track different ctx (in case there is any) const moized = new WeakMap(); -export function postFindOneFactory(ctx) { +export function postFindOneFactory(ctx: Hexo) { if (moized.has(ctx)) { return moized.get(ctx); } @@ -66,7 +66,7 @@ export function postFindOneFactory(ctx) { return moizedPostFindOne; } -function createPostFindOne(ctx) { +function createPostFindOne(ctx: Hexo) { const Post = ctx.model('Post'); return Post.findOne.bind(Post); } diff --git a/lib/plugins/tag/link.ts b/lib/plugins/tag/link.ts index 0df202b574..b0afbacfcd 100644 --- a/lib/plugins/tag/link.ts +++ b/lib/plugins/tag/link.ts @@ -9,7 +9,7 @@ const rUrl = /((([A-Za-z]{3,9}:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:ww * {% link text url [external] [title] %} */ -function linkTag(args, content) { +function linkTag(args: string[]) { let url = ''; const text = []; let external = false; diff --git a/lib/plugins/tag/post_link.ts b/lib/plugins/tag/post_link.ts index 782714a1d2..67289df469 100644 --- a/lib/plugins/tag/post_link.ts +++ b/lib/plugins/tag/post_link.ts @@ -1,5 +1,6 @@ import { encodeURL, escapeHTML } from 'hexo-util'; import { postFindOneFactory } from './'; +import type Hexo from '../../hexo'; /** * Post link tag @@ -7,13 +8,21 @@ import { postFindOneFactory } from './'; * Syntax: * {% post_link slug | title [title] [escape] %} */ -export = ctx => { - return function postLinkTag(args) { - const slug = args.shift(); +export = (ctx: Hexo) => { + return function postLinkTag(args: string[]) { + let slug = args.shift(); if (!slug) { throw new Error(`Post not found: "${slug}" doesn't exist for {% post_link %}`); } + let hash = ''; + const parts = slug.split('#'); + + if (parts.length === 2) { + slug = parts[0]; + hash = parts[1]; + } + let escape = args[args.length - 1]; if (escape === 'true' || escape === 'false') { args.pop(); @@ -32,7 +41,8 @@ export = ctx => { const attrTitle = escapeHTML(post.title || post.slug); if (escape === 'true') title = escapeHTML(title); - const link = encodeURL(new URL(post.path, ctx.config.url).pathname); + const url = new URL(post.path, ctx.config.url).pathname + (hash ? `#${hash}` : ''); + const link = encodeURL(url); return `${title}`; }; diff --git a/lib/plugins/tag/post_path.ts b/lib/plugins/tag/post_path.ts index e9aec212f2..fdc33eccab 100644 --- a/lib/plugins/tag/post_path.ts +++ b/lib/plugins/tag/post_path.ts @@ -1,5 +1,6 @@ import { encodeURL } from 'hexo-util'; import { postFindOneFactory } from './'; +import type Hexo from '../../hexo'; /** * Post path tag @@ -7,8 +8,8 @@ import { postFindOneFactory } from './'; * Syntax: * {% post_path slug | title %} */ -export = ctx => { - return function postPathTag(args) { +export = (ctx: Hexo) => { + return function postPathTag(args: any[]) { const slug = args.shift(); if (!slug) return; diff --git a/lib/plugins/tag/pullquote.ts b/lib/plugins/tag/pullquote.ts index 1235a26d9e..89bf01697a 100644 --- a/lib/plugins/tag/pullquote.ts +++ b/lib/plugins/tag/pullquote.ts @@ -1,3 +1,5 @@ +import type Hexo from '../../hexo'; + /** * Pullquote tag * @@ -6,7 +8,7 @@ * Quote string * {% endpullquote %} */ -export = ctx => function pullquoteTag(args, content) { +export = (ctx: Hexo) => function pullquoteTag(args: string[], content: string) { args.unshift('pullquote'); const result = ctx.render.renderSync({text: content, engine: 'markdown'}); diff --git a/lib/plugins/tag/url_for.ts b/lib/plugins/tag/url_for.ts index 6fb3750886..a417fa6219 100644 --- a/lib/plugins/tag/url_for.ts +++ b/lib/plugins/tag/url_for.ts @@ -1,4 +1,5 @@ import { url_for, htmlTag } from 'hexo-util'; +import type Hexo from '../../hexo'; /** * Url for tag @@ -6,7 +7,7 @@ import { url_for, htmlTag } from 'hexo-util'; * Syntax: * {% url_for text path [relative] %} */ -export = ctx => { +export = (ctx: Hexo) => { return function urlForTag([text, path, relative]) { const url = url_for.call(ctx, path, relative ? { relative: relative !== 'false' } : undefined); const attrs = { diff --git a/lib/theme/index.ts b/lib/theme/index.ts index d37cd281b5..aff5a74711 100644 --- a/lib/theme/index.ts +++ b/lib/theme/index.ts @@ -6,14 +6,16 @@ import { config } from './processors/config'; import { i18n } from './processors/i18n'; import { source } from './processors/source'; import { view } from './processors/view'; +import type Hexo from '../hexo'; class Theme extends Box { public config: any; public views: any; - public i18n: any; + public i18n: I18n; public View: any; + public processors: any[]; - constructor(ctx, options?) { + constructor(ctx: Hexo, options?) { super(ctx, ctx.theme_dir, options); this.config = {}; @@ -46,7 +48,7 @@ class Theme extends Box { _View.prototype._helper = ctx.extend.helper; } - getView(path) { + getView(path: string) { // Replace backslashes on Windows path = path.replace(/\\/g, '/'); @@ -63,7 +65,7 @@ class Theme extends Box { return views[Object.keys(views)[0]]; } - setView(path, data) { + setView(path: string, data) { const ext = extname(path); const name = path.substring(0, path.length - ext.length); this.views[name] = this.views[name] || {}; @@ -72,7 +74,7 @@ class Theme extends Box { views[ext] = new this.View(path, data); } - removeView(path) { + removeView(path: string) { const ext = extname(path); const name = path.substring(0, path.length - ext.length); const views = this.views[name]; diff --git a/lib/theme/processors/config.ts b/lib/theme/processors/config.ts index 64f65e2c28..c37f3ed13a 100644 --- a/lib/theme/processors/config.ts +++ b/lib/theme/processors/config.ts @@ -1,13 +1,15 @@ import { Pattern } from 'hexo-util'; +import type { _File } from '../../box'; +import Theme from '..'; -function process(file) { +function process(file: _File) { if (file.type === 'delete') { - file.box.config = {}; + (file.box as Theme).config = {}; return; } return file.render().then(result => { - file.box.config = result; + (file.box as Theme).config = result; this.log.debug('Theme config loaded.'); }).catch(err => { this.log.error('Theme config load failed.'); diff --git a/lib/theme/processors/i18n.ts b/lib/theme/processors/i18n.ts index ce955eb8a4..af2db9be95 100644 --- a/lib/theme/processors/i18n.ts +++ b/lib/theme/processors/i18n.ts @@ -1,11 +1,13 @@ import { Pattern } from 'hexo-util'; import { extname } from 'path'; +import type { _File } from '../../box'; +import type Theme from '..'; -function process(file) { +function process(file: _File) { const { path } = file.params; const ext = extname(path); const name = path.substring(0, path.length - ext.length); - const { i18n } = file.box; + const { i18n } = (file.box as Theme); if (file.type === 'delete') { i18n.remove(name); diff --git a/lib/theme/processors/source.ts b/lib/theme/processors/source.ts index 40915fae47..ac8c21a1fe 100644 --- a/lib/theme/processors/source.ts +++ b/lib/theme/processors/source.ts @@ -1,7 +1,8 @@ import { Pattern } from 'hexo-util'; import * as common from '../../plugins/processor/common'; +import type { _File } from '../../box'; -function process(file) { +function process(file: _File) { const Asset = this.model('Asset'); const id = file.source.substring(this.base_dir.length).replace(/\\/g, '/'); const { path } = file.params; diff --git a/lib/theme/processors/view.ts b/lib/theme/processors/view.ts index c4011564e2..c127d02e7c 100644 --- a/lib/theme/processors/view.ts +++ b/lib/theme/processors/view.ts @@ -1,15 +1,17 @@ import { Pattern } from 'hexo-util'; +import type { _File } from '../../box'; +import type Theme from '..'; -function process(file) { +function process(file: _File) { const { path } = file.params; if (file.type === 'delete') { - file.box.removeView(path); + (file.box as Theme).removeView(path); return; } return file.read().then(result => { - file.box.setView(path, result); + (file.box as Theme).setView(path, result); }); } diff --git a/lib/theme/view.ts b/lib/theme/view.ts index f2b2234d4f..af4c75be62 100644 --- a/lib/theme/view.ts +++ b/lib/theme/view.ts @@ -1,8 +1,10 @@ import { dirname, extname, join } from 'path'; import { parse as yfm } from 'hexo-front-matter'; import Promise from 'bluebird'; +import type Theme from '.'; +import type Render from '../hexo/render'; -const assignIn = (target, ...sources) => { +const assignIn = (target: any, ...sources: any[]) => { const length = sources.length; if (length < 1 || target == null) return target; @@ -21,18 +23,16 @@ class Options { } class View { - public path: any; + public path: string; public source: any; - public _theme: any; + public _theme: Theme; public data: any; public _compiled: any; public _compiledSync: any; public _helper: any; - public _render: any; - public layout: any; - public _content: any; + public _render: Render; - constructor(path, data) { + constructor(path: string, data) { this.path = path; this.source = join(this._theme.base, 'layout', path); this.data = typeof data === 'string' ? yfm(data) : data; @@ -106,7 +106,7 @@ class View { return locals; } - _resolveLayout(name) { + _resolveLayout(name: string) { // Relative path const layoutPath = join(dirname(this.path), name); let layoutView = this._theme.getView(layoutPath); @@ -128,7 +128,7 @@ class View { text: this.data._content }; - function buildFilterArguments(result) { + function buildFilterArguments(result: any): [string, any, any] { const output = render.getOutput(ext) || ext; return [ `after_render:${output}`, diff --git a/lib/types.ts b/lib/types.ts new file mode 100644 index 0000000000..ea6313c253 --- /dev/null +++ b/lib/types.ts @@ -0,0 +1 @@ +type NodeJSLikeCallback = (err: E, result?: R) => void diff --git a/package.json b/package.json index 8080a1db88..f48e32b70d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hexo", - "version": "7.0.0-rc2", + "version": "7.0.0", "description": "A fast, simple & powerful blog framework, powered by Node.js.", "main": "dist/hexo", "bin": { @@ -44,7 +44,7 @@ "archy": "^1.0.0", "bluebird": "^3.7.2", "hexo-cli": "^4.3.0", - "hexo-front-matter": "4.0.0", + "hexo-front-matter": "^4.2.1", "hexo-fs": "^4.1.1", "hexo-i18n": "^2.0.0", "hexo-log": "^4.0.1", @@ -66,26 +66,24 @@ "warehouse": "^5.0.0" }, "devDependencies": { - "0x": "^5.1.2", - "@easyops/git-exec-and-restage": "^1.0.4", "@types/bluebird": "^3.5.37", "@types/node": "^18.11.8", "@types/nunjucks": "^3.2.2", - "@typescript-eslint/eslint-plugin": "^5.41.0", - "@typescript-eslint/parser": "^5.41.0", - "c8": "^7.12.0", + "@types/text-table": "^0.2.4", + "0x": "^5.1.2", + "c8": "^8.0.0", "chai": "^4.3.6", "cheerio": "0.22.0", "decache": "^4.6.1", - "eslint": "^8.8.0", + "eslint": "^8.48.0", "eslint-config-hexo": "^5.0.0", "hexo-renderer-marked": "^6.0.0", "husky": "^8.0.1", - "lint-staged": "^13.0.3", + "lint-staged": "^14.0.1", "mocha": "^10.0.0", "sinon": "^15.0.0", "ts-node": "^10.9.1", - "typescript": "^4.8.4" + "typescript": "^5.3.2" }, "engines": { "node": ">=14" diff --git a/test/scripts/box/box.js b/test/scripts/box/box.js index 71548cceeb..4aaebd8698 100644 --- a/test/scripts/box/box.js +++ b/test/scripts/box/box.js @@ -9,7 +9,7 @@ const { spy, match, assert: sinonAssert } = require('sinon'); describe('Box', () => { const Hexo = require('../../../dist/hexo'); const baseDir = join(__dirname, 'box_tmp'); - const Box = require('../../../dist/box'); + const Box = require('../../../dist/box').default; const newBox = (path, config) => { const hexo = new Hexo(baseDir, { silent: true }); diff --git a/test/scripts/box/file.js b/test/scripts/box/file.js index e019d913cc..f2fe533b4c 100644 --- a/test/scripts/box/file.js +++ b/test/scripts/box/file.js @@ -7,7 +7,7 @@ const { load } = require('js-yaml'); describe('File', () => { const Hexo = require('../../../dist/hexo'); const hexo = new Hexo(__dirname); - const Box = require('../../../dist/box'); + const Box = require('../../../dist/box').default; const box = new Box(hexo, join(hexo.base_dir, 'file_test')); const { File } = box; diff --git a/test/scripts/extend/renderer.js b/test/scripts/extend/renderer.js index 44327d0cb7..8cb8131251 100644 --- a/test/scripts/extend/renderer.js +++ b/test/scripts/extend/renderer.js @@ -1,7 +1,7 @@ 'use strict'; describe('Renderer', () => { - const Renderer = require('../../../dist/extend/renderer'); + const Renderer = require('../../../dist/extend/renderer').default; it('register()', () => { const r = new Renderer(); diff --git a/test/scripts/filters/backtick_code_block.js b/test/scripts/filters/backtick_code_block.js index b195d76a8e..5c75bbccdb 100644 --- a/test/scripts/filters/backtick_code_block.js +++ b/test/scripts/filters/backtick_code_block.js @@ -650,19 +650,5 @@ describe('Backtick code block', () => { codeBlock(data); data.content.should.eql('' + expected + ''); }); - - it('handle empty code block', () => { - const data = { - content: [ - '``` js', - '```', - '# New line', - '``` js', - '```' - ].join('\n') - }; - codeBlock(data); - data.content.match(//g).length.should.eql(2); - }); }); }); diff --git a/test/scripts/filters/render_post.js b/test/scripts/filters/render_post.js index ab82f405b3..e5540cc7e4 100644 --- a/test/scripts/filters/render_post.js +++ b/test/scripts/filters/render_post.js @@ -46,20 +46,4 @@ describe('Render post', () => { page.remove(); }); - it('use data variables', async () => { - let page = await Page.insert({ - source: 'foo.md', - path: 'foo.html', - _content: '

Hello {{site.data.foo.name}}

' - }); - - const id = page._id; - await renderPost({foo: {name: 'Hexo'}}); - - page = Page.findById(id); - page.content.trim().should.eql('

Hello Hexo

'); - - page.remove(); - }); - }); diff --git a/test/scripts/models/post.js b/test/scripts/models/post.js index e2bf189da0..ded1dc66be 100644 --- a/test/scripts/models/post.js +++ b/test/scripts/models/post.js @@ -31,7 +31,6 @@ describe('Post', () => { data.comments.should.be.true; data.layout.should.eql('post'); data._content.should.eql(''); - data.link.should.eql(''); data.raw.should.eql(''); data.published.should.be.true; should.not.exist(data.updated); diff --git a/test/scripts/processors/post.js b/test/scripts/processors/post.js index 9cb00b3ac7..fea2563d62 100644 --- a/test/scripts/processors/post.js +++ b/test/scripts/processors/post.js @@ -778,33 +778,7 @@ describe('post', () => { ]); }); - it('post - link without title', async () => { - const body = [ - 'link: https://hexo.io/', - '---' - ].join('\n'); - - const file = newFile({ - path: 'foo.html', - published: true, - type: 'create', - renderable: true - }); - - await writeFile(file.source, body); - await process(file); - const post = Post.findOne({ source: file.path }); - - post.link.should.eql('https://hexo.io/'); - post.title.should.eql('hexo.io'); - - return Promise.all([ - post.remove(), - unlink(file.source) - ]); - }); - - it('post - link without title and link', async () => { + it('post - without title', async () => { const body = ''; const file = newFile({ @@ -1282,4 +1256,76 @@ describe('post', () => { unlink(file.source) ]); }); + + it('asset - post - common render', async () => { + hexo.config.post_asset_folder = true; + + const file = newFile({ + path: 'foo.md', + published: true, + type: 'create', + renderable: true + }); + + const assetFile = newFile({ + path: 'foo/test.yml', + published: true, + type: 'create' + }); + + await Promise.all([ + writeFile(file.source, 'test'), + writeFile(assetFile.source, 'test') + ]); + await process(file); + const id = 'source/' + assetFile.path; + const post = Post.findOne({ source: file.path }); + PostAsset.findById(id).renderable.should.be.true; + + hexo.config.post_asset_folder = false; + + return Promise.all([ + unlink(file.source), + unlink(assetFile.source), + post.remove(), + PostAsset.removeById(id) + ]); + }); + + it('asset - post - skip render', async () => { + hexo.config.post_asset_folder = true; + hexo.config.skip_render = '**.yml'; + + const file = newFile({ + path: 'foo.md', + published: true, + type: 'create', + renderable: true + }); + + const assetFile = newFile({ + path: 'foo/test.yml', + published: true, + type: 'create' + }); + + await Promise.all([ + writeFile(file.source, 'test'), + writeFile(assetFile.source, 'test') + ]); + await process(file); + const id = 'source/' + assetFile.path; + const post = Post.findOne({ source: file.path }); + PostAsset.findById(id).renderable.should.be.false; + + hexo.config.post_asset_folder = false; + hexo.config.skip_render = ''; + + return Promise.all([ + unlink(file.source), + unlink(assetFile.source), + post.remove(), + PostAsset.removeById(id) + ]); + }); }); diff --git a/test/scripts/tags/post_link.js b/test/scripts/tags/post_link.js index 999f2cad48..05839a8098 100644 --- a/test/scripts/tags/post_link.js +++ b/test/scripts/tags/post_link.js @@ -73,4 +73,8 @@ describe('post_link', () => { it('should throw if post not found', () => { should.throw(() => postLink(['bar']), Error, /Post not found: post_link bar\./); }); + + it('should keep hash', () => { + postLink(['foo#bar']).should.eql('Hello world'); + }); });