@chisquare-tech/origine-ngx-apps-code 0.3.0 → 0.5.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.
|
@@ -65,8 +65,15 @@ class CodeApi {
|
|
|
65
65
|
tree(project, name, ref, path) {
|
|
66
66
|
return this.api.get(`${this.repoPath(project, name)}/tree`, { ref, path }).pipe(map((r) => data(r)));
|
|
67
67
|
}
|
|
68
|
-
blob(project, name, ref, path) {
|
|
69
|
-
|
|
68
|
+
blob(project, name, ref, path, withText = false) {
|
|
69
|
+
const p = { ref, path };
|
|
70
|
+
if (withText)
|
|
71
|
+
p['format'] = 'text';
|
|
72
|
+
return this.api.get(`${this.repoPath(project, name)}/blob`, p).pipe(map((r) => data(r)));
|
|
73
|
+
}
|
|
74
|
+
/** A commit from the browser (DEV-13): files, base64 uploads and deletes; from a new branch it can open a pull request. */
|
|
75
|
+
commitFiles(project, name, body) {
|
|
76
|
+
return this.api.post(`${this.repoPath(project, name)}/commits`, body).pipe(map((r) => data(r)));
|
|
70
77
|
}
|
|
71
78
|
blame(project, name, ref, path) {
|
|
72
79
|
return this.api.get(`${this.repoPath(project, name)}/blame`, { ref, path }).pipe(map((r) => data(r) ?? []));
|
|
@@ -115,6 +122,15 @@ class CodeApi {
|
|
|
115
122
|
p['q'] = q;
|
|
116
123
|
return this.api.get(`${this.repoPath(project, name)}/refs`, p).pipe(map((r) => data(r) ?? []));
|
|
117
124
|
}
|
|
125
|
+
collaborators(project, name) {
|
|
126
|
+
return this.api.get(`${this.repoPath(project, name)}/collaborators`).pipe(map((r) => data(r) ?? []));
|
|
127
|
+
}
|
|
128
|
+
setCollaborator(project, name, userId, permission) {
|
|
129
|
+
return this.api.put(`${this.repoPath(project, name)}/collaborators/${enc(userId)}`, { permission }).pipe(map((r) => data(r)));
|
|
130
|
+
}
|
|
131
|
+
removeCollaborator(project, name, userId) {
|
|
132
|
+
return this.api.delete(`${this.repoPath(project, name)}/collaborators/${enc(userId)}`);
|
|
133
|
+
}
|
|
118
134
|
createTag(project, name, tag, target) {
|
|
119
135
|
return this.api.post(`${this.repoPath(project, name)}/tags`, { tag, target }).pipe(map((r) => data(r)));
|
|
120
136
|
}
|
|
@@ -343,6 +359,8 @@ class RepoPage {
|
|
|
343
359
|
this.people = computed(() => new People(this.host.people()), ...(ngDevMode ? [{ debugName: "people" }] : /* istanbul ignore next */ []));
|
|
344
360
|
this.canWrite = computed(() => !this.repo()?.archivedAt && (this.repo()?.capabilities ?? []).includes('code.write'), ...(ngDevMode ? [{ debugName: "canWrite" }] : /* istanbul ignore next */ []));
|
|
345
361
|
this.canAdmin = computed(() => (this.repo()?.capabilities ?? []).includes('resources.manage'), ...(ngDevMode ? [{ debugName: "canAdmin" }] : /* istanbul ignore next */ []));
|
|
362
|
+
/** Repository settings short of a rename, the visibility and deletion: Maintainers, and maintain collaborators. */
|
|
363
|
+
this.canMaintain = computed(() => this.canAdmin() || (this.repo()?.capabilities ?? []).includes('repo.maintain'), ...(ngDevMode ? [{ debugName: "canMaintain" }] : /* istanbul ignore next */ []));
|
|
346
364
|
this.canProtect = computed(() => (this.repo()?.capabilities ?? []).includes('protection.manage'), ...(ngDevMode ? [{ debugName: "canProtect" }] : /* istanbul ignore next */ []));
|
|
347
365
|
this.route.paramMap.subscribe((m) => this.name.set(m.get('repo') ?? ''));
|
|
348
366
|
effect(() => {
|
|
@@ -1215,6 +1233,7 @@ class RepoHomePage extends RepoPage {
|
|
|
1215
1233
|
this.path = signal('', ...(ngDevMode ? [{ debugName: "path" }] : /* istanbul ignore next */ []));
|
|
1216
1234
|
this.proto = signal('https', ...(ngDevMode ? [{ debugName: "proto" }] : /* istanbul ignore next */ []));
|
|
1217
1235
|
this.cloneOpen = signal(false, ...(ngDevMode ? [{ debugName: "cloneOpen" }] : /* istanbul ignore next */ []));
|
|
1236
|
+
this.addOpen = signal(false, ...(ngDevMode ? [{ debugName: "addOpen" }] : /* istanbul ignore next */ []));
|
|
1218
1237
|
this.copied = signal(false, ...(ngDevMode ? [{ debugName: "copied" }] : /* istanbul ignore next */ []));
|
|
1219
1238
|
this.finding = signal(false, ...(ngDevMode ? [{ debugName: "finding" }] : /* istanbul ignore next */ []));
|
|
1220
1239
|
this.fq = signal('', ...(ngDevMode ? [{ debugName: "fq" }] : /* istanbul ignore next */ []));
|
|
@@ -1343,6 +1362,17 @@ class RepoHomePage extends RepoPage {
|
|
|
1343
1362
|
</nav>
|
|
1344
1363
|
<span class="o-r"></span>
|
|
1345
1364
|
<button type="button" class="o-btn o-sm" (click)="openFinder()">Go to file <span class="o-kbd">T</span></button>
|
|
1365
|
+
@if (canWrite()) {
|
|
1366
|
+
<span class="anchor add">
|
|
1367
|
+
<button type="button" class="o-btn o-sm" (click)="addOpen.set(!addOpen())" [attr.aria-expanded]="addOpen()" aria-haspopup="menu" data-testid="add-file">Add file ⌄</button>
|
|
1368
|
+
@if (addOpen()) {
|
|
1369
|
+
<div class="o-menu" role="menu" style="right:0;top:34px;width:200px">
|
|
1370
|
+
<a role="menuitem" [routerLink]="link('new')" [queryParams]="{ ref: ref() || null, path: path() || null }" data-testid="add-new">Create new file</a>
|
|
1371
|
+
<a role="menuitem" [routerLink]="link('upload')" [queryParams]="{ ref: ref() || null, path: path() || null }" data-testid="add-upload">Upload files</a>
|
|
1372
|
+
</div>
|
|
1373
|
+
}
|
|
1374
|
+
</span>
|
|
1375
|
+
}
|
|
1346
1376
|
<span class="anchor">
|
|
1347
1377
|
<button type="button" class="o-btn o-sm o-pri" (click)="cloneOpen.set(!cloneOpen())" [attr.aria-expanded]="cloneOpen()" aria-haspopup="dialog" data-testid="clone-menu">Code ⌄</button>
|
|
1348
1378
|
@if (cloneOpen()) {
|
|
@@ -1463,6 +1493,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
1463
1493
|
</nav>
|
|
1464
1494
|
<span class="o-r"></span>
|
|
1465
1495
|
<button type="button" class="o-btn o-sm" (click)="openFinder()">Go to file <span class="o-kbd">T</span></button>
|
|
1496
|
+
@if (canWrite()) {
|
|
1497
|
+
<span class="anchor add">
|
|
1498
|
+
<button type="button" class="o-btn o-sm" (click)="addOpen.set(!addOpen())" [attr.aria-expanded]="addOpen()" aria-haspopup="menu" data-testid="add-file">Add file ⌄</button>
|
|
1499
|
+
@if (addOpen()) {
|
|
1500
|
+
<div class="o-menu" role="menu" style="right:0;top:34px;width:200px">
|
|
1501
|
+
<a role="menuitem" [routerLink]="link('new')" [queryParams]="{ ref: ref() || null, path: path() || null }" data-testid="add-new">Create new file</a>
|
|
1502
|
+
<a role="menuitem" [routerLink]="link('upload')" [queryParams]="{ ref: ref() || null, path: path() || null }" data-testid="add-upload">Upload files</a>
|
|
1503
|
+
</div>
|
|
1504
|
+
}
|
|
1505
|
+
</span>
|
|
1506
|
+
}
|
|
1466
1507
|
<span class="anchor">
|
|
1467
1508
|
<button type="button" class="o-btn o-sm o-pri" (click)="cloneOpen.set(!cloneOpen())" [attr.aria-expanded]="cloneOpen()" aria-haspopup="dialog" data-testid="clone-menu">Code ⌄</button>
|
|
1468
1509
|
@if (cloneOpen()) {
|
|
@@ -1546,6 +1587,109 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
1546
1587
|
args: ['document:keydown', ['$event']]
|
|
1547
1588
|
}] } });
|
|
1548
1589
|
|
|
1590
|
+
/** Whether a branch protection pattern names the branch (as code's protect.Match: * within a segment, ** across). */
|
|
1591
|
+
function matchesPattern(pattern, branch) {
|
|
1592
|
+
const seg = (p) => '^' + p.split('*').map((x) => x.replace(/[.+?^${}()|[\]\\]/g, '\\$&')).join('[^/]*') + '$';
|
|
1593
|
+
const pat = pattern.split('/');
|
|
1594
|
+
const name = branch.split('/');
|
|
1595
|
+
const go = (i, j) => {
|
|
1596
|
+
if (i === pat.length)
|
|
1597
|
+
return j === name.length;
|
|
1598
|
+
if (pat[i] === '**') {
|
|
1599
|
+
if (i === pat.length - 1)
|
|
1600
|
+
return j < name.length;
|
|
1601
|
+
for (let k = j; k <= name.length; k++)
|
|
1602
|
+
if (go(i + 1, k))
|
|
1603
|
+
return true;
|
|
1604
|
+
return false;
|
|
1605
|
+
}
|
|
1606
|
+
return j < name.length && new RegExp(seg(pat[i])).test(name[j]) && go(i + 1, j + 1);
|
|
1607
|
+
};
|
|
1608
|
+
return go(0, 0);
|
|
1609
|
+
}
|
|
1610
|
+
// The web editor's commit box (prototype code_file "Commit and open PR", DEV-13): a message, and
|
|
1611
|
+
// either the branch itself or a new branch with a pull request. A branch whose rules block direct
|
|
1612
|
+
// pushes only offers the second.
|
|
1613
|
+
class CommitBox {
|
|
1614
|
+
constructor() {
|
|
1615
|
+
this.api = inject(CodeApi);
|
|
1616
|
+
this.host = inject(CodeHost);
|
|
1617
|
+
this.repo = input.required(...(ngDevMode ? [{ debugName: "repo" }] : /* istanbul ignore next */ []));
|
|
1618
|
+
this.branch = input.required(...(ngDevMode ? [{ debugName: "branch" }] : /* istanbul ignore next */ []));
|
|
1619
|
+
this.defaultMessage = input('', ...(ngDevMode ? [{ debugName: "defaultMessage" }] : /* istanbul ignore next */ []));
|
|
1620
|
+
this.busy = input(false, ...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
|
|
1621
|
+
this.commit = output();
|
|
1622
|
+
this.cancel = output();
|
|
1623
|
+
this.message = signal('', ...(ngDevMode ? [{ debugName: "message" }] : /* istanbul ignore next */ []));
|
|
1624
|
+
this.toNew = signal(false, ...(ngDevMode ? [{ debugName: "toNew" }] : /* istanbul ignore next */ []));
|
|
1625
|
+
this.newBranch = signal('', ...(ngDevMode ? [{ debugName: "newBranch" }] : /* istanbul ignore next */ []));
|
|
1626
|
+
this.rules = signal([], ...(ngDevMode ? [{ debugName: "rules" }] : /* istanbul ignore next */ []));
|
|
1627
|
+
this.blocked = computed(() => this.rules().some((r) => r.blockDirectPush && matchesPattern(r.pattern, this.branch())), ...(ngDevMode ? [{ debugName: "blocked" }] : /* istanbul ignore next */ []));
|
|
1628
|
+
effect(() => {
|
|
1629
|
+
const m = this.defaultMessage();
|
|
1630
|
+
untracked(() => this.message.set(m));
|
|
1631
|
+
});
|
|
1632
|
+
effect(() => {
|
|
1633
|
+
const repo = this.repo();
|
|
1634
|
+
const p = this.host.project();
|
|
1635
|
+
if (!p || !repo)
|
|
1636
|
+
return;
|
|
1637
|
+
untracked(() => {
|
|
1638
|
+
this.api.protections(p.id, repo).subscribe({ next: (r) => this.rules.set(r), error: () => undefined });
|
|
1639
|
+
const me = (this.host.people().find((x) => x.id === this.host.meId())?.name || 'patch').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'patch';
|
|
1640
|
+
this.newBranch.set(`${me}/patch-${Math.floor(Date.now() / 1000) % 100000}`);
|
|
1641
|
+
});
|
|
1642
|
+
});
|
|
1643
|
+
}
|
|
1644
|
+
send() {
|
|
1645
|
+
const toNew = this.toNew() || this.blocked();
|
|
1646
|
+
this.commit.emit({ message: this.message().trim(), newBranch: toNew ? this.newBranch().trim() : undefined });
|
|
1647
|
+
}
|
|
1648
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: CommitBox, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
1649
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: CommitBox, isStandalone: true, selector: "o-commit-box", inputs: { repo: { classPropertyName: "repo", publicName: "repo", isSignal: true, isRequired: true, transformFunction: null }, branch: { classPropertyName: "branch", publicName: "branch", isSignal: true, isRequired: true, transformFunction: null }, defaultMessage: { classPropertyName: "defaultMessage", publicName: "defaultMessage", isSignal: true, isRequired: false, transformFunction: null }, busy: { classPropertyName: "busy", publicName: "busy", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { commit: "commit", cancel: "cancel" }, ngImport: i0, template: `
|
|
1650
|
+
<div class="o-col" style="gap:10px" data-testid="commit-box">
|
|
1651
|
+
@if (blocked()) {<div class="o-note o-blu">Your change is committed to a new branch with a pull request because <b>{{ branch() }}</b> is protected.</div>}
|
|
1652
|
+
<div class="o-fld"><label for="cb-msg">Commit message</label><input id="cb-msg" class="o-in" [ngModel]="message()" (ngModelChange)="message.set($event)" data-testid="commit-message" /></div>
|
|
1653
|
+
@if (!blocked()) {
|
|
1654
|
+
<label class="o-flex o-sm"><input type="radio" name="cb-where" [checked]="!toNew()" (change)="toNew.set(false)" /> Commit directly to <b class="o-mono">{{ branch() }}</b></label>
|
|
1655
|
+
<label class="o-flex o-sm"><input type="radio" name="cb-where" [checked]="toNew()" (change)="toNew.set(true)" data-testid="commit-new-branch" /> Create a new branch and open a pull request</label>
|
|
1656
|
+
}
|
|
1657
|
+
@if (toNew() || blocked()) {
|
|
1658
|
+
<div class="o-flex o-sm"><span class="o-faint">→ new branch</span><input class="o-in o-sm o-mono" style="max-width:260px" aria-label="New branch" [ngModel]="newBranch()" (ngModelChange)="newBranch.set($event)" data-testid="commit-branch" /></div>
|
|
1659
|
+
}
|
|
1660
|
+
<div class="o-flex">
|
|
1661
|
+
<button type="button" class="o-btn o-sm o-r" (click)="cancel.emit()">Cancel</button>
|
|
1662
|
+
<button type="button" class="o-btn o-sm o-pri" [disabled]="busy() || !message().trim() || ((toNew() || blocked()) && !newBranch().trim())" (click)="send()" data-testid="commit-submit">{{ toNew() || blocked() ? 'Commit and open PR' : 'Commit changes' }}</button>
|
|
1663
|
+
</div>
|
|
1664
|
+
</div>
|
|
1665
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1666
|
+
}
|
|
1667
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: CommitBox, decorators: [{
|
|
1668
|
+
type: Component,
|
|
1669
|
+
args: [{
|
|
1670
|
+
selector: 'o-commit-box',
|
|
1671
|
+
imports: [FormsModule],
|
|
1672
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
1673
|
+
template: `
|
|
1674
|
+
<div class="o-col" style="gap:10px" data-testid="commit-box">
|
|
1675
|
+
@if (blocked()) {<div class="o-note o-blu">Your change is committed to a new branch with a pull request because <b>{{ branch() }}</b> is protected.</div>}
|
|
1676
|
+
<div class="o-fld"><label for="cb-msg">Commit message</label><input id="cb-msg" class="o-in" [ngModel]="message()" (ngModelChange)="message.set($event)" data-testid="commit-message" /></div>
|
|
1677
|
+
@if (!blocked()) {
|
|
1678
|
+
<label class="o-flex o-sm"><input type="radio" name="cb-where" [checked]="!toNew()" (change)="toNew.set(false)" /> Commit directly to <b class="o-mono">{{ branch() }}</b></label>
|
|
1679
|
+
<label class="o-flex o-sm"><input type="radio" name="cb-where" [checked]="toNew()" (change)="toNew.set(true)" data-testid="commit-new-branch" /> Create a new branch and open a pull request</label>
|
|
1680
|
+
}
|
|
1681
|
+
@if (toNew() || blocked()) {
|
|
1682
|
+
<div class="o-flex o-sm"><span class="o-faint">→ new branch</span><input class="o-in o-sm o-mono" style="max-width:260px" aria-label="New branch" [ngModel]="newBranch()" (ngModelChange)="newBranch.set($event)" data-testid="commit-branch" /></div>
|
|
1683
|
+
}
|
|
1684
|
+
<div class="o-flex">
|
|
1685
|
+
<button type="button" class="o-btn o-sm o-r" (click)="cancel.emit()">Cancel</button>
|
|
1686
|
+
<button type="button" class="o-btn o-sm o-pri" [disabled]="busy() || !message().trim() || ((toNew() || blocked()) && !newBranch().trim())" (click)="send()" data-testid="commit-submit">{{ toNew() || blocked() ? 'Commit and open PR' : 'Commit changes' }}</button>
|
|
1687
|
+
</div>
|
|
1688
|
+
</div>
|
|
1689
|
+
`,
|
|
1690
|
+
}]
|
|
1691
|
+
}], ctorParameters: () => [], propDecorators: { repo: [{ type: i0.Input, args: [{ isSignal: true, alias: "repo", required: true }] }], branch: [{ type: i0.Input, args: [{ isSignal: true, alias: "branch", required: true }] }], defaultMessage: [{ type: i0.Input, args: [{ isSignal: true, alias: "defaultMessage", required: false }] }], busy: [{ type: i0.Input, args: [{ isSignal: true, alias: "busy", required: false }] }], commit: [{ type: i0.Output, args: ["commit"] }], cancel: [{ type: i0.Output, args: ["cancel"] }] } });
|
|
1692
|
+
|
|
1549
1693
|
// /{org}/{project}/code/{repo}/blob?ref=&path= (prototype "code_file"): one file, highlighted, with
|
|
1550
1694
|
// Markdown preview, blame, raw, copy and download; a line number links to that line (#L12).
|
|
1551
1695
|
class FilePage extends RepoPage {
|
|
@@ -1572,6 +1716,17 @@ class FilePage extends RepoPage {
|
|
|
1572
1716
|
return '';
|
|
1573
1717
|
return [b.binary ? '' : `${b.lineCount} lines`, size(b.size), b.language ?? ''].filter(Boolean).join(' · ');
|
|
1574
1718
|
}, ...(ngDevMode ? [{ debugName: "meta" }] : /* istanbul ignore next */ []));
|
|
1719
|
+
this.mode = signal('', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
|
|
1720
|
+
this.text = signal(null, ...(ngDevMode ? [{ debugName: "text" }] : /* istanbul ignore next */ []));
|
|
1721
|
+
this.busy = signal(false, ...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
|
|
1722
|
+
this.branches = signal([], ...(ngDevMode ? [{ debugName: "branches" }] : /* istanbul ignore next */ []));
|
|
1723
|
+
/** The branch the view is on ('' for a tag or a commit, which cannot take commits). */
|
|
1724
|
+
this.branch = computed(() => {
|
|
1725
|
+
const r = this.repo();
|
|
1726
|
+
const ref = this.ref() || r?.defaultBranch || '';
|
|
1727
|
+
return this.branches().includes(ref) ? ref : '';
|
|
1728
|
+
}, ...(ngDevMode ? [{ debugName: "branch" }] : /* istanbul ignore next */ []));
|
|
1729
|
+
this.editable = computed(() => this.canWrite() && !!this.branch(), ...(ngDevMode ? [{ debugName: "editable" }] : /* istanbul ignore next */ []));
|
|
1575
1730
|
this.route.queryParamMap.subscribe((q) => {
|
|
1576
1731
|
this.ref.set(q.get('ref') ?? '');
|
|
1577
1732
|
this.path.set(q.get('path') ?? '');
|
|
@@ -1583,6 +1738,60 @@ class FilePage extends RepoPage {
|
|
|
1583
1738
|
}
|
|
1584
1739
|
onRepo(r) {
|
|
1585
1740
|
this.load(r);
|
|
1741
|
+
this.api.refs(this.projectId(), r.name, 'branch').subscribe({ next: (b) => this.branches.set(b.map((x) => x.name)), error: () => undefined });
|
|
1742
|
+
}
|
|
1743
|
+
startEdit() {
|
|
1744
|
+
const r = this.repo();
|
|
1745
|
+
if (!r)
|
|
1746
|
+
return;
|
|
1747
|
+
this.mode.set('edit');
|
|
1748
|
+
this.text.set(null);
|
|
1749
|
+
this.api.blob(this.projectId(), r.name, this.branch(), this.path(), true).subscribe({
|
|
1750
|
+
next: (b) => {
|
|
1751
|
+
if (b.text === undefined) {
|
|
1752
|
+
this.mode.set('');
|
|
1753
|
+
this.error.set('This file cannot be edited in the browser.');
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
this.blob.set(b);
|
|
1757
|
+
this.text.set(b.text);
|
|
1758
|
+
},
|
|
1759
|
+
error: (e) => { this.mode.set(''); this.error.set(apiMessage(e, 'Could not open the file.')); },
|
|
1760
|
+
});
|
|
1761
|
+
}
|
|
1762
|
+
/** Commits the edit (or the delete), onto the branch or onto a new branch with a pull request. */
|
|
1763
|
+
save(c, del = false) {
|
|
1764
|
+
const r = this.repo();
|
|
1765
|
+
const b = this.blob();
|
|
1766
|
+
if (!r || !b)
|
|
1767
|
+
return;
|
|
1768
|
+
const body = c.newBranch
|
|
1769
|
+
? { branch: c.newBranch, from: this.branch(), message: c.message, pull: {} }
|
|
1770
|
+
: { branch: this.branch(), expect: b.commit, message: c.message };
|
|
1771
|
+
if (del)
|
|
1772
|
+
body.deletes = [b.path];
|
|
1773
|
+
else
|
|
1774
|
+
body.files = { [b.path]: this.text() ?? '' };
|
|
1775
|
+
this.busy.set(true);
|
|
1776
|
+
this.error.set('');
|
|
1777
|
+
this.api.commitFiles(this.projectId(), r.name, body).subscribe({
|
|
1778
|
+
next: (res) => {
|
|
1779
|
+
this.busy.set(false);
|
|
1780
|
+
this.mode.set('');
|
|
1781
|
+
if (res.pull) {
|
|
1782
|
+
void this.router.navigate(this.link('pull/' + res.pull));
|
|
1783
|
+
return;
|
|
1784
|
+
}
|
|
1785
|
+
if (del) {
|
|
1786
|
+
void this.router.navigate(this.link(), { queryParams: { ref: res.branch, path: b.path.split('/').slice(0, -1).join('/') || null } });
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
this.note.set(`Committed ${res.commit.short} to ${res.branch}.`);
|
|
1790
|
+
void this.router.navigate(this.link('blob'), { queryParams: { ref: res.branch, path: b.path } });
|
|
1791
|
+
this.load(r);
|
|
1792
|
+
},
|
|
1793
|
+
error: (e) => { this.busy.set(false); this.error.set(apiMessage(e, 'Could not commit.')); },
|
|
1794
|
+
});
|
|
1586
1795
|
}
|
|
1587
1796
|
pickRef(ref) {
|
|
1588
1797
|
void this.router.navigate(this.link('blob'), { queryParams: { ref, path: this.path() } });
|
|
@@ -1640,6 +1849,7 @@ class FilePage extends RepoPage {
|
|
|
1640
1849
|
</nav>
|
|
1641
1850
|
</div>
|
|
1642
1851
|
@if (error()) {<div class="o-note o-red" role="alert">{{ error() }}</div>}
|
|
1852
|
+
@if (note()) {<div class="o-note" role="status">{{ note() }}</div>}
|
|
1643
1853
|
@if (blob(); as b) {
|
|
1644
1854
|
<div class="o-card">
|
|
1645
1855
|
@if (b.lastCommit; as lc) {
|
|
@@ -1659,8 +1869,26 @@ class FilePage extends RepoPage {
|
|
|
1659
1869
|
@if (b.rawUrl) {<a class="o-btn o-xs" [href]="b.rawUrl" target="_blank" rel="noopener">Raw</a>}
|
|
1660
1870
|
@if (!b.binary) {<button type="button" class="o-btn o-xs" (click)="copyText()">{{ copied() ? 'Copied' : 'Copy' }}</button>}
|
|
1661
1871
|
@if (b.downloadUrl) {<a class="o-btn o-xs" [href]="b.downloadUrl">Download</a>}
|
|
1872
|
+
@if (editable()) {
|
|
1873
|
+
@if (!b.binary && !b.truncated) {<button type="button" class="o-btn o-xs" (click)="startEdit()" [disabled]="mode() !== ''" data-testid="file-edit">Edit</button>}
|
|
1874
|
+
<button type="button" class="o-btn o-xs" (click)="mode.set('delete')" [disabled]="mode() !== ''" data-testid="file-delete">Delete</button>
|
|
1875
|
+
}
|
|
1662
1876
|
</span>
|
|
1663
1877
|
</div>
|
|
1878
|
+
@if (mode() === 'edit') {
|
|
1879
|
+
<div class="o-cb o-col" style="gap:10px;background:var(--o-well);border-bottom:1px solid var(--o-line-soft)">
|
|
1880
|
+
@if (text() === null) {<span class="o-faint o-sm">Loading the file…</span>} @else {
|
|
1881
|
+
<textarea class="o-in o-mono" rows="16" style="font-size:12px;height:auto;padding:8px 10px" aria-label="File contents" [ngModel]="text()" (ngModelChange)="text.set($event)" data-testid="file-text"></textarea>
|
|
1882
|
+
<o-commit-box [repo]="r.name" [branch]="branch()" [defaultMessage]="'Update ' + b.name" [busy]="busy()" (commit)="save($event)" (cancel)="mode.set('')" />
|
|
1883
|
+
}
|
|
1884
|
+
</div>
|
|
1885
|
+
}
|
|
1886
|
+
@if (mode() === 'delete') {
|
|
1887
|
+
<div class="o-cb o-col" style="gap:10px;background:var(--o-well);border-bottom:1px solid var(--o-line-soft)">
|
|
1888
|
+
<span class="o-sm">Delete <b class="o-mono">{{ b.path }}</b> from <b class="o-mono">{{ branch() }}</b>?</span>
|
|
1889
|
+
<o-commit-box [repo]="r.name" [branch]="branch()" [defaultMessage]="'Delete ' + b.name" [busy]="busy()" (commit)="save($event, true)" (cancel)="mode.set('')" />
|
|
1890
|
+
</div>
|
|
1891
|
+
}
|
|
1664
1892
|
@if (b.image && b.rawUrl) {
|
|
1665
1893
|
<div class="o-cb" style="text-align:center"><img [src]="b.rawUrl" [alt]="b.name" style="max-width:100%" /></div>
|
|
1666
1894
|
} @else if (b.binary || b.truncated) {
|
|
@@ -1687,11 +1915,11 @@ class FilePage extends RepoPage {
|
|
|
1687
1915
|
<div class="o-card o-empty"><p>{{ error() }}</p></div>
|
|
1688
1916
|
}
|
|
1689
1917
|
</div>
|
|
1690
|
-
`, isInline: true, styles: [":host ::ng-deep [class^=tk-k]{color:var(--o-tk-k)}:host ::ng-deep [class^=tk-s],:host ::ng-deep [class^=tk-l]{color:var(--o-tk-s)}:host ::ng-deep [class^=tk-c]{color:var(--o-tk-c);font-style:italic}:host ::ng-deep [class^=tk-m]{color:var(--o-tk-n)}:host ::ng-deep .tk-nf,:host ::ng-deep .tk-nx,:host ::ng-deep .tk-fm{color:var(--o-tk-f)}:host ::ng-deep .tk-nb,:host ::ng-deep .tk-nt,:host ::ng-deep .tk-bp{color:var(--o-tk-p)}:host ::ng-deep .tk-gi{color:var(--o-add)}:host ::ng-deep .tk-gd{color:var(--o-bad-tx)}\n", ".o-ln .o-n{text-decoration:none}.o-btn.o-on{background:var(--o-tx);border-color:var(--o-tx);color:var(--o-acc-fg)}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: RefPicker, selector: "o-ref-picker", inputs: ["repo", "current"], outputs: ["picked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1918
|
+
`, isInline: true, styles: [":host ::ng-deep [class^=tk-k]{color:var(--o-tk-k)}:host ::ng-deep [class^=tk-s],:host ::ng-deep [class^=tk-l]{color:var(--o-tk-s)}:host ::ng-deep [class^=tk-c]{color:var(--o-tk-c);font-style:italic}:host ::ng-deep [class^=tk-m]{color:var(--o-tk-n)}:host ::ng-deep .tk-nf,:host ::ng-deep .tk-nx,:host ::ng-deep .tk-fm{color:var(--o-tk-f)}:host ::ng-deep .tk-nb,:host ::ng-deep .tk-nt,:host ::ng-deep .tk-bp{color:var(--o-tk-p)}:host ::ng-deep .tk-gi{color:var(--o-add)}:host ::ng-deep .tk-gd{color:var(--o-bad-tx)}\n", ".o-ln .o-n{text-decoration:none}.o-btn.o-on{background:var(--o-tx);border-color:var(--o-tx);color:var(--o-acc-fg)}\n"], dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: RefPicker, selector: "o-ref-picker", inputs: ["repo", "current"], outputs: ["picked"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: CommitBox, selector: "o-commit-box", inputs: ["repo", "branch", "defaultMessage", "busy"], outputs: ["commit", "cancel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1691
1919
|
}
|
|
1692
1920
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: FilePage, decorators: [{
|
|
1693
1921
|
type: Component,
|
|
1694
|
-
args: [{ selector: 'o-code-file', imports: [RouterLink, RefPicker], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1922
|
+
args: [{ selector: 'o-code-file', imports: [RouterLink, RefPicker, FormsModule, CommitBox], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
1695
1923
|
<div class="o-page o-wide">
|
|
1696
1924
|
@if (repo(); as r) {
|
|
1697
1925
|
<div class="o-flex o-wrap">
|
|
@@ -1705,6 +1933,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
1705
1933
|
</nav>
|
|
1706
1934
|
</div>
|
|
1707
1935
|
@if (error()) {<div class="o-note o-red" role="alert">{{ error() }}</div>}
|
|
1936
|
+
@if (note()) {<div class="o-note" role="status">{{ note() }}</div>}
|
|
1708
1937
|
@if (blob(); as b) {
|
|
1709
1938
|
<div class="o-card">
|
|
1710
1939
|
@if (b.lastCommit; as lc) {
|
|
@@ -1724,8 +1953,26 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
1724
1953
|
@if (b.rawUrl) {<a class="o-btn o-xs" [href]="b.rawUrl" target="_blank" rel="noopener">Raw</a>}
|
|
1725
1954
|
@if (!b.binary) {<button type="button" class="o-btn o-xs" (click)="copyText()">{{ copied() ? 'Copied' : 'Copy' }}</button>}
|
|
1726
1955
|
@if (b.downloadUrl) {<a class="o-btn o-xs" [href]="b.downloadUrl">Download</a>}
|
|
1956
|
+
@if (editable()) {
|
|
1957
|
+
@if (!b.binary && !b.truncated) {<button type="button" class="o-btn o-xs" (click)="startEdit()" [disabled]="mode() !== ''" data-testid="file-edit">Edit</button>}
|
|
1958
|
+
<button type="button" class="o-btn o-xs" (click)="mode.set('delete')" [disabled]="mode() !== ''" data-testid="file-delete">Delete</button>
|
|
1959
|
+
}
|
|
1727
1960
|
</span>
|
|
1728
1961
|
</div>
|
|
1962
|
+
@if (mode() === 'edit') {
|
|
1963
|
+
<div class="o-cb o-col" style="gap:10px;background:var(--o-well);border-bottom:1px solid var(--o-line-soft)">
|
|
1964
|
+
@if (text() === null) {<span class="o-faint o-sm">Loading the file…</span>} @else {
|
|
1965
|
+
<textarea class="o-in o-mono" rows="16" style="font-size:12px;height:auto;padding:8px 10px" aria-label="File contents" [ngModel]="text()" (ngModelChange)="text.set($event)" data-testid="file-text"></textarea>
|
|
1966
|
+
<o-commit-box [repo]="r.name" [branch]="branch()" [defaultMessage]="'Update ' + b.name" [busy]="busy()" (commit)="save($event)" (cancel)="mode.set('')" />
|
|
1967
|
+
}
|
|
1968
|
+
</div>
|
|
1969
|
+
}
|
|
1970
|
+
@if (mode() === 'delete') {
|
|
1971
|
+
<div class="o-cb o-col" style="gap:10px;background:var(--o-well);border-bottom:1px solid var(--o-line-soft)">
|
|
1972
|
+
<span class="o-sm">Delete <b class="o-mono">{{ b.path }}</b> from <b class="o-mono">{{ branch() }}</b>?</span>
|
|
1973
|
+
<o-commit-box [repo]="r.name" [branch]="branch()" [defaultMessage]="'Delete ' + b.name" [busy]="busy()" (commit)="save($event, true)" (cancel)="mode.set('')" />
|
|
1974
|
+
</div>
|
|
1975
|
+
}
|
|
1729
1976
|
@if (b.image && b.rawUrl) {
|
|
1730
1977
|
<div class="o-cb" style="text-align:center"><img [src]="b.rawUrl" [alt]="b.name" style="max-width:100%" /></div>
|
|
1731
1978
|
} @else if (b.binary || b.truncated) {
|
|
@@ -3000,9 +3247,46 @@ class RepoSettingsPage extends RepoPage {
|
|
|
3000
3247
|
{ key: 'dismissStaleApprovals', label: 'Dismiss stale approvals', hint: 'A new commit dismisses approvals given on an older one.' },
|
|
3001
3248
|
{ key: 'requireUpToDate', label: 'Require branches to be up to date', hint: 'The branch must contain the target’s latest commit before it merges.' },
|
|
3002
3249
|
];
|
|
3250
|
+
this.collabs = signal([], ...(ngDevMode ? [{ debugName: "collabs" }] : /* istanbul ignore next */ []));
|
|
3251
|
+
this.perms = [{ id: 'read', label: 'Read' }, { id: 'write', label: 'Write' }, { id: 'maintain', label: 'Maintain' }];
|
|
3252
|
+
this.addable = computed(() => this.host.people().filter((p) => !this.collabs().some((c) => c.userId === p.id)), ...(ngDevMode ? [{ debugName: "addable" }] : /* istanbul ignore next */ []));
|
|
3253
|
+
this.newCollab = '';
|
|
3254
|
+
this.newPerm = 'write';
|
|
3003
3255
|
this.route.queryParamMap.subscribe((q) => this.section.set(q.get('section') || 'general'));
|
|
3004
3256
|
}
|
|
3257
|
+
roleLabel(r) {
|
|
3258
|
+
return r ? r[0].toUpperCase() + r.slice(1) : 'Not in the project';
|
|
3259
|
+
}
|
|
3260
|
+
loadCollabs(r) {
|
|
3261
|
+
if (!this.canAdmin())
|
|
3262
|
+
return;
|
|
3263
|
+
this.api.collaborators(this.projectId(), r.name).subscribe({ next: (l) => this.collabs.set(l), error: () => undefined });
|
|
3264
|
+
}
|
|
3265
|
+
setCollab(userId, permission, adding = false) {
|
|
3266
|
+
const r = this.repo();
|
|
3267
|
+
if (!r || !userId)
|
|
3268
|
+
return;
|
|
3269
|
+
this.busy.set(true);
|
|
3270
|
+
this.error.set('');
|
|
3271
|
+
this.api.setCollaborator(this.projectId(), r.name, userId, permission).subscribe({
|
|
3272
|
+
next: () => { this.busy.set(false); if (adding) {
|
|
3273
|
+
this.newCollab = '';
|
|
3274
|
+
this.note.set(`${this.people().name(userId)} now has ${permission} on ${r.name}.`);
|
|
3275
|
+
} this.loadCollabs(r); },
|
|
3276
|
+
error: (e) => { this.busy.set(false); this.error.set(apiMessage(e, 'Could not change the access.')); this.loadCollabs(r); },
|
|
3277
|
+
});
|
|
3278
|
+
}
|
|
3279
|
+
removeCollab(userId) {
|
|
3280
|
+
const r = this.repo();
|
|
3281
|
+
if (!r)
|
|
3282
|
+
return;
|
|
3283
|
+
this.api.removeCollaborator(this.projectId(), r.name, userId).subscribe({
|
|
3284
|
+
next: () => { this.note.set(`${this.people().name(userId)} is no longer a collaborator.`); this.loadCollabs(r); },
|
|
3285
|
+
error: (e) => this.error.set(apiMessage(e, 'Could not remove the collaborator.')),
|
|
3286
|
+
});
|
|
3287
|
+
}
|
|
3005
3288
|
onRepo(r) {
|
|
3289
|
+
this.loadCollabs(r);
|
|
3006
3290
|
this.g = { name: r.name, description: r.description, visibility: r.visibility, defaultBranch: r.defaultBranch, mergeMethods: [...(r.mergeMethods ?? [])], deleteBranchOnMerge: r.deleteBranchOnMerge };
|
|
3007
3291
|
if (!r.empty)
|
|
3008
3292
|
this.api.refs(this.projectId(), r.name, 'branch').subscribe({ next: (b) => this.branches.set(b), error: () => undefined });
|
|
@@ -3055,7 +3339,11 @@ class RepoSettingsPage extends RepoPage {
|
|
|
3055
3339
|
return;
|
|
3056
3340
|
this.busy.set(true);
|
|
3057
3341
|
const renamed = this.g.name.trim() !== r.name;
|
|
3058
|
-
|
|
3342
|
+
// a maintain collaborator sends what they may change; the name and visibility stay a Maintainer's
|
|
3343
|
+
const body = this.canAdmin()
|
|
3344
|
+
? { name: this.g.name.trim(), description: this.g.description, visibility: this.g.visibility, defaultBranch: this.g.defaultBranch, mergeMethods: this.g.mergeMethods, deleteBranchOnMerge: this.g.deleteBranchOnMerge }
|
|
3345
|
+
: { description: this.g.description, defaultBranch: this.g.defaultBranch, mergeMethods: this.g.mergeMethods, deleteBranchOnMerge: this.g.deleteBranchOnMerge };
|
|
3346
|
+
this.api.update(this.projectId(), r.name, body).subscribe({
|
|
3059
3347
|
next: (n) => {
|
|
3060
3348
|
this.busy.set(false);
|
|
3061
3349
|
this.note.set('Saved.');
|
|
@@ -3120,7 +3408,7 @@ class RepoSettingsPage extends RepoPage {
|
|
|
3120
3408
|
<span class="o-lbl" style="padding-top:0">{{ r.name }}</span>
|
|
3121
3409
|
<button type="button" [class.o-on]="section() === 'general'" (click)="go('general')">General</button>
|
|
3122
3410
|
<button type="button" [class.o-on]="section() === 'protect'" (click)="go('protect')">Branch protection</button>
|
|
3123
|
-
<button type="button" [class.o-on]="section() === 'collab'" (click)="go('collab')">Collaborators
|
|
3411
|
+
<button type="button" [class.o-on]="section() === 'collab'" (click)="go('collab')">Collaborators</button>
|
|
3124
3412
|
<button type="button" [class.o-on]="section() === 'danger'" (click)="go('danger')">Danger zone</button>
|
|
3125
3413
|
</nav>
|
|
3126
3414
|
<div class="o-col" style="gap:16px;min-width:0">
|
|
@@ -3132,9 +3420,9 @@ class RepoSettingsPage extends RepoPage {
|
|
|
3132
3420
|
<form class="o-card o-cb o-form" (ngSubmit)="saveGeneral()">
|
|
3133
3421
|
<div class="o-g2">
|
|
3134
3422
|
<div class="o-fld"><label for="rs-name">Repository name</label><input id="rs-name" class="o-in o-mono" name="name" [(ngModel)]="g.name" [disabled]="!canAdmin()" /><span class="o-hint">Renaming keeps the old clone URL working as a redirect.</span></div>
|
|
3135
|
-
<div class="o-fld"><label for="rs-def">Default branch</label><select id="rs-def" class="o-in" name="def" [(ngModel)]="g.defaultBranch" [disabled]="!
|
|
3423
|
+
<div class="o-fld"><label for="rs-def">Default branch</label><select id="rs-def" class="o-in" name="def" [(ngModel)]="g.defaultBranch" [disabled]="!canMaintain()">@for (b of branches(); track b.name) {<option [value]="b.name">{{ b.name }}</option>}@if (!branches().length) {<option [value]="r.defaultBranch">{{ r.defaultBranch }}</option>}</select></div>
|
|
3136
3424
|
</div>
|
|
3137
|
-
<div class="o-fld"><label for="rs-desc">Description</label><input id="rs-desc" class="o-in" name="desc" [(ngModel)]="g.description" [disabled]="!
|
|
3425
|
+
<div class="o-fld"><label for="rs-desc">Description</label><input id="rs-desc" class="o-in" name="desc" [(ngModel)]="g.description" [disabled]="!canMaintain()" /></div>
|
|
3138
3426
|
<div class="o-fld"><span class="o-lbl" style="padding:0">Visibility</span>
|
|
3139
3427
|
<div class="o-g2">
|
|
3140
3428
|
<button type="button" class="o-opt" [class.o-on]="g.visibility === 'private'" (click)="g.visibility = 'private'" [disabled]="!canAdmin()"><span class="o-rad" [class.o-on]="g.visibility === 'private'"></span><span class="o-col" style="gap:2px"><b>Private</b><span class="o-faint o-sm">Project members only</span></span></button>
|
|
@@ -3142,11 +3430,11 @@ class RepoSettingsPage extends RepoPage {
|
|
|
3142
3430
|
</div>
|
|
3143
3431
|
</div>
|
|
3144
3432
|
<div class="o-fld"><span class="o-lbl" style="padding:0">Allowed merge methods</span>
|
|
3145
|
-
@for (m of methods; track m.id) {<label class="o-flex"><input type="checkbox" [checked]="g.mergeMethods.includes(m.id)" (change)="flipMethod(m.id)" [disabled]="!
|
|
3433
|
+
@for (m of methods; track m.id) {<label class="o-flex"><input type="checkbox" [checked]="g.mergeMethods.includes(m.id)" (change)="flipMethod(m.id)" [disabled]="!canMaintain()" />{{ m.label }}</label>}
|
|
3146
3434
|
</div>
|
|
3147
|
-
<div class="o-flex"><button type="button" class="o-tog" role="switch" aria-labelledby="rs-del" [class.o-on]="g.deleteBranchOnMerge" [attr.aria-checked]="g.deleteBranchOnMerge" (click)="g.deleteBranchOnMerge = !g.deleteBranchOnMerge" [disabled]="!
|
|
3435
|
+
<div class="o-flex"><button type="button" class="o-tog" role="switch" aria-labelledby="rs-del" [class.o-on]="g.deleteBranchOnMerge" [attr.aria-checked]="g.deleteBranchOnMerge" (click)="g.deleteBranchOnMerge = !g.deleteBranchOnMerge" [disabled]="!canMaintain()"></button><span id="rs-del">Delete head branches after merge</span></div>
|
|
3148
3436
|
<div class="o-flex"><button type="button" class="o-tog o-dis" disabled aria-label="Allow auto-merge"></button>Allow auto-merge when all requirements pass<b class="o-ph-tag o-ga">GA</b></div>
|
|
3149
|
-
@if (
|
|
3437
|
+
@if (canMaintain()) {<div class="o-flex">@if (!canAdmin()) {<span class="o-faint o-sm">As a maintain collaborator you change these settings; a project Maintainer renames the repository or changes its visibility.</span>}<button type="submit" class="o-btn o-dark o-r" [disabled]="busy() || !g.mergeMethods.length" data-testid="settings-save">Save</button></div>}
|
|
3150
3438
|
</form>
|
|
3151
3439
|
}
|
|
3152
3440
|
@case ('protect') {
|
|
@@ -3183,7 +3471,34 @@ class RepoSettingsPage extends RepoPage {
|
|
|
3183
3471
|
}
|
|
3184
3472
|
@case ('collab') {
|
|
3185
3473
|
<h1 class="o-t">Collaborators</h1>
|
|
3186
|
-
<
|
|
3474
|
+
<span class="o-mut">Access beyond the project role, for this repository only.</span>
|
|
3475
|
+
@if (canAdmin()) {
|
|
3476
|
+
<div class="o-card" style="overflow-x:auto" data-testid="collaborators">
|
|
3477
|
+
<div class="o-tbl" role="table" aria-label="Collaborators" style="min-width:560px">
|
|
3478
|
+
<div class="o-thead" role="rowgroup"><div class="o-tr" role="row"><div class="o-th" role="columnheader">Person</div><div class="o-th" role="columnheader">Project role</div><div class="o-th" role="columnheader">Repository access</div><div class="o-th" role="columnheader"></div></div></div>
|
|
3479
|
+
<div class="o-tbody" role="rowgroup">
|
|
3480
|
+
@for (cl of collabs(); track cl.userId) {
|
|
3481
|
+
<div class="o-tr" role="row" [attr.data-testid]="'collab-' + cl.userId">
|
|
3482
|
+
<div class="o-td" role="cell"><span class="o-flex"><span class="o-av" aria-hidden="true">{{ people().initials(cl.userId) }}</span>{{ people().name(cl.userId) }}@if (cl.projectRole === 'guest') {<span class="o-chip">Guest</span>}</span></div>
|
|
3483
|
+
<div class="o-td o-mut" role="cell">{{ roleLabel(cl.projectRole) }}</div>
|
|
3484
|
+
<div class="o-td" role="cell"><select class="o-in o-sm" style="width:auto" [attr.aria-label]="'Repository access for ' + people().name(cl.userId)" [ngModel]="cl.permission" (ngModelChange)="setCollab(cl.userId, $event)">@for (p of perms; track p.id) {<option [value]="p.id">{{ p.label }}</option>}</select></div>
|
|
3485
|
+
<div class="o-td o-num" role="cell"><button type="button" class="o-btn o-xs o-ghost" (click)="removeCollab(cl.userId)" [attr.aria-label]="'Remove ' + people().name(cl.userId)">Remove</button></div>
|
|
3486
|
+
</div>
|
|
3487
|
+
} @empty {
|
|
3488
|
+
<div class="o-tr" role="row"><div class="o-td o-faint" role="cell" style="grid-column:1 / -1">No collaborators. Project roles decide who reads and pushes.</div></div>
|
|
3489
|
+
}
|
|
3490
|
+
</div>
|
|
3491
|
+
</div>
|
|
3492
|
+
<div class="o-cf">
|
|
3493
|
+
<select class="o-in o-sm" style="width:auto" aria-label="Person to add" [(ngModel)]="newCollab"><option value="">Add a person…</option>@for (p of addable(); track p.id) {<option [value]="p.id">{{ p.name || p.email }}</option>}</select>
|
|
3494
|
+
<select class="o-in o-sm" style="width:auto" aria-label="Access to give" [(ngModel)]="newPerm">@for (p of perms; track p.id) {<option [value]="p.id">{{ p.label }}</option>}</select>
|
|
3495
|
+
<button type="button" class="o-btn o-sm o-dark" [disabled]="!newCollab || busy()" (click)="setCollab(newCollab, newPerm, true)" data-testid="collab-add">Add</button>
|
|
3496
|
+
<span class="o-faint o-xs">They need to be in the project (any role, Guest included). Write pushes and opens pull requests here; maintain also changes this repository's settings.</span>
|
|
3497
|
+
</div>
|
|
3498
|
+
</div>
|
|
3499
|
+
} @else {
|
|
3500
|
+
<div class="o-card"><div class="o-empty">{{ repo()?.collaborator ? 'You have ' + repo()?.collaborator + ' on this repository as a collaborator. ' : '' }}Project Maintainers manage collaborators.</div></div>
|
|
3501
|
+
}
|
|
3187
3502
|
}
|
|
3188
3503
|
@case ('danger') {
|
|
3189
3504
|
<h1 class="o-t">Danger zone</h1>
|
|
@@ -3218,7 +3533,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3218
3533
|
<span class="o-lbl" style="padding-top:0">{{ r.name }}</span>
|
|
3219
3534
|
<button type="button" [class.o-on]="section() === 'general'" (click)="go('general')">General</button>
|
|
3220
3535
|
<button type="button" [class.o-on]="section() === 'protect'" (click)="go('protect')">Branch protection</button>
|
|
3221
|
-
<button type="button" [class.o-on]="section() === 'collab'" (click)="go('collab')">Collaborators
|
|
3536
|
+
<button type="button" [class.o-on]="section() === 'collab'" (click)="go('collab')">Collaborators</button>
|
|
3222
3537
|
<button type="button" [class.o-on]="section() === 'danger'" (click)="go('danger')">Danger zone</button>
|
|
3223
3538
|
</nav>
|
|
3224
3539
|
<div class="o-col" style="gap:16px;min-width:0">
|
|
@@ -3230,9 +3545,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3230
3545
|
<form class="o-card o-cb o-form" (ngSubmit)="saveGeneral()">
|
|
3231
3546
|
<div class="o-g2">
|
|
3232
3547
|
<div class="o-fld"><label for="rs-name">Repository name</label><input id="rs-name" class="o-in o-mono" name="name" [(ngModel)]="g.name" [disabled]="!canAdmin()" /><span class="o-hint">Renaming keeps the old clone URL working as a redirect.</span></div>
|
|
3233
|
-
<div class="o-fld"><label for="rs-def">Default branch</label><select id="rs-def" class="o-in" name="def" [(ngModel)]="g.defaultBranch" [disabled]="!
|
|
3548
|
+
<div class="o-fld"><label for="rs-def">Default branch</label><select id="rs-def" class="o-in" name="def" [(ngModel)]="g.defaultBranch" [disabled]="!canMaintain()">@for (b of branches(); track b.name) {<option [value]="b.name">{{ b.name }}</option>}@if (!branches().length) {<option [value]="r.defaultBranch">{{ r.defaultBranch }}</option>}</select></div>
|
|
3234
3549
|
</div>
|
|
3235
|
-
<div class="o-fld"><label for="rs-desc">Description</label><input id="rs-desc" class="o-in" name="desc" [(ngModel)]="g.description" [disabled]="!
|
|
3550
|
+
<div class="o-fld"><label for="rs-desc">Description</label><input id="rs-desc" class="o-in" name="desc" [(ngModel)]="g.description" [disabled]="!canMaintain()" /></div>
|
|
3236
3551
|
<div class="o-fld"><span class="o-lbl" style="padding:0">Visibility</span>
|
|
3237
3552
|
<div class="o-g2">
|
|
3238
3553
|
<button type="button" class="o-opt" [class.o-on]="g.visibility === 'private'" (click)="g.visibility = 'private'" [disabled]="!canAdmin()"><span class="o-rad" [class.o-on]="g.visibility === 'private'"></span><span class="o-col" style="gap:2px"><b>Private</b><span class="o-faint o-sm">Project members only</span></span></button>
|
|
@@ -3240,11 +3555,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3240
3555
|
</div>
|
|
3241
3556
|
</div>
|
|
3242
3557
|
<div class="o-fld"><span class="o-lbl" style="padding:0">Allowed merge methods</span>
|
|
3243
|
-
@for (m of methods; track m.id) {<label class="o-flex"><input type="checkbox" [checked]="g.mergeMethods.includes(m.id)" (change)="flipMethod(m.id)" [disabled]="!
|
|
3558
|
+
@for (m of methods; track m.id) {<label class="o-flex"><input type="checkbox" [checked]="g.mergeMethods.includes(m.id)" (change)="flipMethod(m.id)" [disabled]="!canMaintain()" />{{ m.label }}</label>}
|
|
3244
3559
|
</div>
|
|
3245
|
-
<div class="o-flex"><button type="button" class="o-tog" role="switch" aria-labelledby="rs-del" [class.o-on]="g.deleteBranchOnMerge" [attr.aria-checked]="g.deleteBranchOnMerge" (click)="g.deleteBranchOnMerge = !g.deleteBranchOnMerge" [disabled]="!
|
|
3560
|
+
<div class="o-flex"><button type="button" class="o-tog" role="switch" aria-labelledby="rs-del" [class.o-on]="g.deleteBranchOnMerge" [attr.aria-checked]="g.deleteBranchOnMerge" (click)="g.deleteBranchOnMerge = !g.deleteBranchOnMerge" [disabled]="!canMaintain()"></button><span id="rs-del">Delete head branches after merge</span></div>
|
|
3246
3561
|
<div class="o-flex"><button type="button" class="o-tog o-dis" disabled aria-label="Allow auto-merge"></button>Allow auto-merge when all requirements pass<b class="o-ph-tag o-ga">GA</b></div>
|
|
3247
|
-
@if (
|
|
3562
|
+
@if (canMaintain()) {<div class="o-flex">@if (!canAdmin()) {<span class="o-faint o-sm">As a maintain collaborator you change these settings; a project Maintainer renames the repository or changes its visibility.</span>}<button type="submit" class="o-btn o-dark o-r" [disabled]="busy() || !g.mergeMethods.length" data-testid="settings-save">Save</button></div>}
|
|
3248
3563
|
</form>
|
|
3249
3564
|
}
|
|
3250
3565
|
@case ('protect') {
|
|
@@ -3281,7 +3596,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3281
3596
|
}
|
|
3282
3597
|
@case ('collab') {
|
|
3283
3598
|
<h1 class="o-t">Collaborators</h1>
|
|
3284
|
-
<
|
|
3599
|
+
<span class="o-mut">Access beyond the project role, for this repository only.</span>
|
|
3600
|
+
@if (canAdmin()) {
|
|
3601
|
+
<div class="o-card" style="overflow-x:auto" data-testid="collaborators">
|
|
3602
|
+
<div class="o-tbl" role="table" aria-label="Collaborators" style="min-width:560px">
|
|
3603
|
+
<div class="o-thead" role="rowgroup"><div class="o-tr" role="row"><div class="o-th" role="columnheader">Person</div><div class="o-th" role="columnheader">Project role</div><div class="o-th" role="columnheader">Repository access</div><div class="o-th" role="columnheader"></div></div></div>
|
|
3604
|
+
<div class="o-tbody" role="rowgroup">
|
|
3605
|
+
@for (cl of collabs(); track cl.userId) {
|
|
3606
|
+
<div class="o-tr" role="row" [attr.data-testid]="'collab-' + cl.userId">
|
|
3607
|
+
<div class="o-td" role="cell"><span class="o-flex"><span class="o-av" aria-hidden="true">{{ people().initials(cl.userId) }}</span>{{ people().name(cl.userId) }}@if (cl.projectRole === 'guest') {<span class="o-chip">Guest</span>}</span></div>
|
|
3608
|
+
<div class="o-td o-mut" role="cell">{{ roleLabel(cl.projectRole) }}</div>
|
|
3609
|
+
<div class="o-td" role="cell"><select class="o-in o-sm" style="width:auto" [attr.aria-label]="'Repository access for ' + people().name(cl.userId)" [ngModel]="cl.permission" (ngModelChange)="setCollab(cl.userId, $event)">@for (p of perms; track p.id) {<option [value]="p.id">{{ p.label }}</option>}</select></div>
|
|
3610
|
+
<div class="o-td o-num" role="cell"><button type="button" class="o-btn o-xs o-ghost" (click)="removeCollab(cl.userId)" [attr.aria-label]="'Remove ' + people().name(cl.userId)">Remove</button></div>
|
|
3611
|
+
</div>
|
|
3612
|
+
} @empty {
|
|
3613
|
+
<div class="o-tr" role="row"><div class="o-td o-faint" role="cell" style="grid-column:1 / -1">No collaborators. Project roles decide who reads and pushes.</div></div>
|
|
3614
|
+
}
|
|
3615
|
+
</div>
|
|
3616
|
+
</div>
|
|
3617
|
+
<div class="o-cf">
|
|
3618
|
+
<select class="o-in o-sm" style="width:auto" aria-label="Person to add" [(ngModel)]="newCollab"><option value="">Add a person…</option>@for (p of addable(); track p.id) {<option [value]="p.id">{{ p.name || p.email }}</option>}</select>
|
|
3619
|
+
<select class="o-in o-sm" style="width:auto" aria-label="Access to give" [(ngModel)]="newPerm">@for (p of perms; track p.id) {<option [value]="p.id">{{ p.label }}</option>}</select>
|
|
3620
|
+
<button type="button" class="o-btn o-sm o-dark" [disabled]="!newCollab || busy()" (click)="setCollab(newCollab, newPerm, true)" data-testid="collab-add">Add</button>
|
|
3621
|
+
<span class="o-faint o-xs">They need to be in the project (any role, Guest included). Write pushes and opens pull requests here; maintain also changes this repository's settings.</span>
|
|
3622
|
+
</div>
|
|
3623
|
+
</div>
|
|
3624
|
+
} @else {
|
|
3625
|
+
<div class="o-card"><div class="o-empty">{{ repo()?.collaborator ? 'You have ' + repo()?.collaborator + ' on this repository as a collaborator. ' : '' }}Project Maintainers manage collaborators.</div></div>
|
|
3626
|
+
}
|
|
3285
3627
|
}
|
|
3286
3628
|
@case ('danger') {
|
|
3287
3629
|
<h1 class="o-t">Danger zone</h1>
|
|
@@ -3684,6 +4026,169 @@ function sortTags(tags) {
|
|
|
3684
4026
|
return [...tags].sort((a, b) => when(b) - when(a) || b.name.localeCompare(a.name, undefined, { numeric: true }));
|
|
3685
4027
|
}
|
|
3686
4028
|
|
|
4029
|
+
/** Uploads in one commit stay under the server's 25 MB request limit (base64 grows them by a third). */
|
|
4030
|
+
const MAX_UPLOAD = 18 * 1024 * 1024;
|
|
4031
|
+
// /{org}/{project}/code/{repo}/new?ref=&path= and /upload (DEV-13): a new file written in the
|
|
4032
|
+
// browser, or files uploaded from the computer, committed to the branch or to a new branch with a
|
|
4033
|
+
// pull request (protection applies).
|
|
4034
|
+
class NewFilePage extends RepoPage {
|
|
4035
|
+
constructor() {
|
|
4036
|
+
super();
|
|
4037
|
+
this.router = inject(Router);
|
|
4038
|
+
this.upload = signal(false, ...(ngDevMode ? [{ debugName: "upload" }] : /* istanbul ignore next */ []));
|
|
4039
|
+
this.ref = signal('', ...(ngDevMode ? [{ debugName: "ref" }] : /* istanbul ignore next */ []));
|
|
4040
|
+
this.uploads = signal([], ...(ngDevMode ? [{ debugName: "uploads" }] : /* istanbul ignore next */ []));
|
|
4041
|
+
this.busy = signal(false, ...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
|
|
4042
|
+
this.filePath = '';
|
|
4043
|
+
this.content = '';
|
|
4044
|
+
this.dir = '';
|
|
4045
|
+
this.branch = computed(() => this.ref() || this.repo()?.defaultBranch || 'main', ...(ngDevMode ? [{ debugName: "branch" }] : /* istanbul ignore next */ []));
|
|
4046
|
+
this.defaultMessage = computed(() => (this.upload() ? 'Add files via upload' : 'Create a file'), ...(ngDevMode ? [{ debugName: "defaultMessage" }] : /* istanbul ignore next */ []));
|
|
4047
|
+
this.fmt = size;
|
|
4048
|
+
this.upload.set(this.route.snapshot.url.at(-1)?.path === 'upload');
|
|
4049
|
+
this.route.queryParamMap.subscribe((q) => {
|
|
4050
|
+
this.ref.set(q.get('ref') ?? '');
|
|
4051
|
+
const p = q.get('path') ?? '';
|
|
4052
|
+
this.dir = p;
|
|
4053
|
+
this.filePath = p ? p.replace(/\/+$/, '') + '/' : '';
|
|
4054
|
+
});
|
|
4055
|
+
}
|
|
4056
|
+
onRepo(_) { }
|
|
4057
|
+
async pick(ev) {
|
|
4058
|
+
const files = Array.from(ev.target.files ?? []);
|
|
4059
|
+
const out = [...this.uploads()];
|
|
4060
|
+
for (const f of files) {
|
|
4061
|
+
const buf = new Uint8Array(await f.arrayBuffer());
|
|
4062
|
+
let bin = '';
|
|
4063
|
+
for (let i = 0; i < buf.length; i += 0x8000)
|
|
4064
|
+
bin += String.fromCharCode(...buf.subarray(i, i + 0x8000));
|
|
4065
|
+
out.push({ name: f.name, size: f.size, b64: btoa(bin) });
|
|
4066
|
+
}
|
|
4067
|
+
if (out.reduce((a, u) => a + u.size, 0) > MAX_UPLOAD) {
|
|
4068
|
+
this.error.set('One upload is at most 18 MB; commit large files with Git.');
|
|
4069
|
+
return;
|
|
4070
|
+
}
|
|
4071
|
+
this.error.set('');
|
|
4072
|
+
this.uploads.set(out);
|
|
4073
|
+
}
|
|
4074
|
+
shown(name) {
|
|
4075
|
+
const prefix = this.dir.trim().replace(/^\/+|\/+$/g, '');
|
|
4076
|
+
return (prefix ? prefix + '/' : '') + name;
|
|
4077
|
+
}
|
|
4078
|
+
drop(name) {
|
|
4079
|
+
this.uploads.set(this.uploads().filter((u) => u.name !== name));
|
|
4080
|
+
}
|
|
4081
|
+
back() {
|
|
4082
|
+
void this.router.navigate(this.link(), { queryParams: { ref: this.ref() || null, path: this.dir || null } });
|
|
4083
|
+
}
|
|
4084
|
+
save(c) {
|
|
4085
|
+
const r = this.repo();
|
|
4086
|
+
if (!r)
|
|
4087
|
+
return;
|
|
4088
|
+
const body = c.newBranch ? { branch: c.newBranch, from: this.branch(), message: c.message, pull: {} } : { branch: this.branch(), message: c.message };
|
|
4089
|
+
if (this.upload()) {
|
|
4090
|
+
if (!this.uploads().length) {
|
|
4091
|
+
this.error.set('Choose files to upload.');
|
|
4092
|
+
return;
|
|
4093
|
+
}
|
|
4094
|
+
const prefix = this.dir.trim().replace(/^\/+|\/+$/g, '');
|
|
4095
|
+
body.uploads = Object.fromEntries(this.uploads().map((u) => [(prefix ? prefix + '/' : '') + u.name, u.b64]));
|
|
4096
|
+
}
|
|
4097
|
+
else {
|
|
4098
|
+
const p = this.filePath.trim().replace(/^\/+/, '');
|
|
4099
|
+
if (!p || p.endsWith('/')) {
|
|
4100
|
+
this.error.set('Name the file.');
|
|
4101
|
+
return;
|
|
4102
|
+
}
|
|
4103
|
+
body.files = { [p]: this.content };
|
|
4104
|
+
}
|
|
4105
|
+
this.busy.set(true);
|
|
4106
|
+
this.error.set('');
|
|
4107
|
+
this.api.commitFiles(this.projectId(), r.name, body).subscribe({
|
|
4108
|
+
next: (res) => {
|
|
4109
|
+
this.busy.set(false);
|
|
4110
|
+
if (res.pull) {
|
|
4111
|
+
void this.router.navigate(this.link('pull/' + res.pull));
|
|
4112
|
+
return;
|
|
4113
|
+
}
|
|
4114
|
+
if (!this.upload()) {
|
|
4115
|
+
void this.router.navigate(this.link('blob'), { queryParams: { ref: res.branch, path: this.filePath.trim().replace(/^\/+/, '') } });
|
|
4116
|
+
return;
|
|
4117
|
+
}
|
|
4118
|
+
void this.router.navigate(this.link(), { queryParams: { ref: res.branch, path: this.dir.trim() || null } });
|
|
4119
|
+
},
|
|
4120
|
+
error: (e) => { this.busy.set(false); this.error.set(apiMessage(e, 'Could not commit.')); },
|
|
4121
|
+
});
|
|
4122
|
+
}
|
|
4123
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: NewFilePage, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4124
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: NewFilePage, isStandalone: true, selector: "o-code-new-file", usesInheritance: true, ngImport: i0, template: `
|
|
4125
|
+
<div class="o-page o-wide">
|
|
4126
|
+
@if (repo(); as r) {
|
|
4127
|
+
<div class="o-ph"><div class="o-col"><span class="o-eyebrow">{{ r.name }} · {{ branch() }}</span><h1 class="o-t">{{ upload() ? 'Upload files' : 'Create a file' }}</h1></div></div>
|
|
4128
|
+
@if (error()) {<div class="o-note o-red" role="alert">{{ error() }}</div>}
|
|
4129
|
+
@if (!canWrite()) {
|
|
4130
|
+
<div class="o-card"><div class="o-empty">Your role cannot change this repository.</div></div>
|
|
4131
|
+
} @else {
|
|
4132
|
+
<div class="o-card">
|
|
4133
|
+
<div class="o-cb o-col" style="gap:12px">
|
|
4134
|
+
@if (upload()) {
|
|
4135
|
+
<div class="o-fld"><label for="nf-dir">Into folder</label><input id="nf-dir" class="o-in o-mono" [(ngModel)]="dir" placeholder="(the repository root)" /></div>
|
|
4136
|
+
<div class="o-fld"><label for="nf-files">Files</label><input id="nf-files" type="file" multiple class="o-in" (change)="pick($event)" data-testid="upload-files" /></div>
|
|
4137
|
+
@for (u of uploads(); track u.name) {<div class="o-row o-sm"><span class="o-mono">{{ shown(u.name) }}</span><span class="o-r o-faint">{{ fmt(u.size) }}</span><button type="button" class="o-lnk" (click)="drop(u.name)" [attr.aria-label]="'Remove ' + u.name">×</button></div>}
|
|
4138
|
+
} @else {
|
|
4139
|
+
<div class="o-fld"><label for="nf-path">File path</label><input id="nf-path" class="o-in o-mono" [(ngModel)]="filePath" placeholder="docs/notes.md" data-testid="new-file-path" /></div>
|
|
4140
|
+
<textarea class="o-in o-mono" rows="16" style="font-size:12px;height:auto;padding:8px 10px" aria-label="File contents" [(ngModel)]="content" data-testid="new-file-text"></textarea>
|
|
4141
|
+
}
|
|
4142
|
+
</div>
|
|
4143
|
+
<div class="o-cb" style="border-top:1px solid var(--o-line-soft)">
|
|
4144
|
+
<o-commit-box [repo]="r.name" [branch]="branch()" [defaultMessage]="defaultMessage()" [busy]="busy()" (commit)="save($event)" (cancel)="back()" />
|
|
4145
|
+
</div>
|
|
4146
|
+
</div>
|
|
4147
|
+
}
|
|
4148
|
+
} @else if (error()) {
|
|
4149
|
+
<div class="o-card o-empty"><p>{{ error() }}</p><a class="o-btn" [routerLink]="host.link('code')">All repositories</a></div>
|
|
4150
|
+
}
|
|
4151
|
+
</div>
|
|
4152
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: CommitBox, selector: "o-commit-box", inputs: ["repo", "branch", "defaultMessage", "busy"], outputs: ["commit", "cancel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
4153
|
+
}
|
|
4154
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: NewFilePage, decorators: [{
|
|
4155
|
+
type: Component,
|
|
4156
|
+
args: [{
|
|
4157
|
+
selector: 'o-code-new-file',
|
|
4158
|
+
imports: [FormsModule, RouterLink, CommitBox],
|
|
4159
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
4160
|
+
template: `
|
|
4161
|
+
<div class="o-page o-wide">
|
|
4162
|
+
@if (repo(); as r) {
|
|
4163
|
+
<div class="o-ph"><div class="o-col"><span class="o-eyebrow">{{ r.name }} · {{ branch() }}</span><h1 class="o-t">{{ upload() ? 'Upload files' : 'Create a file' }}</h1></div></div>
|
|
4164
|
+
@if (error()) {<div class="o-note o-red" role="alert">{{ error() }}</div>}
|
|
4165
|
+
@if (!canWrite()) {
|
|
4166
|
+
<div class="o-card"><div class="o-empty">Your role cannot change this repository.</div></div>
|
|
4167
|
+
} @else {
|
|
4168
|
+
<div class="o-card">
|
|
4169
|
+
<div class="o-cb o-col" style="gap:12px">
|
|
4170
|
+
@if (upload()) {
|
|
4171
|
+
<div class="o-fld"><label for="nf-dir">Into folder</label><input id="nf-dir" class="o-in o-mono" [(ngModel)]="dir" placeholder="(the repository root)" /></div>
|
|
4172
|
+
<div class="o-fld"><label for="nf-files">Files</label><input id="nf-files" type="file" multiple class="o-in" (change)="pick($event)" data-testid="upload-files" /></div>
|
|
4173
|
+
@for (u of uploads(); track u.name) {<div class="o-row o-sm"><span class="o-mono">{{ shown(u.name) }}</span><span class="o-r o-faint">{{ fmt(u.size) }}</span><button type="button" class="o-lnk" (click)="drop(u.name)" [attr.aria-label]="'Remove ' + u.name">×</button></div>}
|
|
4174
|
+
} @else {
|
|
4175
|
+
<div class="o-fld"><label for="nf-path">File path</label><input id="nf-path" class="o-in o-mono" [(ngModel)]="filePath" placeholder="docs/notes.md" data-testid="new-file-path" /></div>
|
|
4176
|
+
<textarea class="o-in o-mono" rows="16" style="font-size:12px;height:auto;padding:8px 10px" aria-label="File contents" [(ngModel)]="content" data-testid="new-file-text"></textarea>
|
|
4177
|
+
}
|
|
4178
|
+
</div>
|
|
4179
|
+
<div class="o-cb" style="border-top:1px solid var(--o-line-soft)">
|
|
4180
|
+
<o-commit-box [repo]="r.name" [branch]="branch()" [defaultMessage]="defaultMessage()" [busy]="busy()" (commit)="save($event)" (cancel)="back()" />
|
|
4181
|
+
</div>
|
|
4182
|
+
</div>
|
|
4183
|
+
}
|
|
4184
|
+
} @else if (error()) {
|
|
4185
|
+
<div class="o-card o-empty"><p>{{ error() }}</p><a class="o-btn" [routerLink]="host.link('code')">All repositories</a></div>
|
|
4186
|
+
}
|
|
4187
|
+
</div>
|
|
4188
|
+
`,
|
|
4189
|
+
}]
|
|
4190
|
+
}], ctorParameters: () => [] });
|
|
4191
|
+
|
|
3687
4192
|
/*
|
|
3688
4193
|
* @chisquare-tech/origine-ngx-apps-code: the Code module's screens (TRD §6.10). The host app provides
|
|
3689
4194
|
* CodeHost and mounts the pages at /{org}/{project}/code, /code/new, /code/pulls and
|
|
@@ -3694,5 +4199,5 @@ function sortTags(tags) {
|
|
|
3694
4199
|
* Generated bundle index. Do not edit.
|
|
3695
4200
|
*/
|
|
3696
4201
|
|
|
3697
|
-
export { BranchesPage, CodeApi, CodeHost, CommitPage, CommitsPage, CurrentRepo, CurrentRepoPage, DiffView, FilePage, NewRepoPage, People, PullPage, PullsPage, RefPicker, ReleasesPage, RepoHomePage, RepoPage, RepoSettingsPage, ReposPage, TOKEN_CSS, ago, apiMessage, checkDot, copy, dayLabel, initials, langColour, pair, shortDate, size };
|
|
4202
|
+
export { BranchesPage, CodeApi, CodeHost, CommitBox, CommitPage, CommitsPage, CurrentRepo, CurrentRepoPage, DiffView, FilePage, NewFilePage, NewRepoPage, People, PullPage, PullsPage, RefPicker, ReleasesPage, RepoHomePage, RepoPage, RepoSettingsPage, ReposPage, TOKEN_CSS, ago, apiMessage, checkDot, copy, dayLabel, initials, langColour, matchesPattern, pair, shortDate, size };
|
|
3698
4203
|
//# sourceMappingURL=chisquare-tech-origine-ngx-apps-code.mjs.map
|