Skip to content

Commit 19c38f8

Browse files
committed
feat: list refreshing and error handling
1 parent c71ea19 commit 19c38f8

File tree

11 files changed

+177
-26
lines changed

11 files changed

+177
-26
lines changed

.vscode/launch.json

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,37 @@
1414
"--extensionDevelopmentPath=${workspaceFolder}",
1515
"/Users/yukai/Projects/HackMD/hackmd-production/public/docs/features.md"
1616
],
17-
"outFiles": ["${workspaceFolder}/out/**/*.js"]
17+
"outFiles": [
18+
"${workspaceFolder}/dist/**/*.js"
19+
]
20+
},
21+
{
22+
"name": "Run Web Extension in VS Code",
23+
"type": "pwa-extensionHost",
24+
"debugWebWorkerHost": true,
25+
"runtimeExecutable": "${execPath}",
26+
"request": "launch",
27+
"args": [
28+
"--extensionDevelopmentPath=${workspaceFolder}",
29+
"--extensionDevelopmentKind=web"
30+
],
31+
"outFiles": [
32+
"${workspaceFolder}/dist/**/*.js"
33+
],
1834
},
1935
{
2036
"name": "Extension Tests",
2137
"type": "extensionHost",
2238
"request": "launch",
2339
"runtimeExecutable": "${execPath}",
24-
"args": ["--extensionDevelopmentPath=${workspaceFolder}", "--extensionTestsPath=${workspaceFolder}/out/test"],
25-
"outFiles": ["${workspaceFolder}/out/test/**/*.js"],
40+
"args": [
41+
"--extensionDevelopmentPath=${workspaceFolder}",
42+
"--extensionTestsPath=${workspaceFolder}/out/test"
43+
],
44+
"outFiles": [
45+
"${workspaceFolder}/out/test/**/*.js"
46+
],
2647
"preLaunchTask": "npm: watch"
2748
}
2849
]
29-
}
50+
}

package.json

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -101,8 +101,31 @@
101101
"category": "HackMD"
102102
},
103103
{
104-
"command": "treeView.refreshList",
105-
"title": "Refresh",
104+
"command": "HackMD.apiKey",
105+
"title": "API Key",
106+
"category": "HackMD"
107+
},
108+
{
109+
"command": "treeView.refreshMyNotes",
110+
"title": "Refresh My Notes",
111+
"category": "HackMD",
112+
"icon": {
113+
"light": "images/icon/light/refresh-dark.svg",
114+
"dark": "images/icon/dark/refresh-light.svg"
115+
}
116+
},
117+
{
118+
"command": "treeView.refreshHistory",
119+
"title": "Refresh History",
120+
"category": "HackMD",
121+
"icon": {
122+
"light": "images/icon/light/refresh-dark.svg",
123+
"dark": "images/icon/dark/refresh-light.svg"
124+
}
125+
},
126+
{
127+
"command": "treeView.refreshTeamNotes",
128+
"title": "Refresh Team Notes",
106129
"category": "HackMD",
107130
"icon": {
108131
"light": "images/icon/light/refresh-dark.svg",
@@ -145,8 +168,18 @@
145168
"menus": {
146169
"view/title": [
147170
{
148-
"command": "treeView.refreshList",
149-
"when": "view =~ /hackmd.tree/",
171+
"command": "treeView.refreshMyNotes",
172+
"when": "view =~ /hackmd.tree.my-notes/",
173+
"group": "navigation"
174+
},
175+
{
176+
"command": "treeView.refreshHistory",
177+
"when": "view =~ /hackmd.tree.recent-notes/",
178+
"group": "navigation"
179+
},
180+
{
181+
"command": "treeView.refreshTeamNotes",
182+
"when": "view =~ /hackmd.tree.team-notes/",
150183
"group": "navigation"
151184
}
152185
],

src/api.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,18 @@ import { ACCESS_TOKEN_KEY } from './constants';
66

77
let API: ApiClient;
88

9-
export async function initializeAPIClient(context: vscode.ExtensionContext) {
9+
export async function initializeAPIClient(context: vscode.ExtensionContext, forceShowInputBox = false) {
1010
let accessToken = await context.secrets.get(ACCESS_TOKEN_KEY);
1111
const apiEndPoint = vscode.workspace.getConfiguration('Hackmd').get('apiEndPoint') as string;
1212

13-
if (!accessToken) {
13+
if (!accessToken || forceShowInputBox) {
1414
const input = await vscode.window.showInputBox({
1515
prompt: 'Please input your HackMD access token',
1616
password: true,
1717
ignoreFocusOut: true,
1818
placeHolder: 'Access Token',
1919
title: 'HackMD Access Token',
20+
value: accessToken,
2021
});
2122

2223
if (!input) {
@@ -30,6 +31,10 @@ export async function initializeAPIClient(context: vscode.ExtensionContext) {
3031
API = new ApiClient(accessToken, apiEndPoint);
3132
}
3233

34+
export async function forceRefreshAPIClient(context: vscode.ExtensionContext) {
35+
await initializeAPIClient(context, true);
36+
}
37+
3338
vscode.workspace.onDidChangeConfiguration(async (e) => {
3439
if (e.affectsConfiguration('Hackmd')) {
3540
const extension = vscode.extensions.getExtension<{ context: vscode.ExtensionContext }>('HackMD.hackmd-vscode');

src/commands/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import * as vscode from 'vscode';
33
import { registerNoteCommands } from './note';
44
import { registerSnippetCommands } from './snippet';
55
import { registerTreeViewCommands } from './treeView';
6+
import { registerUserCommands } from './user';
67

78
export function registerCommands(context: vscode.ExtensionContext) {
9+
registerUserCommands(context);
810
registerTreeViewCommands(context);
911
registerNoteCommands(context);
1012
registerSnippetCommands(context);

src/commands/treeView.ts

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,29 @@ import * as vscode from 'vscode';
22

33
import { Team } from '@hackmd/api/dist/type';
44

5+
import { refreshMyNotesEvent, refreshHistoryEvent, refreshTeamNotesEvent } from '../treeReactApp/events';
56
import { teamNotesStore } from '../treeReactApp/store';
67

78
import { API } from './../api';
89
import { MdTextDocumentContentProvider, getNoteIdPublishLink } from './../mdTextDocument';
910
import { ReactVSCTreeNode } from './../tree/nodes';
10-
// import { refreshHistoryList, refreshLoginStatus } from './../utils';
1111

1212
export async function registerTreeViewCommands(context: vscode.ExtensionContext) {
1313
context.subscriptions.push(
14-
vscode.commands.registerCommand('treeView.refreshList', async () => {
15-
// await refreshLoginStatus();
16-
// await refreshHistoryList();
14+
vscode.commands.registerCommand('treeView.refreshMyNotes', async () => {
15+
refreshMyNotesEvent.fire();
16+
})
17+
);
18+
19+
context.subscriptions.push(
20+
vscode.commands.registerCommand('treeView.refreshHistory', async () => {
21+
refreshHistoryEvent.fire();
22+
})
23+
);
24+
25+
context.subscriptions.push(
26+
vscode.commands.registerCommand('treeView.refreshTeamNotes', async () => {
27+
refreshTeamNotesEvent.fire();
1728
})
1829
);
1930

src/commands/user.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as vscode from 'vscode';
2+
3+
import { forceRefreshAPIClient } from '../api';
4+
import { refreshHistoryEvent, refreshMyNotesEvent, refreshTeamNotesEvent } from '../treeReactApp/events';
5+
6+
export async function registerUserCommands(context: vscode.ExtensionContext) {
7+
context.subscriptions.push(
8+
vscode.commands.registerCommand('HackMD.apiKey', async () => {
9+
await forceRefreshAPIClient(context);
10+
11+
refreshHistoryEvent.fire();
12+
refreshMyNotesEvent.fire();
13+
refreshTeamNotesEvent.fire();
14+
})
15+
);
16+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { TreeItem } from 'react-vsc-treeview';
2+
3+
export const ErrorListItem = ({ error }: { error: any }) => {
4+
return (
5+
<>
6+
{error && (
7+
<TreeItem
8+
command={{
9+
command: 'HackMD.apiKey',
10+
title: 'Reset API Key',
11+
tooltip: 'Reset API Key',
12+
}}
13+
label="Check your API Endpoint config in settings or click here to reset API key"
14+
/>
15+
)}
16+
</>
17+
);
18+
};

src/treeReactApp/events/index.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import * as vscode from 'vscode';
2+
3+
import { useEffect } from 'react';
4+
5+
export const refreshHistoryEvent = new vscode.EventEmitter<void>();
6+
export const refreshMyNotesEvent = new vscode.EventEmitter<void>();
7+
export const refreshTeamNotesEvent = new vscode.EventEmitter<void>();
8+
9+
export const useEventEmitter = (event: vscode.EventEmitter<void>, cb: () => void) => {
10+
useEffect(() => {
11+
const disposable = event.event(cb);
12+
return () => {
13+
disposable.dispose();
14+
};
15+
}, [cb, event]);
16+
};

src/treeReactApp/pages/History.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
import useSWR from 'swr';
22

33
import { API } from '../../api';
4+
import { ErrorListItem } from '../components/ErrorListItem';
45
import { NoteTreeItem } from '../components/NoteTreeItem';
6+
import { refreshHistoryEvent, useEventEmitter } from '../events';
57

68
export const History = () => {
7-
// TODO: mutate history list on refresh
8-
const { data = [] } = useSWR('history', () => API.getHistory());
9+
const { data = [], mutate, error } = useSWR('history', () => API.getHistory());
10+
11+
useEventEmitter(refreshHistoryEvent, () => {
12+
mutate();
13+
});
914

1015
return (
1116
<>
17+
<ErrorListItem error={error} />
18+
1219
{data.map((note) => {
1320
return <NoteTreeItem key={note.id} note={note} />;
1421
})}

src/treeReactApp/pages/MyNotes.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,24 @@
11
import useSWR from 'swr';
22

33
import { API } from '../../api';
4+
import { ErrorListItem } from '../components/ErrorListItem';
45
import { NoteTreeItem } from '../components/NoteTreeItem';
6+
import { refreshMyNotesEvent, useEventEmitter } from '../events';
57

68
export const MyNotes = () => {
7-
const { data = [] } = useSWR('/my-notes', () => API.getNoteList());
9+
const { data = [], mutate, error } = useSWR('/my-notes', () => API.getNoteList());
10+
11+
useEventEmitter(refreshMyNotesEvent, () => {
12+
mutate();
13+
});
814

915
return (
1016
<>
1117
{data.map((note) => {
1218
return <NoteTreeItem key={note.id} note={note} />;
1319
})}
20+
21+
<ErrorListItem error={error} />
1422
</>
1523
);
1624
};

src/treeReactApp/pages/TeamNotes.tsx

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import useSWR from 'swr';
77

88
import { API } from '../../api';
99
import { useAppContext } from '../AppContainer';
10+
import { ErrorListItem } from '../components/ErrorListItem';
1011
import { NoteTreeItem } from '../components/NoteTreeItem';
12+
import { refreshTeamNotesEvent, useEventEmitter } from '../events';
1113
import { useTeamNotesStore } from '../store';
1214

1315
const TeamTreeItem = ({ team }: { team: Team }) => {
14-
const { data: notes = [] } = useSWR(
16+
const { data: notes = [], mutate } = useSWR(
1517
() => (team ? `/teams/${team.id}/notes` : null),
1618
() => API.getTeamNotes(team.path)
1719
);
@@ -28,6 +30,10 @@ const TeamTreeItem = ({ team }: { team: Team }) => {
2830
}
2931
}, [extensionPath]);
3032

33+
useEventEmitter(refreshTeamNotesEvent, () => {
34+
mutate();
35+
});
36+
3137
return (
3238
<TreeItem label={team.name} expanded iconPath={iconPath} description={team.path}>
3339
{notes.map((note) => {
@@ -40,7 +46,7 @@ const TeamTreeItem = ({ team }: { team: Team }) => {
4046
};
4147

4248
export const TeamNotes = () => {
43-
const { data: teams = [] } = useSWR('/teams', () => API.getTeams());
49+
const { data: teams = [], mutate, error } = useSWR('/teams', () => API.getTeams());
4450
const [selectedTeamId, setSelectedTeamId] = useState(useTeamNotesStore.getState().selectedTeamId);
4551

4652
// I'm not sure why using useTeamNotesStore doesn't trigger re-render
@@ -51,15 +57,23 @@ export const TeamNotes = () => {
5157

5258
const selectedTeam = useMemo(() => teams.find((t) => t.id === selectedTeamId), [teams, selectedTeamId]);
5359

60+
useEventEmitter(refreshTeamNotesEvent, () => {
61+
mutate();
62+
});
63+
5464
return (
5565
<>
56-
<TreeItem
57-
label="Click to select a team"
58-
command={{
59-
title: 'Select a team',
60-
command: 'selectTeam',
61-
}}
62-
/>
66+
<ErrorListItem error={error} />
67+
68+
{!error && (
69+
<TreeItem
70+
label="Click to select a team"
71+
command={{
72+
title: 'Select a team',
73+
command: 'selectTeam',
74+
}}
75+
/>
76+
)}
6377

6478
{selectedTeam && <TeamTreeItem team={selectedTeam} />}
6579
</>

0 commit comments

Comments
 (0)