@chisquare-tech/origine-ngx-apps-code 0.3.0 → 0.4.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.
|
@@ -115,6 +115,15 @@ class CodeApi {
|
|
|
115
115
|
p['q'] = q;
|
|
116
116
|
return this.api.get(`${this.repoPath(project, name)}/refs`, p).pipe(map((r) => data(r) ?? []));
|
|
117
117
|
}
|
|
118
|
+
collaborators(project, name) {
|
|
119
|
+
return this.api.get(`${this.repoPath(project, name)}/collaborators`).pipe(map((r) => data(r) ?? []));
|
|
120
|
+
}
|
|
121
|
+
setCollaborator(project, name, userId, permission) {
|
|
122
|
+
return this.api.put(`${this.repoPath(project, name)}/collaborators/${enc(userId)}`, { permission }).pipe(map((r) => data(r)));
|
|
123
|
+
}
|
|
124
|
+
removeCollaborator(project, name, userId) {
|
|
125
|
+
return this.api.delete(`${this.repoPath(project, name)}/collaborators/${enc(userId)}`);
|
|
126
|
+
}
|
|
118
127
|
createTag(project, name, tag, target) {
|
|
119
128
|
return this.api.post(`${this.repoPath(project, name)}/tags`, { tag, target }).pipe(map((r) => data(r)));
|
|
120
129
|
}
|
|
@@ -343,6 +352,8 @@ class RepoPage {
|
|
|
343
352
|
this.people = computed(() => new People(this.host.people()), ...(ngDevMode ? [{ debugName: "people" }] : /* istanbul ignore next */ []));
|
|
344
353
|
this.canWrite = computed(() => !this.repo()?.archivedAt && (this.repo()?.capabilities ?? []).includes('code.write'), ...(ngDevMode ? [{ debugName: "canWrite" }] : /* istanbul ignore next */ []));
|
|
345
354
|
this.canAdmin = computed(() => (this.repo()?.capabilities ?? []).includes('resources.manage'), ...(ngDevMode ? [{ debugName: "canAdmin" }] : /* istanbul ignore next */ []));
|
|
355
|
+
/** Repository settings short of a rename, the visibility and deletion: Maintainers, and maintain collaborators. */
|
|
356
|
+
this.canMaintain = computed(() => this.canAdmin() || (this.repo()?.capabilities ?? []).includes('repo.maintain'), ...(ngDevMode ? [{ debugName: "canMaintain" }] : /* istanbul ignore next */ []));
|
|
346
357
|
this.canProtect = computed(() => (this.repo()?.capabilities ?? []).includes('protection.manage'), ...(ngDevMode ? [{ debugName: "canProtect" }] : /* istanbul ignore next */ []));
|
|
347
358
|
this.route.paramMap.subscribe((m) => this.name.set(m.get('repo') ?? ''));
|
|
348
359
|
effect(() => {
|
|
@@ -3000,9 +3011,46 @@ class RepoSettingsPage extends RepoPage {
|
|
|
3000
3011
|
{ key: 'dismissStaleApprovals', label: 'Dismiss stale approvals', hint: 'A new commit dismisses approvals given on an older one.' },
|
|
3001
3012
|
{ key: 'requireUpToDate', label: 'Require branches to be up to date', hint: 'The branch must contain the target’s latest commit before it merges.' },
|
|
3002
3013
|
];
|
|
3014
|
+
this.collabs = signal([], ...(ngDevMode ? [{ debugName: "collabs" }] : /* istanbul ignore next */ []));
|
|
3015
|
+
this.perms = [{ id: 'read', label: 'Read' }, { id: 'write', label: 'Write' }, { id: 'maintain', label: 'Maintain' }];
|
|
3016
|
+
this.addable = computed(() => this.host.people().filter((p) => !this.collabs().some((c) => c.userId === p.id)), ...(ngDevMode ? [{ debugName: "addable" }] : /* istanbul ignore next */ []));
|
|
3017
|
+
this.newCollab = '';
|
|
3018
|
+
this.newPerm = 'write';
|
|
3003
3019
|
this.route.queryParamMap.subscribe((q) => this.section.set(q.get('section') || 'general'));
|
|
3004
3020
|
}
|
|
3021
|
+
roleLabel(r) {
|
|
3022
|
+
return r ? r[0].toUpperCase() + r.slice(1) : 'Not in the project';
|
|
3023
|
+
}
|
|
3024
|
+
loadCollabs(r) {
|
|
3025
|
+
if (!this.canAdmin())
|
|
3026
|
+
return;
|
|
3027
|
+
this.api.collaborators(this.projectId(), r.name).subscribe({ next: (l) => this.collabs.set(l), error: () => undefined });
|
|
3028
|
+
}
|
|
3029
|
+
setCollab(userId, permission, adding = false) {
|
|
3030
|
+
const r = this.repo();
|
|
3031
|
+
if (!r || !userId)
|
|
3032
|
+
return;
|
|
3033
|
+
this.busy.set(true);
|
|
3034
|
+
this.error.set('');
|
|
3035
|
+
this.api.setCollaborator(this.projectId(), r.name, userId, permission).subscribe({
|
|
3036
|
+
next: () => { this.busy.set(false); if (adding) {
|
|
3037
|
+
this.newCollab = '';
|
|
3038
|
+
this.note.set(`${this.people().name(userId)} now has ${permission} on ${r.name}.`);
|
|
3039
|
+
} this.loadCollabs(r); },
|
|
3040
|
+
error: (e) => { this.busy.set(false); this.error.set(apiMessage(e, 'Could not change the access.')); this.loadCollabs(r); },
|
|
3041
|
+
});
|
|
3042
|
+
}
|
|
3043
|
+
removeCollab(userId) {
|
|
3044
|
+
const r = this.repo();
|
|
3045
|
+
if (!r)
|
|
3046
|
+
return;
|
|
3047
|
+
this.api.removeCollaborator(this.projectId(), r.name, userId).subscribe({
|
|
3048
|
+
next: () => { this.note.set(`${this.people().name(userId)} is no longer a collaborator.`); this.loadCollabs(r); },
|
|
3049
|
+
error: (e) => this.error.set(apiMessage(e, 'Could not remove the collaborator.')),
|
|
3050
|
+
});
|
|
3051
|
+
}
|
|
3005
3052
|
onRepo(r) {
|
|
3053
|
+
this.loadCollabs(r);
|
|
3006
3054
|
this.g = { name: r.name, description: r.description, visibility: r.visibility, defaultBranch: r.defaultBranch, mergeMethods: [...(r.mergeMethods ?? [])], deleteBranchOnMerge: r.deleteBranchOnMerge };
|
|
3007
3055
|
if (!r.empty)
|
|
3008
3056
|
this.api.refs(this.projectId(), r.name, 'branch').subscribe({ next: (b) => this.branches.set(b), error: () => undefined });
|
|
@@ -3055,7 +3103,11 @@ class RepoSettingsPage extends RepoPage {
|
|
|
3055
3103
|
return;
|
|
3056
3104
|
this.busy.set(true);
|
|
3057
3105
|
const renamed = this.g.name.trim() !== r.name;
|
|
3058
|
-
|
|
3106
|
+
// a maintain collaborator sends what they may change; the name and visibility stay a Maintainer's
|
|
3107
|
+
const body = this.canAdmin()
|
|
3108
|
+
? { 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 }
|
|
3109
|
+
: { description: this.g.description, defaultBranch: this.g.defaultBranch, mergeMethods: this.g.mergeMethods, deleteBranchOnMerge: this.g.deleteBranchOnMerge };
|
|
3110
|
+
this.api.update(this.projectId(), r.name, body).subscribe({
|
|
3059
3111
|
next: (n) => {
|
|
3060
3112
|
this.busy.set(false);
|
|
3061
3113
|
this.note.set('Saved.');
|
|
@@ -3120,7 +3172,7 @@ class RepoSettingsPage extends RepoPage {
|
|
|
3120
3172
|
<span class="o-lbl" style="padding-top:0">{{ r.name }}</span>
|
|
3121
3173
|
<button type="button" [class.o-on]="section() === 'general'" (click)="go('general')">General</button>
|
|
3122
3174
|
<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
|
|
3175
|
+
<button type="button" [class.o-on]="section() === 'collab'" (click)="go('collab')">Collaborators</button>
|
|
3124
3176
|
<button type="button" [class.o-on]="section() === 'danger'" (click)="go('danger')">Danger zone</button>
|
|
3125
3177
|
</nav>
|
|
3126
3178
|
<div class="o-col" style="gap:16px;min-width:0">
|
|
@@ -3132,9 +3184,9 @@ class RepoSettingsPage extends RepoPage {
|
|
|
3132
3184
|
<form class="o-card o-cb o-form" (ngSubmit)="saveGeneral()">
|
|
3133
3185
|
<div class="o-g2">
|
|
3134
3186
|
<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]="!
|
|
3187
|
+
<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
3188
|
</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]="!
|
|
3189
|
+
<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
3190
|
<div class="o-fld"><span class="o-lbl" style="padding:0">Visibility</span>
|
|
3139
3191
|
<div class="o-g2">
|
|
3140
3192
|
<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 +3194,11 @@ class RepoSettingsPage extends RepoPage {
|
|
|
3142
3194
|
</div>
|
|
3143
3195
|
</div>
|
|
3144
3196
|
<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]="!
|
|
3197
|
+
@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
3198
|
</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]="!
|
|
3199
|
+
<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
3200
|
<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 (
|
|
3201
|
+
@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
3202
|
</form>
|
|
3151
3203
|
}
|
|
3152
3204
|
@case ('protect') {
|
|
@@ -3183,7 +3235,34 @@ class RepoSettingsPage extends RepoPage {
|
|
|
3183
3235
|
}
|
|
3184
3236
|
@case ('collab') {
|
|
3185
3237
|
<h1 class="o-t">Collaborators</h1>
|
|
3186
|
-
<
|
|
3238
|
+
<span class="o-mut">Access beyond the project role, for this repository only.</span>
|
|
3239
|
+
@if (canAdmin()) {
|
|
3240
|
+
<div class="o-card" style="overflow-x:auto" data-testid="collaborators">
|
|
3241
|
+
<div class="o-tbl" role="table" aria-label="Collaborators" style="min-width:560px">
|
|
3242
|
+
<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>
|
|
3243
|
+
<div class="o-tbody" role="rowgroup">
|
|
3244
|
+
@for (cl of collabs(); track cl.userId) {
|
|
3245
|
+
<div class="o-tr" role="row" [attr.data-testid]="'collab-' + cl.userId">
|
|
3246
|
+
<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>
|
|
3247
|
+
<div class="o-td o-mut" role="cell">{{ roleLabel(cl.projectRole) }}</div>
|
|
3248
|
+
<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>
|
|
3249
|
+
<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>
|
|
3250
|
+
</div>
|
|
3251
|
+
} @empty {
|
|
3252
|
+
<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>
|
|
3253
|
+
}
|
|
3254
|
+
</div>
|
|
3255
|
+
</div>
|
|
3256
|
+
<div class="o-cf">
|
|
3257
|
+
<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>
|
|
3258
|
+
<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>
|
|
3259
|
+
<button type="button" class="o-btn o-sm o-dark" [disabled]="!newCollab || busy()" (click)="setCollab(newCollab, newPerm, true)" data-testid="collab-add">Add</button>
|
|
3260
|
+
<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>
|
|
3261
|
+
</div>
|
|
3262
|
+
</div>
|
|
3263
|
+
} @else {
|
|
3264
|
+
<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>
|
|
3265
|
+
}
|
|
3187
3266
|
}
|
|
3188
3267
|
@case ('danger') {
|
|
3189
3268
|
<h1 class="o-t">Danger zone</h1>
|
|
@@ -3218,7 +3297,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3218
3297
|
<span class="o-lbl" style="padding-top:0">{{ r.name }}</span>
|
|
3219
3298
|
<button type="button" [class.o-on]="section() === 'general'" (click)="go('general')">General</button>
|
|
3220
3299
|
<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
|
|
3300
|
+
<button type="button" [class.o-on]="section() === 'collab'" (click)="go('collab')">Collaborators</button>
|
|
3222
3301
|
<button type="button" [class.o-on]="section() === 'danger'" (click)="go('danger')">Danger zone</button>
|
|
3223
3302
|
</nav>
|
|
3224
3303
|
<div class="o-col" style="gap:16px;min-width:0">
|
|
@@ -3230,9 +3309,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3230
3309
|
<form class="o-card o-cb o-form" (ngSubmit)="saveGeneral()">
|
|
3231
3310
|
<div class="o-g2">
|
|
3232
3311
|
<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]="!
|
|
3312
|
+
<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
3313
|
</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]="!
|
|
3314
|
+
<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
3315
|
<div class="o-fld"><span class="o-lbl" style="padding:0">Visibility</span>
|
|
3237
3316
|
<div class="o-g2">
|
|
3238
3317
|
<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 +3319,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3240
3319
|
</div>
|
|
3241
3320
|
</div>
|
|
3242
3321
|
<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]="!
|
|
3322
|
+
@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
3323
|
</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]="!
|
|
3324
|
+
<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
3325
|
<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 (
|
|
3326
|
+
@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
3327
|
</form>
|
|
3249
3328
|
}
|
|
3250
3329
|
@case ('protect') {
|
|
@@ -3281,7 +3360,34 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3281
3360
|
}
|
|
3282
3361
|
@case ('collab') {
|
|
3283
3362
|
<h1 class="o-t">Collaborators</h1>
|
|
3284
|
-
<
|
|
3363
|
+
<span class="o-mut">Access beyond the project role, for this repository only.</span>
|
|
3364
|
+
@if (canAdmin()) {
|
|
3365
|
+
<div class="o-card" style="overflow-x:auto" data-testid="collaborators">
|
|
3366
|
+
<div class="o-tbl" role="table" aria-label="Collaborators" style="min-width:560px">
|
|
3367
|
+
<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>
|
|
3368
|
+
<div class="o-tbody" role="rowgroup">
|
|
3369
|
+
@for (cl of collabs(); track cl.userId) {
|
|
3370
|
+
<div class="o-tr" role="row" [attr.data-testid]="'collab-' + cl.userId">
|
|
3371
|
+
<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>
|
|
3372
|
+
<div class="o-td o-mut" role="cell">{{ roleLabel(cl.projectRole) }}</div>
|
|
3373
|
+
<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>
|
|
3374
|
+
<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>
|
|
3375
|
+
</div>
|
|
3376
|
+
} @empty {
|
|
3377
|
+
<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>
|
|
3378
|
+
}
|
|
3379
|
+
</div>
|
|
3380
|
+
</div>
|
|
3381
|
+
<div class="o-cf">
|
|
3382
|
+
<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>
|
|
3383
|
+
<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>
|
|
3384
|
+
<button type="button" class="o-btn o-sm o-dark" [disabled]="!newCollab || busy()" (click)="setCollab(newCollab, newPerm, true)" data-testid="collab-add">Add</button>
|
|
3385
|
+
<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>
|
|
3386
|
+
</div>
|
|
3387
|
+
</div>
|
|
3388
|
+
} @else {
|
|
3389
|
+
<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>
|
|
3390
|
+
}
|
|
3285
3391
|
}
|
|
3286
3392
|
@case ('danger') {
|
|
3287
3393
|
<h1 class="o-t">Danger zone</h1>
|