Skip to content

Commit 781feb2

Browse files
Vigilansjdneo
authored andcommitted
Extract leetcode webview base class & add md settings listener (LeetCode-OpenSource#270)
1 parent ecac298 commit 781feb2

9 files changed

+226
-192
lines changed

src/commands/submit.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { leetCodeExecutor } from "../leetCodeExecutor";
66
import { leetCodeManager } from "../leetCodeManager";
77
import { DialogType, promptForOpenOutputChannel, promptForSignIn } from "../utils/uiUtils";
88
import { getActiveFilePath } from "../utils/workspaceUtils";
9-
import { leetCodeResultProvider } from "../webview/leetCodeResultProvider";
9+
import { leetCodeSubmissionProvider } from "../webview/leetCodeSubmissionProvider";
1010

1111
export async function submitSolution(uri?: vscode.Uri): Promise<void> {
1212
if (!leetCodeManager.getUser()) {
@@ -21,7 +21,7 @@ export async function submitSolution(uri?: vscode.Uri): Promise<void> {
2121

2222
try {
2323
const result: string = await leetCodeExecutor.submitSolution(filePath);
24-
await leetCodeResultProvider.show(result);
24+
await leetCodeSubmissionProvider.show(result);
2525
} catch (error) {
2626
await promptForOpenOutputChannel("Failed to submit the solution. Please open the output channel for details.", DialogType.error);
2727
}

src/commands/test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { isWindows, usingCmd } from "../utils/osUtils";
1010
import { DialogType, promptForOpenOutputChannel, showFileSelectDialog } from "../utils/uiUtils";
1111
import { getActiveFilePath } from "../utils/workspaceUtils";
1212
import * as wsl from "../utils/wslUtils";
13-
import { leetCodeResultProvider } from "../webview/leetCodeResultProvider";
13+
import { leetCodeSubmissionProvider } from "../webview/leetCodeSubmissionProvider";
1414

1515
export async function testSolution(uri?: vscode.Uri): Promise<void> {
1616
try {
@@ -81,7 +81,7 @@ export async function testSolution(uri?: vscode.Uri): Promise<void> {
8181
if (!result) {
8282
return;
8383
}
84-
await leetCodeResultProvider.show(result);
84+
await leetCodeSubmissionProvider.show(result);
8585
} catch (error) {
8686
await promptForOpenOutputChannel("Failed to test the solution. Please open the output channel for details.", DialogType.error);
8787
}

src/extension.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,9 @@ import { leetCodeManager } from "./leetCodeManager";
1818
import { leetCodeStatusBarController } from "./statusbar/leetCodeStatusBarController";
1919
import { DialogType, promptForOpenOutputChannel } from "./utils/uiUtils";
2020
import { leetCodePreviewProvider } from "./webview/leetCodePreviewProvider";
21-
import { leetCodeResultProvider } from "./webview/leetCodeResultProvider";
2221
import { leetCodeSolutionProvider } from "./webview/leetCodeSolutionProvider";
22+
import { leetCodeSubmissionProvider } from "./webview/leetCodeSubmissionProvider";
23+
import { markdownEngine } from "./webview/markdownEngine";
2324

2425
export async function activate(context: vscode.ExtensionContext): Promise<void> {
2526
try {
@@ -33,17 +34,15 @@ export async function activate(context: vscode.ExtensionContext): Promise<void>
3334
});
3435

3536
const leetCodeTreeDataProvider: LeetCodeTreeDataProvider = new LeetCodeTreeDataProvider(context);
36-
leetCodePreviewProvider.initialize(context);
37-
leetCodeResultProvider.initialize(context);
38-
leetCodeSolutionProvider.initialize(context);
3937

4038
context.subscriptions.push(
4139
leetCodeStatusBarController,
4240
leetCodeChannel,
4341
leetCodePreviewProvider,
44-
leetCodeResultProvider,
42+
leetCodeSubmissionProvider,
4543
leetCodeSolutionProvider,
4644
leetCodeExecutor,
45+
markdownEngine,
4746
vscode.window.createTreeView("leetCodeExplorer", { treeDataProvider: leetCodeTreeDataProvider, showCollapseAll: true }),
4847
vscode.languages.registerCodeLensProvider({ scheme: "file" }, codeLensProvider),
4948
vscode.commands.registerCommand("leetcode.deleteCache", () => cache.deleteCache()),

src/webview/LeetCodeWebview.ts

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
// Copyright (c) jdneo. All rights reserved.
2+
// Licensed under the MIT license.
3+
4+
import { ConfigurationChangeEvent, Disposable, ViewColumn, WebviewPanel, window, workspace } from "vscode";
5+
import { markdownEngine } from "./markdownEngine";
6+
7+
export abstract class LeetCodeWebview implements Disposable {
8+
9+
protected panel: WebviewPanel | undefined;
10+
private listeners: Disposable[] = [];
11+
12+
public dispose(): void {
13+
if (this.panel) {
14+
this.panel.dispose();
15+
}
16+
}
17+
18+
protected showWebviewInternal(): void {
19+
const { viewType, title, viewColumn, preserveFocus } = this.getWebviewOption();
20+
if (!this.panel) {
21+
this.panel = window.createWebviewPanel(viewType, title, { viewColumn, preserveFocus }, {
22+
enableScripts: true,
23+
enableCommandUris: true,
24+
enableFindWidget: true,
25+
retainContextWhenHidden: true,
26+
localResourceRoots: markdownEngine.localResourceRoots,
27+
});
28+
this.panel.onDidDispose(this.onDidDisposeWebview, this, this.listeners);
29+
this.panel.webview.onDidReceiveMessage(this.onDidReceiveMessage, this, this.listeners);
30+
workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.listeners);
31+
} else {
32+
this.panel.title = title;
33+
this.panel.reveal(viewColumn, preserveFocus);
34+
}
35+
this.panel.webview.html = this.getWebviewContent();
36+
}
37+
38+
protected onDidDisposeWebview(): void {
39+
this.panel = undefined;
40+
for (const listener of this.listeners) {
41+
listener.dispose();
42+
}
43+
this.listeners = [];
44+
}
45+
46+
protected async onDidChangeConfiguration(event: ConfigurationChangeEvent): Promise<void> {
47+
if (this.panel && event.affectsConfiguration("markdown")) {
48+
this.panel.webview.html = this.getWebviewContent();
49+
}
50+
}
51+
52+
protected async onDidReceiveMessage(_message: any): Promise<void> { /* no special rule */ }
53+
54+
protected abstract getWebviewOption(): ILeetCodeWebviewOption;
55+
56+
protected abstract getWebviewContent(): string;
57+
}
58+
59+
export interface ILeetCodeWebviewOption {
60+
viewType: string;
61+
title: string;
62+
viewColumn: ViewColumn;
63+
preserveFocus?: boolean;
64+
}

src/webview/leetCodePreviewProvider.ts

+69-84
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,39 @@
11
// Copyright (c) jdneo. All rights reserved.
22
// Licensed under the MIT license.
33

4-
import { commands, Disposable, ExtensionContext, ViewColumn, WebviewPanel, window } from "vscode";
4+
import { commands, ViewColumn } from "vscode";
55
import { leetCodeExecutor } from "../leetCodeExecutor";
66
import { IProblem } from "../shared";
7+
import { ILeetCodeWebviewOption, LeetCodeWebview } from "./LeetCodeWebview";
78
import { markdownEngine } from "./markdownEngine";
89

9-
class LeetCodePreviewProvider implements Disposable {
10+
class LeetCodePreviewProvider extends LeetCodeWebview {
1011

11-
private context: ExtensionContext;
1212
private node: IProblem;
13-
private panel: WebviewPanel | undefined;
14-
15-
public initialize(context: ExtensionContext): void {
16-
this.context = context;
17-
}
13+
private description: IDescription;
1814

1915
public async show(node: IProblem): Promise<void> {
20-
// Fetch problem first before creating webview panel
21-
const descString: string = await leetCodeExecutor.getDescription(node);
22-
16+
this.description = this.parseDescription(await leetCodeExecutor.getDescription(node), node);
2317
this.node = node;
24-
if (!this.panel) {
25-
this.panel = window.createWebviewPanel("leetcode.preview", "Preview Problem", ViewColumn.One, {
26-
enableScripts: true,
27-
enableCommandUris: true,
28-
enableFindWidget: true,
29-
retainContextWhenHidden: true,
30-
localResourceRoots: markdownEngine.localResourceRoots,
31-
});
32-
33-
this.panel.webview.onDidReceiveMessage(async (message: IWebViewMessage) => {
34-
switch (message.command) {
35-
case "ShowProblem": {
36-
await commands.executeCommand("leetcode.showProblem", this.node);
37-
break;
38-
}
39-
}
40-
}, this, this.context.subscriptions);
41-
42-
this.panel.onDidDispose(() => {
43-
this.panel = undefined;
44-
}, null, this.context.subscriptions);
45-
}
46-
47-
const description: IDescription = this.parseDescription(descString, node);
48-
this.panel.webview.html = this.getWebViewContent(description);
49-
this.panel.title = `${node.name}: Preview`;
50-
this.panel.reveal(ViewColumn.One);
18+
this.showWebviewInternal();
5119
}
5220

53-
public dispose(): void {
54-
if (this.panel) {
55-
this.panel.dispose();
56-
}
57-
}
58-
59-
private parseDescription(descString: string, problem: IProblem): IDescription {
60-
const [
61-
/* title */, ,
62-
url, ,
63-
/* tags */, ,
64-
/* langs */, ,
65-
category,
66-
difficulty,
67-
likes,
68-
dislikes,
69-
/* accepted */,
70-
/* submissions */,
71-
/* testcase */, ,
72-
...body
73-
] = descString.split("\n");
21+
protected getWebviewOption(): ILeetCodeWebviewOption {
7422
return {
75-
title: problem.name,
76-
url,
77-
tags: problem.tags,
78-
companies: problem.companies,
79-
category: category.slice(2),
80-
difficulty: difficulty.slice(2),
81-
likes: likes.split(": ")[1].trim(),
82-
dislikes: dislikes.split(": ")[1].trim(),
83-
body: body.join("\n").replace(/<pre>\s*([^]+?)\s*<\/pre>/g, "<pre><code>$1</code></pre>"),
23+
viewType: "leetcode.preview",
24+
title: `${this.node.name}: Preview`,
25+
viewColumn: ViewColumn.One,
8426
};
8527
}
8628

87-
private getWebViewContent(desc: IDescription): string {
88-
const mdStyles: string = markdownEngine.getStyles();
89-
const buttonStyle: string = `
90-
<style>
29+
protected getWebviewContent(): string {
30+
const button: { element: string, script: string, style: string } = {
31+
element: `<button id="solve">Code Now</button>`,
32+
script: `const button = document.getElementById('solve');
33+
button.onclick = () => vscode.postMessage({
34+
command: 'ShowProblem',
35+
});`,
36+
style: `<style>
9137
#solve {
9238
position: fixed;
9339
bottom: 1rem;
@@ -104,9 +50,9 @@ class LeetCodePreviewProvider implements Disposable {
10450
#solve:active {
10551
border: 0;
10652
}
107-
</style>
108-
`;
109-
const { title, url, category, difficulty, likes, dislikes, body } = desc;
53+
</style>`,
54+
};
55+
const { title, url, category, difficulty, likes, dislikes, body } = this.description;
11056
const head: string = markdownEngine.render(`# [${title}](${url})`);
11157
const info: string = markdownEngine.render([
11258
`| Category | Difficulty | Likes | Dislikes |`,
@@ -117,7 +63,7 @@ class LeetCodePreviewProvider implements Disposable {
11763
`<details>`,
11864
`<summary><strong>Tags</strong></summary>`,
11965
markdownEngine.render(
120-
desc.tags
66+
this.description.tags
12167
.map((t: string) => `[\`${t}\`](https://leetcode.com/tag/${t})`)
12268
.join(" | "),
12369
),
@@ -127,7 +73,7 @@ class LeetCodePreviewProvider implements Disposable {
12773
`<details>`,
12874
`<summary><strong>Companies</strong></summary>`,
12975
markdownEngine.render(
130-
desc.companies
76+
this.description.companies
13177
.map((c: string) => `\`${c}\``)
13278
.join(" | "),
13379
),
@@ -137,28 +83,67 @@ class LeetCodePreviewProvider implements Disposable {
13783
<!DOCTYPE html>
13884
<html>
13985
<head>
140-
${mdStyles}
141-
${buttonStyle}
86+
${markdownEngine.getStyles()}
87+
${button.style}
14288
</head>
14389
<body>
14490
${head}
14591
${info}
14692
${tags}
14793
${companies}
14894
${body}
149-
<button id="solve">Code Now</button>
95+
${button.element}
15096
<script>
15197
const vscode = acquireVsCodeApi();
152-
const button = document.getElementById('solve');
153-
button.onclick = () => vscode.postMessage({
154-
command: 'ShowProblem',
155-
});
98+
${button.script}
15699
</script>
157100
</body>
158101
</html>
159102
`;
160103
}
161104

105+
protected onDidDisposeWebview(): void {
106+
super.onDidDisposeWebview();
107+
delete this.node;
108+
delete this.description;
109+
}
110+
111+
protected async onDidReceiveMessage(message: IWebViewMessage): Promise<void> {
112+
switch (message.command) {
113+
case "ShowProblem": {
114+
await commands.executeCommand("leetcode.showProblem", this.node);
115+
break;
116+
}
117+
}
118+
}
119+
120+
private parseDescription(descString: string, problem: IProblem): IDescription {
121+
const [
122+
/* title */, ,
123+
url, ,
124+
/* tags */, ,
125+
/* langs */, ,
126+
category,
127+
difficulty,
128+
likes,
129+
dislikes,
130+
/* accepted */,
131+
/* submissions */,
132+
/* testcase */, ,
133+
...body
134+
] = descString.split("\n");
135+
return {
136+
title: problem.name,
137+
url,
138+
tags: problem.tags,
139+
companies: problem.companies,
140+
category: category.slice(2),
141+
difficulty: difficulty.slice(2),
142+
likes: likes.split(": ")[1].trim(),
143+
dislikes: dislikes.split(": ")[1].trim(),
144+
body: body.join("\n").replace(/<pre>\s*([^]+?)\s*<\/pre>/g, "<pre><code>$1</code></pre>"),
145+
};
146+
}
162147
}
163148

164149
interface IDescription {

0 commit comments

Comments
 (0)