@chisquare-tech/origine-ngx-apps-code 0.8.0 → 0.10.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.
|
@@ -122,6 +122,13 @@ class CodeApi {
|
|
|
122
122
|
p['q'] = q;
|
|
123
123
|
return this.api.get(`${this.repoPath(project, name)}/refs`, p).pipe(map((r) => data(r) ?? []));
|
|
124
124
|
}
|
|
125
|
+
/** Searches the repository's default branch (text, or a regular expression). */
|
|
126
|
+
searchCode(project, name, q, regex = false) {
|
|
127
|
+
const p = { q };
|
|
128
|
+
if (regex)
|
|
129
|
+
p['regex'] = 'true';
|
|
130
|
+
return this.api.get(`${this.repoPath(project, name)}/search`, p).pipe(map((r) => data(r) ?? { hits: [], repos: 0 }));
|
|
131
|
+
}
|
|
125
132
|
collaborators(project, name) {
|
|
126
133
|
return this.api.get(`${this.repoPath(project, name)}/collaborators`).pipe(map((r) => data(r) ?? []));
|
|
127
134
|
}
|
|
@@ -349,6 +356,10 @@ const TOKEN_CSS = `
|
|
|
349
356
|
:host ::ng-deep .tk-nb, :host ::ng-deep .tk-nt, :host ::ng-deep .tk-bp{color:var(--o-tk-p)}
|
|
350
357
|
:host ::ng-deep .tk-gi{color:var(--o-add)} :host ::ng-deep .tk-gd{color:var(--o-bad-tx)}
|
|
351
358
|
`;
|
|
359
|
+
/** Why a signed commit is not verified (DEV-31). */
|
|
360
|
+
function verifyWhy(reason) {
|
|
361
|
+
return { unknown_key: 'Signed with a key that is not the committer’s signing key', bad_signature: 'The signature does not match the commit' }[reason] ?? 'Not verified';
|
|
362
|
+
}
|
|
352
363
|
|
|
353
364
|
/**
|
|
354
365
|
* What every page under /code/:repo shares: the project, the repository named in the URL (loaded
|
|
@@ -366,6 +377,7 @@ class RepoPage {
|
|
|
366
377
|
this.error = signal('', ...(ngDevMode ? [{ debugName: "error" }] : /* istanbul ignore next */ []));
|
|
367
378
|
this.note = signal('', ...(ngDevMode ? [{ debugName: "note" }] : /* istanbul ignore next */ []));
|
|
368
379
|
this.people = computed(() => new People(this.host.people()), ...(ngDevMode ? [{ debugName: "people" }] : /* istanbul ignore next */ []));
|
|
380
|
+
this.verifyWhy = verifyWhy;
|
|
369
381
|
this.canWrite = computed(() => !this.repo()?.archivedAt && (this.repo()?.capabilities ?? []).includes('code.write'), ...(ngDevMode ? [{ debugName: "canWrite" }] : /* istanbul ignore next */ []));
|
|
370
382
|
this.canAdmin = computed(() => (this.repo()?.capabilities ?? []).includes('resources.manage'), ...(ngDevMode ? [{ debugName: "canAdmin" }] : /* istanbul ignore next */ []));
|
|
371
383
|
/** Repository settings short of a rename, the visibility and deletion: Maintainers, and maintain collaborators. */
|
|
@@ -1286,6 +1298,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
1286
1298
|
// /{org}/{project}/code/{repo}?ref=&path= (prototype "code_repo"): the repository's files at a
|
|
1287
1299
|
// branch, tag or commit, the last commit of each entry, the README, the clone menu and languages.
|
|
1288
1300
|
class RepoHomePage extends RepoPage {
|
|
1301
|
+
searchCode() {
|
|
1302
|
+
const q = this.codeQuery.trim();
|
|
1303
|
+
if (q)
|
|
1304
|
+
void this.router.navigate(this.link('search'), { queryParams: { q } });
|
|
1305
|
+
}
|
|
1289
1306
|
constructor() {
|
|
1290
1307
|
super();
|
|
1291
1308
|
this.router = inject(Router);
|
|
@@ -1297,6 +1314,7 @@ class RepoHomePage extends RepoPage {
|
|
|
1297
1314
|
this.proto = signal('https', ...(ngDevMode ? [{ debugName: "proto" }] : /* istanbul ignore next */ []));
|
|
1298
1315
|
this.cloneOpen = signal(false, ...(ngDevMode ? [{ debugName: "cloneOpen" }] : /* istanbul ignore next */ []));
|
|
1299
1316
|
this.addOpen = signal(false, ...(ngDevMode ? [{ debugName: "addOpen" }] : /* istanbul ignore next */ []));
|
|
1317
|
+
this.codeQuery = '';
|
|
1300
1318
|
this.copied = signal(false, ...(ngDevMode ? [{ debugName: "copied" }] : /* istanbul ignore next */ []));
|
|
1301
1319
|
this.finding = signal(false, ...(ngDevMode ? [{ debugName: "finding" }] : /* istanbul ignore next */ []));
|
|
1302
1320
|
this.fq = signal('', ...(ngDevMode ? [{ debugName: "fq" }] : /* istanbul ignore next */ []));
|
|
@@ -1424,6 +1442,7 @@ class RepoHomePage extends RepoPage {
|
|
|
1424
1442
|
@for (c of crumbs(); track c.path) {<span class="o-faint">/</span><a [routerLink]="link()" [queryParams]="qp(c.path)">{{ c.name }}</a>}
|
|
1425
1443
|
</nav>
|
|
1426
1444
|
<span class="o-r"></span>
|
|
1445
|
+
<form class="o-flex" (ngSubmit)="searchCode()" role="search"><input class="o-in o-sm o-mono" style="width:180px" name="cq" [(ngModel)]="codeQuery" placeholder="Search code" aria-label="Search code" data-testid="repo-code-search" /></form>
|
|
1427
1446
|
<button type="button" class="o-btn o-sm" (click)="openFinder()">Go to file <span class="o-kbd">T</span></button>
|
|
1428
1447
|
@if (canWrite()) {
|
|
1429
1448
|
<span class="anchor add">
|
|
@@ -1513,7 +1532,7 @@ class RepoHomePage extends RepoPage {
|
|
|
1513
1532
|
</div>
|
|
1514
1533
|
</div>
|
|
1515
1534
|
}
|
|
1516
|
-
`, isInline: true, styles: [".anchor{position:relative}.ic{width:18px;flex:none;color:var(--o-dim);text-align:center}\n"], 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: RefPicker, selector: "o-ref-picker", inputs: ["repo", "current"], outputs: ["picked"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
1535
|
+
`, isInline: true, styles: [".anchor{position:relative}.ic{width:18px;flex:none;color:var(--o-dim);text-align:center}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { 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 }); }
|
|
1517
1536
|
}
|
|
1518
1537
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: RepoHomePage, decorators: [{
|
|
1519
1538
|
type: Component,
|
|
@@ -1555,6 +1574,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
1555
1574
|
@for (c of crumbs(); track c.path) {<span class="o-faint">/</span><a [routerLink]="link()" [queryParams]="qp(c.path)">{{ c.name }}</a>}
|
|
1556
1575
|
</nav>
|
|
1557
1576
|
<span class="o-r"></span>
|
|
1577
|
+
<form class="o-flex" (ngSubmit)="searchCode()" role="search"><input class="o-in o-sm o-mono" style="width:180px" name="cq" [(ngModel)]="codeQuery" placeholder="Search code" aria-label="Search code" data-testid="repo-code-search" /></form>
|
|
1558
1578
|
<button type="button" class="o-btn o-sm" (click)="openFinder()">Go to file <span class="o-kbd">T</span></button>
|
|
1559
1579
|
@if (canWrite()) {
|
|
1560
1580
|
<span class="anchor add">
|
|
@@ -1687,7 +1707,8 @@ class CommitBox {
|
|
|
1687
1707
|
this.toNew = signal(false, ...(ngDevMode ? [{ debugName: "toNew" }] : /* istanbul ignore next */ []));
|
|
1688
1708
|
this.newBranch = signal('', ...(ngDevMode ? [{ debugName: "newBranch" }] : /* istanbul ignore next */ []));
|
|
1689
1709
|
this.rules = signal([], ...(ngDevMode ? [{ debugName: "rules" }] : /* istanbul ignore next */ []));
|
|
1690
|
-
|
|
1710
|
+
/** Direct commits are refused: the rules block direct pushes, or require signed commits (a browser commit is unsigned). */
|
|
1711
|
+
this.blocked = computed(() => this.rules().some((r) => (r.blockDirectPush || r.requireSignedCommits) && matchesPattern(r.pattern, this.branch())), ...(ngDevMode ? [{ debugName: "blocked" }] : /* istanbul ignore next */ []));
|
|
1691
1712
|
effect(() => {
|
|
1692
1713
|
const m = this.defaultMessage();
|
|
1693
1714
|
untracked(() => this.message.set(m));
|
|
@@ -2139,6 +2160,7 @@ class CommitsPage extends RepoPage {
|
|
|
2139
2160
|
@for (k of c.keys; track k) {<a class="o-chip o-blu" [routerLink]="host.link('issues/' + k)">{{ k }}</a>}
|
|
2140
2161
|
</span>
|
|
2141
2162
|
</div>
|
|
2163
|
+
@if (c.verification; as v) {<span class="o-chip" [class.o-grn]="v.verified" [attr.title]="v.verified ? 'Signed with a key of ' + people().name(v.signer) : verifyWhy(v.reason)" [attr.data-testid]="'verify-' + c.short">{{ v.verified ? 'Verified' : 'Unverified' }}</span>}
|
|
2142
2164
|
<a class="o-btn o-sm o-mono" [routerLink]="link('commit/' + c.oid)">{{ c.short }}</a>
|
|
2143
2165
|
<a class="o-btn o-sm" [routerLink]="link()" [queryParams]="{ ref: c.oid }" title="Browse files at this commit" [attr.aria-label]="'Browse files at ' + c.short">‹›</a>
|
|
2144
2166
|
</div>
|
|
@@ -2179,6 +2201,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
2179
2201
|
@for (k of c.keys; track k) {<a class="o-chip o-blu" [routerLink]="host.link('issues/' + k)">{{ k }}</a>}
|
|
2180
2202
|
</span>
|
|
2181
2203
|
</div>
|
|
2204
|
+
@if (c.verification; as v) {<span class="o-chip" [class.o-grn]="v.verified" [attr.title]="v.verified ? 'Signed with a key of ' + people().name(v.signer) : verifyWhy(v.reason)" [attr.data-testid]="'verify-' + c.short">{{ v.verified ? 'Verified' : 'Unverified' }}</span>}
|
|
2182
2205
|
<a class="o-btn o-sm o-mono" [routerLink]="link('commit/' + c.oid)">{{ c.short }}</a>
|
|
2183
2206
|
<a class="o-btn o-sm" [routerLink]="link()" [queryParams]="{ ref: c.oid }" title="Browse files at this commit" [attr.aria-label]="'Browse files at ' + c.short">‹›</a>
|
|
2184
2207
|
</div>
|
|
@@ -2228,6 +2251,7 @@ class CommitPage extends RepoPage {
|
|
|
2228
2251
|
@if (c.body) {<span class="o-mut o-lh" style="white-space:pre-wrap">{{ c.body }}</span>}
|
|
2229
2252
|
<div class="o-flex o-wrap o-sm">
|
|
2230
2253
|
<span class="o-av" aria-hidden="true">{{ initials(c.author.name) }}</span><b class="o-b5">{{ c.author.name }}</b><span class="o-faint">committed {{ ago(c.committer.when) }}</span>
|
|
2254
|
+
@if (c.verification; as v) {<span class="o-chip" [class.o-grn]="v.verified" [attr.title]="v.verified ? 'Signed with a key of ' + people().name(v.signer) : verifyWhy(v.reason)" data-testid="commit-verified">{{ v.verified ? 'Verified' : 'Unverified' }}</span>}
|
|
2231
2255
|
@for (k of c.keys; track k) {<a class="o-chip o-blu" [routerLink]="host.link('issues/' + k)">{{ k }}</a>}
|
|
2232
2256
|
<span class="o-r o-mono o-faint">commit {{ c.oid.slice(0, 10) }}@for (p of c.parents; track p) { · parent <a [routerLink]="link('commit/' + p)">{{ p.slice(0, 7) }}</a>}</span>
|
|
2233
2257
|
</div>
|
|
@@ -2259,6 +2283,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
2259
2283
|
@if (c.body) {<span class="o-mut o-lh" style="white-space:pre-wrap">{{ c.body }}</span>}
|
|
2260
2284
|
<div class="o-flex o-wrap o-sm">
|
|
2261
2285
|
<span class="o-av" aria-hidden="true">{{ initials(c.author.name) }}</span><b class="o-b5">{{ c.author.name }}</b><span class="o-faint">committed {{ ago(c.committer.when) }}</span>
|
|
2286
|
+
@if (c.verification; as v) {<span class="o-chip" [class.o-grn]="v.verified" [attr.title]="v.verified ? 'Signed with a key of ' + people().name(v.signer) : verifyWhy(v.reason)" data-testid="commit-verified">{{ v.verified ? 'Verified' : 'Unverified' }}</span>}
|
|
2262
2287
|
@for (k of c.keys; track k) {<a class="o-chip o-blu" [routerLink]="host.link('issues/' + k)">{{ k }}</a>}
|
|
2263
2288
|
<span class="o-r o-mono o-faint">commit {{ c.oid.slice(0, 10) }}@for (p of c.parents; track p) { · parent <a [routerLink]="link('commit/' + p)">{{ p.slice(0, 7) }}</a>}</span>
|
|
2264
2289
|
</div>
|
|
@@ -3057,7 +3082,7 @@ class PullPage extends RepoPage {
|
|
|
3057
3082
|
@case ('commits') {
|
|
3058
3083
|
<div class="o-card">
|
|
3059
3084
|
@for (c of cmp()?.commits ?? []; track c.oid) {
|
|
3060
|
-
<div class="o-row"><span class="o-av" aria-hidden="true">{{ initials(c.author.name) }}</span><a class="o-ell" style="flex:1;color:var(--o-tx)" [routerLink]="link('commit/' + c.oid)">{{ c.subject }}</a
|
|
3085
|
+
<div class="o-row"><span class="o-av" aria-hidden="true">{{ initials(c.author.name) }}</span><a class="o-ell" style="flex:1;color:var(--o-tx)" [routerLink]="link('commit/' + c.oid)">{{ c.subject }}</a>@if (c.verification; as v) {<span class="o-chip" [class.o-grn]="v.verified" [attr.title]="v.verified ? 'Signed with a key of ' + people().name(v.signer) : verifyWhy(v.reason)" [attr.data-testid]="'verify-' + c.short">{{ v.verified ? 'Verified' : 'Unverified' }}</span>}<a class="o-mono o-sm o-faint" [routerLink]="link('commit/' + c.oid)">{{ c.short }}</a></div>
|
|
3061
3086
|
} @empty {<div class="o-empty">{{ cmp() ? 'No commits.' : 'Loading…' }}</div>}
|
|
3062
3087
|
</div>
|
|
3063
3088
|
}
|
|
@@ -3240,7 +3265,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3240
3265
|
@case ('commits') {
|
|
3241
3266
|
<div class="o-card">
|
|
3242
3267
|
@for (c of cmp()?.commits ?? []; track c.oid) {
|
|
3243
|
-
<div class="o-row"><span class="o-av" aria-hidden="true">{{ initials(c.author.name) }}</span><a class="o-ell" style="flex:1;color:var(--o-tx)" [routerLink]="link('commit/' + c.oid)">{{ c.subject }}</a
|
|
3268
|
+
<div class="o-row"><span class="o-av" aria-hidden="true">{{ initials(c.author.name) }}</span><a class="o-ell" style="flex:1;color:var(--o-tx)" [routerLink]="link('commit/' + c.oid)">{{ c.subject }}</a>@if (c.verification; as v) {<span class="o-chip" [class.o-grn]="v.verified" [attr.title]="v.verified ? 'Signed with a key of ' + people().name(v.signer) : verifyWhy(v.reason)" [attr.data-testid]="'verify-' + c.short">{{ v.verified ? 'Verified' : 'Unverified' }}</span>}<a class="o-mono o-sm o-faint" [routerLink]="link('commit/' + c.oid)">{{ c.short }}</a></div>
|
|
3244
3269
|
} @empty {<div class="o-empty">{{ cmp() ? 'No commits.' : 'Loading…' }}</div>}
|
|
3245
3270
|
</div>
|
|
3246
3271
|
}
|
|
@@ -3329,7 +3354,7 @@ function writePref(key, value) {
|
|
|
3329
3354
|
}
|
|
3330
3355
|
}
|
|
3331
3356
|
|
|
3332
|
-
const BLANK = { id: '', pattern: '', blockDirectPush: true, blockForcePush: true, blockDeletion: true, requiredApprovals: 1, dismissStaleApprovals: true, requireUpToDate: false, requireCodeOwnerReview: false, requiredChecks: [] };
|
|
3357
|
+
const BLANK = { id: '', pattern: '', blockDirectPush: true, blockForcePush: true, blockDeletion: true, requiredApprovals: 1, dismissStaleApprovals: true, requireUpToDate: false, requireCodeOwnerReview: false, requireSignedCommits: false, requireLinearHistory: false, bypass: [], requiredChecks: [] };
|
|
3333
3358
|
// /{org}/{project}/code/{repo}/settings?section= (prototype "code_settings"): general settings and
|
|
3334
3359
|
// merge methods, branch protection rules, and the danger zone (archive, delete).
|
|
3335
3360
|
class RepoSettingsPage extends RepoPage {
|
|
@@ -3352,7 +3377,10 @@ class RepoSettingsPage extends RepoPage {
|
|
|
3352
3377
|
{ key: 'dismissStaleApprovals', label: 'Dismiss stale approvals', hint: 'A new commit dismisses approvals given on an older one.' },
|
|
3353
3378
|
{ key: 'requireUpToDate', label: 'Require branches to be up to date', hint: 'The branch must contain the target’s latest commit before it merges.' },
|
|
3354
3379
|
{ key: 'requireCodeOwnerReview', label: 'Require review from CODEOWNERS', hint: 'Owners of changed paths are requested automatically; one of them approves each owned file.' },
|
|
3380
|
+
{ key: 'requireSignedCommits', label: 'Require signed commits', hint: 'Verified against members’ signing keys, on push and at merge.' },
|
|
3381
|
+
{ key: 'requireLinearHistory', label: 'Require linear history', hint: 'No merge commits: squash or rebase only.' },
|
|
3355
3382
|
];
|
|
3383
|
+
this.bypassRoles = [{ id: 'owner', label: 'Owners' }, { id: 'admin', label: 'Admins' }, { id: 'maintainer', label: 'Maintainers' }];
|
|
3356
3384
|
this.collabs = signal([], ...(ngDevMode ? [{ debugName: "collabs" }] : /* istanbul ignore next */ []));
|
|
3357
3385
|
this.perms = [{ id: 'read', label: 'Read' }, { id: 'write', label: 'Write' }, { id: 'maintain', label: 'Maintain' }];
|
|
3358
3386
|
this.addable = computed(() => this.host.people().filter((p) => !this.collabs().some((c) => c.userId === p.id)), ...(ngDevMode ? [{ debugName: "addable" }] : /* istanbul ignore next */ []));
|
|
@@ -3419,12 +3447,19 @@ class RepoSettingsPage extends RepoPage {
|
|
|
3419
3447
|
parts.push('up to date');
|
|
3420
3448
|
if (p.requireCodeOwnerReview)
|
|
3421
3449
|
parts.push('code owners');
|
|
3450
|
+
if (p.requireSignedCommits)
|
|
3451
|
+
parts.push('signed commits');
|
|
3452
|
+
if (p.requireLinearHistory)
|
|
3453
|
+
parts.push('linear');
|
|
3422
3454
|
if (p.blockDeletion)
|
|
3423
3455
|
parts.push('no deletion');
|
|
3424
3456
|
return parts.join(' · ') || 'no restrictions';
|
|
3425
3457
|
}
|
|
3426
3458
|
edit(p) {
|
|
3427
|
-
this.form.set(p ? { ...BLANK, ...p, requiredChecks: [...(p.requiredChecks ?? [])] } : { ...BLANK, pattern: this.rules().length ? '' : this.repo()?.defaultBranch ?? 'main', requiredChecks: [] });
|
|
3459
|
+
this.form.set(p ? { ...BLANK, ...p, requiredChecks: [...(p.requiredChecks ?? [])], bypass: [...(p.bypass ?? [])] } : { ...BLANK, pattern: this.rules().length ? '' : this.repo()?.defaultBranch ?? 'main', requiredChecks: [] });
|
|
3460
|
+
}
|
|
3461
|
+
flipBypass(f, role) {
|
|
3462
|
+
f.bypass = f.bypass.includes(role) ? f.bypass.filter((x) => x !== role) : [...f.bypass, role];
|
|
3428
3463
|
}
|
|
3429
3464
|
flip(f, k) {
|
|
3430
3465
|
f[k] = !f[k];
|
|
@@ -3567,7 +3602,9 @@ class RepoSettingsPage extends RepoPage {
|
|
|
3567
3602
|
<input id="rule-check" class="o-in o-sm o-mono" style="width:160px" placeholder="+ check name, Enter" [ngModel]="check()" (ngModelChange)="check.set($event)" name="check" (keydown.enter)="$event.preventDefault(); addCheck(f)" />
|
|
3568
3603
|
</div>
|
|
3569
3604
|
</div>
|
|
3570
|
-
<div class="o-fld"><span class="o-lbl" style="padding:0">Allowed to bypass
|
|
3605
|
+
<div class="o-fld"><span class="o-lbl" style="padding:0">Allowed to bypass</span><div class="o-flex o-wrap">
|
|
3606
|
+
@for (b of bypassRoles; track b.id) {<button type="button" class="o-chip" style="cursor:pointer;border:none" [class.o-blu]="f.bypass.includes(b.id)" [attr.aria-pressed]="f.bypass.includes(b.id)" (click)="flipBypass(f, b.id)" [attr.data-testid]="'bypass-' + b.id">{{ b.label }}</button>}
|
|
3607
|
+
<span class="o-hint">Pushes by these roles go through this rule; every bypass is written to the audit log.</span></div></div>
|
|
3571
3608
|
<div class="o-fld"><span class="o-lbl" style="padding:0">What a blocked push sees</span><div class="o-term" style="font-size:11.5px"><div>$ git push origin {{ f.pattern || 'main' }}</div><div>remote: ✕ {{ f.pattern || 'main' }} is protected by rule “{{ f.pattern || 'main' }}”: direct pushes are blocked.</div><div>remote: Open a pull request instead.</div></div></div>
|
|
3572
3609
|
<div class="o-flex">
|
|
3573
3610
|
@if (f.id) {<button type="button" class="o-btn o-sm o-ghost" style="color:var(--o-bad-tx)" (click)="removeRule(f)">Delete rule</button>}
|
|
@@ -3693,7 +3730,9 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3693
3730
|
<input id="rule-check" class="o-in o-sm o-mono" style="width:160px" placeholder="+ check name, Enter" [ngModel]="check()" (ngModelChange)="check.set($event)" name="check" (keydown.enter)="$event.preventDefault(); addCheck(f)" />
|
|
3694
3731
|
</div>
|
|
3695
3732
|
</div>
|
|
3696
|
-
<div class="o-fld"><span class="o-lbl" style="padding:0">Allowed to bypass
|
|
3733
|
+
<div class="o-fld"><span class="o-lbl" style="padding:0">Allowed to bypass</span><div class="o-flex o-wrap">
|
|
3734
|
+
@for (b of bypassRoles; track b.id) {<button type="button" class="o-chip" style="cursor:pointer;border:none" [class.o-blu]="f.bypass.includes(b.id)" [attr.aria-pressed]="f.bypass.includes(b.id)" (click)="flipBypass(f, b.id)" [attr.data-testid]="'bypass-' + b.id">{{ b.label }}</button>}
|
|
3735
|
+
<span class="o-hint">Pushes by these roles go through this rule; every bypass is written to the audit log.</span></div></div>
|
|
3697
3736
|
<div class="o-fld"><span class="o-lbl" style="padding:0">What a blocked push sees</span><div class="o-term" style="font-size:11.5px"><div>$ git push origin {{ f.pattern || 'main' }}</div><div>remote: ✕ {{ f.pattern || 'main' }} is protected by rule “{{ f.pattern || 'main' }}”: direct pushes are blocked.</div><div>remote: Open a pull request instead.</div></div></div>
|
|
3698
3737
|
<div class="o-flex">
|
|
3699
3738
|
@if (f.id) {<button type="button" class="o-btn o-sm o-ghost" style="color:var(--o-bad-tx)" (click)="removeRule(f)">Delete rule</button>}
|
|
@@ -4299,6 +4338,115 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
4299
4338
|
}]
|
|
4300
4339
|
}], ctorParameters: () => [] });
|
|
4301
4340
|
|
|
4341
|
+
// /{org}/{project}/code/{repo}/search?q=®ex= (DEV-11): code search over the repository's default
|
|
4342
|
+
// branch, text or a regular expression; each file with its matching lines, the matches marked, a
|
|
4343
|
+
// line opening the file there.
|
|
4344
|
+
class CodeSearchPage extends RepoPage {
|
|
4345
|
+
constructor() {
|
|
4346
|
+
super();
|
|
4347
|
+
this.router = inject(Router);
|
|
4348
|
+
this.hits = signal([], ...(ngDevMode ? [{ debugName: "hits" }] : /* istanbul ignore next */ []));
|
|
4349
|
+
this.searched = signal(false, ...(ngDevMode ? [{ debugName: "searched" }] : /* istanbul ignore next */ []));
|
|
4350
|
+
this.text = '';
|
|
4351
|
+
this.regex = false;
|
|
4352
|
+
this.route.queryParamMap.subscribe((q) => {
|
|
4353
|
+
this.text = q.get('q') ?? '';
|
|
4354
|
+
this.regex = q.get('regex') === 'true';
|
|
4355
|
+
const r = this.repo();
|
|
4356
|
+
if (r && r.name === this.name())
|
|
4357
|
+
this.load(r);
|
|
4358
|
+
});
|
|
4359
|
+
}
|
|
4360
|
+
onRepo(r) {
|
|
4361
|
+
this.load(r);
|
|
4362
|
+
}
|
|
4363
|
+
go() {
|
|
4364
|
+
void this.router.navigate(this.link('search'), { queryParams: { q: this.text.trim(), regex: this.regex ? 'true' : null } });
|
|
4365
|
+
}
|
|
4366
|
+
load(r) {
|
|
4367
|
+
const q = this.text.trim();
|
|
4368
|
+
if (!q)
|
|
4369
|
+
return;
|
|
4370
|
+
this.error.set('');
|
|
4371
|
+
this.api.searchCode(this.projectId(), r.name, q, this.regex).subscribe({
|
|
4372
|
+
next: (res) => { this.hits.set(res.hits); this.searched.set(true); },
|
|
4373
|
+
error: (e) => { this.hits.set([]); this.searched.set(false); this.error.set(apiMessage(e, 'Could not search the code.')); },
|
|
4374
|
+
});
|
|
4375
|
+
}
|
|
4376
|
+
/** A line cut into plain and matched pieces (ranges are byte offsets; the text is UTF-8). */
|
|
4377
|
+
pieces(text, ranges) {
|
|
4378
|
+
if (!ranges?.length)
|
|
4379
|
+
return [{ text, hit: false }];
|
|
4380
|
+
const bytes = new TextEncoder().encode(text);
|
|
4381
|
+
const dec = new TextDecoder();
|
|
4382
|
+
const out = [];
|
|
4383
|
+
let at = 0;
|
|
4384
|
+
for (const [a, b] of [...ranges].sort((x, y) => x[0] - y[0])) {
|
|
4385
|
+
if (a > at)
|
|
4386
|
+
out.push({ text: dec.decode(bytes.slice(at, a)), hit: false });
|
|
4387
|
+
out.push({ text: dec.decode(bytes.slice(Math.max(a, at), b)), hit: true });
|
|
4388
|
+
at = Math.max(at, b);
|
|
4389
|
+
}
|
|
4390
|
+
if (at < bytes.length)
|
|
4391
|
+
out.push({ text: dec.decode(bytes.slice(at)), hit: false });
|
|
4392
|
+
return out;
|
|
4393
|
+
}
|
|
4394
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: CodeSearchPage, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
4395
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: CodeSearchPage, isStandalone: true, selector: "o-code-search", usesInheritance: true, ngImport: i0, template: `
|
|
4396
|
+
<div class="o-page o-wide">
|
|
4397
|
+
@if (repo(); as r) {
|
|
4398
|
+
<form class="o-flex o-wrap" (ngSubmit)="go()" role="search" aria-label="Search code">
|
|
4399
|
+
<input class="o-in o-mono" style="flex:1;min-width:220px" name="q" [(ngModel)]="text" placeholder="Search {{ r.name }} (default branch)" aria-label="Search code" data-testid="code-search-q" />
|
|
4400
|
+
<label class="o-flex o-sm"><input type="checkbox" name="regex" [(ngModel)]="regex" data-testid="code-search-regex" /> Regular expression</label>
|
|
4401
|
+
<button type="submit" class="o-btn o-dark" [disabled]="!text.trim()">Search</button>
|
|
4402
|
+
</form>
|
|
4403
|
+
@if (error()) {<div class="o-note o-red" role="alert">{{ error() }}</div>}
|
|
4404
|
+
@if (searched()) {
|
|
4405
|
+
<span class="o-faint o-sm" aria-live="polite" data-testid="code-search-count">{{ hits().length }} {{ hits().length === 1 ? 'file' : 'files' }}{{ hits().length >= 50 ? ' (the first 50)' : '' }} on {{ r.defaultBranch }}</span>
|
|
4406
|
+
@for (h of hits(); track h.path) {
|
|
4407
|
+
<div class="o-card" [attr.data-testid]="'code-hit-' + h.path">
|
|
4408
|
+
<div class="o-ch" style="background:var(--o-well)"><a class="o-mono o-sm" [routerLink]="link('blob')" [queryParams]="{ path: h.path }">{{ h.path }}</a>@if (h.nameMatch && !h.lines.length) {<span class="o-faint o-sm">matches the file name</span>}</div>
|
|
4409
|
+
@for (l of h.lines; track l.number) {
|
|
4410
|
+
<a class="ln" [routerLink]="link('blob')" [queryParams]="{ path: h.path }" [fragment]="'L' + l.number"><span class="n">{{ l.number }}</span><span>@for (p of pieces(l.text, l.ranges); track $index) {@if (p.hit) {<mark>{{ p.text }}</mark>} @else {{{ p.text }}}}</span></a>
|
|
4411
|
+
}
|
|
4412
|
+
</div>
|
|
4413
|
+
} @empty {
|
|
4414
|
+
<div class="o-card"><div class="o-empty">Nothing matches on {{ r.defaultBranch }}.</div></div>
|
|
4415
|
+
}
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
</div>
|
|
4419
|
+
`, isInline: true, styles: [".ln{display:flex;gap:10px;font-family:var(--o-mono);font-size:12px;padding:2px 12px;color:var(--o-tx);text-decoration:none;white-space:pre}.ln:hover{background:var(--o-well)}.ln .n{color:var(--o-faint);min-width:40px;text-align:right}mark{background:#fee484;color:inherit;border-radius:2px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
4420
|
+
}
|
|
4421
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: CodeSearchPage, decorators: [{
|
|
4422
|
+
type: Component,
|
|
4423
|
+
args: [{ selector: 'o-code-search', imports: [FormsModule, RouterLink], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
4424
|
+
<div class="o-page o-wide">
|
|
4425
|
+
@if (repo(); as r) {
|
|
4426
|
+
<form class="o-flex o-wrap" (ngSubmit)="go()" role="search" aria-label="Search code">
|
|
4427
|
+
<input class="o-in o-mono" style="flex:1;min-width:220px" name="q" [(ngModel)]="text" placeholder="Search {{ r.name }} (default branch)" aria-label="Search code" data-testid="code-search-q" />
|
|
4428
|
+
<label class="o-flex o-sm"><input type="checkbox" name="regex" [(ngModel)]="regex" data-testid="code-search-regex" /> Regular expression</label>
|
|
4429
|
+
<button type="submit" class="o-btn o-dark" [disabled]="!text.trim()">Search</button>
|
|
4430
|
+
</form>
|
|
4431
|
+
@if (error()) {<div class="o-note o-red" role="alert">{{ error() }}</div>}
|
|
4432
|
+
@if (searched()) {
|
|
4433
|
+
<span class="o-faint o-sm" aria-live="polite" data-testid="code-search-count">{{ hits().length }} {{ hits().length === 1 ? 'file' : 'files' }}{{ hits().length >= 50 ? ' (the first 50)' : '' }} on {{ r.defaultBranch }}</span>
|
|
4434
|
+
@for (h of hits(); track h.path) {
|
|
4435
|
+
<div class="o-card" [attr.data-testid]="'code-hit-' + h.path">
|
|
4436
|
+
<div class="o-ch" style="background:var(--o-well)"><a class="o-mono o-sm" [routerLink]="link('blob')" [queryParams]="{ path: h.path }">{{ h.path }}</a>@if (h.nameMatch && !h.lines.length) {<span class="o-faint o-sm">matches the file name</span>}</div>
|
|
4437
|
+
@for (l of h.lines; track l.number) {
|
|
4438
|
+
<a class="ln" [routerLink]="link('blob')" [queryParams]="{ path: h.path }" [fragment]="'L' + l.number"><span class="n">{{ l.number }}</span><span>@for (p of pieces(l.text, l.ranges); track $index) {@if (p.hit) {<mark>{{ p.text }}</mark>} @else {{{ p.text }}}}</span></a>
|
|
4439
|
+
}
|
|
4440
|
+
</div>
|
|
4441
|
+
} @empty {
|
|
4442
|
+
<div class="o-card"><div class="o-empty">Nothing matches on {{ r.defaultBranch }}.</div></div>
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
</div>
|
|
4447
|
+
`, styles: [".ln{display:flex;gap:10px;font-family:var(--o-mono);font-size:12px;padding:2px 12px;color:var(--o-tx);text-decoration:none;white-space:pre}.ln:hover{background:var(--o-well)}.ln .n{color:var(--o-faint);min-width:40px;text-align:right}mark{background:#fee484;color:inherit;border-radius:2px}\n"] }]
|
|
4448
|
+
}], ctorParameters: () => [] });
|
|
4449
|
+
|
|
4302
4450
|
/*
|
|
4303
4451
|
* @chisquare-tech/origine-ngx-apps-code: the Code module's screens (TRD §6.10). The host app provides
|
|
4304
4452
|
* CodeHost and mounts the pages at /{org}/{project}/code, /code/new, /code/pulls and
|
|
@@ -4309,5 +4457,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
4309
4457
|
* Generated bundle index. Do not edit.
|
|
4310
4458
|
*/
|
|
4311
4459
|
|
|
4312
|
-
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 };
|
|
4460
|
+
export { BranchesPage, CodeApi, CodeHost, CodeSearchPage, 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, verifyWhy };
|
|
4313
4461
|
//# sourceMappingURL=chisquare-tech-origine-ngx-apps-code.mjs.map
|