@chisquare-tech/origine-ngx-apps-code 0.9.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
  }
@@ -1291,6 +1298,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
1291
1298
  // /{org}/{project}/code/{repo}?ref=&path= (prototype "code_repo"): the repository's files at a
1292
1299
  // branch, tag or commit, the last commit of each entry, the README, the clone menu and languages.
1293
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
+ }
1294
1306
  constructor() {
1295
1307
  super();
1296
1308
  this.router = inject(Router);
@@ -1302,6 +1314,7 @@ class RepoHomePage extends RepoPage {
1302
1314
  this.proto = signal('https', ...(ngDevMode ? [{ debugName: "proto" }] : /* istanbul ignore next */ []));
1303
1315
  this.cloneOpen = signal(false, ...(ngDevMode ? [{ debugName: "cloneOpen" }] : /* istanbul ignore next */ []));
1304
1316
  this.addOpen = signal(false, ...(ngDevMode ? [{ debugName: "addOpen" }] : /* istanbul ignore next */ []));
1317
+ this.codeQuery = '';
1305
1318
  this.copied = signal(false, ...(ngDevMode ? [{ debugName: "copied" }] : /* istanbul ignore next */ []));
1306
1319
  this.finding = signal(false, ...(ngDevMode ? [{ debugName: "finding" }] : /* istanbul ignore next */ []));
1307
1320
  this.fq = signal('', ...(ngDevMode ? [{ debugName: "fq" }] : /* istanbul ignore next */ []));
@@ -1429,6 +1442,7 @@ class RepoHomePage extends RepoPage {
1429
1442
  @for (c of crumbs(); track c.path) {<span class="o-faint">/</span><a [routerLink]="link()" [queryParams]="qp(c.path)">{{ c.name }}</a>}
1430
1443
  </nav>
1431
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>
1432
1446
  <button type="button" class="o-btn o-sm" (click)="openFinder()">Go to file <span class="o-kbd">T</span></button>
1433
1447
  @if (canWrite()) {
1434
1448
  <span class="anchor add">
@@ -1518,7 +1532,7 @@ class RepoHomePage extends RepoPage {
1518
1532
  </div>
1519
1533
  </div>
1520
1534
  }
1521
- `, 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 }); }
1522
1536
  }
1523
1537
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: RepoHomePage, decorators: [{
1524
1538
  type: Component,
@@ -1560,6 +1574,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
1560
1574
  @for (c of crumbs(); track c.path) {<span class="o-faint">/</span><a [routerLink]="link()" [queryParams]="qp(c.path)">{{ c.name }}</a>}
1561
1575
  </nav>
1562
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>
1563
1578
  <button type="button" class="o-btn o-sm" (click)="openFinder()">Go to file <span class="o-kbd">T</span></button>
1564
1579
  @if (canWrite()) {
1565
1580
  <span class="anchor add">
@@ -4323,6 +4338,115 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
4323
4338
  }]
4324
4339
  }], ctorParameters: () => [] });
4325
4340
 
4341
+ // /{org}/{project}/code/{repo}/search?q=&regex= (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
+
4326
4450
  /*
4327
4451
  * @chisquare-tech/origine-ngx-apps-code: the Code module's screens (TRD §6.10). The host app provides
4328
4452
  * CodeHost and mounts the pages at /{org}/{project}/code, /code/new, /code/pulls and
@@ -4333,5 +4457,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
4333
4457
  * Generated bundle index. Do not edit.
4334
4458
  */
4335
4459
 
4336
- 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, verifyWhy };
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 };
4337
4461
  //# sourceMappingURL=chisquare-tech-origine-ngx-apps-code.mjs.map