@chisquare-tech/origine-ngx-apps-code 0.5.0 → 0.7.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.
|
@@ -210,6 +210,10 @@ class CodeApi {
|
|
|
210
210
|
comment(project, name, n, c) {
|
|
211
211
|
return this.api.post(`${this.repoPath(project, name)}/pulls/${n}/comments`, c).pipe(map((r) => data(r)));
|
|
212
212
|
}
|
|
213
|
+
/** Commits suggested changes of line comments onto the pull request's branch (DEV-20). */
|
|
214
|
+
applySuggestions(project, name, n, comments, message = '') {
|
|
215
|
+
return this.api.post(`${this.repoPath(project, name)}/pulls/${n}/suggestions`, { comments, message }).pipe(map((r) => data(r)));
|
|
216
|
+
}
|
|
213
217
|
/** Discards one of the caller's pending review comments. */
|
|
214
218
|
discardComment(project, name, n, id) {
|
|
215
219
|
return this.api.delete(`${this.repoPath(project, name)}/pulls/${n}/comments/${enc(id)}`);
|
|
@@ -507,6 +511,12 @@ class DiffView {
|
|
|
507
511
|
this.reply = output();
|
|
508
512
|
this.resolve = output();
|
|
509
513
|
this.discard = output();
|
|
514
|
+
/** The caller can push to the pull request's branch, so may apply suggestions. */
|
|
515
|
+
this.canApply = input(false, ...(ngDevMode ? [{ debugName: "canApply" }] : /* istanbul ignore next */ []));
|
|
516
|
+
/** Suggestions batched to apply together. */
|
|
517
|
+
this.batch = input([], ...(ngDevMode ? [{ debugName: "batch" }] : /* istanbul ignore next */ []));
|
|
518
|
+
this.apply = output();
|
|
519
|
+
this.batchToggle = output();
|
|
510
520
|
this.viewedChange = output();
|
|
511
521
|
this.open = signal(true, ...(ngDevMode ? [{ debugName: "open" }] : /* istanbul ignore next */ []));
|
|
512
522
|
this.visible = signal(false, ...(ngDevMode ? [{ debugName: "visible" }] : /* istanbul ignore next */ []));
|
|
@@ -614,6 +624,18 @@ class DiffView {
|
|
|
614
624
|
this.drafting.set('');
|
|
615
625
|
this.draftBody.set('');
|
|
616
626
|
}
|
|
627
|
+
/** A comment's ```suggestion block, split into the text before it and the suggested lines. */
|
|
628
|
+
suggestionOf(body) {
|
|
629
|
+
const m = /```suggestion[^\n]*\n([\s\S]*?)```/.exec(body);
|
|
630
|
+
if (!m)
|
|
631
|
+
return null;
|
|
632
|
+
const text = m[1].replace(/\n$/, '');
|
|
633
|
+
return { before: body.slice(0, m.index).trim(), lines: text === '' ? [] : text.split('\n') };
|
|
634
|
+
}
|
|
635
|
+
insertSuggestion(l) {
|
|
636
|
+
const cur = this.draftBody();
|
|
637
|
+
this.draftBody.set((cur ? cur.trimEnd() + '\n' : '') + '```suggestion\n' + l.text + '\n```');
|
|
638
|
+
}
|
|
617
639
|
setReply(id, v) {
|
|
618
640
|
this.replies.set({ ...this.replies(), [id]: v });
|
|
619
641
|
}
|
|
@@ -625,7 +647,7 @@ class DiffView {
|
|
|
625
647
|
this.setReply(id, '');
|
|
626
648
|
}
|
|
627
649
|
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: DiffView, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
|
|
628
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: DiffView, isStandalone: true, selector: "o-diff", inputs: { repo: { classPropertyName: "repo", publicName: "repo", isSignal: true, isRequired: true, transformFunction: null }, file: { classPropertyName: "file", publicName: "file", isSignal: true, isRequired: true, transformFunction: null }, head: { classPropertyName: "head", publicName: "head", isSignal: true, isRequired: true, transformFunction: null }, base: { classPropertyName: "base", publicName: "base", isSignal: true, isRequired: false, transformFunction: null }, threads: { classPropertyName: "threads", publicName: "threads", isSignal: true, isRequired: false, transformFunction: null }, canComment: { classPropertyName: "canComment", publicName: "canComment", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, ignoreSpace: { classPropertyName: "ignoreSpace", publicName: "ignoreSpace", isSignal: true, isRequired: false, transformFunction: null }, viewed: { classPropertyName: "viewed", publicName: "viewed", isSignal: true, isRequired: false, transformFunction: null }, reviewing: { classPropertyName: "reviewing", publicName: "reviewing", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { comment: "comment", reply: "reply", resolve: "resolve", discard: "discard", viewedChange: "viewedChange" }, ngImport: i0, template: `
|
|
650
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: DiffView, isStandalone: true, selector: "o-diff", inputs: { repo: { classPropertyName: "repo", publicName: "repo", isSignal: true, isRequired: true, transformFunction: null }, file: { classPropertyName: "file", publicName: "file", isSignal: true, isRequired: true, transformFunction: null }, head: { classPropertyName: "head", publicName: "head", isSignal: true, isRequired: true, transformFunction: null }, base: { classPropertyName: "base", publicName: "base", isSignal: true, isRequired: false, transformFunction: null }, threads: { classPropertyName: "threads", publicName: "threads", isSignal: true, isRequired: false, transformFunction: null }, canComment: { classPropertyName: "canComment", publicName: "canComment", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, ignoreSpace: { classPropertyName: "ignoreSpace", publicName: "ignoreSpace", isSignal: true, isRequired: false, transformFunction: null }, viewed: { classPropertyName: "viewed", publicName: "viewed", isSignal: true, isRequired: false, transformFunction: null }, reviewing: { classPropertyName: "reviewing", publicName: "reviewing", isSignal: true, isRequired: false, transformFunction: null }, canApply: { classPropertyName: "canApply", publicName: "canApply", isSignal: true, isRequired: false, transformFunction: null }, batch: { classPropertyName: "batch", publicName: "batch", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { comment: "comment", reply: "reply", resolve: "resolve", discard: "discard", apply: "apply", batchToggle: "batchToggle", viewedChange: "viewedChange" }, ngImport: i0, template: `
|
|
629
651
|
<div class="o-card" [attr.id]="'file-' + file().path" [attr.data-testid]="'diff-' + file().path">
|
|
630
652
|
<div class="o-ch" style="background:var(--o-well)">
|
|
631
653
|
<button type="button" class="o-btn o-ghost o-xs" (click)="toggleOpen()" [attr.aria-expanded]="open()" [attr.aria-label]="(open() ? 'Collapse ' : 'Expand ') + file().path">{{ open() ? '▾' : '▸' }}</button>
|
|
@@ -712,7 +734,25 @@ class DiffView {
|
|
|
712
734
|
@if (c.authorId === me()) {<button type="button" class="o-lnk" style="margin-left:6px;color:var(--o-mut)" (click)="discard.emit(c.id)" [attr.aria-label]="'Discard pending comment: ' + c.body">Discard</button>}
|
|
713
735
|
}
|
|
714
736
|
</span>
|
|
715
|
-
|
|
737
|
+
@if (suggestionOf(c.body); as sg) {
|
|
738
|
+
@if (sg.before) {<span style="white-space:pre-wrap">{{ sg.before }}</span>}
|
|
739
|
+
<div class="sugg" [attr.data-testid]="'suggestion-' + c.id">
|
|
740
|
+
<div class="sugg-h"><b>Suggested change</b>
|
|
741
|
+
@if (c.appliedSha) {<span class="o-chip o-grn">Applied in {{ c.appliedSha.slice(0, 7) }}</span>}
|
|
742
|
+
@else if (t.outdated) {<span class="o-chip">Outdated</span>}
|
|
743
|
+
@else if (canApply() && !c.pending) {
|
|
744
|
+
<span class="o-r o-flex" style="gap:6px">
|
|
745
|
+
<button type="button" class="o-btn o-xs" (click)="batchToggle.emit(c.id)" [attr.aria-pressed]="batch().includes(c.id)">{{ batch().includes(c.id) ? 'Remove from batch' : 'Add to batch' }}</button>
|
|
746
|
+
<button type="button" class="o-btn o-xs o-pri" (click)="apply.emit([c.id])" data-testid="suggestion-apply">Apply suggestion</button>
|
|
747
|
+
</span>
|
|
748
|
+
}
|
|
749
|
+
</div>
|
|
750
|
+
@for (l of sg.lines; track $index) {<div class="o-ln o-add"><span class="o-s" aria-hidden="true">+</span><span class="o-t">{{ l }}</span></div>}
|
|
751
|
+
@if (!sg.lines.length) {<div class="o-faint o-sm" style="padding:4px 10px">Delete the line(s).</div>}
|
|
752
|
+
</div>
|
|
753
|
+
} @else {
|
|
754
|
+
<span style="white-space:pre-wrap">{{ c.body }}</span>
|
|
755
|
+
}
|
|
716
756
|
}
|
|
717
757
|
</div>
|
|
718
758
|
@if (canComment()) {
|
|
@@ -728,14 +768,14 @@ class DiffView {
|
|
|
728
768
|
<ng-template #draftTpl let-l>
|
|
729
769
|
<div class="o-thread"><div class="o-cb o-col" style="gap:8px;padding:10px 12px">
|
|
730
770
|
<textarea class="o-in" rows="2" [placeholder]="'Leave a comment on line ' + lineOf(l)" [attr.aria-label]="'Comment on line ' + lineOf(l)" [ngModel]="draftBody()" (ngModelChange)="draftBody.set($event)" data-testid="line-comment"></textarea>
|
|
731
|
-
<div class="o-flex o-wrap"><span class="o-faint o-xs">Markdown works.</span>
|
|
771
|
+
<div class="o-flex o-wrap"><button type="button" class="o-btn o-xs" (click)="insertSuggestion(l)" data-testid="insert-suggestion">Insert suggestion</button><span class="o-faint o-xs">Markdown works.</span>
|
|
732
772
|
<button type="button" class="o-btn o-sm o-r" (click)="drafting.set('')">Cancel</button>
|
|
733
773
|
<button type="button" class="o-btn o-sm" [disabled]="!draftBody().trim()" (click)="send(l, false)" data-testid="line-comment-add">Add single comment</button>
|
|
734
774
|
<button type="button" class="o-btn o-sm o-pri" [disabled]="!draftBody().trim()" (click)="send(l, true)" data-testid="line-comment-review">{{ reviewing() ? 'Add review comment' : 'Start a review' }}</button>
|
|
735
775
|
</div>
|
|
736
776
|
</div></div>
|
|
737
777
|
</ng-template>
|
|
738
|
-
`, 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{cursor:default;width:44px}.o-ln .o-n.cmt{cursor:pointer}.o-ln .o-n.cmt:hover{color:var(--o-acc-tx)}.hunk{color:var(--o-dim);background:var(--o-well);padding:2px 12px;font-size:11px}.split{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr)}.split .side-l{border-right:1px solid var(--o-line-soft)}.split .full{grid-column:1 / -1}.split .o-ln .o-t{white-space:pre-wrap;word-break:break-all}.empty-side{background:var(--o-well)}.viewed{display:inline-flex;align-items:center;gap:6px}\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: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
778
|
+
`, 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{cursor:default;width:44px}.o-ln .o-n.cmt{cursor:pointer}.o-ln .o-n.cmt:hover{color:var(--o-acc-tx)}.hunk{color:var(--o-dim);background:var(--o-well);padding:2px 12px;font-size:11px}.split{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr)}.split .side-l{border-right:1px solid var(--o-line-soft)}.split .full{grid-column:1 / -1}.split .o-ln .o-t{white-space:pre-wrap;word-break:break-all}.empty-side{background:var(--o-well)}.viewed{display:inline-flex;align-items:center;gap:6px}.sugg{border:1px solid var(--o-line);border-radius:6px;overflow:hidden;margin-top:2px}.sugg-h{display:flex;align-items:center;gap:8px;padding:6px 10px;background:var(--o-well);font-size:12px}\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: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
739
779
|
}
|
|
740
780
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: DiffView, decorators: [{
|
|
741
781
|
type: Component,
|
|
@@ -826,7 +866,25 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
826
866
|
@if (c.authorId === me()) {<button type="button" class="o-lnk" style="margin-left:6px;color:var(--o-mut)" (click)="discard.emit(c.id)" [attr.aria-label]="'Discard pending comment: ' + c.body">Discard</button>}
|
|
827
867
|
}
|
|
828
868
|
</span>
|
|
829
|
-
|
|
869
|
+
@if (suggestionOf(c.body); as sg) {
|
|
870
|
+
@if (sg.before) {<span style="white-space:pre-wrap">{{ sg.before }}</span>}
|
|
871
|
+
<div class="sugg" [attr.data-testid]="'suggestion-' + c.id">
|
|
872
|
+
<div class="sugg-h"><b>Suggested change</b>
|
|
873
|
+
@if (c.appliedSha) {<span class="o-chip o-grn">Applied in {{ c.appliedSha.slice(0, 7) }}</span>}
|
|
874
|
+
@else if (t.outdated) {<span class="o-chip">Outdated</span>}
|
|
875
|
+
@else if (canApply() && !c.pending) {
|
|
876
|
+
<span class="o-r o-flex" style="gap:6px">
|
|
877
|
+
<button type="button" class="o-btn o-xs" (click)="batchToggle.emit(c.id)" [attr.aria-pressed]="batch().includes(c.id)">{{ batch().includes(c.id) ? 'Remove from batch' : 'Add to batch' }}</button>
|
|
878
|
+
<button type="button" class="o-btn o-xs o-pri" (click)="apply.emit([c.id])" data-testid="suggestion-apply">Apply suggestion</button>
|
|
879
|
+
</span>
|
|
880
|
+
}
|
|
881
|
+
</div>
|
|
882
|
+
@for (l of sg.lines; track $index) {<div class="o-ln o-add"><span class="o-s" aria-hidden="true">+</span><span class="o-t">{{ l }}</span></div>}
|
|
883
|
+
@if (!sg.lines.length) {<div class="o-faint o-sm" style="padding:4px 10px">Delete the line(s).</div>}
|
|
884
|
+
</div>
|
|
885
|
+
} @else {
|
|
886
|
+
<span style="white-space:pre-wrap">{{ c.body }}</span>
|
|
887
|
+
}
|
|
830
888
|
}
|
|
831
889
|
</div>
|
|
832
890
|
@if (canComment()) {
|
|
@@ -842,15 +900,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
842
900
|
<ng-template #draftTpl let-l>
|
|
843
901
|
<div class="o-thread"><div class="o-cb o-col" style="gap:8px;padding:10px 12px">
|
|
844
902
|
<textarea class="o-in" rows="2" [placeholder]="'Leave a comment on line ' + lineOf(l)" [attr.aria-label]="'Comment on line ' + lineOf(l)" [ngModel]="draftBody()" (ngModelChange)="draftBody.set($event)" data-testid="line-comment"></textarea>
|
|
845
|
-
<div class="o-flex o-wrap"><span class="o-faint o-xs">Markdown works.</span>
|
|
903
|
+
<div class="o-flex o-wrap"><button type="button" class="o-btn o-xs" (click)="insertSuggestion(l)" data-testid="insert-suggestion">Insert suggestion</button><span class="o-faint o-xs">Markdown works.</span>
|
|
846
904
|
<button type="button" class="o-btn o-sm o-r" (click)="drafting.set('')">Cancel</button>
|
|
847
905
|
<button type="button" class="o-btn o-sm" [disabled]="!draftBody().trim()" (click)="send(l, false)" data-testid="line-comment-add">Add single comment</button>
|
|
848
906
|
<button type="button" class="o-btn o-sm o-pri" [disabled]="!draftBody().trim()" (click)="send(l, true)" data-testid="line-comment-review">{{ reviewing() ? 'Add review comment' : 'Start a review' }}</button>
|
|
849
907
|
</div>
|
|
850
908
|
</div></div>
|
|
851
909
|
</ng-template>
|
|
852
|
-
`, 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{cursor:default;width:44px}.o-ln .o-n.cmt{cursor:pointer}.o-ln .o-n.cmt:hover{color:var(--o-acc-tx)}.hunk{color:var(--o-dim);background:var(--o-well);padding:2px 12px;font-size:11px}.split{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr)}.split .side-l{border-right:1px solid var(--o-line-soft)}.split .full{grid-column:1 / -1}.split .o-ln .o-t{white-space:pre-wrap;word-break:break-all}.empty-side{background:var(--o-well)}.viewed{display:inline-flex;align-items:center;gap:6px}\n"] }]
|
|
853
|
-
}], ctorParameters: () => [], propDecorators: { repo: [{ type: i0.Input, args: [{ isSignal: true, alias: "repo", required: true }] }], file: [{ type: i0.Input, args: [{ isSignal: true, alias: "file", required: true }] }], head: [{ type: i0.Input, args: [{ isSignal: true, alias: "head", required: true }] }], base: [{ type: i0.Input, args: [{ isSignal: true, alias: "base", required: false }] }], threads: [{ type: i0.Input, args: [{ isSignal: true, alias: "threads", required: false }] }], canComment: [{ type: i0.Input, args: [{ isSignal: true, alias: "canComment", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], ignoreSpace: [{ type: i0.Input, args: [{ isSignal: true, alias: "ignoreSpace", required: false }] }], viewed: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewed", required: false }] }], reviewing: [{ type: i0.Input, args: [{ isSignal: true, alias: "reviewing", required: false }] }], comment: [{ type: i0.Output, args: ["comment"] }], reply: [{ type: i0.Output, args: ["reply"] }], resolve: [{ type: i0.Output, args: ["resolve"] }], discard: [{ type: i0.Output, args: ["discard"] }], viewedChange: [{ type: i0.Output, args: ["viewedChange"] }] } });
|
|
910
|
+
`, 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{cursor:default;width:44px}.o-ln .o-n.cmt{cursor:pointer}.o-ln .o-n.cmt:hover{color:var(--o-acc-tx)}.hunk{color:var(--o-dim);background:var(--o-well);padding:2px 12px;font-size:11px}.split{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr)}.split .side-l{border-right:1px solid var(--o-line-soft)}.split .full{grid-column:1 / -1}.split .o-ln .o-t{white-space:pre-wrap;word-break:break-all}.empty-side{background:var(--o-well)}.viewed{display:inline-flex;align-items:center;gap:6px}.sugg{border:1px solid var(--o-line);border-radius:6px;overflow:hidden;margin-top:2px}.sugg-h{display:flex;align-items:center;gap:8px;padding:6px 10px;background:var(--o-well);font-size:12px}\n"] }]
|
|
911
|
+
}], ctorParameters: () => [], propDecorators: { repo: [{ type: i0.Input, args: [{ isSignal: true, alias: "repo", required: true }] }], file: [{ type: i0.Input, args: [{ isSignal: true, alias: "file", required: true }] }], head: [{ type: i0.Input, args: [{ isSignal: true, alias: "head", required: true }] }], base: [{ type: i0.Input, args: [{ isSignal: true, alias: "base", required: false }] }], threads: [{ type: i0.Input, args: [{ isSignal: true, alias: "threads", required: false }] }], canComment: [{ type: i0.Input, args: [{ isSignal: true, alias: "canComment", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], ignoreSpace: [{ type: i0.Input, args: [{ isSignal: true, alias: "ignoreSpace", required: false }] }], viewed: [{ type: i0.Input, args: [{ isSignal: true, alias: "viewed", required: false }] }], reviewing: [{ type: i0.Input, args: [{ isSignal: true, alias: "reviewing", required: false }] }], comment: [{ type: i0.Output, args: ["comment"] }], reply: [{ type: i0.Output, args: ["reply"] }], resolve: [{ type: i0.Output, args: ["resolve"] }], discard: [{ type: i0.Output, args: ["discard"] }], canApply: [{ type: i0.Input, args: [{ isSignal: true, alias: "canApply", required: false }] }], batch: [{ type: i0.Input, args: [{ isSignal: true, alias: "batch", required: false }] }], apply: [{ type: i0.Output, args: ["apply"] }], batchToggle: [{ type: i0.Output, args: ["batchToggle"] }], viewedChange: [{ type: i0.Output, args: ["viewedChange"] }] } });
|
|
854
912
|
/** Pairs a hunk's lines side by side: context on both sides, a run of removals beside the run of additions that follows it. */
|
|
855
913
|
function pair(lines) {
|
|
856
914
|
const rows = [];
|
|
@@ -2178,7 +2236,7 @@ class CommitPage extends RepoPage {
|
|
|
2178
2236
|
<div class="o-card"><div class="o-empty">Loading…</div></div>
|
|
2179
2237
|
}
|
|
2180
2238
|
</div>
|
|
2181
|
-
`, isInline: true, dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: DiffView, selector: "o-diff", inputs: ["repo", "file", "head", "base", "threads", "canComment", "mode", "ignoreSpace", "viewed", "reviewing"], outputs: ["comment", "reply", "resolve", "discard", "viewedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2239
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "component", type: DiffView, selector: "o-diff", inputs: ["repo", "file", "head", "base", "threads", "canComment", "mode", "ignoreSpace", "viewed", "reviewing", "canApply", "batch"], outputs: ["comment", "reply", "resolve", "discard", "apply", "batchToggle", "viewedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
2182
2240
|
}
|
|
2183
2241
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: CommitPage, decorators: [{
|
|
2184
2242
|
type: Component,
|
|
@@ -2733,6 +2791,7 @@ class PullPage extends RepoPage {
|
|
|
2733
2791
|
out.push({ id: 't' + th.id, at: th.createdAt, kind: 'thread', who: th.createdBy, thread: th });
|
|
2734
2792
|
return out.sort((a, b) => a.at.localeCompare(b.at));
|
|
2735
2793
|
}, ...(ngDevMode ? [{ debugName: "events" }] : /* istanbul ignore next */ []));
|
|
2794
|
+
this.batch = signal([], ...(ngDevMode ? [{ debugName: "batch" }] : /* istanbul ignore next */ []));
|
|
2736
2795
|
this.route.paramMap.subscribe((m) => {
|
|
2737
2796
|
this.number = Number(m.get('number') ?? 0);
|
|
2738
2797
|
const r = this.repo();
|
|
@@ -2781,6 +2840,17 @@ class PullPage extends RepoPage {
|
|
|
2781
2840
|
this.run(this.api.comment(this.projectId(), this.name(), this.number, { body: c.body, path: c.path, side: c.side, line: c.line, pending: c.pending }), 'Could not add the comment.', () => { if (c.pending)
|
|
2782
2841
|
this.note.set('Added to your review. Submit it from Review changes to post your comments.'); });
|
|
2783
2842
|
}
|
|
2843
|
+
toggleBatch(id) {
|
|
2844
|
+
const b = this.batch();
|
|
2845
|
+
this.batch.set(b.includes(id) ? b.filter((x) => x !== id) : [...b, id]);
|
|
2846
|
+
}
|
|
2847
|
+
/** Commits one suggestion, or the batch, onto the pull request's branch. */
|
|
2848
|
+
applySuggestions(ids) {
|
|
2849
|
+
this.run(this.api.applySuggestions(this.projectId(), this.name(), this.number, ids), 'Could not apply the suggestion.', () => {
|
|
2850
|
+
this.note.set(ids.length === 1 ? 'Suggestion applied.' : `${ids.length} suggestions applied in one commit.`);
|
|
2851
|
+
this.batch.set([]);
|
|
2852
|
+
});
|
|
2853
|
+
}
|
|
2784
2854
|
discard(id) {
|
|
2785
2855
|
this.run(this.api.discardComment(this.projectId(), this.name(), this.number, id), 'Could not discard the comment.');
|
|
2786
2856
|
}
|
|
@@ -2977,6 +3047,12 @@ class PullPage extends RepoPage {
|
|
|
2977
3047
|
</div>
|
|
2978
3048
|
}
|
|
2979
3049
|
@case ('files') {
|
|
3050
|
+
@if (batch().length) {
|
|
3051
|
+
<div class="o-note o-blu o-flex" data-testid="suggestion-batch"><span>{{ batch().length }} {{ batch().length === 1 ? 'suggestion' : 'suggestions' }} in a batch</span>
|
|
3052
|
+
<button type="button" class="o-btn o-sm o-r" (click)="batch.set([])">Clear</button>
|
|
3053
|
+
<button type="button" class="o-btn o-sm o-pri" (click)="applySuggestions(batch())" [disabled]="busy()" data-testid="suggestion-commit">Commit suggestions</button>
|
|
3054
|
+
</div>
|
|
3055
|
+
}
|
|
2980
3056
|
<div class="o-flex o-wrap">
|
|
2981
3057
|
<div class="o-seg" role="group" aria-label="Diff view"><button type="button" [class.o-on]="mode() === 'unified'" [attr.aria-pressed]="mode() === 'unified'" (click)="setMode('unified')">Unified</button><button type="button" [class.o-on]="mode() === 'split'" [attr.aria-pressed]="mode() === 'split'" (click)="setMode('split')" data-testid="diff-split">Split</button></div>
|
|
2982
3058
|
<button type="button" class="o-btn o-sm" [class.o-on]="hideWs()" [attr.aria-pressed]="hideWs()" (click)="hideWs.set(!hideWs())" data-testid="hide-ws">Hide whitespace</button>
|
|
@@ -2995,6 +3071,7 @@ class PullPage extends RepoPage {
|
|
|
2995
3071
|
@for (f of cmp()?.files ?? []; track f.path) {
|
|
2996
3072
|
<o-diff [repo]="name()" [file]="f" [head]="p.headSha" [base]="p.mergeBase" [threads]="threads()" [canComment]="p.state === 'open'" [mode]="mode()" [ignoreSpace]="hideWs()"
|
|
2997
3073
|
[viewed]="isViewed(f.path)" [reviewing]="myPending().length > 0" (viewedChange)="setViewed(f.path, $event)"
|
|
3074
|
+
[canApply]="p.state === 'open' && canWrite()" [batch]="batch()" (batchToggle)="toggleBatch($event)" (apply)="applySuggestions($event)"
|
|
2998
3075
|
(comment)="lineComment($event)" (reply)="reply($event)" (resolve)="resolve($event)" (discard)="discard($event)" />
|
|
2999
3076
|
} @empty {<div class="o-card"><div class="o-empty">{{ cmp() ? 'No changes.' : 'Loading…' }}</div></div>}
|
|
3000
3077
|
</div>
|
|
@@ -3015,7 +3092,7 @@ class PullPage extends RepoPage {
|
|
|
3015
3092
|
<span class="o-lbl">Reviewers</span>
|
|
3016
3093
|
@for (r of reviewers(); track r.id) {<div class="o-flex"><span class="o-av" aria-hidden="true">{{ people().initials(r.id) }}</span><span>{{ people().name(r.id) }}</span><span class="o-r o-sm" [style.color]="r.colour">{{ r.label }}</span></div>}
|
|
3017
3094
|
@if (!reviewers().length) {<span class="o-faint o-sm">No reviewers yet.</span>}
|
|
3018
|
-
<span class="o-faint o-xs">
|
|
3095
|
+
<span class="o-faint o-xs">Code owners of changed paths (CODEOWNERS) are asked automatically.</span>
|
|
3019
3096
|
</div>
|
|
3020
3097
|
<div class="o-col"><span class="o-lbl">Labels</span><div class="o-flex o-wrap">@for (l of p.labels; track l) {<span class="o-chip o-out">{{ l }}</span>}@if (!p.labels.length) {<span class="o-faint o-sm">None</span>}</div></div>
|
|
3021
3098
|
<div class="o-col">
|
|
@@ -3031,7 +3108,7 @@ class PullPage extends RepoPage {
|
|
|
3031
3108
|
<div class="o-card"><div class="o-empty">Loading…</div></div>
|
|
3032
3109
|
}
|
|
3033
3110
|
</div>
|
|
3034
|
-
`, isInline: true, styles: [".badge{padding:3px 10px;border-radius:12px;font-size:12px;font-weight:500;color:var(--o-acc-fg)}.anchor{position:relative;align-self:center}.merge{display:flex;align-items:center;position:relative}.merge .go{height:32px;padding:0 14px;border-radius:7px 0 0 7px;border:none;font-size:13px;font-weight:500;color:var(--o-acc-fg);background:var(--o-ok);cursor:pointer}.merge .mn{height:32px;padding:0 10px;border:none;border-radius:0 7px 7px 0;font-size:12px;color:var(--o-acc-fg);background:var(--o-ok);border-left:1px solid rgba(255,255,255,.25);cursor:pointer}.merge button:disabled{background:var(--o-line-mid);cursor:not-allowed}.tl{display:flex;flex-direction:column;gap:10px;padding-left:14px;border-left:2px solid var(--o-line);margin-left:14px}.files-split{display:grid;grid-template-columns:210px minmax(0,1fr);gap:14px;align-items:start}.files-split:has(>:only-child){grid-template-columns:minmax(0,1fr)}.files-tree{position:sticky;top:8px;max-height:calc(100vh - 120px);overflow:auto}@media(max-width:900px){.files-split{grid-template-columns:minmax(0,1fr)}.files-tree{display:none}}\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: DiffView, selector: "o-diff", inputs: ["repo", "file", "head", "base", "threads", "canComment", "mode", "ignoreSpace", "viewed", "reviewing"], outputs: ["comment", "reply", "resolve", "discard", "viewedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3111
|
+
`, isInline: true, styles: [".badge{padding:3px 10px;border-radius:12px;font-size:12px;font-weight:500;color:var(--o-acc-fg)}.anchor{position:relative;align-self:center}.merge{display:flex;align-items:center;position:relative}.merge .go{height:32px;padding:0 14px;border-radius:7px 0 0 7px;border:none;font-size:13px;font-weight:500;color:var(--o-acc-fg);background:var(--o-ok);cursor:pointer}.merge .mn{height:32px;padding:0 10px;border:none;border-radius:0 7px 7px 0;font-size:12px;color:var(--o-acc-fg);background:var(--o-ok);border-left:1px solid rgba(255,255,255,.25);cursor:pointer}.merge button:disabled{background:var(--o-line-mid);cursor:not-allowed}.tl{display:flex;flex-direction:column;gap:10px;padding-left:14px;border-left:2px solid var(--o-line);margin-left:14px}.files-split{display:grid;grid-template-columns:210px minmax(0,1fr);gap:14px;align-items:start}.files-split:has(>:only-child){grid-template-columns:minmax(0,1fr)}.files-tree{position:sticky;top:8px;max-height:calc(100vh - 120px);overflow:auto}@media(max-width:900px){.files-split{grid-template-columns:minmax(0,1fr)}.files-tree{display:none}}\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: DiffView, selector: "o-diff", inputs: ["repo", "file", "head", "base", "threads", "canComment", "mode", "ignoreSpace", "viewed", "reviewing", "canApply", "batch"], outputs: ["comment", "reply", "resolve", "discard", "apply", "batchToggle", "viewedChange"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3035
3112
|
}
|
|
3036
3113
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: PullPage, decorators: [{
|
|
3037
3114
|
type: Component,
|
|
@@ -3147,6 +3224,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3147
3224
|
</div>
|
|
3148
3225
|
}
|
|
3149
3226
|
@case ('files') {
|
|
3227
|
+
@if (batch().length) {
|
|
3228
|
+
<div class="o-note o-blu o-flex" data-testid="suggestion-batch"><span>{{ batch().length }} {{ batch().length === 1 ? 'suggestion' : 'suggestions' }} in a batch</span>
|
|
3229
|
+
<button type="button" class="o-btn o-sm o-r" (click)="batch.set([])">Clear</button>
|
|
3230
|
+
<button type="button" class="o-btn o-sm o-pri" (click)="applySuggestions(batch())" [disabled]="busy()" data-testid="suggestion-commit">Commit suggestions</button>
|
|
3231
|
+
</div>
|
|
3232
|
+
}
|
|
3150
3233
|
<div class="o-flex o-wrap">
|
|
3151
3234
|
<div class="o-seg" role="group" aria-label="Diff view"><button type="button" [class.o-on]="mode() === 'unified'" [attr.aria-pressed]="mode() === 'unified'" (click)="setMode('unified')">Unified</button><button type="button" [class.o-on]="mode() === 'split'" [attr.aria-pressed]="mode() === 'split'" (click)="setMode('split')" data-testid="diff-split">Split</button></div>
|
|
3152
3235
|
<button type="button" class="o-btn o-sm" [class.o-on]="hideWs()" [attr.aria-pressed]="hideWs()" (click)="hideWs.set(!hideWs())" data-testid="hide-ws">Hide whitespace</button>
|
|
@@ -3165,6 +3248,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3165
3248
|
@for (f of cmp()?.files ?? []; track f.path) {
|
|
3166
3249
|
<o-diff [repo]="name()" [file]="f" [head]="p.headSha" [base]="p.mergeBase" [threads]="threads()" [canComment]="p.state === 'open'" [mode]="mode()" [ignoreSpace]="hideWs()"
|
|
3167
3250
|
[viewed]="isViewed(f.path)" [reviewing]="myPending().length > 0" (viewedChange)="setViewed(f.path, $event)"
|
|
3251
|
+
[canApply]="p.state === 'open' && canWrite()" [batch]="batch()" (batchToggle)="toggleBatch($event)" (apply)="applySuggestions($event)"
|
|
3168
3252
|
(comment)="lineComment($event)" (reply)="reply($event)" (resolve)="resolve($event)" (discard)="discard($event)" />
|
|
3169
3253
|
} @empty {<div class="o-card"><div class="o-empty">{{ cmp() ? 'No changes.' : 'Loading…' }}</div></div>}
|
|
3170
3254
|
</div>
|
|
@@ -3185,7 +3269,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3185
3269
|
<span class="o-lbl">Reviewers</span>
|
|
3186
3270
|
@for (r of reviewers(); track r.id) {<div class="o-flex"><span class="o-av" aria-hidden="true">{{ people().initials(r.id) }}</span><span>{{ people().name(r.id) }}</span><span class="o-r o-sm" [style.color]="r.colour">{{ r.label }}</span></div>}
|
|
3187
3271
|
@if (!reviewers().length) {<span class="o-faint o-sm">No reviewers yet.</span>}
|
|
3188
|
-
<span class="o-faint o-xs">
|
|
3272
|
+
<span class="o-faint o-xs">Code owners of changed paths (CODEOWNERS) are asked automatically.</span>
|
|
3189
3273
|
</div>
|
|
3190
3274
|
<div class="o-col"><span class="o-lbl">Labels</span><div class="o-flex o-wrap">@for (l of p.labels; track l) {<span class="o-chip o-out">{{ l }}</span>}@if (!p.labels.length) {<span class="o-faint o-sm">None</span>}</div></div>
|
|
3191
3275
|
<div class="o-col">
|
|
@@ -3224,7 +3308,7 @@ function writePref(key, value) {
|
|
|
3224
3308
|
}
|
|
3225
3309
|
}
|
|
3226
3310
|
|
|
3227
|
-
const BLANK = { id: '', pattern: '', blockDirectPush: true, blockForcePush: true, blockDeletion: true, requiredApprovals: 1, dismissStaleApprovals: true, requireUpToDate: false, requiredChecks: [] };
|
|
3311
|
+
const BLANK = { id: '', pattern: '', blockDirectPush: true, blockForcePush: true, blockDeletion: true, requiredApprovals: 1, dismissStaleApprovals: true, requireUpToDate: false, requireCodeOwnerReview: false, requiredChecks: [] };
|
|
3228
3312
|
// /{org}/{project}/code/{repo}/settings?section= (prototype "code_settings"): general settings and
|
|
3229
3313
|
// merge methods, branch protection rules, and the danger zone (archive, delete).
|
|
3230
3314
|
class RepoSettingsPage extends RepoPage {
|
|
@@ -3246,6 +3330,7 @@ class RepoSettingsPage extends RepoPage {
|
|
|
3246
3330
|
{ key: 'blockDeletion', label: 'Block deletion', hint: 'Matching branches cannot be deleted.' },
|
|
3247
3331
|
{ key: 'dismissStaleApprovals', label: 'Dismiss stale approvals', hint: 'A new commit dismisses approvals given on an older one.' },
|
|
3248
3332
|
{ key: 'requireUpToDate', label: 'Require branches to be up to date', hint: 'The branch must contain the target’s latest commit before it merges.' },
|
|
3333
|
+
{ key: 'requireCodeOwnerReview', label: 'Require review from CODEOWNERS', hint: 'Owners of changed paths are requested automatically; one of them approves each owned file.' },
|
|
3249
3334
|
];
|
|
3250
3335
|
this.collabs = signal([], ...(ngDevMode ? [{ debugName: "collabs" }] : /* istanbul ignore next */ []));
|
|
3251
3336
|
this.perms = [{ id: 'read', label: 'Read' }, { id: 'write', label: 'Write' }, { id: 'maintain', label: 'Maintain' }];
|
|
@@ -3311,6 +3396,8 @@ class RepoSettingsPage extends RepoPage {
|
|
|
3311
3396
|
parts.push('no direct push');
|
|
3312
3397
|
if (p.requireUpToDate)
|
|
3313
3398
|
parts.push('up to date');
|
|
3399
|
+
if (p.requireCodeOwnerReview)
|
|
3400
|
+
parts.push('code owners');
|
|
3314
3401
|
if (p.blockDeletion)
|
|
3315
3402
|
parts.push('no deletion');
|
|
3316
3403
|
return parts.join(' · ') || 'no restrictions';
|