From 0e97e36c43c6b262f1a385757abecc9c38091ee8 Mon Sep 17 00:00:00 2001 From: Vigilans Date: Sat, 23 Mar 2019 22:24:57 +0800 Subject: [PATCH 1/7] Apply markdown engine to result provider --- src/webview/leetCodeResultProvider.ts | 108 +++++++++++++++++++++++--- 1 file changed, 97 insertions(+), 11 deletions(-) diff --git a/src/webview/leetCodeResultProvider.ts b/src/webview/leetCodeResultProvider.ts index 189eed17..d6d7d762 100644 --- a/src/webview/leetCodeResultProvider.ts +++ b/src/webview/leetCodeResultProvider.ts @@ -2,21 +2,26 @@ // Licensed under the MIT license. import { Disposable, ExtensionContext, ViewColumn, WebviewPanel, window } from "vscode"; +import { leetCodeChannel } from "../leetCodeChannel"; +import { MarkdownEngine } from "./MarkdownEngine"; class LeetCodeResultProvider implements Disposable { private context: ExtensionContext; private panel: WebviewPanel | undefined; + private mdEngine: MarkdownEngine; public initialize(context: ExtensionContext): void { + this.mdEngine = new MarkdownEngine(); this.context = context; } - public async show(result: string): Promise { + public async show(resultString: string): Promise { if (!this.panel) { this.panel = window.createWebviewPanel("leetcode.result", "LeetCode Results", ViewColumn.Two, { retainContextWhenHidden: true, enableFindWidget: true, + localResourceRoots: this.mdEngine.localResourceRoots, }); this.panel.onDidDispose(() => { @@ -24,7 +29,9 @@ class LeetCodeResultProvider implements Disposable { }, null, this.context.subscriptions); } - this.panel.webview.html = await this.provideHtmlContent(result); + const result: Result = this.parseResult(resultString); + this.panel.title = `${""}: Result`; + this.panel.webview.html = this.getWebViewContent(result); this.panel.reveal(ViewColumn.Two); } @@ -34,19 +41,98 @@ class LeetCodeResultProvider implements Disposable { } } - private async provideHtmlContent(result: string): Promise { - return ` - + private parseResult(raw: string): Result { + try { + switch (raw[2]) { + case "√": { + const result: AcceptResult = new AcceptResult(); + [result.status, raw] = raw.split(/ . (.+)([^]+)/).slice(1); + [result.passed, raw] = raw.split(/\n . (.+)([^]+)/).slice(1); + [result.runtime, raw] = raw.split(/\n . (.+)([^]+)/).slice(1); + [result.memory, raw] = raw.split(/\n . (.+)/).slice(1); + return result; + } + case "×": { + const result: FailedResult = new FailedResult(); + [result.status, raw] = raw.split(/ . (.+)([^]+)/).slice(1); + [result.passed, raw] = raw.split(/\n . (.+)([^]+)/).slice(1); + [result.testcase, raw] = raw.split(/\n . testcase: '(.+)'([^]+)/).slice(1); + [result.answer, raw] = raw.split(/\n . answer: (.+)([^]+)/).slice(1); + [result.expected, raw] = raw.split(/\n . expected_answer: (.+)([^]+)/).slice(1); + [result.stdout, raw] = raw.split(/\n . stdout: ([^]+?)\n$/).slice(1); + result.testcase = result.testcase.replace("\\n", "\n"); + return result; + } + default: { + throw new TypeError(raw); + } + } + } catch (error) { + leetCodeChannel.appendLine(`Result parsing failed: ${error.message}`); + throw error; + } + } + + private getWebViewContent(result: Result): string { + const styles: string = this.mdEngine.getStylesHTML(); + let body: string; + if (result instanceof AcceptResult) { + const accpet: AcceptResult = result as AcceptResult; + body = this.mdEngine.render([ + `## ${result.status}`, + ``, + `* ${result.passed}`, + `* ${accpet.runtime}`, + `* ${accpet.memory}`, + ].join("\n")); + } else { + const failed: FailedResult = result as FailedResult; + body = this.mdEngine.render([ + `## ${result.status}`, + `* ${result.passed}`, + ``, + `### Testcase`, // TODO: add command to copy raw testcase + `\`\`\`\n${failed.testcase}\n\`\`\``, + `### Answer`, + `\`\`\`\n${failed.answer}\n\`\`\``, + `### Expected`, + `\`\`\`\n${failed.expected}\n\`\`\``, + `### Stdout`, + `\`\`\`\n${failed.stdout}\n\`\`\``, + ].join("\n")); + } + return ` + + - - - LeetCode Results + ${styles} - -
${result.trim()}
+ + ${body} - `; + + `; } } +// tslint:disable-next-line:max-classes-per-file +abstract class Result { + public status: string; + public passed: string; +} + +// tslint:disable-next-line:max-classes-per-file +class AcceptResult extends Result { + public runtime: string; + public memory: string; +} + +// tslint:disable-next-line:max-classes-per-file +class FailedResult extends Result { + public testcase: string; + public answer: string; + public expected: string; + public stdout: string; +} + export const leetCodeResultProvider: LeetCodeResultProvider = new LeetCodeResultProvider(); From 4bb90189cbf02936429b00a2e06c1624d8c1d7cf Mon Sep 17 00:00:00 2001 From: Vigilans Date: Sat, 23 Mar 2019 22:27:38 +0800 Subject: [PATCH 2/7] Minor modification --- src/webview/leetCodePreviewProvider.ts | 2 +- src/webview/leetCodeResultProvider.ts | 4 ++-- src/webview/leetCodeSolutionProvider.ts | 9 ++++----- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/webview/leetCodePreviewProvider.ts b/src/webview/leetCodePreviewProvider.ts index 7e3c491c..8acdfeed 100644 --- a/src/webview/leetCodePreviewProvider.ts +++ b/src/webview/leetCodePreviewProvider.ts @@ -39,7 +39,7 @@ class LeetCodePreviewProvider implements Disposable { } this.panel.webview.html = await this.provideHtmlContent(node); - this.panel.title = node.name; + this.panel.title = `${node.name}: Preview`; this.panel.reveal(); } diff --git a/src/webview/leetCodeResultProvider.ts b/src/webview/leetCodeResultProvider.ts index d6d7d762..208bb0ff 100644 --- a/src/webview/leetCodeResultProvider.ts +++ b/src/webview/leetCodeResultProvider.ts @@ -3,6 +3,7 @@ import { Disposable, ExtensionContext, ViewColumn, WebviewPanel, window } from "vscode"; import { leetCodeChannel } from "../leetCodeChannel"; +import { IProblem } from "../shared"; import { MarkdownEngine } from "./MarkdownEngine"; class LeetCodeResultProvider implements Disposable { @@ -18,7 +19,7 @@ class LeetCodeResultProvider implements Disposable { public async show(resultString: string): Promise { if (!this.panel) { - this.panel = window.createWebviewPanel("leetcode.result", "LeetCode Results", ViewColumn.Two, { + this.panel = window.createWebviewPanel("leetcode.result", "Submission Result", ViewColumn.Two, { retainContextWhenHidden: true, enableFindWidget: true, localResourceRoots: this.mdEngine.localResourceRoots, @@ -30,7 +31,6 @@ class LeetCodeResultProvider implements Disposable { } const result: Result = this.parseResult(resultString); - this.panel.title = `${""}: Result`; this.panel.webview.html = this.getWebViewContent(result); this.panel.reveal(ViewColumn.Two); } diff --git a/src/webview/leetCodeSolutionProvider.ts b/src/webview/leetCodeSolutionProvider.ts index ac5f5765..51f0ddb6 100644 --- a/src/webview/leetCodeSolutionProvider.ts +++ b/src/webview/leetCodeSolutionProvider.ts @@ -10,7 +10,6 @@ class LeetCodeSolutionProvider implements Disposable { private context: ExtensionContext; private panel: WebviewPanel | undefined; private mdEngine: MarkdownEngine; - private solution: Solution; public initialize(context: ExtensionContext): void { this.context = context; @@ -30,9 +29,9 @@ class LeetCodeSolutionProvider implements Disposable { }, null, this.context.subscriptions); } - this.solution = this.parseSolution(solutionString); - this.panel.title = problem.name; - this.panel.webview.html = this.getWebViewContent(this.solution); + const solution: Solution = this.parseSolution(solutionString); + this.panel.title = `${problem.name}: Solution`; + this.panel.webview.html = this.getWebViewContent(solution); this.panel.reveal(ViewColumn.Active); } @@ -66,7 +65,7 @@ class LeetCodeSolutionProvider implements Disposable { `| ${lang} | ${auth} | ${votes} |`, ].join("\n")); const body: string = this.mdEngine.render(solution.body, { - lang: this.solution.lang, + lang: solution.lang, host: "https://discuss.leetcode.com/", }); return ` From 983885ea414b6648bf2958d1659b52d12af6df90 Mon Sep 17 00:00:00 2001 From: Vigilans Date: Sat, 23 Mar 2019 23:17:42 +0800 Subject: [PATCH 3/7] Address comments in LGTM.com --- src/webview/leetCodeResultProvider.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/webview/leetCodeResultProvider.ts b/src/webview/leetCodeResultProvider.ts index 208bb0ff..a8337476 100644 --- a/src/webview/leetCodeResultProvider.ts +++ b/src/webview/leetCodeResultProvider.ts @@ -3,7 +3,6 @@ import { Disposable, ExtensionContext, ViewColumn, WebviewPanel, window } from "vscode"; import { leetCodeChannel } from "../leetCodeChannel"; -import { IProblem } from "../shared"; import { MarkdownEngine } from "./MarkdownEngine"; class LeetCodeResultProvider implements Disposable { @@ -49,7 +48,7 @@ class LeetCodeResultProvider implements Disposable { [result.status, raw] = raw.split(/ . (.+)([^]+)/).slice(1); [result.passed, raw] = raw.split(/\n . (.+)([^]+)/).slice(1); [result.runtime, raw] = raw.split(/\n . (.+)([^]+)/).slice(1); - [result.memory, raw] = raw.split(/\n . (.+)/).slice(1); + [result.memory] = raw.split(/\n . (.+)/).slice(1); return result; } case "×": { @@ -59,7 +58,7 @@ class LeetCodeResultProvider implements Disposable { [result.testcase, raw] = raw.split(/\n . testcase: '(.+)'([^]+)/).slice(1); [result.answer, raw] = raw.split(/\n . answer: (.+)([^]+)/).slice(1); [result.expected, raw] = raw.split(/\n . expected_answer: (.+)([^]+)/).slice(1); - [result.stdout, raw] = raw.split(/\n . stdout: ([^]+?)\n$/).slice(1); + [result.stdout] = raw.split(/\n . stdout: ([^]+?)\n$/).slice(1); result.testcase = result.testcase.replace("\\n", "\n"); return result; } From 7559af59d403ce40a4952cc4df92388f389570fe Mon Sep 17 00:00:00 2001 From: Vigilans Date: Mon, 25 Mar 2019 10:44:00 +0800 Subject: [PATCH 4/7] Export markdownEngine as global variable --- src/webview/leetCodeResultProvider.ts | 12 +++++------- src/webview/leetCodeSolutionProvider.ts | 14 ++++++-------- .../{MarkdownEngine.ts => markdownEngine.ts} | 4 +++- 3 files changed, 14 insertions(+), 16 deletions(-) rename src/webview/{MarkdownEngine.ts => markdownEngine.ts} (97%) diff --git a/src/webview/leetCodeResultProvider.ts b/src/webview/leetCodeResultProvider.ts index a8337476..a2f3c785 100644 --- a/src/webview/leetCodeResultProvider.ts +++ b/src/webview/leetCodeResultProvider.ts @@ -3,16 +3,14 @@ import { Disposable, ExtensionContext, ViewColumn, WebviewPanel, window } from "vscode"; import { leetCodeChannel } from "../leetCodeChannel"; -import { MarkdownEngine } from "./MarkdownEngine"; +import { markdownEngine } from "./markdownEngine"; class LeetCodeResultProvider implements Disposable { private context: ExtensionContext; private panel: WebviewPanel | undefined; - private mdEngine: MarkdownEngine; public initialize(context: ExtensionContext): void { - this.mdEngine = new MarkdownEngine(); this.context = context; } @@ -21,7 +19,7 @@ class LeetCodeResultProvider implements Disposable { this.panel = window.createWebviewPanel("leetcode.result", "Submission Result", ViewColumn.Two, { retainContextWhenHidden: true, enableFindWidget: true, - localResourceRoots: this.mdEngine.localResourceRoots, + localResourceRoots: markdownEngine.localResourceRoots, }); this.panel.onDidDispose(() => { @@ -73,11 +71,11 @@ class LeetCodeResultProvider implements Disposable { } private getWebViewContent(result: Result): string { - const styles: string = this.mdEngine.getStylesHTML(); + const styles: string = markdownEngine.getStylesHTML(); let body: string; if (result instanceof AcceptResult) { const accpet: AcceptResult = result as AcceptResult; - body = this.mdEngine.render([ + body = markdownEngine.render([ `## ${result.status}`, ``, `* ${result.passed}`, @@ -86,7 +84,7 @@ class LeetCodeResultProvider implements Disposable { ].join("\n")); } else { const failed: FailedResult = result as FailedResult; - body = this.mdEngine.render([ + body = markdownEngine.render([ `## ${result.status}`, `* ${result.passed}`, ``, diff --git a/src/webview/leetCodeSolutionProvider.ts b/src/webview/leetCodeSolutionProvider.ts index 51f0ddb6..6243af02 100644 --- a/src/webview/leetCodeSolutionProvider.ts +++ b/src/webview/leetCodeSolutionProvider.ts @@ -3,17 +3,15 @@ import { Disposable, ExtensionContext, ViewColumn, WebviewPanel, window } from "vscode"; import { IProblem } from "../shared"; -import { MarkdownEngine } from "./MarkdownEngine"; +import { markdownEngine } from "./markdownEngine"; class LeetCodeSolutionProvider implements Disposable { private context: ExtensionContext; private panel: WebviewPanel | undefined; - private mdEngine: MarkdownEngine; public initialize(context: ExtensionContext): void { this.context = context; - this.mdEngine = new MarkdownEngine(); } public async show(solutionString: string, problem: IProblem): Promise { @@ -21,7 +19,7 @@ class LeetCodeSolutionProvider implements Disposable { this.panel = window.createWebviewPanel("leetCode.solution", "Top Voted Solution", ViewColumn.Active, { retainContextWhenHidden: true, enableFindWidget: true, - localResourceRoots: this.mdEngine.localResourceRoots, + localResourceRoots: markdownEngine.localResourceRoots, }); this.panel.onDidDispose(() => { @@ -55,16 +53,16 @@ class LeetCodeSolutionProvider implements Disposable { } private getWebViewContent(solution: Solution): string { - const styles: string = this.mdEngine.getStylesHTML(); + const styles: string = markdownEngine.getStylesHTML(); const { title, url, lang, author, votes } = solution; - const head: string = this.mdEngine.render(`# [${title}](${url})`); + const head: string = markdownEngine.render(`# [${title}](${url})`); const auth: string = `[${author}](https://leetcode.com/${author}/)`; - const info: string = this.mdEngine.render([ + const info: string = markdownEngine.render([ `| Language | Author | Votes |`, `| :------: | :------: | :------: |`, `| ${lang} | ${auth} | ${votes} |`, ].join("\n")); - const body: string = this.mdEngine.render(solution.body, { + const body: string = markdownEngine.render(solution.body, { lang: solution.lang, host: "https://discuss.leetcode.com/", }); diff --git a/src/webview/MarkdownEngine.ts b/src/webview/markdownEngine.ts similarity index 97% rename from src/webview/MarkdownEngine.ts rename to src/webview/markdownEngine.ts index 0d56382e..b3a45a4a 100644 --- a/src/webview/MarkdownEngine.ts +++ b/src/webview/markdownEngine.ts @@ -8,7 +8,7 @@ import * as path from "path"; import * as vscode from "vscode"; import { leetCodeChannel } from "../leetCodeChannel"; -export class MarkdownEngine { +class MarkdownEngine { private readonly engine: MarkdownIt; private readonly extRoot: string; // root path of vscode built-in markdown extension @@ -106,3 +106,5 @@ export class MarkdownEngine { }; } } + +export const markdownEngine: MarkdownEngine = new MarkdownEngine(); From 72022999885fce169738175e0a28db985a48f7a5 Mon Sep 17 00:00:00 2001 From: Vigilans Date: Tue, 26 Mar 2019 13:52:05 +0800 Subject: [PATCH 5/7] New generic parsing logic for result string --- src/webview/leetCodeResultProvider.ts | 115 ++++++++++---------------- 1 file changed, 42 insertions(+), 73 deletions(-) diff --git a/src/webview/leetCodeResultProvider.ts b/src/webview/leetCodeResultProvider.ts index a2f3c785..3082f206 100644 --- a/src/webview/leetCodeResultProvider.ts +++ b/src/webview/leetCodeResultProvider.ts @@ -2,7 +2,6 @@ // Licensed under the MIT license. import { Disposable, ExtensionContext, ViewColumn, WebviewPanel, window } from "vscode"; -import { leetCodeChannel } from "../leetCodeChannel"; import { markdownEngine } from "./markdownEngine"; class LeetCodeResultProvider implements Disposable { @@ -27,7 +26,7 @@ class LeetCodeResultProvider implements Disposable { }, null, this.context.subscriptions); } - const result: Result = this.parseResult(resultString); + const result: IResult = this.parseResult(resultString); this.panel.webview.html = this.getWebViewContent(result); this.panel.reveal(ViewColumn.Two); } @@ -38,66 +37,50 @@ class LeetCodeResultProvider implements Disposable { } } - private parseResult(raw: string): Result { - try { - switch (raw[2]) { - case "√": { - const result: AcceptResult = new AcceptResult(); - [result.status, raw] = raw.split(/ . (.+)([^]+)/).slice(1); - [result.passed, raw] = raw.split(/\n . (.+)([^]+)/).slice(1); - [result.runtime, raw] = raw.split(/\n . (.+)([^]+)/).slice(1); - [result.memory] = raw.split(/\n . (.+)/).slice(1); - return result; - } - case "×": { - const result: FailedResult = new FailedResult(); - [result.status, raw] = raw.split(/ . (.+)([^]+)/).slice(1); - [result.passed, raw] = raw.split(/\n . (.+)([^]+)/).slice(1); - [result.testcase, raw] = raw.split(/\n . testcase: '(.+)'([^]+)/).slice(1); - [result.answer, raw] = raw.split(/\n . answer: (.+)([^]+)/).slice(1); - [result.expected, raw] = raw.split(/\n . expected_answer: (.+)([^]+)/).slice(1); - [result.stdout] = raw.split(/\n . stdout: ([^]+?)\n$/).slice(1); - result.testcase = result.testcase.replace("\\n", "\n"); - return result; + private parseResult(raw: string): IResult { + raw = raw.concat(" √ "); // Append a dummy sentinel to the end of raw string + const regSplit: RegExp = / [√×✔✘vx] ([^]+?)\n(?= [√×✔✘vx] )/g; + const regKeyVal: RegExp = /(.+?): ([^]*)/; + const result: IResult = { messages: [] }; + let entry: RegExpExecArray | null; + do { + entry = regSplit.exec(raw); + if (!entry) { + continue; + } + const kvMatch: RegExpExecArray | null = regKeyVal.exec(entry[1]); + if (kvMatch) { + const key: string = kvMatch[1].split("_").map((k: string) => `${k[0].toUpperCase()}${k.slice(1)}`).join(" "); + let value: string = kvMatch[2]; + if (!result[key]) { + result[key] = []; } - default: { - throw new TypeError(raw); + if (key === "Testcase") { + value = value.slice(1, -1).replace("\\n", "\n"); } + result[key].push(value); + } else { + result.messages.push(entry[1]); } - } catch (error) { - leetCodeChannel.appendLine(`Result parsing failed: ${error.message}`); - throw error; - } + } while (entry); + return result; } - private getWebViewContent(result: Result): string { + private getWebViewContent(result: IResult): string { const styles: string = markdownEngine.getStylesHTML(); - let body: string; - if (result instanceof AcceptResult) { - const accpet: AcceptResult = result as AcceptResult; - body = markdownEngine.render([ - `## ${result.status}`, - ``, - `* ${result.passed}`, - `* ${accpet.runtime}`, - `* ${accpet.memory}`, - ].join("\n")); - } else { - const failed: FailedResult = result as FailedResult; - body = markdownEngine.render([ - `## ${result.status}`, - `* ${result.passed}`, - ``, - `### Testcase`, // TODO: add command to copy raw testcase - `\`\`\`\n${failed.testcase}\n\`\`\``, - `### Answer`, - `\`\`\`\n${failed.answer}\n\`\`\``, - `### Expected`, - `\`\`\`\n${failed.expected}\n\`\`\``, - `### Stdout`, - `\`\`\`\n${failed.stdout}\n\`\`\``, - ].join("\n")); - } + const title: string = `## ${result.messages[0]}`; + const messages: string[] = result.messages.slice(1).map((m: string) => `* ${m}`); + const sections: string[] = Object.keys(result).filter((k: string) => k !== "messages").map((key: string) => [ + `### ${key}`, + "```", + result[key].join("\n\n"), + "```", + ].join("\n")); + const body: string = markdownEngine.render([ + title, + ...messages, + ...sections, + ].join("\n")); return ` @@ -113,23 +96,9 @@ class LeetCodeResultProvider implements Disposable { } // tslint:disable-next-line:max-classes-per-file -abstract class Result { - public status: string; - public passed: string; -} - -// tslint:disable-next-line:max-classes-per-file -class AcceptResult extends Result { - public runtime: string; - public memory: string; -} - -// tslint:disable-next-line:max-classes-per-file -class FailedResult extends Result { - public testcase: string; - public answer: string; - public expected: string; - public stdout: string; +interface IResult { + [key: string]: string[]; + messages: string[]; } export const leetCodeResultProvider: LeetCodeResultProvider = new LeetCodeResultProvider(); From 738ffd918b196a34c8473fe741671d1c2f4b527b Mon Sep 17 00:00:00 2001 From: Vigilans Date: Tue, 26 Mar 2019 23:45:29 +0800 Subject: [PATCH 6/7] Address comments in review --- src/explorer/LeetCodeTreeDataProvider.ts | 9 +++------ src/webview/leetCodeResultProvider.ts | 4 ++-- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/explorer/LeetCodeTreeDataProvider.ts b/src/explorer/LeetCodeTreeDataProvider.ts index 8b5ed89d..09f47530 100644 --- a/src/explorer/LeetCodeTreeDataProvider.ts +++ b/src/explorer/LeetCodeTreeDataProvider.ts @@ -1,6 +1,7 @@ // Copyright (c) jdneo. All rights reserved. // Licensed under the MIT license. +import * as _ from "lodash"; import * as os from "os"; import * as path from "path"; import * as vscode from "vscode"; @@ -201,10 +202,10 @@ export class LeetCodeTreeDataProvider implements vscode.TreeDataProvider c[0].toUpperCase() + c.slice(1)).join(" "); - } - private getSubCategoryNodes(map: Map, category: Category): LeetCodeNode[] { const subCategoryNodes: LeetCodeNode[] = Array.from(map.keys()).map((subCategory: string) => { return new LeetCodeNode(Object.assign({}, defaultProblem, { diff --git a/src/webview/leetCodeResultProvider.ts b/src/webview/leetCodeResultProvider.ts index 3082f206..4886fef6 100644 --- a/src/webview/leetCodeResultProvider.ts +++ b/src/webview/leetCodeResultProvider.ts @@ -1,6 +1,7 @@ // Copyright (c) jdneo. All rights reserved. // Licensed under the MIT license. +import * as _ from "lodash"; import { Disposable, ExtensionContext, ViewColumn, WebviewPanel, window } from "vscode"; import { markdownEngine } from "./markdownEngine"; @@ -50,7 +51,7 @@ class LeetCodeResultProvider implements Disposable { } const kvMatch: RegExpExecArray | null = regKeyVal.exec(entry[1]); if (kvMatch) { - const key: string = kvMatch[1].split("_").map((k: string) => `${k[0].toUpperCase()}${k.slice(1)}`).join(" "); + const key: string = _.startCase(kvMatch[1]); let value: string = kvMatch[2]; if (!result[key]) { result[key] = []; @@ -95,7 +96,6 @@ class LeetCodeResultProvider implements Disposable { } } -// tslint:disable-next-line:max-classes-per-file interface IResult { [key: string]: string[]; messages: string[]; From e5f3f0ed5ff545a97c295254b00e66d3e427fe14 Mon Sep 17 00:00:00 2001 From: Vigilans Date: Wed, 27 Mar 2019 08:36:48 +0800 Subject: [PATCH 7/7] Add lodash dependency in package.json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 56343331..8b841821 100644 --- a/package.json +++ b/package.json @@ -321,6 +321,7 @@ "dependencies": { "fs-extra": "^6.0.1", "highlight.js": "^9.15.6", + "lodash": "^4.17.11", "lodash.kebabcase": "^4.1.1", "markdown-it": "^8.4.2", "require-from-string": "^2.0.2",