@kubohiroya/turbowarp-plus-editor-git 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +13 -3
- package/README.md +12 -2
- package/dist/branch-button.d.ts +47 -0
- package/dist/change-marks.d.ts +8 -0
- package/dist/connection-settings.d.ts +3 -1
- package/dist/github-pull-requests.d.ts +2 -2
- package/dist/github-review-api.d.ts +112 -0
- package/dist/index.d.ts +29 -5
- package/dist/index.js +830 -303
- package/dist/messages.d.ts +109 -2
- package/dist/{project-asset-paths-nL5sV35A.js → project-asset-paths-DNaoWs3E.js} +2 -2
- package/dist/{project-comparison-DjMlwyp8.js → project-comparison-B_3QZ6M-.js} +1414 -33
- package/dist/project-source-diff.d.ts +5 -0
- package/dist/project-source-diff.js +4 -4
- package/dist/project-source-review.d.ts +6 -1
- package/dist/pull-request-panel.d.ts +2 -0
- package/dist/remote-operations.d.ts +1 -1
- package/dist/{repository-xIv0iZkj.js → repository-DXRZEtYk.js} +26 -10
- package/dist/repository.d.ts +7 -0
- package/dist/repository.js +1 -1
- package/dist/review-composer.d.ts +23 -0
- package/dist/review-submit.d.ts +144 -0
- package/dist/{smart-http-BmHEkE7K.js → smart-http-eARzlXHO.js} +101 -2
- package/dist/smart-http.d.ts +2 -0
- package/dist/smart-http.js +1 -1
- package/dist/style.css +51 -0
- package/package.json +1 -1
- package/src/branch-button.tsx +289 -0
- package/src/change-marks.ts +22 -0
- package/src/connection-settings.tsx +9 -2
- package/src/github-review-api.ts +298 -0
- package/src/index.tsx +322 -66
- package/src/messages.ts +80 -4
- package/src/project-source-diff.css +10 -0
- package/src/project-source-diff.tsx +6 -0
- package/src/project-source-review.tsx +33 -5
- package/src/pull-request-panel.tsx +27 -6
- package/src/remote-operations.ts +1 -1
- package/src/repository.ts +22 -8
- package/src/review-composer.tsx +166 -0
- package/src/review-submit.ts +519 -0
- package/src/smart-http.ts +2 -2
- package/src/style.css +41 -0
- package/dist/objectSpread2-Dkd68nJH.js +0 -101
package/src/index.tsx
CHANGED
|
@@ -5,7 +5,11 @@ import {FileDiffView} from './file-diff-view.js';
|
|
|
5
5
|
import {findElement, ProjectChangeListView, ProjectElementView, projectChangeLabels, useProjectChanges} from './project-change-list.js';
|
|
6
6
|
import type {ChangedFile} from './project-change-model.js';
|
|
7
7
|
import {openRepository} from './repository.js';
|
|
8
|
-
import type {Repository, Snapshot, FileChange, FileDiff, ChangeGroup, HistoryCommit, HistoryCursor} from './repository.js';
|
|
8
|
+
import type {BranchRefs, Repository, Snapshot, FileChange, FileDiff, ChangeGroup, HistoryCommit, HistoryCursor} from './repository.js';
|
|
9
|
+
import {BranchButton, useBranchSnackbar} from './branch-button.js';
|
|
10
|
+
import {changeMarks} from './change-marks.js';
|
|
11
|
+
import type {ChangeMark} from './change-marks.js';
|
|
12
|
+
import type {BranchButtonState} from './branch-button.js';
|
|
9
13
|
import {HistoryGraph} from './history-graph-view.js';
|
|
10
14
|
import type {HistoryActionResult} from './history-graph-view.js';
|
|
11
15
|
import {labelsFor} from './messages.js';
|
|
@@ -34,11 +38,15 @@ export {openProjectComparison} from './project-comparison.js';
|
|
|
34
38
|
export {openProjectSourceTabs} from './project-source-tabs.js';
|
|
35
39
|
export type {ProjectSourceTabsFile, ProjectSourceTabsOptions} from './project-source-tabs.js';
|
|
36
40
|
export type {ProjectComparisonAction, ProjectComparisonOptions, ProjectComparisonSection} from './project-comparison.js';
|
|
37
|
-
export type {PullRequestBase, PullRequestFetch, PullRequestFiles, Repository, Snapshot, FileChange, FileDiff, HistoryCommit, HistoryCursor, HistoryPage, HistoryRef} from './repository.js';
|
|
41
|
+
export type {BranchRefs, PullRequestBase, PullRequestFetch, PullRequestFiles, Repository, Snapshot, FileChange, FileDiff, HistoryCommit, HistoryCursor, HistoryPage, HistoryRef} from './repository.js';
|
|
42
|
+
export {BranchButton} from './branch-button.js';
|
|
43
|
+
export type {BranchButtonActions, BranchButtonProps, BranchButtonState} from './branch-button.js';
|
|
38
44
|
export {HistoryGraph, laneColors} from './history-graph-view.js';
|
|
39
45
|
export type {HistoryActionResult, HistoryGraphProps} from './history-graph-view.js';
|
|
40
46
|
export {layoutHistory} from './history-graph.js';
|
|
41
47
|
export type {GraphCommit, GraphLayout, GraphRow, GraphSegment} from './history-graph.js';
|
|
48
|
+
export {changeMarks} from './change-marks.js';
|
|
49
|
+
export type {ChangeMark} from './change-marks.js';
|
|
42
50
|
export type {GitAuthor, LocalOperation} from './local-operations.js';
|
|
43
51
|
export {pullRequestBranch} from './local-operations.js';
|
|
44
52
|
|
|
@@ -77,7 +85,56 @@ export interface GitPanelRequest {
|
|
|
77
85
|
inactive?: boolean;
|
|
78
86
|
mount(element: HTMLElement, close: () => void): {requestClose(): void; dispose(): void};
|
|
79
87
|
}
|
|
80
|
-
|
|
88
|
+
type BranchOperation = Extract<LocalOperation, {kind: 'createBranch' | 'switchBranch'}>;
|
|
89
|
+
/** The panel's side of its entries: set by the panel as it renders, used by the branch buttons and `mountGitPanel`'s result. */
|
|
90
|
+
interface Controller {
|
|
91
|
+
open(locale: string): void;
|
|
92
|
+
refresh(automatic?: boolean): Promise<void>;
|
|
93
|
+
/** Asks for a repository, then opens the panel on it. */
|
|
94
|
+
choose(locale: string): Promise<void>;
|
|
95
|
+
changeBranch(operation: BranchOperation): Promise<HistoryActionResult>;
|
|
96
|
+
/** Reloads the project folder selected in the panel, as Reload Project does. */
|
|
97
|
+
reload(): void;
|
|
98
|
+
/** What the branch buttons show. */
|
|
99
|
+
branch: BranchButtonState;
|
|
100
|
+
/** The chosen repository and its last snapshot, published with `branch`. */
|
|
101
|
+
repository: Repository | null;
|
|
102
|
+
snapshot: Snapshot | null;
|
|
103
|
+
publish(state: BranchButtonState, repository: Repository | null, snapshot: Snapshot | null): void;
|
|
104
|
+
/** Follows `branch`. While anything follows it, the closed panel keeps watching the branches. */
|
|
105
|
+
follow(listener: () => void): () => void;
|
|
106
|
+
/** How many follow `branch`. */
|
|
107
|
+
followers(): number;
|
|
108
|
+
/** Set by the panel while it is mounted, to hear when `followers()` changes. */
|
|
109
|
+
onFollowers(count: number): void;
|
|
110
|
+
}
|
|
111
|
+
function createController(): Controller {
|
|
112
|
+
const listeners = new Set<() => void>();
|
|
113
|
+
const controller: Controller = {
|
|
114
|
+
open() {}, async refresh() {}, async choose() {}, async changeBranch() {return {done: false};}, reload() {},
|
|
115
|
+
branch: {repository: false, branch: undefined, head: null, branches: [], remoteBranches: [], writable: false, busy: false,
|
|
116
|
+
project: false},
|
|
117
|
+
repository: null,
|
|
118
|
+
snapshot: null,
|
|
119
|
+
publish(state, repository, snapshot) {
|
|
120
|
+
controller.branch = state;
|
|
121
|
+
controller.repository = repository;
|
|
122
|
+
controller.snapshot = snapshot;
|
|
123
|
+
for (const listener of [...listeners]) listener();
|
|
124
|
+
},
|
|
125
|
+
follow(listener) {
|
|
126
|
+
listeners.add(listener);
|
|
127
|
+
controller.onFollowers(listeners.size);
|
|
128
|
+
return () => {
|
|
129
|
+
listeners.delete(listener);
|
|
130
|
+
controller.onFollowers(listeners.size);
|
|
131
|
+
};
|
|
132
|
+
},
|
|
133
|
+
followers: () => listeners.size,
|
|
134
|
+
onFollowers() {}
|
|
135
|
+
};
|
|
136
|
+
return controller;
|
|
137
|
+
}
|
|
81
138
|
interface State {
|
|
82
139
|
repository: Repository | null;
|
|
83
140
|
snapshot: Snapshot | null;
|
|
@@ -94,6 +151,8 @@ interface State {
|
|
|
94
151
|
const initial: State = {repository: null, snapshot: null, project: null, browser: null,
|
|
95
152
|
changes: [], selected: null, diff: null, commit: null, error: null, busy: false};
|
|
96
153
|
const controllers = new WeakMap<object, Controller>();
|
|
154
|
+
// Branch buttons rendered before the panel is mounted, or after it is disposed, wait for the VM's controller here.
|
|
155
|
+
const registrations = new Set<() => void>();
|
|
97
156
|
type PickerWindow = Window & {showDirectoryPicker?: (options: {id: string; mode: 'readwrite'}) => Promise<FileSystemDirectoryHandle>};
|
|
98
157
|
type ObserverWindow = Window & {FileSystemObserver?: new (callback: () => void) => {
|
|
99
158
|
observe(handle: FileSystemDirectoryHandle, options: {recursive: boolean}): Promise<void>;
|
|
@@ -101,6 +160,53 @@ type ObserverWindow = Window & {FileSystemObserver?: new (callback: () => void)
|
|
|
101
160
|
}};
|
|
102
161
|
const watchInterval = 2000;
|
|
103
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Calls `check` every `watchInterval`, when the window regains focus and shortly after a File System Observer reports
|
|
165
|
+
* a change under `root`, one call at a time and never while the page is hidden. Returns the function that stops it.
|
|
166
|
+
*/
|
|
167
|
+
function watchRepository(document: Document, root: FileSystemDirectoryHandle, check: (stopped: () => boolean) => Promise<void>) {
|
|
168
|
+
const view = document.defaultView as ObserverWindow;
|
|
169
|
+
let stopped = false;
|
|
170
|
+
let checking = false;
|
|
171
|
+
const run = async () => {
|
|
172
|
+
if (stopped || checking || document.visibilityState === 'hidden') return;
|
|
173
|
+
checking = true;
|
|
174
|
+
try {
|
|
175
|
+
await check(() => stopped);
|
|
176
|
+
} catch {
|
|
177
|
+
// Entries can disappear while another tool writes. The next check reads them again.
|
|
178
|
+
} finally {
|
|
179
|
+
checking = false;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
let soon: number | undefined;
|
|
183
|
+
const schedule = () => {view.clearTimeout(soon); soon = view.setTimeout(() => void run(), 300);};
|
|
184
|
+
const interval = view.setInterval(() => void run(), watchInterval);
|
|
185
|
+
const onFocus = () => void run();
|
|
186
|
+
view.addEventListener('focus', onFocus);
|
|
187
|
+
document.addEventListener('visibilitychange', onFocus);
|
|
188
|
+
let observer: InstanceType<NonNullable<ObserverWindow['FileSystemObserver']>> | null = null;
|
|
189
|
+
try {
|
|
190
|
+
if (view.FileSystemObserver) {
|
|
191
|
+
observer = new view.FileSystemObserver(schedule);
|
|
192
|
+
observer.observe(root, {recursive: true}).catch(() => {});
|
|
193
|
+
}
|
|
194
|
+
} catch {
|
|
195
|
+
observer = null;
|
|
196
|
+
}
|
|
197
|
+
void run();
|
|
198
|
+
return () => {
|
|
199
|
+
stopped = true;
|
|
200
|
+
view.clearInterval(interval);
|
|
201
|
+
view.clearTimeout(soon);
|
|
202
|
+
view.removeEventListener('focus', onFocus);
|
|
203
|
+
document.removeEventListener('visibilitychange', onFocus);
|
|
204
|
+
observer?.disconnect();
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const sameRefs = (a: BranchRefs | null, b: BranchRefs) => !!a && a.branch === b.branch && a.head === b.head &&
|
|
208
|
+
a.branches.join('\n') === b.branches.join('\n') && a.remoteBranches.join('\n') === b.remoteBranches.join('\n');
|
|
209
|
+
|
|
104
210
|
function GitPanel({vm, document, host, controller}: {vm: object; document: Document; host: GitHost; controller: Controller}) {
|
|
105
211
|
const [visible, setVisible] = useState(false);
|
|
106
212
|
// A layout tab that holds the panel. The panel component stays mounted, so the repository survives closing the tab.
|
|
@@ -233,61 +339,48 @@ function GitPanel({vm, document, host, controller}: {vm: object; document: Docum
|
|
|
233
339
|
void controller.refresh();
|
|
234
340
|
}
|
|
235
341
|
}, [shown]);
|
|
342
|
+
// The branches the branch buttons show: those of the last snapshot, or newer ones read while the panel is not in view.
|
|
343
|
+
const [refs, setRefs] = useState<BranchRefs | null>(null);
|
|
344
|
+
useEffect(() => {
|
|
345
|
+
if (!state.repository) setRefs(null);
|
|
346
|
+
else if (state.snapshot) setRefs(refsOf(state.snapshot));
|
|
347
|
+
}, [state.repository, state.snapshot]);
|
|
348
|
+
const [followers, setFollowers] = useState(controller.followers());
|
|
349
|
+
useEffect(() => {
|
|
350
|
+
controller.onFollowers = setFollowers;
|
|
351
|
+
// A button may have started following before the panel was mounted.
|
|
352
|
+
setFollowers(controller.followers());
|
|
353
|
+
return () => {controller.onFollowers = () => {};};
|
|
354
|
+
}, []);
|
|
236
355
|
// While the panel is open, notice Git commands, agents and editors that change the repository outside the Editor.
|
|
356
|
+
// While it is out of view and a branch button follows it, read only the refs at the same moments, so the button
|
|
357
|
+
// follows branch changes; the panel reads everything again when it comes into view.
|
|
358
|
+
const following = followers > 0;
|
|
237
359
|
useEffect(() => {
|
|
238
360
|
const repository = state.repository;
|
|
239
|
-
if (!
|
|
240
|
-
|
|
241
|
-
let stopped = false;
|
|
242
|
-
let checking = false;
|
|
243
|
-
const check = async () => {
|
|
361
|
+
if (!repository || !shown && !following) return;
|
|
362
|
+
return watchRepository(document, repository.root, async stopped => {
|
|
244
363
|
const {snapshot} = current.current;
|
|
364
|
+
if (running.current || current.current.repository !== repository) return;
|
|
245
365
|
// A tab behind another one has no boxes; it reads again when it is shown.
|
|
246
|
-
const
|
|
247
|
-
if (
|
|
248
|
-
!
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (stopped || probe === 'locked' || probe === snapshot.probe || probe === handled.current ||
|
|
253
|
-
current.current.snapshot !== snapshot) return;
|
|
254
|
-
handled.current = probe;
|
|
255
|
-
const {selected, commit} = current.current;
|
|
256
|
-
// Keep an open diff, commit or review in place and ask for a refresh instead.
|
|
257
|
-
if (selected || commit || reviewing.current) {
|
|
258
|
-
setState(previous => previous.snapshot === snapshot ? {...previous, notice: labelsFor(locale).outdated} : previous);
|
|
259
|
-
} else await controller.refresh(true);
|
|
260
|
-
} catch {
|
|
261
|
-
// Entries can disappear while another tool writes. The next check reads them again.
|
|
262
|
-
} finally {
|
|
263
|
-
checking = false;
|
|
366
|
+
const inView = shown && !!dialog.current && dialog.current.getClientRects().length > 0;
|
|
367
|
+
if (!inView || !snapshot) {
|
|
368
|
+
if (!following) return;
|
|
369
|
+
const next = await repository.refs();
|
|
370
|
+
if (!stopped() && current.current.repository === repository) setRefs(previous => sameRefs(previous, next) ? previous : next);
|
|
371
|
+
return;
|
|
264
372
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
observer.observe(repository.root, {recursive: true}).catch(() => {});
|
|
277
|
-
}
|
|
278
|
-
} catch {
|
|
279
|
-
observer = null;
|
|
280
|
-
}
|
|
281
|
-
void check();
|
|
282
|
-
return () => {
|
|
283
|
-
stopped = true;
|
|
284
|
-
view.clearInterval(interval);
|
|
285
|
-
view.clearTimeout(soon);
|
|
286
|
-
view.removeEventListener('focus', onFocus);
|
|
287
|
-
document.removeEventListener('visibilitychange', onFocus);
|
|
288
|
-
observer?.disconnect();
|
|
289
|
-
};
|
|
290
|
-
}, [shown, state.repository, locale]);
|
|
373
|
+
const probe = await repository.probe(snapshot.scope);
|
|
374
|
+
if (stopped() || probe === 'locked' || probe === snapshot.probe || probe === handled.current ||
|
|
375
|
+
current.current.snapshot !== snapshot) return;
|
|
376
|
+
handled.current = probe;
|
|
377
|
+
const {selected, commit} = current.current;
|
|
378
|
+
// Keep an open diff, commit or review in place and ask for a refresh instead.
|
|
379
|
+
if (selected || commit || reviewing.current) {
|
|
380
|
+
setState(previous => previous.snapshot === snapshot ? {...previous, notice: labelsFor(locale).outdated} : previous);
|
|
381
|
+
} else await controller.refresh(true);
|
|
382
|
+
});
|
|
383
|
+
}, [shown, following, state.repository, locale]);
|
|
291
384
|
useEffect(() => {
|
|
292
385
|
if (!visible || slot) return;
|
|
293
386
|
const previous = document.activeElement as HTMLElement | null;
|
|
@@ -296,7 +389,7 @@ function GitPanel({vm, document, host, controller}: {vm: object; document: Docum
|
|
|
296
389
|
}, [visible, slot]);
|
|
297
390
|
const chooseRoot = async () => {
|
|
298
391
|
const view = document.defaultView as PickerWindow;
|
|
299
|
-
|
|
392
|
+
return run(async () => {
|
|
300
393
|
let root: FileSystemDirectoryHandle;
|
|
301
394
|
try {root = await view.showDirectoryPicker!({id: 'turbowarp-plus-git-root', mode: 'readwrite'});} catch (error) {
|
|
302
395
|
if ((error as DOMException).name === 'AbortError') return {};
|
|
@@ -371,7 +464,8 @@ function GitPanel({vm, document, host, controller}: {vm: object; document: Docum
|
|
|
371
464
|
const pullRequestConnection = useMemo(() => {
|
|
372
465
|
const settings = host.connectionSettings;
|
|
373
466
|
const getSession = settings?.getSession;
|
|
374
|
-
return getSession ? {getSession, connected: () => settings.getView().state.status === 'connected'
|
|
467
|
+
return getSession ? {getSession, connected: () => settings.getView().state.status === 'connected',
|
|
468
|
+
reviewGranted: () => settings.getView().review === true} : undefined;
|
|
375
469
|
}, [host]);
|
|
376
470
|
const [message, setMessage] = useState('');
|
|
377
471
|
const localOperation = (operation: LocalOperation, notice?: string) => {
|
|
@@ -394,7 +488,7 @@ function GitPanel({vm, document, host, controller}: {vm: object; document: Docum
|
|
|
394
488
|
};
|
|
395
489
|
const [newBranch, setNewBranch] = useState('');
|
|
396
490
|
const [switchAfterCreate, setSwitchAfterCreate] = useState(true);
|
|
397
|
-
const changeBranch = async (operation:
|
|
491
|
+
const changeBranch = async (operation: BranchOperation): Promise<HistoryActionResult> => {
|
|
398
492
|
const repository = current.current.repository;
|
|
399
493
|
const project = current.current.project;
|
|
400
494
|
const switching = operation.kind === 'switchBranch' || operation.checkout;
|
|
@@ -472,6 +566,22 @@ function GitPanel({vm, document, host, controller}: {vm: object; document: Docum
|
|
|
472
566
|
// One loader per selected change, so thumbnails read each asset once.
|
|
473
567
|
const loadAsset = useMemo(() => state.repository && state.snapshot && state.selected ?
|
|
474
568
|
state.repository.assetLoader(state.snapshot, state.selected) : undefined, [state.repository, state.snapshot, state.selected]);
|
|
569
|
+
controller.changeBranch = changeBranch;
|
|
570
|
+
controller.reload = () => {
|
|
571
|
+
if (current.current.project !== null) void openProject(current.current.project);
|
|
572
|
+
};
|
|
573
|
+
controller.choose = async language => {
|
|
574
|
+
setLocale(language);
|
|
575
|
+
const before = current.current.repository;
|
|
576
|
+
const result = await chooseRoot();
|
|
577
|
+
// The panel shows the chosen repository, or why it could not be read; a cancelled choice opens nothing.
|
|
578
|
+
if (!result.done && result.error || current.current.repository !== before) controller.open(language);
|
|
579
|
+
};
|
|
580
|
+
const branchState: BranchButtonState = {repository: !!state.repository, branch: refs?.branch, head: refs?.head ?? null,
|
|
581
|
+
branches: refs?.branches ?? [], remoteBranches: refs?.remoteBranches ?? [], writable: !!host.localWrite && !!refs, busy: state.busy,
|
|
582
|
+
project: state.project !== null};
|
|
583
|
+
const published = JSON.stringify(branchState);
|
|
584
|
+
useEffect(() => controller.publish(branchState, state.repository, state.snapshot), [published, state.repository, state.snapshot]);
|
|
475
585
|
if (!shown) return null;
|
|
476
586
|
const groups: ChangeGroup[] = state.commit ? ['commit'] : ['staged', 'unstaged', 'untracked'];
|
|
477
587
|
// Branches can be switched and created whenever writes are allowed and the repository has been read.
|
|
@@ -656,28 +766,174 @@ function upstreamText({upstream}: Snapshot, labels: ReturnType<typeof labelsFor>
|
|
|
656
766
|
}
|
|
657
767
|
}
|
|
658
768
|
|
|
769
|
+
const refsOf = ({branch, head, branches, remoteBranches}: Snapshot): BranchRefs => ({branch, head, branches, remoteBranches});
|
|
770
|
+
const announce = () => {for (const listener of [...registrations]) listener();};
|
|
771
|
+
|
|
659
772
|
export function mountGitPanel({vm, document, host}: {vm: object; document: Document; host: GitHost}) {
|
|
660
773
|
if (!(document.defaultView as PickerWindow)?.showDirectoryPicker) return null;
|
|
661
774
|
const root = document.createElement('div');
|
|
662
775
|
document.body.append(root);
|
|
663
|
-
const controller
|
|
776
|
+
const controller = createController();
|
|
664
777
|
controllers.set(vm, controller);
|
|
665
778
|
render(<GitPanel vm={vm} document={document} host={host} controller={controller}/>, root);
|
|
666
|
-
|
|
779
|
+
announce();
|
|
780
|
+
return {refresh: () => controller.refresh(), open: (locale: string) => controller.open(locale), dispose() {
|
|
667
781
|
if (controllers.get(vm) === controller) controllers.delete(vm);
|
|
668
782
|
void host.connectionSettings?.disconnect().catch(() => {});
|
|
669
783
|
unmountComponentAtNode(root);
|
|
670
784
|
root.remove();
|
|
785
|
+
announce();
|
|
671
786
|
}};
|
|
672
787
|
}
|
|
673
788
|
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
return
|
|
681
|
-
|
|
682
|
-
|
|
789
|
+
/** The branch button of the panel mounted for `vm`, or nothing while no panel is mounted for it. */
|
|
790
|
+
/** Calls `listener` with the controller of the panel mounted for `vm`, now and whenever it is mounted or disposed. */
|
|
791
|
+
function findController(vm: object, listener: (controller: Controller | undefined) => void) {
|
|
792
|
+
const update = () => listener(controllers.get(vm));
|
|
793
|
+
registrations.add(update);
|
|
794
|
+
update();
|
|
795
|
+
return () => {registrations.delete(update);};
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/** The panel mounted for `vm` and what it last published. Following it keeps the closed panel watching the branches. */
|
|
799
|
+
function useController(vm: object) {
|
|
800
|
+
const [controller, setController] = useState(() => controllers.get(vm));
|
|
801
|
+
useEffect(() => findController(vm, setController), [vm]);
|
|
802
|
+
const [state, setState] = useState(controller?.branch);
|
|
803
|
+
useEffect(() => {
|
|
804
|
+
if (!controller) return;
|
|
805
|
+
const update = () => setState(controller.branch);
|
|
806
|
+
update();
|
|
807
|
+
return controller.follow(update);
|
|
808
|
+
}, [controller]);
|
|
809
|
+
return {controller, state};
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function BranchEntry({vm, locale}: {vm: object; locale: string}) {
|
|
813
|
+
const {controller, state} = useController(vm);
|
|
814
|
+
if (!controller || !state) return null;
|
|
815
|
+
return <BranchButton state={state} locale={locale} open={() => controller.open(locale)} choose={() => void controller.choose(locale)}
|
|
816
|
+
reload={() => controller.reload()}
|
|
817
|
+
switchBranch={name => controller.changeBranch({kind: 'switchBranch', name})}
|
|
818
|
+
createBranch={(name, checkout) => controller.changeBranch({kind: 'createBranch', name, checkout})}
|
|
819
|
+
createFromRemote={name => controller.changeBranch({kind: 'createBranch', name, fromRemote: true, checkout: true})}/>;
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
/**
|
|
823
|
+
* Renders the button for the host's menu bar that shows the current branch of the panel mounted for `vm` and opens the
|
|
824
|
+
* menu of branches, the panel and the repository choice. It renders nothing while no panel is mounted for `vm`, such as
|
|
825
|
+
* when the host does not offer local Git, and shares the panel's repository, operations and checks.
|
|
826
|
+
*/
|
|
827
|
+
export function renderBranchButton(createElement: (component: any, props: any, ...children: any[]) => any,
|
|
828
|
+
{vm, locale = 'en'}: {vm: object; locale?: string}) {
|
|
829
|
+
return createElement(BranchEntry, {vm, locale});
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
type History = {commits: HistoryCommit[]; cursor: HistoryCursor | null; loading: boolean; failed: boolean};
|
|
833
|
+
|
|
834
|
+
/** The history of the panel's repository, read again whenever its branches move. */
|
|
835
|
+
function HistoryEntry({vm, locale}: {vm: object; locale: string}) {
|
|
836
|
+
const {controller, state} = useController(vm);
|
|
837
|
+
const labels = labelsFor(locale);
|
|
838
|
+
const repository = state && controller?.repository || null;
|
|
839
|
+
const [history, setHistory] = useState<History>({commits: [], cursor: null, loading: false, failed: false});
|
|
840
|
+
const [selected, setSelected] = useState<string | null>(null);
|
|
841
|
+
const ticket = useRef(0);
|
|
842
|
+
const root = useRef<HTMLDivElement>(null);
|
|
843
|
+
const refs = state ? [state.head, ...state.branches, '', ...state.remoteBranches].join('\n') : '';
|
|
844
|
+
useEffect(() => {
|
|
845
|
+
const current = ++ticket.current;
|
|
846
|
+
if (!repository) {
|
|
847
|
+
setHistory({commits: [], cursor: null, loading: false, failed: false});
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
// The rows read before stay until the new page arrives, so focus in the list is kept.
|
|
851
|
+
setHistory(previous => ({...previous, cursor: null, loading: true, failed: false}));
|
|
852
|
+
repository.history().then(page => {
|
|
853
|
+
if (ticket.current === current) setHistory({commits: page.commits, cursor: page.cursor, loading: false, failed: false});
|
|
854
|
+
}, () => {
|
|
855
|
+
if (ticket.current === current) setHistory({commits: [], cursor: null, loading: false, failed: true});
|
|
856
|
+
});
|
|
857
|
+
}, [repository, refs]);
|
|
858
|
+
const showMore = () => {
|
|
859
|
+
if (!repository || !history.cursor || history.loading) return;
|
|
860
|
+
const current = ticket.current;
|
|
861
|
+
setHistory(previous => ({...previous, loading: true}));
|
|
862
|
+
repository.history({after: history.cursor}).then(page => {
|
|
863
|
+
if (ticket.current === current) setHistory(previous => ({commits: [...previous.commits, ...page.commits],
|
|
864
|
+
cursor: page.cursor, loading: false, failed: false}));
|
|
865
|
+
}, () => {
|
|
866
|
+
if (ticket.current === current) setHistory(previous => ({...previous, cursor: null, loading: false, failed: true}));
|
|
867
|
+
});
|
|
868
|
+
};
|
|
869
|
+
const {report, element: snackbar} = useBranchSnackbar(locale, !!state?.project, {
|
|
870
|
+
open: () => controller?.open(locale), reload: () => controller?.reload()
|
|
871
|
+
}, () => root.current?.querySelector<HTMLElement>('[tabindex="0"]')?.focus({preventScroll: true}));
|
|
872
|
+
if (!controller || !state) return null;
|
|
873
|
+
if (!repository) return <div className="twgit-history-view" ref={root}>
|
|
874
|
+
<p>{labels.historyViewEmpty}</p>
|
|
875
|
+
<button type="button" onClick={() => void controller.choose(locale)}>{labels.choose}</button>
|
|
876
|
+
</div>;
|
|
877
|
+
const change = async (operation: BranchOperation) => {
|
|
878
|
+
const result = await controller.changeBranch(operation);
|
|
879
|
+
report(result, operation.kind === 'switchBranch' || !!operation.checkout);
|
|
880
|
+
return result;
|
|
881
|
+
};
|
|
882
|
+
return <div className="twgit-history-view" ref={root}>
|
|
883
|
+
<HistoryGraph commits={history.commits} selected={selected} locale={locale} busy={state.busy} onSelect={setSelected}
|
|
884
|
+
hasMore={!!history.cursor} loadingMore={history.loading} onShowMore={showMore} localBranches={state.branches}
|
|
885
|
+
{...state.writable ? {
|
|
886
|
+
// A refused create stays in the graph's form with its reason; only a done one is told in the snackbar.
|
|
887
|
+
onCreateBranch: async (oid: string, name: string, checkout: boolean) => {
|
|
888
|
+
const result = await controller.changeBranch({kind: 'createBranch', name, at: oid, checkout});
|
|
889
|
+
if (result.done) report(result, checkout);
|
|
890
|
+
return result;
|
|
891
|
+
},
|
|
892
|
+
onSwitchBranch: (name: string) => change({kind: 'switchBranch', name}),
|
|
893
|
+
onCreateFromRemote: (name: string) => change({kind: 'createBranch', name, fromRemote: true, checkout: true})
|
|
894
|
+
} : {}}/>
|
|
895
|
+
{history.loading && !history.commits.length && <p role="status">{labels.historyLoading}</p>}
|
|
896
|
+
{history.failed && <p>{labels.historyFailed}</p>}
|
|
897
|
+
{snackbar}
|
|
898
|
+
</div>;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* Renders the history graph of the repository chosen in the panel mounted for `vm`, for a side panel of the host. It
|
|
903
|
+
* follows branch changes made anywhere, offers the graph's branch actions when the panel allows writes and tells their
|
|
904
|
+
* results in the branch button's snackbar. Before a repository is chosen it offers to choose one; it renders nothing
|
|
905
|
+
* while no panel is mounted for `vm`.
|
|
906
|
+
*/
|
|
907
|
+
export function renderHistoryView(createElement: (component: any, props: any, ...children: any[]) => any,
|
|
908
|
+
{vm, locale = 'en'}: {vm: object; locale?: string}) {
|
|
909
|
+
return createElement(HistoryEntry, {vm, locale});
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
/**
|
|
913
|
+
* Follows the Git status of the files under `directory`, as the panel mounted for `vm` last read it, and calls
|
|
914
|
+
* `listener` with their marks by path relative to `directory`. The marks are empty while no repository is chosen or
|
|
915
|
+
* `directory` is outside it. Returns the function that stops following.
|
|
916
|
+
*/
|
|
917
|
+
export function followChangeMarks(vm: object, directory: FileSystemDirectoryHandle,
|
|
918
|
+
listener: (marks: ReadonlyMap<string, ChangeMark>) => void) {
|
|
919
|
+
let stopped = false;
|
|
920
|
+
let read: Snapshot | null | undefined;
|
|
921
|
+
let unfollow = () => {};
|
|
922
|
+
const update = async (controller: Controller | undefined) => {
|
|
923
|
+
const {repository, snapshot} = controller ?? {repository: null, snapshot: null};
|
|
924
|
+
if (snapshot === read) return;
|
|
925
|
+
read = snapshot;
|
|
926
|
+
const path = repository && snapshot ? await repository.root.resolve(directory).catch(() => null) : null;
|
|
927
|
+
if (!stopped && read === snapshot) listener(path ? changeMarks(snapshot!.changes, path.join('/')) : new Map());
|
|
928
|
+
};
|
|
929
|
+
const unregister = findController(vm, controller => {
|
|
930
|
+
unfollow();
|
|
931
|
+
unfollow = controller ? controller.follow(() => void update(controller)) : () => {};
|
|
932
|
+
void update(controller);
|
|
933
|
+
});
|
|
934
|
+
return () => {
|
|
935
|
+
stopped = true;
|
|
936
|
+
unregister();
|
|
937
|
+
unfollow();
|
|
938
|
+
};
|
|
683
939
|
}
|
package/src/messages.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const messages = {
|
|
2
2
|
en: {
|
|
3
3
|
pullRequests: 'Pull requests', localChanges: 'Local changes',
|
|
4
|
-
title: 'Local Git',
|
|
4
|
+
title: 'Local Git', close: 'Close', choose: 'Choose Repository…', refresh: 'Refresh',
|
|
5
5
|
empty: 'Choose an existing Git repository to inspect its files and history.',
|
|
6
6
|
repository: 'Repository', project: 'Project folder', none: 'No project selected', root: 'Repository root',
|
|
7
7
|
open: 'Open This Project', reload: 'Reload Project', up: 'Parent folder', browse: 'Choose a project inside this repository',
|
|
@@ -54,11 +54,13 @@ export const messages = {
|
|
|
54
54
|
historyMore: 'Show more', historyLoading: 'Reading records…', historyFailed: 'Could not read the records.',
|
|
55
55
|
historyActions: 'Actions', historyCreateHere: 'Create branch here…', historySwitch: 'Switch to {name}',
|
|
56
56
|
historyCreateFromRemote: 'Create and switch to {name}', historyCopy: 'Copy commit ID', historyCopied: 'Copied the commit ID.',
|
|
57
|
-
historyCopyFailed: 'Could not copy the commit ID.', historyRefs: 'Branches', cancel: 'Cancel'
|
|
57
|
+
historyCopyFailed: 'Could not copy the commit ID.', historyRefs: 'Branches', cancel: 'Cancel',
|
|
58
|
+
branchMenu: 'Branches', openPanel: 'Open Local Git', newBranchItem: 'New Branch…', newBranchTitle: 'New Branch',
|
|
59
|
+
switchedShort: 'Switched branches.', historyViewEmpty: 'Choose a Git repository to show its history here.'
|
|
58
60
|
},
|
|
59
61
|
ja: {
|
|
60
62
|
pullRequests: 'プルリクエスト', localChanges: 'ローカルの変更',
|
|
61
|
-
title: 'ローカル Git',
|
|
63
|
+
title: 'ローカル Git', close: '閉じる', choose: 'リポジトリを選択…', refresh: '再取得',
|
|
62
64
|
empty: '既存の Git リポジトリを選び、ファイルの状態と履歴を確認できます。',
|
|
63
65
|
repository: 'リポジトリ', project: '作品フォルダ', none: '作品は未選択', root: 'リポジトリルート',
|
|
64
66
|
open: 'この作品を開く', reload: '作品を再読込', up: '上のフォルダ', browse: 'リポジトリ内の作品を選択',
|
|
@@ -111,8 +113,82 @@ export const messages = {
|
|
|
111
113
|
historyMore: 'さらに表示', historyLoading: '記録を読込中…', historyFailed: '記録を読み込めませんでした。',
|
|
112
114
|
historyActions: '操作', historyCreateHere: 'ここからブランチを作る…', historySwitch: '{name} に切り替える',
|
|
113
115
|
historyCreateFromRemote: '{name} を作って切り替える', historyCopy: 'コミットの ID をコピー', historyCopied: 'コミットの ID をコピーしました。',
|
|
114
|
-
historyCopyFailed: 'コミットの ID をコピーできませんでした。', historyRefs: 'ブランチ', cancel: 'キャンセル'
|
|
116
|
+
historyCopyFailed: 'コミットの ID をコピーできませんでした。', historyRefs: 'ブランチ', cancel: 'キャンセル',
|
|
117
|
+
branchMenu: 'ブランチ', openPanel: 'ローカル Git を開く', newBranchItem: '新しいブランチ…', newBranchTitle: '新しいブランチ',
|
|
118
|
+
switchedShort: 'ブランチを切り替えました。', historyViewEmpty: 'Git リポジトリを選ぶと、ここに履歴を表示します。'
|
|
115
119
|
}
|
|
116
120
|
};
|
|
117
121
|
export type Labels = typeof messages.en;
|
|
118
122
|
export const labelsFor = (locale: string): Labels => locale.startsWith('ja') ? messages.ja : messages.en;
|
|
123
|
+
|
|
124
|
+
/** The words of sending a pull request review through the GitHub connection. */
|
|
125
|
+
export const reviewMessages = {
|
|
126
|
+
en: {
|
|
127
|
+
composer: 'Send a review', overall: 'Overall comment', event: 'Result',
|
|
128
|
+
COMMENT: 'Comment', APPROVE: 'Approve', REQUEST_CHANGES: 'Request changes',
|
|
129
|
+
scriptComment: 'Comment on this code', commented: 'Commented',
|
|
130
|
+
summaryThreads: 'Code comments sent as threads on their project.source.json: {threads}. Code comments in the review body: {body}.',
|
|
131
|
+
summaryBody: 'This connection sends only the review body: {body} code comments go in it.',
|
|
132
|
+
summaryNone: 'No code comments yet. Write them under each code change.',
|
|
133
|
+
send: 'Send Review', sending: 'Sending the review…', checking: 'Checking GitHub…',
|
|
134
|
+
sendAgain: 'Send Again', check: 'Check Whether It Was Sent', discard: 'Discard the Pending Review',
|
|
135
|
+
forget: 'Stop Tracking This Submission', useVersion: 'Review This Version',
|
|
136
|
+
sent: 'Sent the review.', openReview: 'Open the review on GitHub', openPullRequest: 'Open the pull request on GitHub',
|
|
137
|
+
dismiss: 'Dismiss', discarded: 'Discarded the pending review. Your words are kept.',
|
|
138
|
+
notStored: 'Browser storage is unavailable, so this draft is kept only while the panel is open.',
|
|
139
|
+
headChanged: 'The pull request changed after you started this review. The review is sent for the version shown.',
|
|
140
|
+
unsupported: 'This GitHub connection cannot send reviews.',
|
|
141
|
+
needsGrant: 'To send a review, disconnect and connect again with “Allow sending reviews”.',
|
|
142
|
+
allowReview: 'Allow sending reviews',
|
|
143
|
+
PENDING_REVIEW_EXISTS: 'You already have a pending review on this pull request on GitHub. Submit or discard it on GitHub, then send again.',
|
|
144
|
+
PENDING_REVIEW_LEFT: 'A pending review remains on GitHub. Only you can see it. Discard it here, or on GitHub with “Finish your review”.',
|
|
145
|
+
REVIEW_OUTCOME_UNKNOWN: 'It is unknown whether GitHub received the review. Check before sending again.',
|
|
146
|
+
pending: 'The review waits on GitHub with its code comments but is not published. Change it and send again, or discard it.',
|
|
147
|
+
REVIEW_REJECTED: 'GitHub refused the review, for example an approval of your own pull request.',
|
|
148
|
+
REVIEW_THREAD_REJECTED: 'GitHub refused a comment on a project file, so nothing was published. Comments on that file now go in the review body. Send again.',
|
|
149
|
+
RATE_LIMITED: 'The GitHub rate limit was reached. Try again in {seconds} seconds.',
|
|
150
|
+
EMPTY_REVIEW: 'Write an overall comment (needed to request changes) or a comment on the code.',
|
|
151
|
+
TOO_LONG: 'The review is longer than GitHub accepts (65,536 characters).',
|
|
152
|
+
closed: 'GitHub has no such review: it was not published, or it was discarded. You can send it again.',
|
|
153
|
+
lost: 'The connection no longer knows this submission. Check on GitHub whether the review was published ({count} of your reviews on this version found).',
|
|
154
|
+
session: 'Connect to GitHub again to send the review.',
|
|
155
|
+
FORBIDDEN: 'The GitHub connection is not allowed to send reviews to this repository.',
|
|
156
|
+
GITHUB_API_UNAVAILABLE: 'The connection cannot add code comments as threads. They now go in the review body: check the summary and send again.',
|
|
157
|
+
failed: 'Could not send the review. Nothing was published. Try again.'
|
|
158
|
+
},
|
|
159
|
+
ja: {
|
|
160
|
+
composer: 'レビューを送る', overall: '全体のコメント', event: '結果',
|
|
161
|
+
COMMENT: 'コメント', APPROVE: '承認', REQUEST_CHANGES: '修正を依頼',
|
|
162
|
+
scriptComment: 'このコードへのコメント', commented: 'コメントあり',
|
|
163
|
+
summaryThreads: 'コードへのコメントのうち {threads} 件をその project.source.json へのスレッドとして、{body} 件をレビュー本文に入れて送ります。',
|
|
164
|
+
summaryBody: 'この接続ではレビュー本文だけを送れます。コードへのコメント {body} 件を本文に入れます。',
|
|
165
|
+
summaryNone: 'コードへのコメントはまだありません。各コードの変更の下に書けます。',
|
|
166
|
+
send: 'レビューを送信', sending: 'レビューを送信中…', checking: 'GitHub で確かめています…',
|
|
167
|
+
sendAgain: 'もう一度送る', check: '送信されたか確かめる', discard: '保留中のレビューを破棄',
|
|
168
|
+
forget: 'この送信の記録を消す', useVersion: 'この版をレビューする',
|
|
169
|
+
sent: 'レビューを送りました。', openReview: 'GitHub でレビューを開く', openPullRequest: 'GitHub で PR を開く',
|
|
170
|
+
dismiss: '閉じる', discarded: '保留中のレビューを破棄しました。書いた内容は残っています。',
|
|
171
|
+
notStored: 'ブラウザーのストレージを使えないため、この下書きはパネルを開いている間だけ残ります。',
|
|
172
|
+
headChanged: 'レビューを書き始めた後に PR が更新されました。レビューは表示中の版に対して送ります。',
|
|
173
|
+
unsupported: 'この GitHub 接続ではレビューを送れません。',
|
|
174
|
+
needsGrant: 'レビューを送るには、切断して「レビューの送信を許可する」を付けて接続し直してください。',
|
|
175
|
+
allowReview: 'レビューの送信を許可する',
|
|
176
|
+
PENDING_REVIEW_EXISTS: 'この PR に、GitHub で書きかけの保留中のレビューがあります。GitHub で送るか破棄してから、もう一度送ってください。',
|
|
177
|
+
PENDING_REVIEW_LEFT: 'GitHub に保留中のレビューが残っています。あなたにしか見えません。ここで破棄するか、GitHub の「Finish your review」から破棄してください。',
|
|
178
|
+
REVIEW_OUTCOME_UNKNOWN: 'GitHub がレビューを受け取ったかわかりません。もう一度送る前に確かめてください。',
|
|
179
|
+
pending: 'レビューはコードへのコメント付きで GitHub に保留中ですが、公開されていません。直してもう一度送るか、破棄してください。',
|
|
180
|
+
REVIEW_REJECTED: 'GitHub がレビューを拒みました(自分の PR の承認など)。',
|
|
181
|
+
REVIEW_THREAD_REJECTED: 'GitHub が作品ファイルへのコメントを拒んだため、何も公開していません。そのファイルへのコメントはレビュー本文に入れます。もう一度送ってください。',
|
|
182
|
+
RATE_LIMITED: 'GitHub の利用上限に達しました。{seconds} 秒後に再試行してください。',
|
|
183
|
+
EMPTY_REVIEW: '全体のコメント(修正の依頼では必須)か、コードへのコメントを書いてください。',
|
|
184
|
+
TOO_LONG: 'レビューが GitHub の上限(65,536 文字)を超えています。',
|
|
185
|
+
closed: 'GitHub にそのレビューはありません。公開されなかったか、破棄されています。もう一度送れます。',
|
|
186
|
+
lost: '接続がこの送信の記録を持っていません。レビューが公開されたかを GitHub で確かめてください(この版へのあなたのレビュー {count} 件)。',
|
|
187
|
+
session: 'レビューを送るには、GitHub にもう一度接続してください。',
|
|
188
|
+
FORBIDDEN: 'GitHub 接続に、このリポジトリへレビューを送る権限がありません。',
|
|
189
|
+
GITHUB_API_UNAVAILABLE: 'この接続ではコードへのコメントをスレッドにできません。コメントはレビュー本文に入れます。件数を確かめて、もう一度送ってください。',
|
|
190
|
+
failed: 'レビューを送れませんでした。何も公開していません。再試行してください。'
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
export type ReviewLabels = typeof reviewMessages.en;
|
|
194
|
+
export const reviewLabelsFor = (locale: string): ReviewLabels => locale.startsWith('ja') ? reviewMessages.ja : reviewMessages.en;
|
|
@@ -120,3 +120,13 @@
|
|
|
120
120
|
.twgit-review-target-status { font-weight: 600; }
|
|
121
121
|
.twgit-review-asset { display: flex; align-items: center; gap: 8px; }
|
|
122
122
|
.twgit-review-asset img { width: 32px; height: 32px; object-fit: contain; }
|
|
123
|
+
.twgit-review-field { display: grid; gap: 4px; }
|
|
124
|
+
.twgit-review-field textarea { font: inherit; resize: vertical; min-height: 3em; }
|
|
125
|
+
.twgit-review-comment { padding: 0 12px 10px; }
|
|
126
|
+
.twgit-review-composer { display: grid; gap: 10px; margin-top: 14px; padding: 12px 14px; border: 1px solid var(--twgit-review-line, color-mix(in srgb, CanvasText 18%, Canvas)); border-radius: 8px; }
|
|
127
|
+
.twgit-review-composer h3 { margin: 0; }
|
|
128
|
+
.twgit-review-events { display: flex; flex-wrap: wrap; gap: 6px 16px; margin: 0; padding: 6px 10px; border: 1px solid var(--twgit-review-line, color-mix(in srgb, CanvasText 18%, Canvas)); border-radius: 6px; }
|
|
129
|
+
.twgit-review-summary, .twgit-review-note, .twgit-review-state { margin: 0; }
|
|
130
|
+
.twgit-review-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
|
|
131
|
+
.twgit-review-result { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; margin: 0; }
|
|
132
|
+
.twgit-review-result p { margin: 0; }
|
|
@@ -8,6 +8,12 @@ import type {LocationStatus} from './block-diff-preview.js';
|
|
|
8
8
|
import './project-source-diff.css';
|
|
9
9
|
|
|
10
10
|
export {createGitHubPullRequests, gitHubRepositoryFromOrigin} from './github-pull-requests.js';
|
|
11
|
+
export {createGitHubReviewApi, GitHubReviewError, isReviewThreadPath} from './github-review-api.js';
|
|
12
|
+
export type {GitHubReviewApi, GitHubReviewApiOptions, GitHubReviewErrorCode, ReviewContext, ReviewEvent} from './github-review-api.js';
|
|
13
|
+
export {checkReview, discardReview, emptyReviewDraft, forgetReviewSubmission, planReview, probeReviewRoutes, readReviewDraft,
|
|
14
|
+
reviewBody, reviewProblem, reviewRoute, sendReview, subscribeReviewDraft, writeReviewDraft} from './review-submit.js';
|
|
15
|
+
export {PullRequestReviewComposer} from './review-composer.js';
|
|
16
|
+
export type {ReviewComment, ReviewDraft, ReviewPlan, ReviewProblem, ReviewResult, ReviewSubmission, ReviewTarget} from './review-submit.js';
|
|
11
17
|
export {ProjectSourceComparison, ProjectSourceReview} from './project-source-review.js';
|
|
12
18
|
export {ProjectComparisonDialog, openProjectComparison} from './project-comparison.js';
|
|
13
19
|
export {openProjectSourceTabs} from './project-source-tabs.js';
|