@kubohiroya/turbowarp-plus-editor-git 0.15.0 → 0.16.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/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
- import { S as parseProjectSource, a as PullRequestError, c as ProjectSourceComparison, d as organizeProjectChanges, f as projectPropertyLabel, i as createScratchBlocksDiffLocator, l as ProjectSourceReview, n as openProjectComparison, o as createGitHubPullRequests, r as createScratchBlocksDiffRenderer, s as gitHubRepositoryFromOrigin, v as useAssetImage, y as useCostumeImage } from "./project-comparison-LAyP_beL.js";
1
+ import { C as parseProjectSource, 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, w as readProjectSourceDiff, y as useAssetImage } from "./project-comparison-MU0qD2wo.js";
2
2
  import { n as init_objectSpread2, r as __commonJSMin, t as _objectSpread2$1 } from "./objectSpread2-Dkd68nJH.js";
3
- import { i as createReadOnlyFs, r as GitPanelError, t as openRepository } from "./repository-Cx-mxT9l.js";
3
+ import { a as createReadOnlyFs, i as GitPanelError, n as openRepository } from "./repository-BYno-5f8.js";
4
4
  import * as m from "react";
5
5
  import React, { createContext, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
6
6
  import { createPortal, render, unmountComponentAtNode } from "react-dom";
@@ -2672,19 +2672,21 @@ diffMatchPatch.DIFF_INSERT;
2672
2672
  var en$3 = {
2673
2673
  view: "Show changes as",
2674
2674
  blocks: "Blocks",
2675
- text: "Text"
2675
+ text: "Text",
2676
+ openTabs: "Open as tabs"
2676
2677
  };
2677
2678
  var ja$3 = {
2678
2679
  view: "変更の表示",
2679
2680
  blocks: "ブロック",
2680
- text: "テキスト"
2681
+ text: "テキスト",
2682
+ openTabs: "タブで開く"
2681
2683
  };
2682
2684
  /** Project sources are the files the panel opens as projects. */
2683
2685
  var isProjectSource = (path) => path.split("/").pop() === "project.source.json";
2684
2686
  /**
2685
2687
  * Shows one changed file. Project sources are compared as blocks by default, with their text diff one switch away.
2686
2688
  */
2687
- function FileDiffView({ change, diff, labels, locale, renderBlock, loadAsset, locateBlock }) {
2689
+ function FileDiffView({ change, diff, labels, locale, renderBlock, loadAsset, locateBlock, onOpenTabs }) {
2688
2690
  var _change$oldMode, _change$newMode;
2689
2691
  const [view, setView] = useState("blocks");
2690
2692
  const names = locale.startsWith("ja") ? ja$3 : en$3;
@@ -2708,7 +2710,11 @@ function FileDiffView({ change, diff, labels, locale, renderBlock, loadAsset, lo
2708
2710
  "aria-pressed": view === mode,
2709
2711
  "data-git-view": mode,
2710
2712
  onClick: () => setView(mode)
2711
- }, names[mode]))), sources && view === "blocks" ? /* @__PURE__ */ React.createElement(ProjectSourceComparison, {
2713
+ }, names[mode])), onOpenTabs && /* @__PURE__ */ React.createElement("button", {
2714
+ type: "button",
2715
+ "data-git-open-tabs": "",
2716
+ onClick: () => onOpenTabs(change.before === null ? null : sources.before, change.after === null ? null : sources.after)
2717
+ }, names.openTabs)), sources && view === "blocks" ? /* @__PURE__ */ React.createElement(ProjectSourceComparison, {
2712
2718
  renderBlock,
2713
2719
  locale,
2714
2720
  fallback: text,
@@ -3019,6 +3025,84 @@ function ProjectElementView({ changes, selection, locale, renderBlock, onSelectF
3019
3025
  onClick: () => onSelectFile(file)
3020
3026
  }, file.path, " ", /* @__PURE__ */ React.createElement("small", null, labels.groups[file.group])))))));
3021
3027
  }
3028
+ /**
3029
+ * Lays out commits ordered newest first with every child above its parents. Parents missing from `commits`, such as
3030
+ * those on a page not loaded yet or cut off by a shallow clone, keep their lane running to the bottom.
3031
+ */
3032
+ function layoutHistory(commits) {
3033
+ const lanes = [];
3034
+ let nextColor = 0;
3035
+ const free = () => {
3036
+ const index = lanes.indexOf(null);
3037
+ return index === -1 ? lanes.length : index;
3038
+ };
3039
+ const rows = [];
3040
+ for (const commit of commits) {
3041
+ const segments = [];
3042
+ const before = lanes.slice();
3043
+ const waiting = before.flatMap((lane, index) => (lane === null || lane === void 0 ? void 0 : lane.oid) === commit.oid ? [index] : []);
3044
+ const column = waiting.length ? waiting[0] : free();
3045
+ const color = waiting.length ? before[column].color : nextColor++ % 8;
3046
+ for (const [index, lane] of before.entries()) {
3047
+ if (!lane) continue;
3048
+ segments.push({
3049
+ part: "top",
3050
+ from: index,
3051
+ to: lane.oid === commit.oid ? column : index,
3052
+ color: lane.color
3053
+ });
3054
+ }
3055
+ for (const index of waiting) lanes[index] = null;
3056
+ const parents = [...new Set(commit.parents)];
3057
+ lanes[column] = parents.length ? {
3058
+ oid: parents[0],
3059
+ color
3060
+ } : null;
3061
+ const joins = parents.length ? [{
3062
+ index: column,
3063
+ color
3064
+ }] : [];
3065
+ for (const parent of parents.slice(1)) {
3066
+ let index = lanes.findIndex((lane) => (lane === null || lane === void 0 ? void 0 : lane.oid) === parent);
3067
+ if (index === -1) {
3068
+ index = free();
3069
+ lanes[index] = {
3070
+ oid: parent,
3071
+ color: nextColor++ % 8
3072
+ };
3073
+ }
3074
+ joins.push({
3075
+ index,
3076
+ color: lanes[index].color
3077
+ });
3078
+ }
3079
+ while (lanes.length && lanes[lanes.length - 1] === null) lanes.pop();
3080
+ for (const [index, lane] of before.entries()) if (lane && lane.oid !== commit.oid) segments.push({
3081
+ part: "bottom",
3082
+ from: index,
3083
+ to: index,
3084
+ color: lane.color
3085
+ });
3086
+ for (const join of joins) segments.push({
3087
+ part: "bottom",
3088
+ from: column,
3089
+ to: join.index,
3090
+ color: join.color
3091
+ });
3092
+ const width = Math.max(column + 1, ...segments.map((segment) => Math.max(segment.from, segment.to) + 1));
3093
+ rows.push({
3094
+ oid: commit.oid,
3095
+ column,
3096
+ color,
3097
+ segments,
3098
+ width
3099
+ });
3100
+ }
3101
+ return {
3102
+ rows,
3103
+ width: Math.max(0, ...rows.map((row) => row.width))
3104
+ };
3105
+ }
3022
3106
  // src/messages.ts
3023
3107
  var messages = {
3024
3108
  en: {
@@ -3064,6 +3148,10 @@ var messages = {
3064
3148
  outdated: "The repository changed outside the Editor. Refresh to read the current state.",
3065
3149
  unsupported: "This repository contains an entry the browser cannot represent.",
3066
3150
  commitMessage: "Write a message for this record.",
3151
+ switchUnsafe: "Stop the project, and save or record (or undo) every change, before switching branches.",
3152
+ branchExists: "A branch with this name already exists.",
3153
+ 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.",
3154
+ branchName: "Git cannot use this name for a branch.",
3067
3155
  pullUnsafe: "Stop the project, and save or undo unsaved edits and unrecorded changes, before getting changes from GitHub (pull).",
3068
3156
  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.",
3069
3157
  session: "Reconnect GitHub for this repository and requested permission.",
@@ -3082,6 +3170,16 @@ var messages = {
3082
3170
  saveFirst: "Save the project before adding its changes to the next record.",
3083
3171
  author: "Set user.name and user.email for this repository in Git before recording. The Editor does not change Git settings.",
3084
3172
  nothingStaged: "Add changes to the next record first.",
3173
+ switchTo: "Switch",
3174
+ currentBranch: "current",
3175
+ createBranch: "Create Branch",
3176
+ newBranch: "New branch name",
3177
+ switchAfterCreate: "Switch to it",
3178
+ createFromRemote: "Create and Switch",
3179
+ remoteBranches: "Branches on GitHub (origin)",
3180
+ switched: "Switched branches. Use Reload Project to open the files of this branch.",
3181
+ branchCreated: "Created the branch.",
3182
+ projectMissing: "The open project folder is not on this branch. Switch anyway?",
3085
3183
  pulledProject: "Got the changes from GitHub. Use Reload Project to open the updated files.",
3086
3184
  confirmPush: "Send the records on this branch to GitHub (push)?",
3087
3185
  details: "Git details",
@@ -3101,7 +3199,19 @@ var messages = {
3101
3199
  upstreamUnknown: "Not checked with GitHub yet",
3102
3200
  records: "records",
3103
3201
  checkedAt: "as of the last check",
3104
- failed: "Could not read the repository. Check folder access and refresh."
3202
+ failed: "Could not read the repository. Check folder access and refresh.",
3203
+ historyMore: "Show more",
3204
+ historyLoading: "Reading records…",
3205
+ historyFailed: "Could not read the records.",
3206
+ historyActions: "Actions",
3207
+ historyCreateHere: "Create branch here…",
3208
+ historySwitch: "Switch to {name}",
3209
+ historyCreateFromRemote: "Create and switch to {name}",
3210
+ historyCopy: "Copy commit ID",
3211
+ historyCopied: "Copied the commit ID.",
3212
+ historyCopyFailed: "Could not copy the commit ID.",
3213
+ historyRefs: "Branches",
3214
+ cancel: "Cancel"
3105
3215
  },
3106
3216
  ja: {
3107
3217
  pullRequests: "プルリクエスト",
@@ -3146,6 +3256,10 @@ var messages = {
3146
3256
  outdated: "Editor の外でリポジトリが変更されました。再取得すると最新の状態を読み込みます。",
3147
3257
  unsupported: "ブラウザーで扱えない種類の Git エントリーがあります。",
3148
3258
  commitMessage: "この記録の説明を書いてください。",
3259
+ switchUnsafe: "ブランチを切り替える前に、実行を止め、すべての変更を保存して記録(または取り消し)してください。",
3260
+ branchExists: "同じ名前のブランチがすでにあります。",
3261
+ locked: "ほかの Git の操作が、このリポジトリを使っています(ロックファイル、またはマージ・cherry-pick・revert・rebase の途中)。終わってから、もう一度試してください。",
3262
+ branchName: "この名前は Git のブランチ名に使えません。",
3149
3263
  pullUnsafe: "GitHub から取り込む前に、実行を止め、保存していない編集とまだ記録していない変更をなくしてください。",
3150
3264
  diverged: "このブランチと GitHub の両方に新しい記録があるため、ここでは取り込めません(pull は fast-forward のみ)。ブランチとファイルはそのままです。",
3151
3265
  session: "このリポジトリと操作に必要な権限で GitHub に接続してください。",
@@ -3164,6 +3278,16 @@ var messages = {
3164
3278
  saveFirst: "次の記録に入れる前に、作品を保存してください。",
3165
3279
  author: "記録する前に、Git でこのリポジトリの user.name と user.email を設定してください。Editor は Git の設定を変えません。",
3166
3280
  nothingStaged: "先に、変更を次の記録に入れてください。",
3281
+ switchTo: "切り替える",
3282
+ currentBranch: "現在",
3283
+ createBranch: "ブランチを作る",
3284
+ newBranch: "新しいブランチの名前",
3285
+ switchAfterCreate: "作ったら切り替える",
3286
+ createFromRemote: "作って切り替える",
3287
+ remoteBranches: "GitHub(origin)のブランチ",
3288
+ switched: "ブランチを切り替えました。「作品を再読込」で、このブランチのファイルを開けます。",
3289
+ branchCreated: "ブランチを作りました。",
3290
+ projectMissing: "開いている作品フォルダは、このブランチにありません。切り替えますか?",
3167
3291
  pulledProject: "GitHub から取り込みました。「作品を再読込」で更新したファイルを開けます。",
3168
3292
  confirmPush: "このブランチの記録を GitHub に送りますか(push)?",
3169
3293
  details: "Git の詳細",
@@ -3183,10 +3307,354 @@ var messages = {
3183
3307
  upstreamUnknown: "まだ GitHub と確かめていない",
3184
3308
  records: "件",
3185
3309
  checkedAt: "最後に確かめた時点",
3186
- failed: "リポジトリを読み込めませんでした。フォルダへのアクセスを確認し、再取得してください。"
3310
+ failed: "リポジトリを読み込めませんでした。フォルダへのアクセスを確認し、再取得してください。",
3311
+ historyMore: "さらに表示",
3312
+ historyLoading: "記録を読込中…",
3313
+ historyFailed: "記録を読み込めませんでした。",
3314
+ historyActions: "操作",
3315
+ historyCreateHere: "ここからブランチを作る…",
3316
+ historySwitch: "{name} に切り替える",
3317
+ historyCreateFromRemote: "{name} を作って切り替える",
3318
+ historyCopy: "コミットの ID をコピー",
3319
+ historyCopied: "コミットの ID をコピーしました。",
3320
+ historyCopyFailed: "コミットの ID をコピーできませんでした。",
3321
+ historyRefs: "ブランチ",
3322
+ cancel: "キャンセル"
3187
3323
  }
3188
3324
  };
3189
3325
  var labelsFor = (locale) => locale.startsWith("ja") ? messages.ja : messages.en;
3326
+ // src/history-graph-view.tsx
3327
+ /** Lane colors that read on light and dark backgrounds. */
3328
+ var laneColors = [
3329
+ "#2f7ed8",
3330
+ "#d9480f",
3331
+ "#2b9348",
3332
+ "#9c36b5",
3333
+ "#c2255c",
3334
+ "#0c8599",
3335
+ "#b7791f",
3336
+ "#5c7cfa"
3337
+ ];
3338
+ var column = 14;
3339
+ var rowHeight = 44;
3340
+ var middle = rowHeight / 2;
3341
+ var x = (index) => column / 2 + index * column;
3342
+ function Lanes({ row, width }) {
3343
+ return /* @__PURE__ */ React.createElement("svg", {
3344
+ className: "twgit-graph-lanes",
3345
+ "aria-hidden": "true",
3346
+ focusable: "false",
3347
+ width: width * column,
3348
+ height: rowHeight,
3349
+ viewBox: `0 0 ${width * column} ${rowHeight}`
3350
+ }, row.segments.map((segment, index) => {
3351
+ const [top, bottom] = segment.part === "top" ? [0, middle] : [middle, rowHeight];
3352
+ const from = x(segment.from), to = x(segment.to), bend = (top + bottom) / 2;
3353
+ const d = from === to ? `M${from} ${top}V${bottom}` : `M${from} ${top}C${from} ${bend} ${to} ${bend} ${to} ${bottom}`;
3354
+ return /* @__PURE__ */ React.createElement("path", {
3355
+ key: index,
3356
+ d,
3357
+ fill: "none",
3358
+ stroke: laneColors[segment.color % laneColors.length],
3359
+ strokeWidth: 2
3360
+ });
3361
+ }), /* @__PURE__ */ React.createElement("circle", {
3362
+ cx: x(row.column),
3363
+ cy: middle,
3364
+ r: 4.5,
3365
+ fill: laneColors[row.color % laneColors.length],
3366
+ stroke: "Canvas",
3367
+ strokeWidth: 1.5
3368
+ }));
3369
+ }
3370
+ /**
3371
+ * The history as a graph: one row per commit with its lanes drawn in SVG, a button that selects the commit and a
3372
+ * button that opens its actions. ArrowUp and ArrowDown move between commits, Home and End to the ends; Enter or
3373
+ * Space selects; the context menu key or Shift+F10 opens the actions.
3374
+ */
3375
+ function HistoryGraph(props) {
3376
+ const { commits, selected = null, onSelect, locale = "en", busy = false } = props;
3377
+ const labels = labelsFor(locale);
3378
+ const layout = useMemo(() => layoutHistory(commits), [commits]);
3379
+ const rows = useRef([]);
3380
+ const menuButtons = useRef([]);
3381
+ const popup = useRef(null);
3382
+ const [active, setActive] = useState(null);
3383
+ const [menu, setMenu] = useState(null);
3384
+ const [branchName, setBranchName] = useState("");
3385
+ const [checkout, setCheckout] = useState(true);
3386
+ const [status, setStatus] = useState("");
3387
+ const [creating, setCreating] = useState(false);
3388
+ const [createError, setCreateError] = useState("");
3389
+ const mounted = useRef(true);
3390
+ useEffect(() => () => {
3391
+ mounted.current = false;
3392
+ }, []);
3393
+ const focusIndex = Math.max(0, commits.findIndex((commit) => commit.oid === (active !== null && active !== void 0 ? active : selected)));
3394
+ const locals = useMemo(() => {
3395
+ var _props$localBranches;
3396
+ return new Set((_props$localBranches = props.localBranches) !== null && _props$localBranches !== void 0 ? _props$localBranches : commits.flatMap((commit) => commit.refs.filter((ref) => ref.kind === "branch").map((ref) => ref.name)));
3397
+ }, [commits, props.localBranches]);
3398
+ useEffect(() => {
3399
+ var _element$querySelecto;
3400
+ const element = popup.current;
3401
+ if (!element) return;
3402
+ (_element$querySelecto = element.querySelector((menu === null || menu === void 0 ? void 0 : menu.mode) === "create" ? "input" : "[role=\"menuitem\"]")) === null || _element$querySelecto === void 0 || _element$querySelecto.focus();
3403
+ }, [menu === null || menu === void 0 ? void 0 : menu.oid, menu === null || menu === void 0 ? void 0 : menu.mode]);
3404
+ useEffect(() => {
3405
+ var _popup$current;
3406
+ if (!createError || creating) return;
3407
+ const input = (_popup$current = popup.current) === null || _popup$current === void 0 ? void 0 : _popup$current.querySelector("input:not([type])");
3408
+ input === null || input === void 0 || input.focus();
3409
+ input === null || input === void 0 || input.select();
3410
+ }, [createError, creating]);
3411
+ const date = (timestamp) => (/* @__PURE__ */ new Date(timestamp * 1e3)).toLocaleString(locale, {
3412
+ dateStyle: "medium",
3413
+ timeStyle: "short"
3414
+ });
3415
+ const refName = (ref) => ref.current && ref.kind === "branch" ? `${ref.name} (${labels.currentBranch})` : ref.name;
3416
+ const closeMenu = (focus) => {
3417
+ var _current$index;
3418
+ const index = commits.findIndex((commit) => commit.oid === (menu === null || menu === void 0 ? void 0 : menu.oid));
3419
+ setMenu(null);
3420
+ if (focus && index !== -1) (_current$index = (focus === "menu" ? menuButtons : rows).current[index]) === null || _current$index === void 0 || _current$index.focus();
3421
+ };
3422
+ const actionsFor = (commit) => {
3423
+ const actions = [];
3424
+ if (props.onCreateBranch) actions.push({
3425
+ id: "create",
3426
+ label: labels.historyCreateHere,
3427
+ run: () => {
3428
+ setBranchName("");
3429
+ setCheckout(true);
3430
+ setCreateError("");
3431
+ setMenu({
3432
+ oid: commit.oid,
3433
+ mode: "create"
3434
+ });
3435
+ }
3436
+ });
3437
+ for (const ref of commit.refs) {
3438
+ if (ref.kind === "branch" && !ref.current && props.onSwitchBranch) {
3439
+ const run = props.onSwitchBranch;
3440
+ actions.push({
3441
+ id: "switch:" + ref.name,
3442
+ label: labels.historySwitch.replace("{name}", ref.name),
3443
+ run: () => {
3444
+ closeMenu("row");
3445
+ run(ref.name);
3446
+ }
3447
+ });
3448
+ }
3449
+ const name = ref.name.slice(7);
3450
+ if (ref.kind === "remote" && !locals.has(name) && props.onCreateFromRemote) {
3451
+ const run = props.onCreateFromRemote;
3452
+ actions.push({
3453
+ id: "remote:" + name,
3454
+ label: labels.historyCreateFromRemote.replace("{name}", name),
3455
+ run: () => {
3456
+ closeMenu("row");
3457
+ run(name);
3458
+ }
3459
+ });
3460
+ }
3461
+ }
3462
+ actions.push({
3463
+ id: "copy",
3464
+ label: labels.historyCopy,
3465
+ run: () => {
3466
+ var _globalThis$navigator;
3467
+ closeMenu("menu");
3468
+ const clipboard = (_globalThis$navigator = globalThis.navigator) === null || _globalThis$navigator === void 0 ? void 0 : _globalThis$navigator.clipboard;
3469
+ Promise.resolve().then(() => clipboard.writeText(commit.oid)).then(() => setStatus(labels.historyCopied), () => setStatus(labels.historyCopyFailed));
3470
+ }
3471
+ });
3472
+ return actions;
3473
+ };
3474
+ const onRowKey = (event, index) => {
3475
+ const target = {
3476
+ ArrowDown: index + 1,
3477
+ ArrowUp: index - 1,
3478
+ Home: 0,
3479
+ End: commits.length - 1
3480
+ }[event.key];
3481
+ if (target !== void 0) {
3482
+ var _rows$current$Math$mi;
3483
+ event.preventDefault();
3484
+ (_rows$current$Math$mi = rows.current[Math.min(commits.length - 1, Math.max(0, target))]) === null || _rows$current$Math$mi === void 0 || _rows$current$Math$mi.focus();
3485
+ } else if (event.key === "ContextMenu" || event.key === "F10" && event.shiftKey) {
3486
+ event.preventDefault();
3487
+ setMenu({
3488
+ oid: commits[index].oid,
3489
+ mode: "menu"
3490
+ });
3491
+ }
3492
+ };
3493
+ const onMenuKey = (event) => {
3494
+ if (event.key === "Escape") {
3495
+ event.preventDefault();
3496
+ event.stopPropagation();
3497
+ if (!creating) closeMenu("menu");
3498
+ return;
3499
+ }
3500
+ if ((menu === null || menu === void 0 ? void 0 : menu.mode) !== "menu") return;
3501
+ if (event.key === "Tab") {
3502
+ setMenu(null);
3503
+ return;
3504
+ }
3505
+ const items = [...event.currentTarget.querySelectorAll("[role=\"menuitem\"]:not(:disabled)")];
3506
+ const index = items.indexOf(event.target);
3507
+ const target = {
3508
+ ArrowDown: index + 1,
3509
+ ArrowUp: index - 1,
3510
+ Home: 0,
3511
+ End: items.length - 1
3512
+ }[event.key];
3513
+ if (target === void 0 || !items.length) return;
3514
+ event.preventDefault();
3515
+ items[(target + items.length) % items.length].focus();
3516
+ };
3517
+ const onMenuBlur = (event) => {
3518
+ var _event$currentTarget$;
3519
+ const next = event.relatedTarget;
3520
+ if (!next || !((_event$currentTarget$ = event.currentTarget.parentElement) === null || _event$currentTarget$ === void 0 ? void 0 : _event$currentTarget$.contains(next))) setMenu(null);
3521
+ };
3522
+ const submitBranch = async (event, oid) => {
3523
+ var _result$error;
3524
+ event.preventDefault();
3525
+ const name = branchName.trim();
3526
+ if (!name || !props.onCreateBranch || creating) return;
3527
+ setCreating(true);
3528
+ setCreateError("");
3529
+ let result;
3530
+ try {
3531
+ result = await props.onCreateBranch(oid, name, checkout);
3532
+ } catch (error) {
3533
+ result = {
3534
+ done: false,
3535
+ error: error instanceof Error && error.message ? error.message : labels.failed
3536
+ };
3537
+ }
3538
+ if (!mounted.current) return;
3539
+ setCreating(false);
3540
+ if (!result || result.done) closeMenu("menu");
3541
+ else setCreateError((_result$error = result.error) !== null && _result$error !== void 0 ? _result$error : "");
3542
+ };
3543
+ rows.current.length = commits.length;
3544
+ menuButtons.current.length = commits.length;
3545
+ return /* @__PURE__ */ React.createElement("div", { className: "twgit-graph" }, /* @__PURE__ */ React.createElement("ol", { className: "twgit-graph-list" }, commits.map((commit, index) => {
3546
+ const row = layout.rows[index];
3547
+ const refs = commit.refs.map(refName);
3548
+ const name = [
3549
+ commit.message,
3550
+ commit.author,
3551
+ date(commit.timestamp),
3552
+ ...refs.length ? [`${labels.historyRefs}: ${refs.join(", ")}`] : []
3553
+ ].join(", ");
3554
+ const open = (menu === null || menu === void 0 ? void 0 : menu.oid) === commit.oid ? menu : null;
3555
+ const actions = (open === null || open === void 0 ? void 0 : open.mode) === "menu" ? actionsFor(commit) : [];
3556
+ return /* @__PURE__ */ React.createElement("li", {
3557
+ key: commit.oid,
3558
+ className: "twgit-graph-row"
3559
+ }, /* @__PURE__ */ React.createElement(Lanes, {
3560
+ row,
3561
+ width: layout.width
3562
+ }), /* @__PURE__ */ React.createElement("button", {
3563
+ type: "button",
3564
+ className: "twgit-graph-commit",
3565
+ ref: (element) => {
3566
+ rows.current[index] = element;
3567
+ },
3568
+ tabIndex: index === focusIndex ? 0 : -1,
3569
+ "aria-pressed": selected === commit.oid,
3570
+ "aria-disabled": busy || void 0,
3571
+ "aria-label": name,
3572
+ title: commit.oid,
3573
+ "data-oid": commit.oid,
3574
+ onFocus: () => setActive(commit.oid),
3575
+ onKeyDown: (event) => onRowKey(event, index),
3576
+ onClick: () => {
3577
+ if (!busy) onSelect(commit.oid);
3578
+ }
3579
+ }, /* @__PURE__ */ React.createElement("span", { className: "twgit-graph-subject" }, commit.refs.map((ref) => /* @__PURE__ */ React.createElement("span", {
3580
+ key: ref.kind + ref.name,
3581
+ className: `twgit-graph-ref twgit-graph-ref-${ref.kind}${ref.current ? " twgit-graph-ref-current" : ""}`
3582
+ }, refName(ref))), commit.message), /* @__PURE__ */ React.createElement("span", { className: "twgit-graph-meta" }, commit.author, " · ", date(commit.timestamp))), /* @__PURE__ */ React.createElement("button", {
3583
+ type: "button",
3584
+ className: "twgit-graph-menu-button",
3585
+ ref: (element) => {
3586
+ menuButtons.current[index] = element;
3587
+ },
3588
+ tabIndex: index === focusIndex || open ? 0 : -1,
3589
+ "aria-haspopup": "menu",
3590
+ "aria-expanded": (open === null || open === void 0 ? void 0 : open.mode) === "menu",
3591
+ "aria-label": `${labels.historyActions}: ${commit.message}`,
3592
+ onClick: () => setMenu(open ? null : {
3593
+ oid: commit.oid,
3594
+ mode: "menu"
3595
+ }),
3596
+ onKeyDown: (event) => {
3597
+ if (event.key === "ArrowDown" || event.key === "ArrowUp") {
3598
+ event.preventDefault();
3599
+ setMenu({
3600
+ oid: commit.oid,
3601
+ mode: "menu"
3602
+ });
3603
+ }
3604
+ }
3605
+ }, "⋯"), (open === null || open === void 0 ? void 0 : open.mode) === "menu" && /* @__PURE__ */ React.createElement("div", {
3606
+ ref: popup,
3607
+ className: "twgit-graph-popup",
3608
+ role: "menu",
3609
+ "aria-label": labels.historyActions,
3610
+ onKeyDown: onMenuKey,
3611
+ onBlur: onMenuBlur
3612
+ }, actions.map((action) => /* @__PURE__ */ React.createElement("button", {
3613
+ key: action.id,
3614
+ type: "button",
3615
+ role: "menuitem",
3616
+ tabIndex: -1,
3617
+ "data-action": action.id,
3618
+ disabled: busy && action.id !== "copy",
3619
+ onClick: action.run
3620
+ }, action.label))), (open === null || open === void 0 ? void 0 : open.mode) === "create" && /* @__PURE__ */ React.createElement("div", {
3621
+ ref: popup,
3622
+ className: "twgit-graph-popup",
3623
+ role: "dialog",
3624
+ "aria-label": labels.historyCreateHere,
3625
+ onKeyDown: onMenuKey
3626
+ }, /* @__PURE__ */ React.createElement("form", { onSubmit: (event) => void submitBranch(event, commit.oid) }, /* @__PURE__ */ React.createElement("label", null, labels.newBranch, " ", /* @__PURE__ */ React.createElement("input", {
3627
+ value: branchName,
3628
+ required: true,
3629
+ disabled: creating,
3630
+ "aria-invalid": createError ? true : void 0,
3631
+ onChange: (event) => setBranchName(event.currentTarget.value)
3632
+ })), /* @__PURE__ */ React.createElement("label", null, /* @__PURE__ */ React.createElement("input", {
3633
+ type: "checkbox",
3634
+ checked: checkout,
3635
+ disabled: creating,
3636
+ onChange: (event) => setCheckout(event.currentTarget.checked)
3637
+ }), " ", labels.switchAfterCreate), createError && /* @__PURE__ */ React.createElement("p", {
3638
+ className: "twgit-graph-form-error",
3639
+ role: "alert"
3640
+ }, createError), /* @__PURE__ */ React.createElement("span", { className: "twgit-graph-form-actions" }, /* @__PURE__ */ React.createElement("button", {
3641
+ type: "submit",
3642
+ disabled: creating || busy || !branchName.trim()
3643
+ }, labels.createBranch), /* @__PURE__ */ React.createElement("button", {
3644
+ type: "button",
3645
+ disabled: creating,
3646
+ onClick: () => closeMenu("menu")
3647
+ }, labels.cancel)))));
3648
+ })), props.hasMore && props.onShowMore && /* @__PURE__ */ React.createElement("button", {
3649
+ type: "button",
3650
+ className: "twgit-graph-more",
3651
+ disabled: props.loadingMore,
3652
+ onClick: props.onShowMore
3653
+ }, props.loadingMore ? labels.historyLoading : labels.historyMore), /* @__PURE__ */ React.createElement("p", {
3654
+ className: "twgit-graph-status",
3655
+ role: "status"
3656
+ }, status));
3657
+ }
3190
3658
  // src/connection-settings.tsx
3191
3659
  var en$1 = {
3192
3660
  title: "GitHub connection",
@@ -3343,7 +3811,8 @@ var en = {
3343
3811
  rateLimit: "The public GitHub API rate limit was reached. Try again after the limit resets.",
3344
3812
  failed: "Could not read GitHub. Try again.",
3345
3813
  changed: "The pull request changed while it was being read. Open it again.",
3346
- tooManyFiles: "GitHub only returns the first 3,000 changed files. This pull request exceeds that limit."
3814
+ tooManyFiles: "GitHub only returns the first 3,000 changed files. This pull request exceeds that limit.",
3815
+ openTabs: "Open as tabs"
3347
3816
  };
3348
3817
  var ja = {
3349
3818
  title: "プルリクエスト",
@@ -3364,12 +3833,13 @@ var ja = {
3364
3833
  rateLimit: "公開 GitHub API の利用上限に達しました。上限の解除後に再試行してください。",
3365
3834
  failed: "GitHub から取得できませんでした。再試行してください。",
3366
3835
  changed: "取得中にプルリクエストが更新されました。もう一度開いてください。",
3367
- tooManyFiles: "変更ファイルが GitHub API の取得上限である 3,000 件を超えています。"
3836
+ tooManyFiles: "変更ファイルが GitHub API の取得上限である 3,000 件を超えています。",
3837
+ openTabs: "タブで開く"
3368
3838
  };
3369
3839
  function errorMessage(error, labels) {
3370
3840
  return error instanceof PullRequestError ? labels[error.code] : labels.failed;
3371
3841
  }
3372
- function PullRequests({ name, labels, locale, renderBlock, locateFor }) {
3842
+ function PullRequests({ name, labels, locale, renderBlock, locateFor, openTabs }) {
3373
3843
  const client = useMemo(() => createGitHubPullRequests(name), [name]);
3374
3844
  const [page, setPage] = useState(1);
3375
3845
  const [listing, setListing] = useState();
@@ -3454,7 +3924,26 @@ function PullRequests({ name, labels, locale, renderBlock, locateFor }) {
3454
3924
  href: pr.url,
3455
3925
  target: "_blank",
3456
3926
  rel: "noreferrer"
3457
- }, labels.onGitHub)), /* @__PURE__ */ React.createElement(ProjectSourceReview, {
3927
+ }, labels.onGitHub), openTabs && /* @__PURE__ */ React.createElement("button", {
3928
+ type: "button",
3929
+ "data-git-open-tabs": "",
3930
+ onClick: () => openTabs(`${name}#${pr.number}@${pr.head.sha}`, pr.files.flatMap((file) => {
3931
+ if (!("patch" in file)) return [];
3932
+ let projects;
3933
+ try {
3934
+ projects = readProjectSourceDiff(file.patch, file.beforeSource);
3935
+ } catch (_unused) {
3936
+ return [];
3937
+ }
3938
+ return projects ? [{
3939
+ path: file.path,
3940
+ before: projects[0],
3941
+ after: projects[1],
3942
+ loadAsset: file.loadAsset,
3943
+ locateBlock: locateFor === null || locateFor === void 0 ? void 0 : locateFor(file.path, pr.head.sha)
3944
+ }] : [];
3945
+ }), `${name}#${pr.number}`)
3946
+ }, labels.openTabs)), /* @__PURE__ */ React.createElement(ProjectSourceReview, {
3458
3947
  title: pr.title,
3459
3948
  description: pr.description,
3460
3949
  files: locateFor ? pr.files.map((file) => "patch" in file ? _objectSpread2$1(_objectSpread2$1({}, file), {}, { locateBlock: locateFor(file.path, pr.head.sha) }) : file) : pr.files,
@@ -3463,7 +3952,7 @@ function PullRequests({ name, labels, locale, renderBlock, locateFor }) {
3463
3952
  rememberAs: `${name}#${pr.number}`
3464
3953
  }))));
3465
3954
  }
3466
- function PullRequestReviewPanel({ repository, locale, renderBlock, locateFor }) {
3955
+ function PullRequestReviewPanel({ repository, locale, renderBlock, locateFor, openTabs }) {
3467
3956
  const labels = locale.startsWith("ja") ? ja : en;
3468
3957
  const [origin, setOrigin] = useState();
3469
3958
  useEffect(() => {
@@ -3498,7 +3987,8 @@ function PullRequestReviewPanel({ repository, locale, renderBlock, locateFor })
3498
3987
  labels,
3499
3988
  locale,
3500
3989
  renderBlock,
3501
- locateFor
3990
+ locateFor,
3991
+ openTabs
3502
3992
  });
3503
3993
  }
3504
3994
  // src/editor-block-locator.ts
@@ -3554,7 +4044,7 @@ var initial = {
3554
4044
  var controllers = /* @__PURE__ */ new WeakMap();
3555
4045
  var watchInterval = 2e3;
3556
4046
  function GitPanel({ vm, document, host, controller }) {
3557
- var _state$snapshot, _state$repository, _state$snapshot$head$, _state$snapshot$head, _state$snapshot3;
4047
+ var _state$snapshot, _state$repository, _state$snapshot$head$, _state$snapshot$head;
3558
4048
  const [visible, setVisible] = useState(false);
3559
4049
  const [slot, setSlot] = useState(null);
3560
4050
  const shown = visible || slot !== null;
@@ -3587,9 +4077,10 @@ function GitPanel({ vm, document, host, controller }) {
3587
4077
  const handled = useRef(null);
3588
4078
  const labels = labelsFor(locale);
3589
4079
  const background = useRef(null);
4080
+ /** Whether an operation ran to the end, or the message the panel shows for why it did not. */
3590
4081
  const run = async (operation, automatic = false) => {
3591
4082
  if (running.current && !automatic && background.current) await background.current;
3592
- if (running.current || !alive.current) return;
4083
+ if (running.current || !alive.current) return { done: false };
3593
4084
  let settle = () => {};
3594
4085
  if (automatic) background.current = new Promise((resolve) => {
3595
4086
  settle = resolve;
@@ -3604,12 +4095,13 @@ function GitPanel({ vm, document, host, controller }) {
3604
4095
  try {
3605
4096
  const update = await operation();
3606
4097
  if (epoch.current === ticket) setState((previous) => _objectSpread2$1(_objectSpread2$1(_objectSpread2$1({}, previous), update), {}, { busy: automatic ? previous.busy : false }));
4098
+ return { done: true };
3607
4099
  } catch (error) {
3608
- if (epoch.current !== ticket) return;
4100
+ if (epoch.current !== ticket) return { done: false };
3609
4101
  const code = error instanceof GitPanelError ? error.code : "failed";
3610
4102
  if (automatic && code === "changed") {
3611
4103
  handled.current = null;
3612
- return;
4104
+ return { done: false };
3613
4105
  }
3614
4106
  const message = labels[code] || labels.failed;
3615
4107
  setState((previous) => _objectSpread2$1(_objectSpread2$1({}, previous), {}, {
@@ -3617,6 +4109,10 @@ function GitPanel({ vm, document, host, controller }) {
3617
4109
  busy: automatic ? previous.busy : false,
3618
4110
  diff: null
3619
4111
  }));
4112
+ return {
4113
+ done: false,
4114
+ error: message
4115
+ };
3620
4116
  } finally {
3621
4117
  running.current = false;
3622
4118
  if (epoch.current !== ticket && !refreshPending.current && alive.current) setState((previous) => _objectSpread2$1(_objectSpread2$1({}, previous), {}, { busy: false }));
@@ -3901,13 +4397,15 @@ function GitPanel({ vm, document, host, controller }) {
3901
4397
  const [message, setMessage] = useState("");
3902
4398
  const localOperation = (operation, notice) => {
3903
4399
  const repository = current.current.repository;
3904
- if (!repository || !host.localWrite) return Promise.resolve();
4400
+ if (!repository || !host.localWrite) return Promise.resolve({ done: false });
3905
4401
  return run(async () => {
3906
4402
  var _current$current$proj2;
3907
4403
  const ticket = epoch.current;
3908
4404
  await repository.localOperation(operation, () => {
4405
+ var _host$isProjectRunnin2;
3909
4406
  if (epoch.current !== ticket) throw new GitPanelError("changed");
3910
4407
  if (operation.kind === "stage" && host.isProjectDirty()) throw new GitPanelError("saveFirst");
4408
+ if ((operation.kind === "switchBranch" || operation.kind === "createBranch" && operation.checkout) && (host.isProjectDirty() || ((_host$isProjectRunnin2 = host.isProjectRunning) === null || _host$isProjectRunnin2 === void 0 ? void 0 : _host$isProjectRunnin2.call(host)))) throw new GitPanelError("switchUnsafe");
3911
4409
  });
3912
4410
  if (operation.kind === "commit") setMessage("");
3913
4411
  const snapshot = await repository.read((_current$current$proj2 = current.current.project) !== null && _current$current$proj2 !== void 0 ? _current$current$proj2 : "");
@@ -3921,6 +4419,80 @@ function GitPanel({ vm, document, host, controller }) {
3921
4419
  };
3922
4420
  });
3923
4421
  };
4422
+ const [newBranch, setNewBranch] = useState("");
4423
+ const [switchAfterCreate, setSwitchAfterCreate] = useState(true);
4424
+ const changeBranch = async (operation) => {
4425
+ var _document$defaultView2;
4426
+ const repository = current.current.repository;
4427
+ const project = current.current.project;
4428
+ const switching = operation.kind === "switchBranch" || operation.checkout;
4429
+ if (!repository) return { done: false };
4430
+ const at = operation.kind === "createBranch" ? operation.at : void 0;
4431
+ if (switching && project && !(operation.kind === "createBranch" && !operation.fromRemote && !at) && !await (at ? repository.hasFolder(at, project, "commit") : repository.hasFolder(operation.name, project, operation.kind === "createBranch" && operation.fromRemote)) && !((_document$defaultView2 = document.defaultView) === null || _document$defaultView2 === void 0 ? void 0 : _document$defaultView2.confirm(labels.projectMissing))) return { done: false };
4432
+ const result = await localOperation(operation, switching ? labels.switched : labels.branchCreated);
4433
+ if (operation.kind === "createBranch" && !operation.fromRemote && !at) setNewBranch("");
4434
+ return result;
4435
+ };
4436
+ const [history, setHistory] = useState({
4437
+ commits: [],
4438
+ cursor: null,
4439
+ loading: false,
4440
+ failed: false
4441
+ });
4442
+ const historyTicket = useRef(0);
4443
+ useEffect(() => {
4444
+ const ticket = ++historyTicket.current;
4445
+ const repository = state.repository;
4446
+ if (!repository || !state.snapshot) {
4447
+ setHistory({
4448
+ commits: [],
4449
+ cursor: null,
4450
+ loading: false,
4451
+ failed: false
4452
+ });
4453
+ return;
4454
+ }
4455
+ setHistory((previous) => _objectSpread2$1(_objectSpread2$1({}, previous), {}, {
4456
+ cursor: null,
4457
+ loading: true,
4458
+ failed: false
4459
+ }));
4460
+ repository.history().then((page) => {
4461
+ if (historyTicket.current === ticket) setHistory({
4462
+ commits: page.commits,
4463
+ cursor: page.cursor,
4464
+ loading: false,
4465
+ failed: false
4466
+ });
4467
+ }, () => {
4468
+ if (historyTicket.current === ticket) setHistory({
4469
+ commits: [],
4470
+ cursor: null,
4471
+ loading: false,
4472
+ failed: true
4473
+ });
4474
+ });
4475
+ }, [state.repository, state.snapshot]);
4476
+ const showMoreHistory = () => {
4477
+ const repository = state.repository;
4478
+ if (!repository || !history.cursor || history.loading) return;
4479
+ const ticket = historyTicket.current;
4480
+ setHistory((previous) => _objectSpread2$1(_objectSpread2$1({}, previous), {}, { loading: true }));
4481
+ repository.history({ after: history.cursor }).then((page) => {
4482
+ if (historyTicket.current === ticket) setHistory((previous) => ({
4483
+ commits: [...previous.commits, ...page.commits],
4484
+ cursor: page.cursor,
4485
+ loading: false,
4486
+ failed: false
4487
+ }));
4488
+ }, () => {
4489
+ if (historyTicket.current === ticket) setHistory((previous) => _objectSpread2$1(_objectSpread2$1({}, previous), {}, {
4490
+ cursor: null,
4491
+ loading: false,
4492
+ failed: true
4493
+ }));
4494
+ });
4495
+ };
3924
4496
  const focusTrap = (event) => {
3925
4497
  if (event.key === "Escape") {
3926
4498
  event.stopPropagation();
@@ -3928,7 +4500,7 @@ function GitPanel({ vm, document, host, controller }) {
3928
4500
  return;
3929
4501
  }
3930
4502
  if (event.key !== "Tab") return;
3931
- const items = [...event.currentTarget.querySelectorAll("button:not(:disabled), input:not(:disabled), select:not(:disabled), a[href], summary, [tabindex=\"0\"]")].filter((item) => item.getClientRects().length > 0);
4503
+ const items = [...event.currentTarget.querySelectorAll("button:not(:disabled):not([tabindex=\"-1\"]), input:not(:disabled), select:not(:disabled), a[href], summary, [tabindex=\"0\"]")].filter((item) => item.getClientRects().length > 0);
3932
4504
  const first = items[0], last = items[items.length - 1];
3933
4505
  if (event.shiftKey && (document.activeElement === first || document.activeElement === event.currentTarget)) {
3934
4506
  event.preventDefault();
@@ -3949,6 +4521,7 @@ function GitPanel({ vm, document, host, controller }) {
3949
4521
  "unstaged",
3950
4522
  "untracked"
3951
4523
  ];
4524
+ const branching = !!host.localWrite && !!state.snapshot;
3952
4525
  const writable = !!host.localWrite && !state.commit && !!((_state$snapshot = state.snapshot) === null || _state$snapshot === void 0 ? void 0 : _state$snapshot.branch);
3953
4526
  const unrecorded = [...new Set(state.changes.filter((change) => change.group === "unstaged" || change.group === "untracked").map((change) => change.path))];
3954
4527
  const staged = state.changes.some((change) => change.group === "staged");
@@ -3960,6 +4533,29 @@ function GitPanel({ vm, document, host, controller }) {
3960
4533
  return locator && path === projectFile && ((_state$snapshot2 = state.snapshot) === null || _state$snapshot2 === void 0 ? void 0 : _state$snapshot2.head) === headSha && !state.changes.length ? locator : void 0;
3961
4534
  };
3962
4535
  const selection = projectList && !state.selected ? findElement(projectList.list, element) : null;
4536
+ const openTabs = slot && host.openPanel && host.renderReviewBlock ? (key, files, rememberAs) => openProjectSourceTabs(host.openPanel, {
4537
+ key,
4538
+ files,
4539
+ renderBlock: host.renderReviewBlock,
4540
+ locale,
4541
+ rememberAs
4542
+ }) : void 0;
4543
+ const openFileTabs = (change) => openTabs && ((before, after) => {
4544
+ var _state$commit;
4545
+ let files;
4546
+ try {
4547
+ files = [{
4548
+ path: change.path,
4549
+ before: parseProjectSource(before),
4550
+ after: parseProjectSource(after),
4551
+ loadAsset: loadAsset !== null && loadAsset !== void 0 ? loadAsset : void 0,
4552
+ locateBlock: fileLocator(change)
4553
+ }];
4554
+ } catch (_unused3) {
4555
+ return;
4556
+ }
4557
+ openTabs(`${(_state$commit = state.commit) !== null && _state$commit !== void 0 ? _state$commit : change.group}:${change.path}`, files);
4558
+ });
3963
4559
  const content = /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("header", null, /* @__PURE__ */ React.createElement("h2", null, labels.title), /* @__PURE__ */ React.createElement("button", {
3964
4560
  onClick: chooseRoot,
3965
4561
  disabled: state.busy
@@ -3995,7 +4591,8 @@ function GitPanel({ vm, document, host, controller }) {
3995
4591
  repository: state.repository,
3996
4592
  locale,
3997
4593
  renderBlock: host.renderReviewBlock,
3998
- locateFor: reviewLocator
4594
+ locateFor: reviewLocator,
4595
+ openTabs
3999
4596
  }), !review && state.repository && /* @__PURE__ */ React.createElement("div", { className: "twgit-content" }, /* @__PURE__ */ React.createElement("aside", null, state.browser && /* @__PURE__ */ React.createElement("details", null, /* @__PURE__ */ React.createElement("summary", null, labels.browse), /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("code", null, state.browser.path || "/")), /* @__PURE__ */ React.createElement("button", {
4000
4597
  disabled: state.busy || !state.browser.path,
4001
4598
  onClick: () => void browse(state.browser.path.split("/").slice(0, -1).join("/"))
@@ -4010,7 +4607,10 @@ function GitPanel({ vm, document, host, controller }) {
4010
4607
  disabled: state.busy,
4011
4608
  onClick: () => void controller.refresh()
4012
4609
  }, labels.worktree)), projectChanges === "loading" && /* @__PURE__ */ React.createElement("p", { role: "status" }, projectChangeLabels(locale).loading), projectChanges === "unreadable" && /* @__PURE__ */ React.createElement("p", null, projectChangeLabels(locale).unreadable), (() => {
4013
- const fileList = groups.map((group) => /* @__PURE__ */ React.createElement("section", { key: group }, /* @__PURE__ */ React.createElement("h3", null, labels[group], " · ", state.changes.filter((change) => change.group === group).length), /* @__PURE__ */ React.createElement("ul", null, state.changes.filter((change) => change.group === group).map((change) => /* @__PURE__ */ React.createElement("li", { key: change.path }, /* @__PURE__ */ React.createElement("button", {
4610
+ const fileList = groups.map((group) => /* @__PURE__ */ React.createElement("section", { key: group }, /* @__PURE__ */ React.createElement("h3", null, labels[group], " · ", state.changes.filter((change) => change.group === group).length), /* @__PURE__ */ React.createElement("ul", null, state.changes.filter((change) => change.group === group).map((change) => /* @__PURE__ */ React.createElement("li", {
4611
+ key: change.path,
4612
+ className: writable ? "twgit-file-row" : void 0
4613
+ }, /* @__PURE__ */ React.createElement("button", {
4014
4614
  className: "twgit-file",
4015
4615
  "data-git-group": group,
4016
4616
  disabled: state.busy,
@@ -4063,12 +4663,74 @@ function GitPanel({ vm, document, host, controller }) {
4063
4663
  author: (_host$localWrite = host.localWrite) === null || _host$localWrite === void 0 || (_host$localWrite$auth = _host$localWrite.author) === null || _host$localWrite$auth === void 0 ? void 0 : _host$localWrite$auth.call(_host$localWrite)
4064
4664
  }, labels.committed);
4065
4665
  }
4066
- }, labels.commitButton)), /* @__PURE__ */ React.createElement("h3", null, labels.history), /* @__PURE__ */ React.createElement("ol", { className: "twgit-history" }, (_state$snapshot3 = state.snapshot) === null || _state$snapshot3 === void 0 ? void 0 : _state$snapshot3.commits.map((commit) => /* @__PURE__ */ React.createElement("li", { key: commit.oid }, /* @__PURE__ */ React.createElement("button", {
4666
+ }, labels.commitButton)), branching && state.snapshot && /* @__PURE__ */ React.createElement("section", {
4667
+ className: "twgit-branches",
4668
+ "aria-label": labels.branches
4669
+ }, /* @__PURE__ */ React.createElement("h3", null, labels.branches), /* @__PURE__ */ React.createElement("ul", null, state.snapshot.branches.map((name) => /* @__PURE__ */ React.createElement("li", { key: name }, /* @__PURE__ */ React.createElement("code", null, name), name === state.snapshot.branch ? /* @__PURE__ */ React.createElement("small", null, " · ", labels.currentBranch) : /* @__PURE__ */ React.createElement("button", {
4670
+ type: "button",
4671
+ disabled: state.busy,
4672
+ "aria-label": `${labels.switchTo}: ${name}`,
4673
+ onClick: () => void changeBranch({
4674
+ kind: "switchBranch",
4675
+ name
4676
+ })
4677
+ }, labels.switchTo)))), state.snapshot.head && /* @__PURE__ */ React.createElement("form", { onSubmit: (event) => {
4678
+ event.preventDefault();
4679
+ if (newBranch.trim()) changeBranch({
4680
+ kind: "createBranch",
4681
+ name: newBranch.trim(),
4682
+ checkout: switchAfterCreate
4683
+ });
4684
+ } }, /* @__PURE__ */ React.createElement("label", null, labels.newBranch, " ", /* @__PURE__ */ React.createElement("input", {
4685
+ value: newBranch,
4067
4686
  disabled: state.busy,
4068
- "aria-pressed": state.commit === commit.oid,
4069
- title: commit.oid,
4070
- onClick: () => void selectCommit(commit.oid)
4071
- }, commit.message.split("\n")[0]), /* @__PURE__ */ React.createElement("small", null, commit.author, " · ", (/* @__PURE__ */ new Date(commit.timestamp * 1e3)).toLocaleString(locale)))))), /* @__PURE__ */ React.createElement("main", { className: "twgit-diff" }, selection && projectList ? /* @__PURE__ */ React.createElement(ProjectElementView, {
4687
+ onChange: (event) => setNewBranch(event.currentTarget.value)
4688
+ })), /* @__PURE__ */ React.createElement("label", null, /* @__PURE__ */ React.createElement("input", {
4689
+ type: "checkbox",
4690
+ checked: switchAfterCreate,
4691
+ disabled: state.busy,
4692
+ onChange: (event) => setSwitchAfterCreate(event.currentTarget.checked)
4693
+ }), " ", labels.switchAfterCreate), /* @__PURE__ */ React.createElement("button", {
4694
+ type: "submit",
4695
+ disabled: state.busy || !newBranch.trim()
4696
+ }, labels.createBranch)), state.snapshot.remoteBranches.length > 0 && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("h3", null, labels.remoteBranches), /* @__PURE__ */ React.createElement("ul", null, state.snapshot.remoteBranches.map((name) => /* @__PURE__ */ React.createElement("li", { key: name }, /* @__PURE__ */ React.createElement("code", null, "origin/", name), /* @__PURE__ */ React.createElement("button", {
4697
+ type: "button",
4698
+ disabled: state.busy,
4699
+ "aria-label": `${labels.createFromRemote}: origin/${name}`,
4700
+ onClick: () => void changeBranch({
4701
+ kind: "createBranch",
4702
+ name,
4703
+ fromRemote: true,
4704
+ checkout: true
4705
+ })
4706
+ }, labels.createFromRemote)))))), /* @__PURE__ */ React.createElement("h3", null, labels.history), state.snapshot && /* @__PURE__ */ React.createElement(HistoryGraph, _objectSpread2$1({
4707
+ commits: history.commits,
4708
+ selected: state.commit,
4709
+ locale,
4710
+ busy: state.busy,
4711
+ onSelect: (oid) => void selectCommit(oid),
4712
+ hasMore: !!history.cursor,
4713
+ loadingMore: history.loading,
4714
+ onShowMore: showMoreHistory,
4715
+ localBranches: state.snapshot.branches
4716
+ }, branching ? {
4717
+ onCreateBranch: (oid, name, checkout) => changeBranch({
4718
+ kind: "createBranch",
4719
+ name,
4720
+ at: oid,
4721
+ checkout
4722
+ }),
4723
+ onSwitchBranch: (name) => changeBranch({
4724
+ kind: "switchBranch",
4725
+ name
4726
+ }),
4727
+ onCreateFromRemote: (name) => changeBranch({
4728
+ kind: "createBranch",
4729
+ name,
4730
+ fromRemote: true,
4731
+ checkout: true
4732
+ })
4733
+ } : {})), history.loading && !history.commits.length && /* @__PURE__ */ React.createElement("p", null, labels.historyLoading), history.failed && /* @__PURE__ */ React.createElement("p", null, labels.historyFailed)), /* @__PURE__ */ React.createElement("main", { className: "twgit-diff" }, selection && projectList ? /* @__PURE__ */ React.createElement(ProjectElementView, {
4072
4734
  changes: projectList,
4073
4735
  selection,
4074
4736
  locale,
@@ -4081,6 +4743,7 @@ function GitPanel({ vm, document, host, controller }) {
4081
4743
  labels,
4082
4744
  locale,
4083
4745
  locateBlock: fileLocator(state.selected),
4746
+ onOpenTabs: openFileTabs(state.selected),
4084
4747
  renderBlock: host.renderReviewBlock,
4085
4748
  loadAsset
4086
4749
  }) : /* @__PURE__ */ React.createElement("p", null, labels.select))));
@@ -4111,8 +4774,8 @@ function upstreamText({ upstream }, labels) {
4111
4774
  }
4112
4775
  }
4113
4776
  function mountGitPanel({ vm, document, host }) {
4114
- var _document$defaultView2;
4115
- if (!((_document$defaultView2 = document.defaultView) === null || _document$defaultView2 === void 0 ? void 0 : _document$defaultView2.showDirectoryPicker)) return null;
4777
+ var _document$defaultView3;
4778
+ if (!((_document$defaultView3 = document.defaultView) === null || _document$defaultView3 === void 0 ? void 0 : _document$defaultView3.showDirectoryPicker)) return null;
4116
4779
  const root = document.createElement("div");
4117
4780
  document.body.append(root);
4118
4781
  const controller = {
@@ -4148,4 +4811,4 @@ function renderGitMenu(createElement, { vm, locale = "en", MenuItem, MenuSection
4148
4811
  }
4149
4812
  }, labelsFor(locale).menu));
4150
4813
  }
4151
- export { GitPanelError, createEditorBlockLocator, createReadOnlyFs, createScratchBlocksDiffLocator, createScratchBlocksDiffRenderer, mountGitPanel, openProjectComparison, openRepository, renderGitMenu };
4814
+ export { GitPanelError, HistoryGraph, createEditorBlockLocator, createReadOnlyFs, createScratchBlocksDiffLocator, createScratchBlocksDiffRenderer, laneColors, layoutHistory, mountGitPanel, openProjectComparison, openProjectSourceTabs, openRepository, renderGitMenu };