@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
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
- import { a as openProjectSourceTabs, b as useCostumeImage, c as gitHubRepositoryFromOrigin, f as organizeProjectChanges, i as createScratchBlocksDiffLocator, l as ProjectSourceComparison, n as openProjectComparison, o as PullRequestError, p as projectPropertyLabel, r as createScratchBlocksDiffRenderer, s as createGitHubPullRequests, u as ProjectSourceReview, x as CloseButton, y as useAssetImage } from "./project-comparison-BXOQcZ73.js";
2
- import { n as init_objectSpread2, r as __commonJSMin, t as _objectSpread2$1 } from "./objectSpread2-Dkd68nJH.js";
3
- import { f as _objectWithoutProperties$1, l as parseProjectSource, u as readProjectSourceDiff } from "./project-asset-paths-BzwVf9HE.js";
4
- import { a as pullRequestRef, i as pullRequestBranch, n as openRepository, o as GitPanelError, s as createReadOnlyFs } from "./repository-ByKRBftJ.js";
1
+ import { B as CloseButton, E as reviewLabelsFor, M as projectPropertyLabel, O as createGitHubReviewApi, R as useAssetImage, T as labelsFor, a as openProjectSourceTabs, c as gitHubRepositoryFromOrigin, d as PullRequestReviewComposer, i as createScratchBlocksDiffLocator, j as organizeProjectChanges, l as ProjectSourceComparison, n as openProjectComparison, o as PullRequestError, r as createScratchBlocksDiffRenderer, s as createGitHubPullRequests, u as ProjectSourceReview, z as useCostumeImage } from "./project-comparison-B_3QZ6M-.js";
2
+ import { d as __commonJSMin, l as _objectSpread2$1, u as init_objectSpread2 } from "./smart-http-eARzlXHO.js";
3
+ import { f as _objectWithoutProperties$1, l as parseProjectSource, u as readProjectSourceDiff } from "./project-asset-paths-DNaoWs3E.js";
4
+ import { a as pullRequestRef, i as pullRequestBranch, n as openRepository, o as GitPanelError, s as createReadOnlyFs } from "./repository-DXRZEtYk.js";
5
5
  import * as m from "react";
6
6
  import React, { createContext, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
7
7
  import { createPortal, render, unmountComponentAtNode } from "react-dom";
@@ -3026,243 +3026,103 @@ function ProjectElementView({ changes, selection, locale, renderBlock, onSelectF
3026
3026
  onClick: () => onSelectFile(file)
3027
3027
  }, file.path, " ", /* @__PURE__ */ React.createElement("small", null, labels.groups[file.group])))))));
3028
3028
  }
3029
- // src/messages.ts
3030
- var messages = {
3031
- en: {
3032
- pullRequests: "Pull requests",
3033
- localChanges: "Local changes",
3034
- title: "Local Git",
3035
- close: "Close",
3036
- choose: "Choose Repository…",
3037
- refresh: "Refresh",
3038
- empty: "Choose an existing Git repository to inspect its files and history.",
3039
- repository: "Repository",
3040
- project: "Project folder",
3041
- none: "No project selected",
3042
- root: "Repository root",
3043
- open: "Open This Project",
3044
- reload: "Reload Project",
3045
- up: "Parent folder",
3046
- browse: "Choose a project inside this repository",
3047
- replace: "Replace the open project and discard its unsaved changes?",
3048
- dirty: "The Editor has unsaved changes.",
3049
- saved: "The Editor has no unsaved changes.",
3050
- staged: "Changes in the next record",
3051
- unstaged: "Saved, not yet recorded",
3052
- untracked: "New files never recorded",
3053
- commit: "Changes in this record",
3054
- history: "Records (commits)",
3055
- worktree: "Saved files",
3056
- clean: "No saved changes in the selected folder.",
3057
- branches: "Local branches",
3058
- unborn: "No records yet",
3059
- detached: "Detached HEAD",
3060
- select: "Select a file to view its diff.",
3061
- binary: "Binary asset",
3062
- large: "Text preview exceeds its display limit.",
3063
- bytes: "bytes",
3064
- mode: "Git file mode",
3065
- permissions: "Working-tree executable permission changes are unavailable in the browser.",
3066
- unchanged: "The text is unchanged.",
3067
- loading: "Reading repository…",
3068
- invalid: "Choose a repository with a .git directory, and a project inside that repository.",
3069
- changed: "Files or Git state changed while reading. Refresh to read the current state.",
3070
- outdated: "The repository changed outside the Editor. Refresh to read the current state.",
3071
- unsupported: "This repository contains an entry the browser cannot represent.",
3072
- commitMessage: "Write a message for this record.",
3073
- switchUnsafe: "Stop the project, and save or record (or undo) every change, before switching branches.",
3074
- branchExists: "A branch with this name already exists.",
3075
- branchConflict: "This name clashes with an existing branch: Git cannot keep both a branch and another branch under its name followed by /, such as pr and pr/7. Rename or delete the other branch with Git, or choose another name.",
3076
- pullRequestBranch: "The branch pr/<number> already exists with other records, so this version was not opened. The branch is left unchanged. Switch to it in Local changes, or rename or delete it with Git, then open the pull request here again.",
3077
- openedPullRequest: "Opened pull request #{number} on the local branch {branch}. To go back, switch to {previous} in Local changes.",
3078
- openedPullRequestDetached: "Opened pull request #{number} on the local branch {branch}. To go back, switch to another branch in Local changes.",
3079
- locked: "Another Git tool is working on this repository (a lock file, or a merge, cherry-pick, revert or rebase in progress). Try again after it finishes.",
3080
- branchName: "Git cannot use this name for a branch.",
3081
- pullUnsafe: "Stop the project, and save or undo unsaved edits and unrecorded changes, before getting changes from GitHub (pull).",
3082
- diverged: "This branch and GitHub's have both gained records, so they cannot be combined here (pull only fast-forwards). Your branch and files are unchanged.",
3083
- session: "Reconnect GitHub for this repository and requested permission.",
3084
- pushUnknown: "It is unknown whether GitHub received the records. Check GitHub (fetch) before sending again.",
3085
- pushRefresh: "GitHub received the records. Check GitHub (fetch) to update the status.",
3086
- rollbackFailed: "Some local changes could not be restored. Check the files and the Git details.",
3087
- remoteFailed: "Could not complete the GitHub operation. Check the connection and try again.",
3088
- remoteDone: "The GitHub operation finished.",
3089
- stage: "Add to Next Record",
3090
- unstage: "Remove from Next Record",
3091
- stageAll: "Add All Changes to Next Record",
3092
- commitLabel: "Message for this record",
3093
- commitButton: "Record (Commit)",
3094
- committed: "Recorded the changes (commit).",
3095
- commitAuthor: "Recorded as",
3096
- saveFirst: "Save the project before adding its changes to the next record.",
3097
- author: "Set user.name and user.email for this repository in Git before recording. The Editor does not change Git settings.",
3098
- nothingStaged: "Add changes to the next record first.",
3099
- switchTo: "Switch",
3100
- currentBranch: "current",
3101
- createBranch: "Create Branch",
3102
- newBranch: "New branch name",
3103
- switchAfterCreate: "Switch to it",
3104
- createFromRemote: "Create and Switch",
3105
- remoteBranches: "Branches on GitHub (origin)",
3106
- switched: "Switched branches. Use Reload Project to open the files of this branch.",
3107
- branchCreated: "Created the branch.",
3108
- projectMissing: "The open project folder is not on this branch. Switch anyway?",
3109
- pulledProject: "Got the changes from GitHub. Use Reload Project to open the updated files.",
3110
- confirmPush: "Send the records on this branch to GitHub (push)?",
3111
- details: "Git details",
3112
- branch: "Branch",
3113
- head: "HEAD",
3114
- groups: "Git groups",
3115
- added: "Added",
3116
- modified: "Changed",
3117
- deleted: "Deleted",
3118
- gitStaged: "Staged",
3119
- gitUnstaged: "Unstaged",
3120
- gitUntracked: "Untracked",
3121
- upstreamSame: "Same as GitHub",
3122
- upstreamAhead: "Ahead of GitHub",
3123
- upstreamBehind: "Behind GitHub",
3124
- upstreamDiverged: "Diverged from GitHub",
3125
- upstreamUnknown: "Not checked with GitHub yet",
3126
- records: "records",
3127
- checkedAt: "as of the last check",
3128
- failed: "Could not read the repository. Check folder access and refresh.",
3129
- historyMore: "Show more",
3130
- historyLoading: "Reading records…",
3131
- historyFailed: "Could not read the records.",
3132
- historyActions: "Actions",
3133
- historyCreateHere: "Create branch here…",
3134
- historySwitch: "Switch to {name}",
3135
- historyCreateFromRemote: "Create and switch to {name}",
3136
- historyCopy: "Copy commit ID",
3137
- historyCopied: "Copied the commit ID.",
3138
- historyCopyFailed: "Could not copy the commit ID.",
3139
- historyRefs: "Branches",
3140
- cancel: "Cancel",
3141
- branchMenu: "Branches",
3142
- openPanel: "Open Local Git",
3143
- newBranchItem: "New Branch…",
3144
- newBranchTitle: "New Branch"
3145
- },
3146
- ja: {
3147
- pullRequests: "プルリクエスト",
3148
- localChanges: "ローカルの変更",
3149
- title: "ローカル Git",
3150
- close: "閉じる",
3151
- choose: "リポジトリを選択…",
3152
- refresh: "再取得",
3153
- empty: "既存の Git リポジトリを選び、ファイルの状態と履歴を確認できます。",
3154
- repository: "リポジトリ",
3155
- project: "作品フォルダ",
3156
- none: "作品は未選択",
3157
- root: "リポジトリルート",
3158
- open: "この作品を開く",
3159
- reload: "作品を再読込",
3160
- up: "上のフォルダ",
3161
- browse: "リポジトリ内の作品を選択",
3162
- replace: "保存していない編集を破棄して、別の作品を開きますか?",
3163
- dirty: "Editor に保存していない編集があります。",
3164
- saved: "Editor に保存していない編集はありません。",
3165
- staged: "次の記録に入る変更",
3166
- unstaged: "保存したが、まだ記録していない変更",
3167
- untracked: "まだ記録したことのない新しいファイル",
3168
- commit: "この記録の変更",
3169
- history: "記録(コミット)の履歴",
3170
- worktree: "保存したファイル",
3171
- clean: "対象フォルダに、保存した変更はありません。",
3172
- branches: "ローカルブランチ",
3173
- unborn: "まだ記録がありません",
3174
- detached: "ブランチ未選択の HEAD",
3175
- select: "ファイルを選ぶと差分を表示します。",
3176
- binary: "バイナリアセット",
3177
- large: "テキスト差分の表示上限を超えています。",
3178
- bytes: "バイト",
3179
- mode: "Git のファイルモード",
3180
- permissions: "作業ツリーの実行権限の変更はブラウザーから確認できません。",
3181
- unchanged: "テキストに変更はありません。",
3182
- loading: "リポジトリを読込中…",
3183
- invalid: ".git ディレクトリを持つリポジトリと、その配下の作品を選んでください。",
3184
- changed: "読込中にファイルまたは Git の状態が変わりました。再取得してください。",
3185
- outdated: "Editor の外でリポジトリが変更されました。再取得すると最新の状態を読み込みます。",
3186
- unsupported: "ブラウザーで扱えない種類の Git エントリーがあります。",
3187
- commitMessage: "この記録の説明を書いてください。",
3188
- switchUnsafe: "ブランチを切り替える前に、実行を止め、すべての変更を保存して記録(または取り消し)してください。",
3189
- branchExists: "同じ名前のブランチがすでにあります。",
3190
- branchConflict: "この名前は既存のブランチとぶつかります。Git では、pr と pr/7 のように、あるブランチと、その名前に / を続けたブランチを両方は持てません。Git でもう一方の名前を変えるか消すか、別の名前にしてください。",
3191
- pullRequestBranch: "ブランチ pr/<番号> が、別の記録を指してすでにあるため、この版を開きませんでした。ブランチはそのままです。「ローカルの変更」で切り替えるか、Git で名前を変えるか消してから、もう一度ここで開いてください。",
3192
- openedPullRequest: "プルリクエスト #{number} を、ローカルブランチ {branch} で開きました。戻るときは「ローカルの変更」で {previous} に切り替えてください。",
3193
- openedPullRequestDetached: "プルリクエスト #{number} を、ローカルブランチ {branch} で開きました。戻るときは「ローカルの変更」で別のブランチに切り替えてください。",
3194
- locked: "ほかの Git の操作が、このリポジトリを使っています(ロックファイル、またはマージ・cherry-pick・revert・rebase の途中)。終わってから、もう一度試してください。",
3195
- branchName: "この名前は Git のブランチ名に使えません。",
3196
- pullUnsafe: "GitHub から取り込む前に、実行を止め、保存していない編集とまだ記録していない変更をなくしてください。",
3197
- diverged: "このブランチと GitHub の両方に新しい記録があるため、ここでは取り込めません(pull は fast-forward のみ)。ブランチとファイルはそのままです。",
3198
- session: "このリポジトリと操作に必要な権限で GitHub に接続してください。",
3199
- pushUnknown: "GitHub が記録を受け取ったか分かりません。送り直す前に「GitHub と確かめる」を使ってください。",
3200
- pushRefresh: "GitHub が記録を受け取りました。「GitHub と確かめる」で状態を更新してください。",
3201
- rollbackFailed: "一部のローカルの変更を元に戻せませんでした。ファイルと Git の詳細を確認してください。",
3202
- remoteFailed: "GitHub との操作を完了できませんでした。接続を確認して、もう一度試してください。",
3203
- remoteDone: "GitHub との操作が終わりました。",
3204
- stage: "次の記録に入れる",
3205
- unstage: "次の記録から外す",
3206
- stageAll: "すべての変更を次の記録に入れる",
3207
- commitLabel: "この記録の説明",
3208
- commitButton: "記録する(コミット)",
3209
- committed: "変更を記録しました(コミット)。",
3210
- commitAuthor: "記録する人",
3211
- saveFirst: "次の記録に入れる前に、作品を保存してください。",
3212
- author: "記録する前に、Git でこのリポジトリの user.name と user.email を設定してください。Editor は Git の設定を変えません。",
3213
- nothingStaged: "先に、変更を次の記録に入れてください。",
3214
- switchTo: "切り替える",
3215
- currentBranch: "現在",
3216
- createBranch: "ブランチを作る",
3217
- newBranch: "新しいブランチの名前",
3218
- switchAfterCreate: "作ったら切り替える",
3219
- createFromRemote: "作って切り替える",
3220
- remoteBranches: "GitHub(origin)のブランチ",
3221
- switched: "ブランチを切り替えました。「作品を再読込」で、このブランチのファイルを開けます。",
3222
- branchCreated: "ブランチを作りました。",
3223
- projectMissing: "開いている作品フォルダは、このブランチにありません。切り替えますか?",
3224
- pulledProject: "GitHub から取り込みました。「作品を再読込」で更新したファイルを開けます。",
3225
- confirmPush: "このブランチの記録を GitHub に送りますか(push)?",
3226
- details: "Git の詳細",
3227
- branch: "ブランチ",
3228
- head: "HEAD",
3229
- groups: "Git の区分",
3230
- added: "追加",
3231
- modified: "変更",
3232
- deleted: "削除",
3233
- gitStaged: "ステージ済み (staged)",
3234
- gitUnstaged: "未ステージ (unstaged)",
3235
- gitUntracked: "未追跡 (untracked)",
3236
- upstreamSame: "GitHub と同じ",
3237
- upstreamAhead: "GitHub より進んでいる",
3238
- upstreamBehind: "GitHub より遅れている",
3239
- upstreamDiverged: "GitHub と分かれている",
3240
- upstreamUnknown: "まだ GitHub と確かめていない",
3241
- records: "件",
3242
- checkedAt: "最後に確かめた時点",
3243
- failed: "リポジトリを読み込めませんでした。フォルダへのアクセスを確認し、再取得してください。",
3244
- historyMore: "さらに表示",
3245
- historyLoading: "記録を読込中…",
3246
- historyFailed: "記録を読み込めませんでした。",
3247
- historyActions: "操作",
3248
- historyCreateHere: "ここからブランチを作る…",
3249
- historySwitch: "{name} に切り替える",
3250
- historyCreateFromRemote: "{name} を作って切り替える",
3251
- historyCopy: "コミットの ID をコピー",
3252
- historyCopied: "コミットの ID をコピーしました。",
3253
- historyCopyFailed: "コミットの ID をコピーできませんでした。",
3254
- historyRefs: "ブランチ",
3255
- cancel: "キャンセル",
3256
- branchMenu: "ブランチ",
3257
- openPanel: "ローカル Git を開く",
3258
- newBranchItem: "新しいブランチ…",
3259
- newBranchTitle: "新しいブランチ"
3260
- }
3261
- };
3262
- var labelsFor = (locale) => locale.startsWith("ja") ? messages.ja : messages.en;
3263
3029
  // src/branch-button.tsx
3030
+ init_objectSpread2();
3264
3031
  var _excluded = ["state", "locale"];
3265
3032
  var menuItems = "[role=\"menuitem\"], [role=\"menuitemradio\"]";
3033
+ /** How long a snackbar stays when it is neither hovered nor focused. */
3034
+ var snackbarDuration = 6e3;
3035
+ /**
3036
+ * A short result at the bottom of the window, with the project folder's status look. It stays while hovered or
3037
+ * focused, Escape or the close button dismisses it, and focus is never moved into it.
3038
+ */
3039
+ function Snackbar({ snack, closeLabel, onClose }) {
3040
+ const held = useRef({
3041
+ hover: false,
3042
+ focus: false
3043
+ });
3044
+ const timer = useRef();
3045
+ const hold = (change) => {
3046
+ held.current = _objectSpread2$1(_objectSpread2$1({}, held.current), change);
3047
+ clearTimeout(timer.current);
3048
+ if (!held.current.hover && !held.current.focus) timer.current = setTimeout(() => onClose(false), snackbarDuration);
3049
+ };
3050
+ useEffect(() => {
3051
+ hold({});
3052
+ return () => clearTimeout(timer.current);
3053
+ }, []);
3054
+ return createPortal(/* @__PURE__ */ React.createElement("div", {
3055
+ className: "twgit-snackbar",
3056
+ "data-git-snackbar": "",
3057
+ onMouseEnter: () => hold({ hover: true }),
3058
+ onMouseLeave: () => hold({ hover: false }),
3059
+ onFocus: () => hold({ focus: true }),
3060
+ onBlur: (event) => {
3061
+ if (!event.currentTarget.contains(event.relatedTarget)) hold({ focus: false });
3062
+ },
3063
+ onKeyDown: (event) => {
3064
+ if (event.key !== "Escape") return;
3065
+ event.preventDefault();
3066
+ event.stopPropagation();
3067
+ onClose(true);
3068
+ }
3069
+ }, /* @__PURE__ */ React.createElement("span", { role: snack.alert ? "alert" : "status" }, snack.text), snack.action && /* @__PURE__ */ React.createElement("button", {
3070
+ type: "button",
3071
+ onClick: () => {
3072
+ onClose(true);
3073
+ snack.action.run();
3074
+ }
3075
+ }, snack.action.label), /* @__PURE__ */ React.createElement(CloseButton, {
3076
+ label: closeLabel,
3077
+ onClick: () => onClose(true)
3078
+ })), document.body);
3079
+ }
3080
+ /**
3081
+ * The results of branch operations in a snackbar, as the branch button and the history view tell them: a switch with
3082
+ * Reload Project while a project folder is selected, a new branch, or the reason for a refusal with Open Local Git.
3083
+ * A declined confirmation tells nothing. `restoreFocus` takes the focus back when the snackbar closes from inside.
3084
+ */
3085
+ function useBranchSnackbar(locale, project, actions, restoreFocus) {
3086
+ const labels = labelsFor(locale);
3087
+ const [snack, setSnack] = useState(null);
3088
+ const snacks = useRef(0);
3089
+ const mounted = useRef(true);
3090
+ useEffect(() => () => {
3091
+ mounted.current = false;
3092
+ }, []);
3093
+ const show = (text, alert, action) => {
3094
+ if (mounted.current) setSnack({
3095
+ id: ++snacks.current,
3096
+ text,
3097
+ alert,
3098
+ action
3099
+ });
3100
+ };
3101
+ const report = (result, switched) => {
3102
+ if (result.done) {
3103
+ if (!switched) show(labels.branchCreated, false);
3104
+ else show(labels.switchedShort, false, project ? {
3105
+ label: labels.reload,
3106
+ run: actions.reload
3107
+ } : void 0);
3108
+ } else if (result.error) show(result.error, true, {
3109
+ label: labels.openPanel,
3110
+ run: actions.open
3111
+ });
3112
+ };
3113
+ return {
3114
+ report,
3115
+ element: snack && /* @__PURE__ */ React.createElement(Snackbar, {
3116
+ key: snack.id,
3117
+ snack,
3118
+ closeLabel: labels.close,
3119
+ onClose: (focus) => {
3120
+ setSnack((previous) => (previous === null || previous === void 0 ? void 0 : previous.id) === snack.id ? null : previous);
3121
+ if (focus) restoreFocus === null || restoreFocus === void 0 || restoreFocus();
3122
+ }
3123
+ })
3124
+ };
3125
+ }
3266
3126
  /** Git's branch glyph, drawn in the text color. */
3267
3127
  function BranchIcon() {
3268
3128
  return /* @__PURE__ */ React.createElement("svg", {
@@ -3354,11 +3214,11 @@ function BranchButton(_ref) {
3354
3214
  setInitial(from);
3355
3215
  setMode("menu");
3356
3216
  };
3357
- /** Runs a branch operation and shows its result, or why it did not run, in the panel. */
3358
- const change = async (operation) => {
3359
- const result = await operation();
3360
- if (result.done || result.error) actions.open();
3361
- };
3217
+ const { report, element: snackbar } = useBranchSnackbar(locale, state.project, actions, () => {
3218
+ var _trigger$current2;
3219
+ return (_trigger$current2 = trigger.current) === null || _trigger$current2 === void 0 ? void 0 : _trigger$current2.focus({ preventScroll: true });
3220
+ });
3221
+ const change = async (operation) => report(await operation(), true);
3362
3222
  const items = [];
3363
3223
  if (!state.repository) items.push({
3364
3224
  id: "choose",
@@ -3459,7 +3319,7 @@ function BranchButton(_ref) {
3459
3319
  setCreating(false);
3460
3320
  if (result.done) {
3461
3321
  close();
3462
- actions.open();
3322
+ report(result, checkout);
3463
3323
  } else setCreateError((_result$error = result.error) !== null && _result$error !== void 0 ? _result$error : "");
3464
3324
  };
3465
3325
  return /* @__PURE__ */ React.createElement("div", {
@@ -3535,7 +3395,27 @@ function BranchButton(_ref) {
3535
3395
  type: "button",
3536
3396
  disabled: creating,
3537
3397
  onClick: () => close()
3538
- }, labels.cancel)))));
3398
+ }, labels.cancel)))), snackbar);
3399
+ }
3400
+ // src/change-marks.ts
3401
+ /**
3402
+ * The marks of the working tree's changes under `folder` (a path inside the repository, '' for its root), by path
3403
+ * relative to `folder`. A file with both staged and unstaged changes is marked as its working tree stands.
3404
+ */
3405
+ function changeMarks(changes, folder) {
3406
+ const prefix = folder.replace(/^\/+|\/+$/gu, "");
3407
+ const marks = /* @__PURE__ */ new Map();
3408
+ for (const group of [
3409
+ "staged",
3410
+ "unstaged",
3411
+ "untracked"
3412
+ ]) for (const change of changes) {
3413
+ if (change.group !== group) continue;
3414
+ if (prefix && !change.path.startsWith(prefix + "/")) continue;
3415
+ const path = prefix ? change.path.slice(prefix.length + 1) : change.path;
3416
+ marks.set(path, group === "untracked" ? "untracked" : !change.after ? "deleted" : !change.before ? "added" : "modified");
3417
+ }
3418
+ return marks;
3539
3419
  }
3540
3420
  /**
3541
3421
  * Lays out commits ordered newest first with every child above its parents. Parents missing from `commits`, such as
@@ -4005,6 +3885,7 @@ function ConnectionSettings({ settings, locale, readOrigin, runOperation }) {
4005
3885
  const [, update] = useState(0);
4006
3886
  const [busy, setBusy] = useState(false);
4007
3887
  const [allowPush, setAllowPush] = useState(false);
3888
+ const [allowReview, setAllowReview] = useState(false);
4008
3889
  const [error, setError] = useState(null);
4009
3890
  const active = useRef(true);
4010
3891
  useEffect(() => {
@@ -4032,7 +3913,7 @@ function ConnectionSettings({ settings, locale, readOrigin, runOperation }) {
4032
3913
  const connect = () => run(async () => {
4033
3914
  const remote = await (readOrigin === null || readOrigin === void 0 ? void 0 : readOrigin());
4034
3915
  if (!remote) throw new Error("INVALID_REMOTE");
4035
- await settings.connect(remote, allowPush);
3916
+ await (allowReview ? settings.connect(remote, allowPush, true) : settings.connect(remote, allowPush));
4036
3917
  });
4037
3918
  const connected = !!mode && state.status === "connected";
4038
3919
  return /* @__PURE__ */ React.createElement(React.Fragment, null, runOperation && connected && /* @__PURE__ */ React.createElement("span", { className: "twgit-sync" }, [
@@ -4064,7 +3945,13 @@ function ConnectionSettings({ settings, locale, readOrigin, runOperation }) {
4064
3945
  checked: allowPush,
4065
3946
  disabled: busy || state.status === "connecting" || state.status === "connected",
4066
3947
  onChange: (event) => setAllowPush(event.currentTarget.checked)
4067
- }), " ", labels.allowPush), /* @__PURE__ */ React.createElement("button", {
3948
+ }), " ", labels.allowPush), /* @__PURE__ */ React.createElement("label", null, /* @__PURE__ */ React.createElement("input", {
3949
+ type: "checkbox",
3950
+ "data-git-allow-review": "",
3951
+ checked: allowReview,
3952
+ disabled: busy || state.status === "connecting" || state.status === "connected",
3953
+ onChange: (event) => setAllowReview(event.currentTarget.checked)
3954
+ }), " ", reviewLabelsFor(locale).allowReview), /* @__PURE__ */ React.createElement("button", {
4068
3955
  disabled: busy || !readOrigin || state.status === "connecting" || state.status === "connected",
4069
3956
  onClick: connect
4070
3957
  }, labels.connect), authorization && /* @__PURE__ */ React.createElement("p", null, authorization.userCode && /* @__PURE__ */ React.createElement("code", null, authorization.userCode, " "), /* @__PURE__ */ React.createElement("a", {
@@ -4171,9 +4058,20 @@ function errorMessage(error, labels) {
4171
4058
  }
4172
4059
  return labels.failed;
4173
4060
  }
4174
- function PullRequests({ name, labels, locale, renderBlock, locateFor, openTabs, repository, connection, openLocally }) {
4175
- var _pr$base$ref, _pr$git, _pr$base$ref2;
4061
+ function PullRequests({ name, remote, labels, locale, renderBlock, locateFor, openTabs, repository, connection, openLocally }) {
4062
+ var _connection$reviewGra, _pr$base$ref, _pr$git, _pr$base$ref2, _connection$reviewGra2;
4176
4063
  const client = useMemo(() => createGitHubPullRequests(name), [name]);
4064
+ const reviewApi = useMemo(() => {
4065
+ if (!remote || !connection) return null;
4066
+ try {
4067
+ return createGitHubReviewApi({
4068
+ remote,
4069
+ getSession: connection.getSession
4070
+ });
4071
+ } catch (_unused) {
4072
+ return null;
4073
+ }
4074
+ }, [remote, connection]);
4177
4075
  const [page, setPage] = useState(1);
4178
4076
  const [listing, setListing] = useState();
4179
4077
  const [input, setInput] = useState("");
@@ -4280,6 +4178,7 @@ function PullRequests({ name, labels, locale, renderBlock, locateFor, openTabs,
4280
4178
  setInput(String(number));
4281
4179
  };
4282
4180
  const pr = detail === null || detail === void 0 ? void 0 : detail.data;
4181
+ const reviewing = !!(pr === null || pr === void 0 ? void 0 : pr.git) && !!reviewApi && !!(connection === null || connection === void 0 ? void 0 : connection.connected()) && !!((_connection$reviewGra = connection.reviewGranted) === null || _connection$reviewGra === void 0 ? void 0 : _connection$reviewGra.call(connection));
4283
4182
  return /* @__PURE__ */ React.createElement("div", { className: "twgit-content" }, /* @__PURE__ */ React.createElement("aside", null, /* @__PURE__ */ React.createElement("h3", null, labels.title), /* @__PURE__ */ React.createElement("p", null, name), /* @__PURE__ */ React.createElement("form", {
4284
4183
  className: "twgit-pr-number",
4285
4184
  onSubmit: (event) => {
@@ -4328,7 +4227,7 @@ function PullRequests({ name, labels, locale, renderBlock, locateFor, openTabs,
4328
4227
  let projects;
4329
4228
  try {
4330
4229
  projects = readProjectSourceDiff(file.patch, file.beforeSource);
4331
- } catch (_unused) {
4230
+ } catch (_unused2) {
4332
4231
  return [];
4333
4232
  }
4334
4233
  return projects ? [{
@@ -4348,13 +4247,26 @@ function PullRequests({ name, labels, locale, renderBlock, locateFor, openTabs,
4348
4247
  }, labels.openLocally.replace("{branch}", pullRequestBranch(pr.number)))), pr.git && /* @__PURE__ */ React.createElement("p", {
4349
4248
  className: "twgit-context",
4350
4249
  role: "status"
4351
- }, labels.viaConnection, pr.git.branches && /* @__PURE__ */ React.createElement(React.Fragment, null, " ", labels.noMetadata)), /* @__PURE__ */ React.createElement(ProjectSourceReview, {
4250
+ }, labels.viaConnection, pr.git.branches && /* @__PURE__ */ React.createElement(React.Fragment, null, " ", labels.noMetadata)), pr.git && (connection === null || connection === void 0 ? void 0 : connection.connected()) && !((_connection$reviewGra2 = connection.reviewGranted) === null || _connection$reviewGra2 === void 0 ? void 0 : _connection$reviewGra2.call(connection)) && /* @__PURE__ */ React.createElement("p", {
4251
+ className: "twgit-context",
4252
+ role: "status"
4253
+ }, reviewLabelsFor(locale).needsGrant), /* @__PURE__ */ React.createElement(ProjectSourceReview, {
4352
4254
  title: pr.title,
4353
4255
  description: pr.description,
4354
4256
  files: locateFor ? pr.files.map((file) => "patch" in file ? _objectSpread2$1(_objectSpread2$1({}, file), {}, { locateBlock: locateFor(file.path, pr.head.sha) }) : file) : pr.files,
4355
4257
  renderBlock,
4356
4258
  locale,
4357
- rememberAs: `${name}#${pr.number}`
4259
+ rememberAs: `${name}#${pr.number}`,
4260
+ commentAs: reviewing ? `${name}#${pr.number}` : void 0
4261
+ }), reviewing && reviewApi && /* @__PURE__ */ React.createElement(PullRequestReviewComposer, {
4262
+ key: `${name}#${pr.number}`,
4263
+ api: reviewApi,
4264
+ name: `${name}#${pr.number}`,
4265
+ number: pr.number,
4266
+ head: pr.head.sha,
4267
+ changedFiles: pr.files.map((file) => file.path),
4268
+ url: pr.url,
4269
+ locale
4358
4270
  }))));
4359
4271
  }
4360
4272
  function PullRequestReviewPanel({ repository, locale, renderBlock, locateFor, openTabs, connection, openLocally }) {
@@ -4365,7 +4277,8 @@ function PullRequestReviewPanel({ repository, locale, renderBlock, locateFor, op
4365
4277
  repository.readOrigin().then((value) => {
4366
4278
  if (active) setOrigin({
4367
4279
  repository,
4368
- name: gitHubRepositoryFromOrigin(value)
4280
+ name: gitHubRepositoryFromOrigin(value),
4281
+ remote: value
4369
4282
  });
4370
4283
  }, () => {
4371
4284
  if (active) setOrigin({
@@ -4389,6 +4302,7 @@ function PullRequestReviewPanel({ repository, locale, renderBlock, locateFor, op
4389
4302
  return /* @__PURE__ */ React.createElement(PullRequests, {
4390
4303
  key: origin.name,
4391
4304
  name: origin.name,
4305
+ remote: origin.remote,
4392
4306
  labels,
4393
4307
  locale,
4394
4308
  renderBlock,
@@ -4446,6 +4360,7 @@ function createController() {
4446
4360
  async changeBranch() {
4447
4361
  return { done: false };
4448
4362
  },
4363
+ reload() {},
4449
4364
  branch: {
4450
4365
  repository: false,
4451
4366
  branch: void 0,
@@ -4453,10 +4368,15 @@ function createController() {
4453
4368
  branches: [],
4454
4369
  remoteBranches: [],
4455
4370
  writable: false,
4456
- busy: false
4371
+ busy: false,
4372
+ project: false
4457
4373
  },
4458
- publish(state) {
4374
+ repository: null,
4375
+ snapshot: null,
4376
+ publish(state, repository, snapshot) {
4459
4377
  controller.branch = state;
4378
+ controller.repository = repository;
4379
+ controller.snapshot = snapshot;
4460
4380
  for (const listener of [...listeners]) listener();
4461
4381
  },
4462
4382
  follow(listener) {
@@ -4874,7 +4794,8 @@ function GitPanel({ vm, document, host, controller }) {
4874
4794
  const getSession = settings === null || settings === void 0 ? void 0 : settings.getSession;
4875
4795
  return getSession ? {
4876
4796
  getSession,
4877
- connected: () => settings.getView().state.status === "connected"
4797
+ connected: () => settings.getView().state.status === "connected",
4798
+ reviewGranted: () => settings.getView().review === true
4878
4799
  } : void 0;
4879
4800
  }, [host]);
4880
4801
  const [message, setMessage] = useState("");
@@ -5024,6 +4945,9 @@ function GitPanel({ vm, document, host, controller }) {
5024
4945
  state.selected
5025
4946
  ]);
5026
4947
  controller.changeBranch = changeBranch;
4948
+ controller.reload = () => {
4949
+ if (current.current.project !== null) openProject(current.current.project);
4950
+ };
5027
4951
  controller.choose = async (language) => {
5028
4952
  setLocale(language);
5029
4953
  const before = current.current.repository;
@@ -5037,10 +4961,15 @@ function GitPanel({ vm, document, host, controller }) {
5037
4961
  branches: (_refs$branches = refs === null || refs === void 0 ? void 0 : refs.branches) !== null && _refs$branches !== void 0 ? _refs$branches : [],
5038
4962
  remoteBranches: (_refs$remoteBranches = refs === null || refs === void 0 ? void 0 : refs.remoteBranches) !== null && _refs$remoteBranches !== void 0 ? _refs$remoteBranches : [],
5039
4963
  writable: !!host.localWrite && !!refs,
5040
- busy: state.busy
4964
+ busy: state.busy,
4965
+ project: state.project !== null
5041
4966
  };
5042
4967
  const published = JSON.stringify(branchState);
5043
- useEffect(() => controller.publish(branchState), [published]);
4968
+ useEffect(() => controller.publish(branchState, state.repository, state.snapshot), [
4969
+ published,
4970
+ state.repository,
4971
+ state.snapshot
4972
+ ]);
5044
4973
  if (!shown) return null;
5045
4974
  const groups = state.commit ? ["commit"] : [
5046
4975
  "staged",
@@ -5344,16 +5273,19 @@ function mountGitPanel({ vm, document, host }) {
5344
5273
  };
5345
5274
  }
5346
5275
  /** The branch button of the panel mounted for `vm`, or nothing while no panel is mounted for it. */
5347
- function BranchEntry({ vm, locale }) {
5276
+ /** Calls `listener` with the controller of the panel mounted for `vm`, now and whenever it is mounted or disposed. */
5277
+ function findController(vm, listener) {
5278
+ const update = () => listener(controllers.get(vm));
5279
+ registrations.add(update);
5280
+ update();
5281
+ return () => {
5282
+ registrations.delete(update);
5283
+ };
5284
+ }
5285
+ /** The panel mounted for `vm` and what it last published. Following it keeps the closed panel watching the branches. */
5286
+ function useController(vm) {
5348
5287
  const [controller, setController] = useState(() => controllers.get(vm));
5349
- useEffect(() => {
5350
- const update = () => setController(controllers.get(vm));
5351
- registrations.add(update);
5352
- update();
5353
- return () => {
5354
- registrations.delete(update);
5355
- };
5356
- }, [vm]);
5288
+ useEffect(() => findController(vm, setController), [vm]);
5357
5289
  const [state, setState] = useState(controller === null || controller === void 0 ? void 0 : controller.branch);
5358
5290
  useEffect(() => {
5359
5291
  if (!controller) return;
@@ -5361,12 +5293,20 @@ function BranchEntry({ vm, locale }) {
5361
5293
  update();
5362
5294
  return controller.follow(update);
5363
5295
  }, [controller]);
5296
+ return {
5297
+ controller,
5298
+ state
5299
+ };
5300
+ }
5301
+ function BranchEntry({ vm, locale }) {
5302
+ const { controller, state } = useController(vm);
5364
5303
  if (!controller || !state) return null;
5365
5304
  return /* @__PURE__ */ React.createElement(BranchButton, {
5366
5305
  state,
5367
5306
  locale,
5368
5307
  open: () => controller.open(locale),
5369
5308
  choose: () => void controller.choose(locale),
5309
+ reload: () => controller.reload(),
5370
5310
  switchBranch: (name) => controller.changeBranch({
5371
5311
  kind: "switchBranch",
5372
5312
  name
@@ -5395,4 +5335,173 @@ function renderBranchButton(createElement, { vm, locale = "en" }) {
5395
5335
  locale
5396
5336
  });
5397
5337
  }
5398
- export { BranchButton, GitPanelError, HistoryGraph, createEditorBlockLocator, createReadOnlyFs, createScratchBlocksDiffLocator, createScratchBlocksDiffRenderer, laneColors, layoutHistory, mountGitPanel, openProjectComparison, openProjectSourceTabs, openRepository, pullRequestBranch, pullRequestRef, renderBranchButton };
5338
+ /** The history of the panel's repository, read again whenever its branches move. */
5339
+ function HistoryEntry({ vm, locale }) {
5340
+ const { controller, state } = useController(vm);
5341
+ const labels = labelsFor(locale);
5342
+ const repository = state && (controller === null || controller === void 0 ? void 0 : controller.repository) || null;
5343
+ const [history, setHistory] = useState({
5344
+ commits: [],
5345
+ cursor: null,
5346
+ loading: false,
5347
+ failed: false
5348
+ });
5349
+ const [selected, setSelected] = useState(null);
5350
+ const ticket = useRef(0);
5351
+ const root = useRef(null);
5352
+ const refs = state ? [
5353
+ state.head,
5354
+ ...state.branches,
5355
+ "",
5356
+ ...state.remoteBranches
5357
+ ].join("\n") : "";
5358
+ useEffect(() => {
5359
+ const current = ++ticket.current;
5360
+ if (!repository) {
5361
+ setHistory({
5362
+ commits: [],
5363
+ cursor: null,
5364
+ loading: false,
5365
+ failed: false
5366
+ });
5367
+ return;
5368
+ }
5369
+ setHistory((previous) => _objectSpread2$1(_objectSpread2$1({}, previous), {}, {
5370
+ cursor: null,
5371
+ loading: true,
5372
+ failed: false
5373
+ }));
5374
+ repository.history().then((page) => {
5375
+ if (ticket.current === current) setHistory({
5376
+ commits: page.commits,
5377
+ cursor: page.cursor,
5378
+ loading: false,
5379
+ failed: false
5380
+ });
5381
+ }, () => {
5382
+ if (ticket.current === current) setHistory({
5383
+ commits: [],
5384
+ cursor: null,
5385
+ loading: false,
5386
+ failed: true
5387
+ });
5388
+ });
5389
+ }, [repository, refs]);
5390
+ const showMore = () => {
5391
+ if (!repository || !history.cursor || history.loading) return;
5392
+ const current = ticket.current;
5393
+ setHistory((previous) => _objectSpread2$1(_objectSpread2$1({}, previous), {}, { loading: true }));
5394
+ repository.history({ after: history.cursor }).then((page) => {
5395
+ if (ticket.current === current) setHistory((previous) => ({
5396
+ commits: [...previous.commits, ...page.commits],
5397
+ cursor: page.cursor,
5398
+ loading: false,
5399
+ failed: false
5400
+ }));
5401
+ }, () => {
5402
+ if (ticket.current === current) setHistory((previous) => _objectSpread2$1(_objectSpread2$1({}, previous), {}, {
5403
+ cursor: null,
5404
+ loading: false,
5405
+ failed: true
5406
+ }));
5407
+ });
5408
+ };
5409
+ const { report, element: snackbar } = useBranchSnackbar(locale, !!(state === null || state === void 0 ? void 0 : state.project), {
5410
+ open: () => controller === null || controller === void 0 ? void 0 : controller.open(locale),
5411
+ reload: () => controller === null || controller === void 0 ? void 0 : controller.reload()
5412
+ }, () => {
5413
+ var _root$current;
5414
+ return (_root$current = root.current) === null || _root$current === void 0 || (_root$current = _root$current.querySelector("[tabindex=\"0\"]")) === null || _root$current === void 0 ? void 0 : _root$current.focus({ preventScroll: true });
5415
+ });
5416
+ if (!controller || !state) return null;
5417
+ if (!repository) return /* @__PURE__ */ React.createElement("div", {
5418
+ className: "twgit-history-view",
5419
+ ref: root
5420
+ }, /* @__PURE__ */ React.createElement("p", null, labels.historyViewEmpty), /* @__PURE__ */ React.createElement("button", {
5421
+ type: "button",
5422
+ onClick: () => void controller.choose(locale)
5423
+ }, labels.choose));
5424
+ const change = async (operation) => {
5425
+ const result = await controller.changeBranch(operation);
5426
+ report(result, operation.kind === "switchBranch" || !!operation.checkout);
5427
+ return result;
5428
+ };
5429
+ return /* @__PURE__ */ React.createElement("div", {
5430
+ className: "twgit-history-view",
5431
+ ref: root
5432
+ }, /* @__PURE__ */ React.createElement(HistoryGraph, _objectSpread2$1({
5433
+ commits: history.commits,
5434
+ selected,
5435
+ locale,
5436
+ busy: state.busy,
5437
+ onSelect: setSelected,
5438
+ hasMore: !!history.cursor,
5439
+ loadingMore: history.loading,
5440
+ onShowMore: showMore,
5441
+ localBranches: state.branches
5442
+ }, state.writable ? {
5443
+ onCreateBranch: async (oid, name, checkout) => {
5444
+ const result = await controller.changeBranch({
5445
+ kind: "createBranch",
5446
+ name,
5447
+ at: oid,
5448
+ checkout
5449
+ });
5450
+ if (result.done) report(result, checkout);
5451
+ return result;
5452
+ },
5453
+ onSwitchBranch: (name) => change({
5454
+ kind: "switchBranch",
5455
+ name
5456
+ }),
5457
+ onCreateFromRemote: (name) => change({
5458
+ kind: "createBranch",
5459
+ name,
5460
+ fromRemote: true,
5461
+ checkout: true
5462
+ })
5463
+ } : {})), history.loading && !history.commits.length && /* @__PURE__ */ React.createElement("p", { role: "status" }, labels.historyLoading), history.failed && /* @__PURE__ */ React.createElement("p", null, labels.historyFailed), snackbar);
5464
+ }
5465
+ /**
5466
+ * Renders the history graph of the repository chosen in the panel mounted for `vm`, for a side panel of the host. It
5467
+ * follows branch changes made anywhere, offers the graph's branch actions when the panel allows writes and tells their
5468
+ * results in the branch button's snackbar. Before a repository is chosen it offers to choose one; it renders nothing
5469
+ * while no panel is mounted for `vm`.
5470
+ */
5471
+ function renderHistoryView(createElement, { vm, locale = "en" }) {
5472
+ return createElement(HistoryEntry, {
5473
+ vm,
5474
+ locale
5475
+ });
5476
+ }
5477
+ /**
5478
+ * Follows the Git status of the files under `directory`, as the panel mounted for `vm` last read it, and calls
5479
+ * `listener` with their marks by path relative to `directory`. The marks are empty while no repository is chosen or
5480
+ * `directory` is outside it. Returns the function that stops following.
5481
+ */
5482
+ function followChangeMarks(vm, directory, listener) {
5483
+ let stopped = false;
5484
+ let read;
5485
+ let unfollow = () => {};
5486
+ const update = async (controller) => {
5487
+ const { repository, snapshot } = controller !== null && controller !== void 0 ? controller : {
5488
+ repository: null,
5489
+ snapshot: null
5490
+ };
5491
+ if (snapshot === read) return;
5492
+ read = snapshot;
5493
+ const path = repository && snapshot ? await repository.root.resolve(directory).catch(() => null) : null;
5494
+ if (!stopped && read === snapshot) listener(path ? changeMarks(snapshot.changes, path.join("/")) : /* @__PURE__ */ new Map());
5495
+ };
5496
+ const unregister = findController(vm, (controller) => {
5497
+ unfollow();
5498
+ unfollow = controller ? controller.follow(() => void update(controller)) : () => {};
5499
+ update(controller);
5500
+ });
5501
+ return () => {
5502
+ stopped = true;
5503
+ unregister();
5504
+ unfollow();
5505
+ };
5506
+ }
5507
+ export { BranchButton, GitPanelError, HistoryGraph, changeMarks, createEditorBlockLocator, createReadOnlyFs, createScratchBlocksDiffLocator, createScratchBlocksDiffRenderer, followChangeMarks, laneColors, layoutHistory, mountGitPanel, openProjectComparison, openProjectSourceTabs, openRepository, pullRequestBranch, pullRequestRef, renderBranchButton, renderHistoryView };