@kubohiroya/turbowarp-plus-editor-git 0.18.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.
Files changed (42) hide show
  1. package/README.ja.md +10 -2
  2. package/README.md +10 -2
  3. package/dist/branch-button.d.ts +15 -0
  4. package/dist/change-marks.d.ts +8 -0
  5. package/dist/connection-settings.d.ts +3 -1
  6. package/dist/github-pull-requests.d.ts +2 -2
  7. package/dist/github-review-api.d.ts +112 -0
  8. package/dist/index.d.ts +19 -0
  9. package/dist/index.js +377 -268
  10. package/dist/messages.d.ts +101 -0
  11. package/dist/{project-asset-paths-BzwVf9HE.js → project-asset-paths-DNaoWs3E.js} +1 -1
  12. package/dist/{project-comparison-BXOQcZ73.js → project-comparison-B_3QZ6M-.js} +1414 -33
  13. package/dist/project-source-diff.d.ts +5 -0
  14. package/dist/project-source-diff.js +4 -4
  15. package/dist/project-source-review.d.ts +6 -1
  16. package/dist/pull-request-panel.d.ts +2 -0
  17. package/dist/remote-operations.d.ts +1 -1
  18. package/dist/{repository-ByKRBftJ.js → repository-DXRZEtYk.js} +2 -3
  19. package/dist/repository.js +1 -1
  20. package/dist/review-composer.d.ts +23 -0
  21. package/dist/review-submit.d.ts +144 -0
  22. package/dist/{smart-http-BmHEkE7K.js → smart-http-eARzlXHO.js} +101 -2
  23. package/dist/smart-http.d.ts +2 -0
  24. package/dist/smart-http.js +1 -1
  25. package/dist/style.css +23 -0
  26. package/package.json +1 -1
  27. package/src/branch-button.tsx +79 -6
  28. package/src/change-marks.ts +22 -0
  29. package/src/connection-settings.tsx +9 -2
  30. package/src/github-review-api.ts +298 -0
  31. package/src/index.tsx +153 -15
  32. package/src/messages.ts +76 -2
  33. package/src/project-source-diff.css +10 -0
  34. package/src/project-source-diff.tsx +6 -0
  35. package/src/project-source-review.tsx +33 -5
  36. package/src/pull-request-panel.tsx +27 -6
  37. package/src/remote-operations.ts +1 -1
  38. package/src/review-composer.tsx +166 -0
  39. package/src/review-submit.ts +519 -0
  40. package/src/smart-http.ts +2 -2
  41. package/src/style.css +13 -0
  42. package/dist/objectSpread2-Dkd68nJH.js +0 -101
@@ -3,6 +3,11 @@ import type { ProjectBlockLocator } from './block-diff-location.js';
3
3
  import type { ProjectBlockRenderer } from './scratch-block-diff-renderer.js';
4
4
  import './project-source-diff.css';
5
5
  export { createGitHubPullRequests, gitHubRepositoryFromOrigin } from './github-pull-requests.js';
6
+ export { createGitHubReviewApi, GitHubReviewError, isReviewThreadPath } from './github-review-api.js';
7
+ export type { GitHubReviewApi, GitHubReviewApiOptions, GitHubReviewErrorCode, ReviewContext, ReviewEvent } from './github-review-api.js';
8
+ export { checkReview, discardReview, emptyReviewDraft, forgetReviewSubmission, planReview, probeReviewRoutes, readReviewDraft, reviewBody, reviewProblem, reviewRoute, sendReview, subscribeReviewDraft, writeReviewDraft } from './review-submit.js';
9
+ export { PullRequestReviewComposer } from './review-composer.js';
10
+ export type { ReviewComment, ReviewDraft, ReviewPlan, ReviewProblem, ReviewResult, ReviewSubmission, ReviewTarget } from './review-submit.js';
6
11
  export { ProjectSourceComparison, ProjectSourceReview } from './project-source-review.js';
7
12
  export { ProjectComparisonDialog, openProjectComparison } from './project-comparison.js';
8
13
  export { openProjectSourceTabs } from './project-source-tabs.js';
@@ -1,6 +1,6 @@
1
- import { a as compareProjectBlocks, o as createProjectSourceDiff, u as readProjectSourceDiff } from "./project-asset-paths-BzwVf9HE.js";
2
- import { _ as usePreviewSync, a as openProjectSourceTabs, c as gitHubRepositoryFromOrigin, d as assetFileName, f as organizeProjectChanges, g as createPreviewSync, h as BlockPreview, i as createScratchBlocksDiffLocator, l as ProjectSourceComparison, m as compareProjectForReview, n as openProjectComparison, p as projectPropertyLabel, r as createScratchBlocksDiffRenderer, s as createGitHubPullRequests, t as ProjectComparisonDialog, u as ProjectSourceReview, v as currentCostume } from "./project-comparison-BXOQcZ73.js";
3
- import { n as init_objectSpread2, t as _objectSpread2 } from "./objectSpread2-Dkd68nJH.js";
1
+ import { a as compareProjectBlocks, o as createProjectSourceDiff, u as readProjectSourceDiff } from "./project-asset-paths-DNaoWs3E.js";
2
+ import { A as assetFileName, C as subscribeReviewDraft, D as GitHubReviewError, F as createPreviewSync, I as usePreviewSync, L as currentCostume, M as projectPropertyLabel, N as compareProjectForReview, O as createGitHubReviewApi, P as BlockPreview, S as sendReview, _ as probeReviewRoutes, a as openProjectSourceTabs, b as reviewProblem, c as gitHubRepositoryFromOrigin, d as PullRequestReviewComposer, f as checkReview, g as planReview, h as forgetReviewSubmission, i as createScratchBlocksDiffLocator, j as organizeProjectChanges, k as isReviewThreadPath, l as ProjectSourceComparison, m as emptyReviewDraft, n as openProjectComparison, p as discardReview, r as createScratchBlocksDiffRenderer, s as createGitHubPullRequests, t as ProjectComparisonDialog, u as ProjectSourceReview, v as readReviewDraft, w as writeReviewDraft, x as reviewRoute, y as reviewBody } from "./project-comparison-B_3QZ6M-.js";
3
+ import { l as _objectSpread2, u as init_objectSpread2 } from "./smart-http-eARzlXHO.js";
4
4
  import React, { useEffect, useMemo, useRef, useState } from "react";
5
5
  // src/project-source-diff.tsx
6
6
  init_objectSpread2();
@@ -170,4 +170,4 @@ function ProjectSourceBlockDiff({ patch, beforeSource, renderBlock, locateBlock,
170
170
  })
171
171
  }, labels.more));
172
172
  }
173
- export { ProjectComparisonDialog, ProjectSourceBlockDiff, ProjectSourceComparison, ProjectSourceReview, assetFileName, compareProjectForReview, createGitHubPullRequests, createPreviewSync, createProjectSourceDiff, createScratchBlocksDiffLocator, createScratchBlocksDiffRenderer, currentCostume, gitHubRepositoryFromOrigin, openProjectComparison, openProjectSourceTabs, organizeProjectChanges, projectPropertyLabel };
173
+ export { GitHubReviewError, ProjectComparisonDialog, ProjectSourceBlockDiff, ProjectSourceComparison, ProjectSourceReview, PullRequestReviewComposer, assetFileName, checkReview, compareProjectForReview, createGitHubPullRequests, createGitHubReviewApi, createPreviewSync, createProjectSourceDiff, createScratchBlocksDiffLocator, createScratchBlocksDiffRenderer, currentCostume, discardReview, emptyReviewDraft, forgetReviewSubmission, gitHubRepositoryFromOrigin, isReviewThreadPath, openProjectComparison, openProjectSourceTabs, organizeProjectChanges, planReview, probeReviewRoutes, projectPropertyLabel, readReviewDraft, reviewBody, reviewProblem, reviewRoute, sendReview, subscribeReviewDraft, writeReviewDraft };
@@ -31,6 +31,11 @@ export interface ProjectSourceReviewProps {
31
31
  * the content of each script, so a mark stays until that script changes. Without it, marks last while the review is open.
32
32
  */
33
33
  rememberAs?: string;
34
+ /**
35
+ * Offers a comment field under each code change, kept in the review draft of this name (such as `owner/repo#42`) and
36
+ * sent with the review.
37
+ */
38
+ commentAs?: string;
34
39
  }
35
40
  export interface ProjectSourceComparisonProps {
36
41
  /** The earlier project.source.json, or `null` when the file did not exist yet. */
@@ -53,7 +58,7 @@ export interface ProjectSourceComparisonProps {
53
58
  }
54
59
  /** Compares two versions of a project.source.json by stage, sprite and code, as the pull request review does. */
55
60
  export declare function ProjectSourceComparison({ before, after, renderBlock, locale, fallback, loadAsset, locateBlock, target }: ProjectSourceComparisonProps): React.JSX.Element;
56
- export declare function ProjectSourceReview({ title, description, verification, files, renderBlock, locale, rememberAs }: ProjectSourceReviewProps): React.JSX.Element;
61
+ export declare function ProjectSourceReview({ title, description, verification, files, renderBlock, locale, rememberAs, commentAs }: ProjectSourceReviewProps): React.JSX.Element;
57
62
  /** The words of one tab of a project comparison opened as tabs. */
58
63
  export declare function reviewTabWords(locale: string): {
59
64
  script: (script: ScriptReview) => string;
@@ -17,6 +17,8 @@ export interface PullRequestOpenLocally {
17
17
  export interface PullRequestConnection {
18
18
  getSession: GetRemoteSession;
19
19
  connected(): boolean;
20
+ /** Whether the connection was made with the grant to send reviews; then a review can be sent through it. */
21
+ reviewGranted?(): boolean;
20
22
  }
21
23
  export declare function PullRequestReviewPanel({ repository, locale, renderBlock, locateFor, openTabs, connection, openLocally }: {
22
24
  repository: Repository;
@@ -1,7 +1,7 @@
1
1
  import type { GitSmartHttpOptions } from './smart-http.js';
2
2
  export type RemoteOperation = 'fetch' | 'pull' | 'push';
3
3
  export type RemoteSession = NonNullable<Awaited<ReturnType<GitSmartHttpOptions['getSession']>>>;
4
- export type GetRemoteSession = (remote: string, write: boolean) => Promise<RemoteSession | null>;
4
+ export type GetRemoteSession = (remote: string, write: boolean, review?: boolean) => Promise<RemoteSession | null>;
5
5
  /**
6
6
  * Where a fetched pull request head is kept: outside `refs/heads`, `refs/remotes` and `refs/tags`, so Git CLI lists it
7
7
  * with `git for-each-ref refs/pull` and `git log --all` but not as a branch or tag, `git fetch --prune` leaves it alone,
@@ -1,6 +1,5 @@
1
- import { i as __toESM, n as init_objectSpread2, r as __commonJSMin, t as _objectSpread2 } from "./objectSpread2-Dkd68nJH.js";
2
- import { i as isAssetName, n as assetsDirectoryParts, o as createProjectSourceDiff, p as createTwoFilesPatch, r as folderSettingsLimit, t as assetLimit } from "./project-asset-paths-BzwVf9HE.js";
3
- import { a as _OverloadYield, i as _wrapAsyncGenerator, o as _asyncIterator, r as createGitSmartHttp, t as GitSmartHttpError } from "./smart-http-BmHEkE7K.js";
1
+ import { c as _asyncIterator, d as __commonJSMin, f as __toESM, i as createGitSmartHttp, l as _objectSpread2, o as _wrapAsyncGenerator, s as _OverloadYield, t as GitSmartHttpError, u as init_objectSpread2 } from "./smart-http-eARzlXHO.js";
2
+ import { i as isAssetName, n as assetsDirectoryParts, o as createProjectSourceDiff, p as createTwoFilesPatch, r as folderSettingsLimit, t as assetLimit } from "./project-asset-paths-DNaoWs3E.js";
4
3
  // node_modules/inherits/inherits_browser.js
5
4
  var require_inherits_browser = /* @__PURE__ */ __commonJSMin(((exports, module) => {
6
5
  if (typeof Object.create === "function") module.exports = function inherits(ctor, superCtor) {
@@ -1 +1 @@
1
- export { o as GitPanelError, s as createReadOnlyFs, t as historyPageSize, n as openRepository, a as pullRequestRef, r as upstreamWalkLimit } from "./repository-ByKRBftJ.js";
1
+ export { o as GitPanelError, s as createReadOnlyFs, t as historyPageSize, n as openRepository, a as pullRequestRef, r as upstreamWalkLimit } from "./repository-DXRZEtYk.js";
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import type { GitHubReviewApi } from './github-review-api.js';
3
+ import type { ReviewDraft } from './review-submit.js';
4
+ /** The saved draft of a review, updated when any view of the same review writes it. */
5
+ export declare function useReviewDraft(name: string | undefined): ReviewDraft | null;
6
+ /** Changes the saved draft from what is stored now, so other views' words are kept. */
7
+ export declare function changeReviewDraft(name: string, change: (draft: ReviewDraft) => ReviewDraft): void;
8
+ /**
9
+ * The overall comment, the result, how the code comments will be sent, and the send button of a pull request review.
10
+ * The code comments are written under each code change of the review that shares `name`.
11
+ */
12
+ export declare function PullRequestReviewComposer({ api, name, number, head, changedFiles, url, locale }: {
13
+ api: GitHubReviewApi;
14
+ /** Where the draft is kept, such as `owner/repo#42`. */
15
+ name: string;
16
+ number: number;
17
+ /** The compared head commit. */
18
+ head: string;
19
+ changedFiles: readonly string[];
20
+ /** The pull request on GitHub. */
21
+ url: string;
22
+ locale: string;
23
+ }): React.JSX.Element;
@@ -0,0 +1,144 @@
1
+ import type { GitHubReviewApi, GitHubReviewErrorCode, ReviewEvent } from './github-review-api.js';
2
+ /**
3
+ * Sending a pull request review through the GitHub connection, apart from React.
4
+ *
5
+ * With the proxy's GraphQL routes, a review is sent in three steps under one Review-Submit-Id: a pending review on the
6
+ * compared commit, one file-level thread on a project.source.json per script comment, then the submission. Until the
7
+ * last step nobody else can see anything, so a failure before it discards the pending review. An unknown outcome is
8
+ * never sent again on its own: the person checks it, and only then sends again. Without the GraphQL routes, the whole
9
+ * review goes in the body of one REST review. The draft stays in the browser until GitHub has the review.
10
+ */
11
+ /** One comment on a script, kept under the script's review key. */
12
+ export interface ReviewComment {
13
+ /** The project.source.json the script is in. */
14
+ file: string;
15
+ /** Names the stage or sprite, the script and its first block, in the words of the person writing. */
16
+ heading: string;
17
+ body: string;
18
+ }
19
+ /**
20
+ * A submission in progress. The ID is ours, not GitHub's. `threadKeys` are the comments already on the pending review.
21
+ * - `starting`, `threading`, `submitting`: a step was sent; a reload while it was out reads as unknown.
22
+ * - `start-unknown`, `submit-unknown`: whether GitHub applied the step is unknown; check before anything else.
23
+ * - `pending`: the pending review has all its threads, and the last step was refused; send again or discard.
24
+ * - `left`: a pending review could not be discarded; discard it again, or on GitHub.
25
+ */
26
+ export interface ReviewSubmission {
27
+ id: string;
28
+ commitOid: string;
29
+ route: 'threads' | 'body';
30
+ state: 'starting' | 'threading' | 'submitting' | 'start-unknown' | 'submit-unknown' | 'pending' | 'left';
31
+ threadKeys: string[];
32
+ /** The body-only route: a fingerprint of the body that was sent, to recognise the review on GitHub. */
33
+ sent?: string;
34
+ }
35
+ export interface ReviewDraft {
36
+ event: ReviewEvent;
37
+ body: string;
38
+ comments: Record<string, ReviewComment>;
39
+ /** Project files whose comments go in the review body because GitHub refused a thread on them. */
40
+ inBody: string[];
41
+ /** The pull request head the comments were written for. */
42
+ head?: string;
43
+ submit?: ReviewSubmission;
44
+ }
45
+ export declare const emptyReviewDraft: () => ReviewDraft;
46
+ /** The saved draft of a review, such as `owner/repo#42`, or an empty one. */
47
+ export declare function readReviewDraft(name: string): ReviewDraft;
48
+ /** Saves a draft, or removes it with `null`. Without browser storage, the draft is kept while the page is open. */
49
+ export declare function writeReviewDraft(name: string, draft: ReviewDraft | null): void;
50
+ /** Whether the last write reached browser storage. */
51
+ export declare const reviewDraftPersistent: () => boolean;
52
+ export declare function subscribeReviewDraft(name: string, listener: () => void): () => void;
53
+ export declare const MAX_REVIEW_THREADS = 50;
54
+ export declare const MAX_REVIEW_BODY = 65536;
55
+ export interface ReviewPlan {
56
+ /** File-level threads, in the order they are sent. */
57
+ threads: {
58
+ key: string;
59
+ path: string;
60
+ body: string;
61
+ }[];
62
+ /** Script comments that go in the review body. */
63
+ inBody: {
64
+ key: string;
65
+ comment: ReviewComment;
66
+ }[];
67
+ /** The review body: the overall comment, then the script comments that are not threads. */
68
+ body: string;
69
+ }
70
+ /** Puts the overall comment first, then each script comment that is not a thread under its file and heading. */
71
+ export declare function reviewBody(overall: string, comments: readonly {
72
+ comment: ReviewComment;
73
+ }[]): string;
74
+ /**
75
+ * Decides where each script comment goes. A comment becomes a thread when threads are used, its project.source.json is
76
+ * among the pull request's changed files, GitHub has not refused a thread on that file, and fewer than 50 threads come
77
+ * before it. Comments in `exclude` are already on the pending review.
78
+ */
79
+ export declare function planReview(draft: ReviewDraft, { threads, changedFiles, exclude }: {
80
+ threads: boolean;
81
+ changedFiles: readonly string[];
82
+ exclude?: readonly string[];
83
+ }): ReviewPlan;
84
+ export declare const bodyFingerprint: (body: string) => string;
85
+ export type ReviewProblem = GitHubReviewErrorCode | 'EMPTY_REVIEW' | 'TOO_LONG' | 'UNSUPPORTED' | 'BUSY' | 'REVIEW_OUTCOME_UNKNOWN' | 'PENDING_REVIEW_LEFT' | 'NOT_ALLOWED';
86
+ /** What GitHub would refuse before anything is sent. */
87
+ export declare function reviewProblem(event: ReviewEvent, plan: ReviewPlan, route: 'threads' | 'body'): ReviewProblem | null;
88
+ /**
89
+ * The result of an action. `outcome` says what is now on GitHub:
90
+ * - `submitted`: the review is published (the draft is removed);
91
+ * - `not-sent`: nothing is on GitHub (the draft is kept);
92
+ * - `pending`: a pending review with every thread waits for the last step; send again or discard;
93
+ * - `unknown`: whether a step reached GitHub is unknown; check it;
94
+ * - `left`: a pending review could not be discarded;
95
+ * - `closed`: the check found no review: it was not published, or was discarded on GitHub; send again;
96
+ * - `lost`: the proxy no longer knows the submission (the session ended); look on GitHub.
97
+ */
98
+ export interface ReviewResult {
99
+ outcome: 'submitted' | 'not-sent' | 'pending' | 'unknown' | 'left' | 'closed' | 'lost';
100
+ code?: ReviewProblem;
101
+ /** Seconds to wait before the same action, after a rate limit. */
102
+ retryAfter?: number;
103
+ url?: string;
104
+ /** For `lost`: the person's reviews on the compared commit that GitHub lists, newest first. */
105
+ reviews?: {
106
+ state: string;
107
+ submittedAt: string | null;
108
+ }[];
109
+ }
110
+ export interface ReviewTarget {
111
+ api: GitHubReviewApi;
112
+ /** Where the draft is kept, such as `owner/repo#42`. */
113
+ name: string;
114
+ number: number;
115
+ /** The compared head commit, fetched over Git. */
116
+ head: string;
117
+ /** The pull request's changed files. */
118
+ changedFiles: readonly string[];
119
+ signal?: AbortSignal;
120
+ }
121
+ /** The route the next submission takes, or `null` when the proxy offers neither. */
122
+ export declare function reviewRoute(api: GitHubReviewApi): 'threads' | 'body' | null;
123
+ /**
124
+ * Asks the proxy which review routes it offers, with one read: the review context, then, when the GraphQL routes are
125
+ * off, the list of reviews. A failure other than an unavailable route leaves the route as it was.
126
+ */
127
+ export declare function probeReviewRoutes(api: GitHubReviewApi, number: number, signal?: AbortSignal): Promise<{
128
+ context: import("./github-review-api.js").ReviewContext | null;
129
+ error: unknown;
130
+ } | {
131
+ context: import("./github-review-api.js").ReviewContext | null;
132
+ error?: undefined;
133
+ }>;
134
+ /**
135
+ * Sends the draft, or, after the last step was refused (`pending`), sends that step again with the current event and
136
+ * overall comment. A submission whose outcome is unknown, or whose pending review was left, is not sent again.
137
+ */
138
+ export declare function sendReview(target: ReviewTarget): Promise<ReviewResult>;
139
+ /** Finds out what happened to a submission whose outcome is unknown. Reading is safe to repeat. */
140
+ export declare function checkReview(target: ReviewTarget): Promise<ReviewResult>;
141
+ /** Discards the submission's pending review on GitHub, when one was left or the last step was refused. */
142
+ export declare function discardReview(target: ReviewTarget): Promise<ReviewResult>;
143
+ /** Forgets a submission the proxy no longer knows, keeping the words. Anything left on GitHub is handled there. */
144
+ export declare function forgetReviewSubmission(name: string): void;
@@ -1,4 +1,103 @@
1
- import { n as init_objectSpread2, t as _objectSpread2 } from "./objectSpread2-Dkd68nJH.js";
1
+ // \0rolldown/runtime.js
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esmMin = (fn, res, err) => () => {
9
+ if (err) throw err[0];
10
+ try {
11
+ return fn && (res = fn(fn = 0)), res;
12
+ } catch (e) {
13
+ throw err = [e], e;
14
+ }
15
+ };
16
+ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod, mod.exports), cb = null), mod.exports);
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
19
+ key = keys[i];
20
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
21
+ get: ((k) => from[k]).bind(null, key),
22
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
23
+ });
24
+ }
25
+ return to;
26
+ };
27
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
28
+ value: mod,
29
+ enumerable: true
30
+ }) : target, mod));
31
+ // \0@oxc-project+runtime@0.156.0/helpers/esm/typeof.js
32
+ function _typeof(o) {
33
+ "@babel/helpers - typeof";
34
+ return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
35
+ return typeof o;
36
+ } : function(o) {
37
+ return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
38
+ }, _typeof(o);
39
+ }
40
+ var init_typeof = __esmMin((() => {}));
41
+ // \0@oxc-project+runtime@0.156.0/helpers/esm/toPrimitive.js
42
+ function toPrimitive(t, r) {
43
+ if ("object" != _typeof(t) || !t) return t;
44
+ var e = t[Symbol.toPrimitive];
45
+ if (void 0 !== e) {
46
+ var i = e.call(t, r || "default");
47
+ if ("object" != _typeof(i)) return i;
48
+ throw new TypeError("@@toPrimitive must return a primitive value.");
49
+ }
50
+ return ("string" === r ? String : Number)(t);
51
+ }
52
+ var init_toPrimitive = __esmMin((() => {
53
+ init_typeof();
54
+ }));
55
+ // \0@oxc-project+runtime@0.156.0/helpers/esm/toPropertyKey.js
56
+ function toPropertyKey(t) {
57
+ var i = toPrimitive(t, "string");
58
+ return "symbol" == _typeof(i) ? i : i + "";
59
+ }
60
+ var init_toPropertyKey = __esmMin((() => {
61
+ init_typeof();
62
+ init_toPrimitive();
63
+ }));
64
+ // \0@oxc-project+runtime@0.156.0/helpers/esm/defineProperty.js
65
+ function _defineProperty(e, r, t) {
66
+ return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
67
+ value: t,
68
+ enumerable: !0,
69
+ configurable: !0,
70
+ writable: !0
71
+ }) : e[r] = t, e;
72
+ }
73
+ var init_defineProperty = __esmMin((() => {
74
+ init_toPropertyKey();
75
+ }));
76
+ // \0@oxc-project+runtime@0.156.0/helpers/esm/objectSpread2.js
77
+ function ownKeys(e, r) {
78
+ var t = Object.keys(e);
79
+ if (Object.getOwnPropertySymbols) {
80
+ var o = Object.getOwnPropertySymbols(e);
81
+ r && (o = o.filter(function(r) {
82
+ return Object.getOwnPropertyDescriptor(e, r).enumerable;
83
+ })), t.push.apply(t, o);
84
+ }
85
+ return t;
86
+ }
87
+ function _objectSpread2(e) {
88
+ for (var r = 1; r < arguments.length; r++) {
89
+ var t = null != arguments[r] ? arguments[r] : {};
90
+ r % 2 ? ownKeys(Object(t), !0).forEach(function(r) {
91
+ _defineProperty(e, r, t[r]);
92
+ }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r) {
93
+ Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
94
+ });
95
+ }
96
+ return e;
97
+ }
98
+ var init_objectSpread2 = __esmMin((() => {
99
+ init_defineProperty();
100
+ }));
2
101
  // \0@oxc-project+runtime@0.156.0/helpers/esm/asyncIterator.js
3
102
  function _asyncIterator(r) {
4
103
  var n, t, o, e = 2;
@@ -300,4 +399,4 @@ function createGitSmartHttp(options) {
300
399
  http: { request }
301
400
  };
302
401
  }
303
- export { _OverloadYield as a, _wrapAsyncGenerator as i, MAX_GIT_REQUEST_BYTES as n, _asyncIterator as o, createGitSmartHttp as r, GitSmartHttpError as t };
402
+ export { remoteUrl as a, _asyncIterator as c, __commonJSMin as d, __toESM as f, createGitSmartHttp as i, _objectSpread2 as l, MAX_GIT_REQUEST_BYTES as n, _wrapAsyncGenerator as o, checkConnection as r, _OverloadYield as s, GitSmartHttpError as t, init_objectSpread2 as u };
@@ -33,6 +33,8 @@ export declare class GitSmartHttpError extends Error {
33
33
  readonly requestId?: string | undefined;
34
34
  constructor(code: GitSmartHttpErrorCode, statusCode?: number | undefined, requestId?: string | undefined);
35
35
  }
36
+ export declare function remoteUrl(remote: string): string;
37
+ export declare function checkConnection({ mode, proxyOrigin }: GitProxyConnection): void;
36
38
  export declare function createGitSmartHttp(options: GitSmartHttpOptions): {
37
39
  url: string;
38
40
  corsProxy: "";
@@ -1 +1 @@
1
- export { t as GitSmartHttpError, n as MAX_GIT_REQUEST_BYTES, r as createGitSmartHttp } from "./smart-http-BmHEkE7K.js";
1
+ export { t as GitSmartHttpError, n as MAX_GIT_REQUEST_BYTES, r as checkConnection, i as createGitSmartHttp, a as remoteUrl } from "./smart-http-eARzlXHO.js";
package/dist/style.css CHANGED
@@ -120,6 +120,16 @@
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; }
123
133
  :root{--diff-background-color:initial;--diff-text-color:initial;--diff-font-family:Consolas,Courier,monospace;--diff-selection-background-color:#b3d7ff;--diff-selection-text-color:var(--diff-text-color);--diff-gutter-insert-background-color:#d6fedb;--diff-gutter-insert-text-color:var(--diff-text-color);--diff-gutter-delete-background-color:#fadde0;--diff-gutter-delete-text-color:var(--diff-text-color);--diff-gutter-selected-background-color:#fffce0;--diff-gutter-selected-text-color:var(--diff-text-color);--diff-code-insert-background-color:#eaffee;--diff-code-insert-text-color:var(--diff-text-color);--diff-code-delete-background-color:#fdeff0;--diff-code-delete-text-color:var(--diff-text-color);--diff-code-insert-edit-background-color:#c0dc91;--diff-code-insert-edit-text-color:var(--diff-text-color);--diff-code-delete-edit-background-color:#f39ea2;--diff-code-delete-edit-text-color:var(--diff-text-color);--diff-code-selected-background-color:#fffce0;--diff-code-selected-text-color:var(--diff-text-color);--diff-omit-gutter-line-color:#cb2a1d;}.diff{background-color:var(--diff-background-color);border-collapse:collapse;color:var(--diff-text-color);table-layout:fixed;width:100%;}.diff::-moz-selection{background-color:#b3d7ff;background-color:var(--diff-selection-background-color);color:var(--diff-text-color);color:var(--diff-selection-text-color);}.diff::selection{background-color:#b3d7ff;background-color:var(--diff-selection-background-color);color:var(--diff-text-color);color:var(--diff-selection-text-color);}.diff td{padding-bottom:0;padding-top:0;vertical-align:top;}.diff-line{font-family:Consolas,Courier,monospace;font-family:var(--diff-font-family);line-height:1.5;}.diff-gutter>a{color:inherit;display:block;}.diff-gutter{cursor:pointer;padding:0 1ch;text-align:right;-webkit-user-select:none;-moz-user-select:none;user-select:none;}.diff-gutter-insert{background-color:#d6fedb;background-color:var(--diff-gutter-insert-background-color);color:var(--diff-text-color);color:var(--diff-gutter-insert-text-color);}.diff-gutter-delete{background-color:#fadde0;background-color:var(--diff-gutter-delete-background-color);color:var(--diff-text-color);color:var(--diff-gutter-delete-text-color);}.diff-gutter-omit{cursor:default;}.diff-gutter-selected{background-color:#fffce0;background-color:var(--diff-gutter-selected-background-color);color:var(--diff-text-color);color:var(--diff-gutter-selected-text-color);}.diff-code{word-wrap:break-word;padding:0 0 0 .5em;white-space:pre-wrap;word-break:break-all;}.diff-code-edit{color:inherit;}.diff-code-insert{background-color:#eaffee;background-color:var(--diff-code-insert-background-color);color:var(--diff-text-color);color:var(--diff-code-insert-text-color);}.diff-code-insert .diff-code-edit{background-color:#c0dc91;background-color:var(--diff-code-insert-edit-background-color);color:var(--diff-text-color);color:var(--diff-code-insert-edit-text-color);}.diff-code-delete{background-color:#fdeff0;background-color:var(--diff-code-delete-background-color);color:var(--diff-text-color);color:var(--diff-code-delete-text-color);}.diff-code-delete .diff-code-edit{background-color:#f39ea2;background-color:var(--diff-code-delete-edit-background-color);color:var(--diff-text-color);color:var(--diff-code-delete-edit-text-color);}.diff-code-selected{background-color:#fffce0;background-color:var(--diff-code-selected-background-color);color:var(--diff-text-color);color:var(--diff-code-selected-text-color);}.diff-widget-content{vertical-align:top;}.diff-gutter-col{width:7ch;}.diff-gutter-omit{height:0;}.diff-gutter-omit:before{background-color:#cb2a1d;background-color:var(--diff-omit-gutter-line-color);content:" ";display:block;height:100%;margin-left:4.6ch;overflow:hidden;white-space:pre;width:2px;}.diff-decoration{line-height:1.5;-webkit-user-select:none;-moz-user-select:none;user-select:none;}.diff-decoration-content{font-family:Consolas,Courier,monospace;font-family:var(--diff-font-family);padding:0;}.twgit-backdrop { position: fixed; inset: 0; z-index: 65000; background: #0006; display: flex; align-items: center; justify-content: center; }
124
134
  .twgit-panel { width: 94vw; height: 90vh; display: flex; flex-direction: column; background: Canvas; color: CanvasText; border: 1px solid GrayText; border-radius: 8px; box-shadow: 0 12px 48px #0005; font: 14px/1.5 system-ui, sans-serif; overflow: hidden; }
125
135
  .twgit-panel > header { display: flex; gap: 8px; align-items: center; padding: 10px 16px; border-bottom: 1px solid GrayText; }
@@ -242,4 +252,17 @@
242
252
  .twgit-branch-popup form input:not([type]) { flex: 1; min-width: 10em; font: inherit; }
243
253
  .twgit-branch-popup form button { color: inherit; background: transparent; border: 1px solid currentColor; border-radius: 4px; padding: 3px 8px; font: inherit; cursor: pointer; }
244
254
  .twgit-branch-popup form button:disabled { opacity: .5; cursor: default; }
255
+ /* The branch button's result, at the bottom of the window, in the project folder status's look. */
256
+ .twgit-snackbar { position: fixed; z-index: 1000; bottom: 1rem; left: 50%; transform: translateX(-50%); display: flex; align-items: center;
257
+ gap: 10px; box-sizing: border-box; max-width: min(32rem, calc(100vw - 2rem)); padding: .5rem .5rem .5rem .85rem; background: Canvas;
258
+ color: CanvasText; border: 1px solid currentColor; border-radius: .4rem; box-shadow: 0 4px 16px #0003; font: 14px/1.5 system-ui, sans-serif; }
259
+ .twgit-snackbar > span { flex: 1; min-width: 0; overflow-wrap: anywhere; }
260
+ .twgit-snackbar button { flex: none; color: inherit; background: transparent; border: 1px solid currentColor; border-radius: 4px;
261
+ padding: 2px 8px; font: inherit; cursor: pointer; }
262
+ .twgit-snackbar button.twgit-close { border-color: transparent; padding: 0; }
263
+ .twgit-snackbar button:focus-visible { outline: 2px solid currentColor; outline-offset: 1px; }
264
+ /* The history in a side panel of the host. */
265
+ .twgit-history-view { box-sizing: border-box; padding: 8px; font: 13px/1.5 system-ui, sans-serif; }
266
+ .twgit-history-view > p { margin: 0 0 8px; }
267
+ .twgit-history-view > button { color: inherit; background: transparent; border: 1px solid currentColor; border-radius: 4px; padding: 3px 8px; font: inherit; cursor: pointer; }
245
268
  /*$vite$:1*/
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubohiroya/turbowarp-plus-editor-git",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Read-only local Git panel for TurboWarp Plus Editor",
5
5
  "type": "module",
6
6
  "license": "MPL-2.0",
@@ -1,4 +1,6 @@
1
1
  import React, {useEffect, useLayoutEffect, useRef, useState} from 'react';
2
+ import {createPortal} from 'react-dom';
3
+ import {CloseButton} from './close-button.js';
2
4
  import type {HistoryActionResult} from './history-graph-view.js';
3
5
  import {labelsFor} from './messages.js';
4
6
  import type {BranchRefs} from './repository.js';
@@ -11,11 +13,15 @@ export interface BranchButtonState extends BranchRefs {
11
13
  writable: boolean;
12
14
  /** Whether the panel is running an operation. */
13
15
  busy: boolean;
16
+ /** Whether a project folder is selected in the panel, so a switch can offer to reload it. */
17
+ project: boolean;
14
18
  }
15
19
  /** What the branch button asks the Git panel to do. Branch operations resolve as the panel's own buttons do. */
16
20
  export interface BranchButtonActions {
17
21
  open(): void;
18
22
  choose(): void;
23
+ /** The panel's Reload Project, which asks before replacing unsaved edits. */
24
+ reload(): void;
19
25
  switchBranch(name: string): Promise<HistoryActionResult>;
20
26
  /** Creates a branch at HEAD. */
21
27
  createBranch(name: string, checkout: boolean): Promise<HistoryActionResult>;
@@ -29,6 +35,74 @@ export interface BranchButtonProps extends BranchButtonActions {
29
35
  interface Item {id: string; label: string; checked?: boolean; disabled?: boolean; separatorBefore?: boolean; run(): void}
30
36
 
31
37
  const menuItems = '[role="menuitem"], [role="menuitemradio"]';
38
+ /** How long a snackbar stays when it is neither hovered nor focused. */
39
+ export const snackbarDuration = 6000;
40
+ interface Snack {id: number; text: string; alert: boolean; action?: {label: string; run(): void}}
41
+
42
+ /**
43
+ * A short result at the bottom of the window, with the project folder's status look. It stays while hovered or
44
+ * focused, Escape or the close button dismisses it, and focus is never moved into it.
45
+ */
46
+ function Snackbar({snack, closeLabel, onClose}: {snack: Snack; closeLabel: string; onClose(focus: boolean): void}) {
47
+ // Kept outside React state, so a pointer or focus arriving just before the timeout stops it at once.
48
+ const held = useRef({hover: false, focus: false});
49
+ const timer = useRef<ReturnType<typeof setTimeout>>();
50
+ const hold = (change: Partial<typeof held.current>) => {
51
+ held.current = {...held.current, ...change};
52
+ clearTimeout(timer.current);
53
+ if (!held.current.hover && !held.current.focus) timer.current = setTimeout(() => onClose(false), snackbarDuration);
54
+ };
55
+ useEffect(() => {
56
+ hold({});
57
+ return () => clearTimeout(timer.current);
58
+ }, []);
59
+ return createPortal(<div className="twgit-snackbar" data-git-snackbar=""
60
+ onMouseEnter={() => hold({hover: true})} onMouseLeave={() => hold({hover: false})}
61
+ onFocus={() => hold({focus: true})}
62
+ onBlur={event => {
63
+ if (!event.currentTarget.contains(event.relatedTarget as Node | null)) hold({focus: false});
64
+ }}
65
+ onKeyDown={event => {
66
+ if (event.key !== 'Escape') return;
67
+ event.preventDefault();
68
+ event.stopPropagation();
69
+ onClose(true);
70
+ }}>
71
+ <span role={snack.alert ? 'alert' : 'status'}>{snack.text}</span>
72
+ {snack.action && <button type="button" onClick={() => {onClose(true); snack.action!.run();}}>{snack.action.label}</button>}
73
+ <CloseButton label={closeLabel} onClick={() => onClose(true)}/>
74
+ </div>, document.body);
75
+ }
76
+
77
+ /**
78
+ * The results of branch operations in a snackbar, as the branch button and the history view tell them: a switch with
79
+ * Reload Project while a project folder is selected, a new branch, or the reason for a refusal with Open Local Git.
80
+ * A declined confirmation tells nothing. `restoreFocus` takes the focus back when the snackbar closes from inside.
81
+ */
82
+ export function useBranchSnackbar(locale: string, project: boolean, actions: Pick<BranchButtonActions, 'open' | 'reload'>,
83
+ restoreFocus?: () => void) {
84
+ const labels = labelsFor(locale);
85
+ const [snack, setSnack] = useState<Snack | null>(null);
86
+ const snacks = useRef(0);
87
+ const mounted = useRef(true);
88
+ useEffect(() => () => {mounted.current = false;}, []);
89
+ const show = (text: string, alert: boolean, action?: Snack['action']) => {
90
+ if (mounted.current) setSnack({id: ++snacks.current, text, alert, action});
91
+ };
92
+ const report = (result: HistoryActionResult, switched: boolean) => {
93
+ if (result.done) {
94
+ if (!switched) show(labels.branchCreated, false);
95
+ // The switch rewrote the files; the open project shows them after Reload Project.
96
+ else show(labels.switchedShort, false, project ? {label: labels.reload, run: actions.reload} : undefined);
97
+ } else if (result.error) show(result.error, true, {label: labels.openPanel, run: actions.open});
98
+ };
99
+ const element = snack && <Snackbar key={snack.id} snack={snack} closeLabel={labels.close} onClose={focus => {
100
+ setSnack(previous => previous?.id === snack.id ? null : previous);
101
+ // Focus inside the snackbar would be lost with it.
102
+ if (focus) restoreFocus?.();
103
+ }}/>;
104
+ return {report, element};
105
+ }
32
106
 
33
107
  /** Git's branch glyph, drawn in the text color. */
34
108
  function BranchIcon() {
@@ -100,11 +174,9 @@ export function BranchButton({state, locale = 'en', ...actions}: BranchButtonPro
100
174
  setInitial(from);
101
175
  setMode('menu');
102
176
  };
103
- /** Runs a branch operation and shows its result, or why it did not run, in the panel. */
104
- const change = async (operation: () => Promise<HistoryActionResult>) => {
105
- const result = await operation();
106
- if (result.done || result.error) actions.open();
107
- };
177
+ const {report, element: snackbar} = useBranchSnackbar(locale, state.project, actions,
178
+ () => trigger.current?.focus({preventScroll: true}));
179
+ const change = async (operation: () => Promise<HistoryActionResult>) => report(await operation(), true);
108
180
  const items: Item[] = [];
109
181
  if (!state.repository) {
110
182
  items.push({id: 'choose', label: labels.choose, run: () => {close(); actions.choose();}});
@@ -169,7 +241,7 @@ export function BranchButton({state, locale = 'en', ...actions}: BranchButtonPro
169
241
  setCreating(false);
170
242
  if (result.done) {
171
243
  close();
172
- actions.open();
244
+ report(result, checkout);
173
245
  } else setCreateError(result.error ?? '');
174
246
  };
175
247
  return <div className="twgit-branch-entry" ref={root} onBlur={event => {
@@ -212,5 +284,6 @@ export function BranchButton({state, locale = 'en', ...actions}: BranchButtonPro
212
284
  </span>
213
285
  </form>
214
286
  </div>}
287
+ {snackbar}
215
288
  </div>;
216
289
  }
@@ -0,0 +1,22 @@
1
+ import type {FileChange} from './repository.js';
2
+
3
+ /** How a file stands in Git, as a folder explorer marks it. */
4
+ export type ChangeMark = 'added' | 'modified' | 'deleted' | 'untracked';
5
+
6
+ /**
7
+ * The marks of the working tree's changes under `folder` (a path inside the repository, '' for its root), by path
8
+ * relative to `folder`. A file with both staged and unstaged changes is marked as its working tree stands.
9
+ */
10
+ export function changeMarks(changes: readonly FileChange[], folder: string): Map<string, ChangeMark> {
11
+ const prefix = folder.replace(/^\/+|\/+$/gu, '');
12
+ const marks = new Map<string, ChangeMark>();
13
+ for (const group of ['staged', 'unstaged', 'untracked'] as const) {
14
+ for (const change of changes) {
15
+ if (change.group !== group) continue;
16
+ if (prefix && !change.path.startsWith(prefix + '/')) continue;
17
+ const path = prefix ? change.path.slice(prefix.length + 1) : change.path;
18
+ marks.set(path, group === 'untracked' ? 'untracked' : !change.after ? 'deleted' : !change.before ? 'added' : 'modified');
19
+ }
20
+ }
21
+ return marks;
22
+ }
@@ -1,6 +1,7 @@
1
1
  import React, {useEffect, useRef, useState} from 'react';
2
2
  import type {GitAuthState, GitConnectionMode as Mode} from '@kubohiroya/turbowarp-plus-editor-auth';
3
3
  import type {GetRemoteSession, RemoteOperation} from './remote-operations.js';
4
+ import {reviewLabelsFor} from './messages.js';
4
5
 
5
6
  export interface GitConnectionSettings {
6
7
  modes: readonly Mode[];
@@ -9,10 +10,12 @@ export interface GitConnectionSettings {
9
10
  state: GitAuthState;
10
11
  proxyOrigin?: string;
11
12
  write?: boolean;
13
+ /** Whether the connection was made with the grant to send pull request reviews. */
14
+ review?: boolean;
12
15
  authorization?: {url: string; userCode?: string};
13
16
  };
14
17
  select(mode: Mode): Promise<void>;
15
- connect(remote: string, write?: boolean): Promise<void>;
18
+ connect(remote: string, write?: boolean, review?: boolean): Promise<void>;
16
19
  refresh(): Promise<void>;
17
20
  disconnect(): Promise<void>;
18
21
  getSession?: GetRemoteSession;
@@ -49,6 +52,7 @@ export function ConnectionSettings({settings, locale, readOrigin, runOperation}:
49
52
  const [, update] = useState(0);
50
53
  const [busy, setBusy] = useState(false);
51
54
  const [allowPush, setAllowPush] = useState(false);
55
+ const [allowReview, setAllowReview] = useState(false);
52
56
  const [error, setError] = useState<'error' | 'invalid' | null>(null);
53
57
  const active = useRef(true);
54
58
  useEffect(() => {active.current = true; return () => {active.current = false;};}, []);
@@ -66,7 +70,7 @@ export function ConnectionSettings({settings, locale, readOrigin, runOperation}:
66
70
  const connect = () => run(async () => {
67
71
  const remote = await readOrigin?.();
68
72
  if (!remote) throw new Error('INVALID_REMOTE');
69
- await settings.connect(remote, allowPush);
73
+ await (allowReview ? settings.connect(remote, allowPush, true) : settings.connect(remote, allowPush));
70
74
  });
71
75
  const connected = !!mode && state.status === 'connected';
72
76
  // Sync sits beside the GitHub status; the settings open over the list instead of pushing it down.
@@ -92,6 +96,9 @@ export function ConnectionSettings({settings, locale, readOrigin, runOperation}:
92
96
  {runOperation && <label><input type="checkbox" checked={allowPush}
93
97
  disabled={busy || state.status === 'connecting' || state.status === 'connected'}
94
98
  onChange={event => setAllowPush(event.currentTarget.checked)}/> {labels.allowPush}</label>}
99
+ <label><input type="checkbox" data-git-allow-review="" checked={allowReview}
100
+ disabled={busy || state.status === 'connecting' || state.status === 'connected'}
101
+ onChange={event => setAllowReview(event.currentTarget.checked)}/> {reviewLabelsFor(locale).allowReview}</label>
95
102
  <button disabled={busy || !readOrigin || state.status === 'connecting' || state.status === 'connected'}
96
103
  onClick={connect}>{labels.connect}</button>
97
104
  {authorization && <p>{authorization.userCode && <code>{authorization.userCode} </code>}