@chisquare-tech/origine-ngx-apps-code 0.2.0 → 0.3.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,42 @@ 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
|
+
createTag(project, name, tag, target) {
|
|
119
|
+
return this.api.post(`${this.repoPath(project, name)}/tags`, { tag, target }).pipe(map((r) => data(r)));
|
|
120
|
+
}
|
|
121
|
+
deleteTag(project, name, tag) {
|
|
122
|
+
return this.api.delete(`${this.repoPath(project, name)}/tags/${enc(tag)}`);
|
|
123
|
+
}
|
|
124
|
+
releases(project, name) {
|
|
125
|
+
return this.api.get(`${this.repoPath(project, name)}/releases`).pipe(map((r) => data(r) ?? []));
|
|
126
|
+
}
|
|
127
|
+
createRelease(project, name, r) {
|
|
128
|
+
return this.api.post(`${this.repoPath(project, name)}/releases`, r).pipe(map((x) => data(x)));
|
|
129
|
+
}
|
|
130
|
+
updateRelease(project, name, id, r) {
|
|
131
|
+
return this.api.patch(`${this.repoPath(project, name)}/releases/${enc(id)}`, r).pipe(map((x) => data(x)));
|
|
132
|
+
}
|
|
133
|
+
deleteRelease(project, name, id, deleteTag) {
|
|
134
|
+
return this.api.delete(`${this.repoPath(project, name)}/releases/${enc(id)}${deleteTag ? '?deleteTag=true' : ''}`);
|
|
135
|
+
}
|
|
136
|
+
/** Notes drafted from the done issues and merged pull requests since the previous release. */
|
|
137
|
+
releaseNotes(project, name, target, tag = '') {
|
|
138
|
+
return this.api.post(`${this.repoPath(project, name)}/releases/notes`, { target, tag: tag || undefined }).pipe(map((r) => data(r)?.notes ?? ''));
|
|
139
|
+
}
|
|
140
|
+
addAsset(project, name, id, file) {
|
|
141
|
+
return this.api
|
|
142
|
+
.post(`${this.repoPath(project, name)}/releases/${enc(id)}/assets?filename=${encodeURIComponent(file.name)}`, null, {
|
|
143
|
+
body: file, headers: { 'Content-Type': file.type || 'application/octet-stream' },
|
|
144
|
+
})
|
|
145
|
+
.pipe(map((r) => data(r)));
|
|
146
|
+
}
|
|
147
|
+
/** A release file's bytes (the download needs the session, so no plain link works). */
|
|
148
|
+
assetBlob(project, name, id, asset) {
|
|
149
|
+
return this.api.get(`${this.repoPath(project, name)}/releases/${enc(id)}/assets/${enc(asset)}`, null, { responseType: 'blob' }).pipe(map((b) => b));
|
|
150
|
+
}
|
|
151
|
+
deleteAsset(project, name, id, asset) {
|
|
152
|
+
return this.api.delete(`${this.repoPath(project, name)}/releases/${enc(id)}/assets/${enc(asset)}`);
|
|
153
|
+
}
|
|
118
154
|
createBranch(project, name, branch, from) {
|
|
119
155
|
return this.api.post(`${this.repoPath(project, name)}/branches`, { name: branch, from });
|
|
120
156
|
}
|
|
@@ -1352,7 +1388,7 @@ class RepoHomePage extends RepoPage {
|
|
|
1352
1388
|
</div>
|
|
1353
1389
|
<div class="o-col" style="gap:14px">
|
|
1354
1390
|
<div class="o-card o-cb o-col"><b>About</b><span class="o-mut o-lh">{{ r.description || 'No description.' }}</span><span class="o-faint o-sm">Default branch <span class="o-mono">{{ r.defaultBranch }}</span></span></div>
|
|
1355
|
-
<div class="o-card o-cb o-col"><
|
|
1391
|
+
<div class="o-card o-cb o-col"><b>Releases</b><a class="o-sm" [routerLink]="link('releases')" data-testid="repo-releases">Releases and tags</a></div>
|
|
1356
1392
|
<div class="o-card o-cb o-col"><b>Deployments</b><span class="o-faint o-sm">Environments show here once Deploy is set up.</span></div>
|
|
1357
1393
|
@if (langs().length) {
|
|
1358
1394
|
<div class="o-card o-cb o-col">
|
|
@@ -1472,7 +1508,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
1472
1508
|
</div>
|
|
1473
1509
|
<div class="o-col" style="gap:14px">
|
|
1474
1510
|
<div class="o-card o-cb o-col"><b>About</b><span class="o-mut o-lh">{{ r.description || 'No description.' }}</span><span class="o-faint o-sm">Default branch <span class="o-mono">{{ r.defaultBranch }}</span></span></div>
|
|
1475
|
-
<div class="o-card o-cb o-col"><
|
|
1511
|
+
<div class="o-card o-cb o-col"><b>Releases</b><a class="o-sm" [routerLink]="link('releases')" data-testid="repo-releases">Releases and tags</a></div>
|
|
1476
1512
|
<div class="o-card o-cb o-col"><b>Deployments</b><span class="o-faint o-sm">Environments show here once Deploy is set up.</span></div>
|
|
1477
1513
|
@if (langs().length) {
|
|
1478
1514
|
<div class="o-card o-cb o-col">
|
|
@@ -3268,6 +3304,386 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3268
3304
|
}]
|
|
3269
3305
|
}], ctorParameters: () => [] });
|
|
3270
3306
|
|
|
3307
|
+
// /{org}/{project}/code/{repo}/releases (prototype code_tags): releases newest first with their
|
|
3308
|
+
// notes, files and source archives, the latest marked; drafts for those who can push; a release
|
|
3309
|
+
// drafted on a new or existing tag with notes generated from the done issues since the previous
|
|
3310
|
+
// one; and the repository's tags.
|
|
3311
|
+
class ReleasesPage extends RepoPage {
|
|
3312
|
+
constructor() {
|
|
3313
|
+
super(...arguments);
|
|
3314
|
+
this.releases = signal([], ...(ngDevMode ? [{ debugName: "releases" }] : /* istanbul ignore next */ []));
|
|
3315
|
+
this.tags = signal([], ...(ngDevMode ? [{ debugName: "tags" }] : /* istanbul ignore next */ []));
|
|
3316
|
+
this.loaded = signal(false, ...(ngDevMode ? [{ debugName: "loaded" }] : /* istanbul ignore next */ []));
|
|
3317
|
+
this.tagging = signal(false, ...(ngDevMode ? [{ debugName: "tagging" }] : /* istanbul ignore next */ []));
|
|
3318
|
+
this.draft = signal(null, ...(ngDevMode ? [{ debugName: "draft" }] : /* istanbul ignore next */ []));
|
|
3319
|
+
this.busy = signal(false, ...(ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []));
|
|
3320
|
+
this.confirming = signal('', ...(ngDevMode ? [{ debugName: "confirming" }] : /* istanbul ignore next */ []));
|
|
3321
|
+
this.freeTags = computed(() => this.tags().filter((t) => !this.used(t.name)), ...(ngDevMode ? [{ debugName: "freeTags" }] : /* istanbul ignore next */ []));
|
|
3322
|
+
this.ago = (s) => ago(s);
|
|
3323
|
+
this.tagName = '';
|
|
3324
|
+
this.tagFrom = '';
|
|
3325
|
+
this.alsoTag = false;
|
|
3326
|
+
}
|
|
3327
|
+
onRepo(r) {
|
|
3328
|
+
this.tagFrom = r.defaultBranch;
|
|
3329
|
+
this.load();
|
|
3330
|
+
}
|
|
3331
|
+
md(s) {
|
|
3332
|
+
return marked.parse(s || '', { async: false, gfm: true, breaks: true });
|
|
3333
|
+
}
|
|
3334
|
+
used(tag) {
|
|
3335
|
+
return this.releases().some((r) => r.tag === tag);
|
|
3336
|
+
}
|
|
3337
|
+
load() {
|
|
3338
|
+
const r = this.repo();
|
|
3339
|
+
if (!r)
|
|
3340
|
+
return;
|
|
3341
|
+
forkJoin({ rel: this.api.releases(this.projectId(), r.name), tags: this.api.refs(this.projectId(), r.name, 'tag') }).subscribe({
|
|
3342
|
+
next: ({ rel, tags }) => { this.releases.set(rel); this.tags.set(sortTags(tags)); this.loaded.set(true); },
|
|
3343
|
+
error: (e) => { this.loaded.set(true); this.error.set(apiMessage(e, 'Could not load the releases.')); },
|
|
3344
|
+
});
|
|
3345
|
+
}
|
|
3346
|
+
run(o, fallback, done) {
|
|
3347
|
+
this.busy.set(true);
|
|
3348
|
+
this.error.set('');
|
|
3349
|
+
this.note.set('');
|
|
3350
|
+
o.subscribe({
|
|
3351
|
+
next: () => { this.busy.set(false); done?.(); this.load(); },
|
|
3352
|
+
error: (e) => { this.busy.set(false); this.error.set(apiMessage(e, fallback)); },
|
|
3353
|
+
});
|
|
3354
|
+
}
|
|
3355
|
+
createTag() {
|
|
3356
|
+
const r = this.repo();
|
|
3357
|
+
const t = this.tagName.trim();
|
|
3358
|
+
if (!r || !t)
|
|
3359
|
+
return;
|
|
3360
|
+
this.run(this.api.createTag(this.projectId(), r.name, t, this.tagFrom.trim() || r.defaultBranch), 'Could not create the tag.', () => {
|
|
3361
|
+
this.tagging.set(false);
|
|
3362
|
+
this.note.set(`Tag ${t} created.`);
|
|
3363
|
+
this.tagName = '';
|
|
3364
|
+
});
|
|
3365
|
+
}
|
|
3366
|
+
removeTag(t) {
|
|
3367
|
+
const r = this.repo();
|
|
3368
|
+
if (!r)
|
|
3369
|
+
return;
|
|
3370
|
+
this.run(this.api.deleteTag(this.projectId(), r.name, t.name), `Could not delete ${t.name}.`, () => this.note.set(`Tag ${t.name} deleted.`));
|
|
3371
|
+
}
|
|
3372
|
+
startRelease() {
|
|
3373
|
+
const r = this.repo();
|
|
3374
|
+
this.tagging.set(false);
|
|
3375
|
+
this.draft.set({ tag: '', newTag: true, target: r?.defaultBranch ?? 'main', title: '', notes: '', prerelease: false, draft: true });
|
|
3376
|
+
}
|
|
3377
|
+
setTag(d, tag) {
|
|
3378
|
+
d.tag = tag.trim();
|
|
3379
|
+
d.newTag = !this.tags().some((t) => t.name === d.tag);
|
|
3380
|
+
}
|
|
3381
|
+
generate(d) {
|
|
3382
|
+
const r = this.repo();
|
|
3383
|
+
if (!r)
|
|
3384
|
+
return;
|
|
3385
|
+
this.busy.set(true);
|
|
3386
|
+
const target = d.newTag ? d.target || r.defaultBranch : 'refs/tags/' + d.tag;
|
|
3387
|
+
this.api.releaseNotes(this.projectId(), r.name, target).subscribe({
|
|
3388
|
+
next: (n) => { this.busy.set(false); d.notes = n; this.draft.set({ ...d }); },
|
|
3389
|
+
error: (e) => { this.busy.set(false); this.error.set(apiMessage(e, 'Could not generate the notes.')); },
|
|
3390
|
+
});
|
|
3391
|
+
}
|
|
3392
|
+
edit(r) {
|
|
3393
|
+
this.tagging.set(false);
|
|
3394
|
+
this.draft.set({ id: r.id, version: r.version, tag: r.tag, newTag: false, target: '', title: r.title === r.tag ? '' : r.title, notes: r.notes, prerelease: r.prerelease, draft: r.draft });
|
|
3395
|
+
}
|
|
3396
|
+
save(d, asDraft) {
|
|
3397
|
+
const r = this.repo();
|
|
3398
|
+
if (!r)
|
|
3399
|
+
return;
|
|
3400
|
+
if (d.id) {
|
|
3401
|
+
this.run(this.api.updateRelease(this.projectId(), r.name, d.id, { title: d.title, notes: d.notes, prerelease: d.prerelease, draft: d.draft && asDraft, version: d.version }), 'Could not save the release.', () => { this.draft.set(null); this.note.set(asDraft ? 'Draft saved.' : `${d.tag} saved.`); });
|
|
3402
|
+
return;
|
|
3403
|
+
}
|
|
3404
|
+
this.run(this.api.createRelease(this.projectId(), r.name, { tag: d.tag, target: d.newTag ? d.target || r.defaultBranch : undefined, title: d.title, notes: d.notes, prerelease: d.prerelease, draft: asDraft }), 'Could not create the release.', () => { this.draft.set(null); this.note.set(asDraft ? `Draft ${d.tag} saved.` : `${d.tag} published.`); });
|
|
3405
|
+
}
|
|
3406
|
+
remove(rel) {
|
|
3407
|
+
const r = this.repo();
|
|
3408
|
+
if (!r)
|
|
3409
|
+
return;
|
|
3410
|
+
this.confirming.set('');
|
|
3411
|
+
this.run(this.api.deleteRelease(this.projectId(), r.name, rel.id, this.alsoTag), `Could not delete ${rel.tag}.`, () => this.note.set(`Release ${rel.tag} deleted${this.alsoTag ? ' with its tag' : ''}.`));
|
|
3412
|
+
}
|
|
3413
|
+
upload(rel, ev) {
|
|
3414
|
+
const r = this.repo();
|
|
3415
|
+
const input = ev.target;
|
|
3416
|
+
const file = input.files?.[0];
|
|
3417
|
+
if (!r || !file)
|
|
3418
|
+
return;
|
|
3419
|
+
if (file.size > 25 * 1024 * 1024) {
|
|
3420
|
+
this.error.set('A release file is at most 25 MB.');
|
|
3421
|
+
return;
|
|
3422
|
+
}
|
|
3423
|
+
this.run(this.api.addAsset(this.projectId(), r.name, rel.id, file), 'Could not add the file.', () => { input.value = ''; this.note.set(`${file.name} added to ${rel.tag}.`); });
|
|
3424
|
+
}
|
|
3425
|
+
removeAsset(rel, id) {
|
|
3426
|
+
const r = this.repo();
|
|
3427
|
+
if (!r)
|
|
3428
|
+
return;
|
|
3429
|
+
this.run(this.api.deleteAsset(this.projectId(), r.name, rel.id, id), 'Could not remove the file.');
|
|
3430
|
+
}
|
|
3431
|
+
download(rel, id, filename) {
|
|
3432
|
+
const r = this.repo();
|
|
3433
|
+
if (!r)
|
|
3434
|
+
return;
|
|
3435
|
+
this.api.assetBlob(this.projectId(), r.name, rel.id, id).subscribe({
|
|
3436
|
+
next: (b) => this.save_(b, filename),
|
|
3437
|
+
error: (e) => this.error.set(apiMessage(e, 'Could not download the file.')),
|
|
3438
|
+
});
|
|
3439
|
+
}
|
|
3440
|
+
archive(rel, format) {
|
|
3441
|
+
const r = this.repo();
|
|
3442
|
+
if (!r)
|
|
3443
|
+
return;
|
|
3444
|
+
this.api.archiveUrl(this.projectId(), r.name, 'refs/tags/' + rel.tag, format).subscribe({
|
|
3445
|
+
next: (url) => { if (url)
|
|
3446
|
+
window.location.assign(url); },
|
|
3447
|
+
error: (e) => this.error.set(apiMessage(e, 'Could not prepare the archive.')),
|
|
3448
|
+
});
|
|
3449
|
+
}
|
|
3450
|
+
save_(b, filename) {
|
|
3451
|
+
const url = URL.createObjectURL(b);
|
|
3452
|
+
const link = document.createElement('a');
|
|
3453
|
+
link.href = url;
|
|
3454
|
+
link.download = filename;
|
|
3455
|
+
link.click();
|
|
3456
|
+
setTimeout(() => URL.revokeObjectURL(url), 10_000);
|
|
3457
|
+
}
|
|
3458
|
+
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ReleasesPage, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
|
|
3459
|
+
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.12", type: ReleasesPage, isStandalone: true, selector: "o-code-releases", usesInheritance: true, ngImport: i0, template: `
|
|
3460
|
+
<div class="o-page o-narrow">
|
|
3461
|
+
<div class="o-ph">
|
|
3462
|
+
<div class="o-col"><span class="o-eyebrow">{{ name() }}</span><h1 class="o-t">Releases and tags</h1></div>
|
|
3463
|
+
@if (canWrite()) {
|
|
3464
|
+
<div class="o-acts">
|
|
3465
|
+
<button type="button" class="o-btn o-sm" (click)="tagging.set(!tagging()); draft.set(null)" data-testid="tag-new">New tag</button>
|
|
3466
|
+
<button type="button" class="o-btn o-sm o-dark" (click)="startRelease()" data-testid="release-new">Draft release</button>
|
|
3467
|
+
</div>
|
|
3468
|
+
}
|
|
3469
|
+
</div>
|
|
3470
|
+
@if (error()) {<div class="o-note o-red" role="alert">{{ error() }}</div>}
|
|
3471
|
+
@if (note()) {<div class="o-note" role="status">{{ note() }}</div>}
|
|
3472
|
+
|
|
3473
|
+
@if (tagging()) {
|
|
3474
|
+
<form class="o-card" (ngSubmit)="createTag()" data-testid="tag-form">
|
|
3475
|
+
<div class="o-ch"><b>New tag</b></div>
|
|
3476
|
+
<div class="o-cb o-flex o-wrap" style="align-items:flex-end">
|
|
3477
|
+
<div class="o-fld"><label for="tg-name">Tag</label><input id="tg-name" class="o-in o-mono" name="tag" placeholder="v1.8.0" [(ngModel)]="tagName" /></div>
|
|
3478
|
+
<div class="o-fld"><label for="tg-from">At</label><input id="tg-from" class="o-in o-mono" name="from" [(ngModel)]="tagFrom" placeholder="main or a commit" /></div>
|
|
3479
|
+
<button type="button" class="o-btn o-sm o-r" (click)="tagging.set(false)">Cancel</button>
|
|
3480
|
+
<button type="submit" class="o-btn o-sm o-dark" [disabled]="busy() || !tagName.trim()">Create tag</button>
|
|
3481
|
+
</div>
|
|
3482
|
+
</form>
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3485
|
+
@if (draft(); as d) {
|
|
3486
|
+
<div class="o-card" data-testid="release-form">
|
|
3487
|
+
<div class="o-ch"><b>{{ d.id ? 'Edit ' + d.tag : 'Draft a release' }}</b></div>
|
|
3488
|
+
<div class="o-cb o-col" style="gap:12px">
|
|
3489
|
+
@if (!d.id) {
|
|
3490
|
+
<div class="o-flex o-wrap" style="align-items:flex-end">
|
|
3491
|
+
<div class="o-fld"><label for="rl-tag">Tag</label>
|
|
3492
|
+
<input id="rl-tag" class="o-in o-mono" list="rl-tags" placeholder="v1.8.0" [ngModel]="d.tag" (ngModelChange)="setTag(d, $event)" data-testid="release-tag" />
|
|
3493
|
+
<datalist id="rl-tags">@for (t of freeTags(); track t.name) {<option [value]="t.name"></option>}</datalist>
|
|
3494
|
+
</div>
|
|
3495
|
+
@if (d.newTag) {<div class="o-fld"><label for="rl-target">New tag at</label><input id="rl-target" class="o-in o-mono" [(ngModel)]="d.target" placeholder="main" /></div>}
|
|
3496
|
+
<span class="o-hint" style="padding-bottom:8px">{{ d.tag ? (d.newTag ? 'A new tag, made when you save.' : 'An existing tag.') : '' }}</span>
|
|
3497
|
+
</div>
|
|
3498
|
+
}
|
|
3499
|
+
<div class="o-fld"><label for="rl-title">Title</label><input id="rl-title" class="o-in" maxlength="255" [(ngModel)]="d.title" placeholder="Defaults to the tag" data-testid="release-title" /></div>
|
|
3500
|
+
<div class="o-fld">
|
|
3501
|
+
<div class="o-flex"><label for="rl-notes">Notes</label>
|
|
3502
|
+
@if (!d.id) {<button type="button" class="o-lnk o-r o-sm" [disabled]="busy() || !d.tag" (click)="generate(d)" data-testid="release-generate">Generate from done issues</button>}
|
|
3503
|
+
</div>
|
|
3504
|
+
<textarea id="rl-notes" class="o-in" rows="7" style="height:auto;padding:8px 10px" [(ngModel)]="d.notes" placeholder="Markdown works." data-testid="release-notes"></textarea>
|
|
3505
|
+
<span class="o-hint">Generated notes list the done issues and merged pull requests since the previous release.</span>
|
|
3506
|
+
</div>
|
|
3507
|
+
<label class="o-flex o-sm"><input type="checkbox" [(ngModel)]="d.prerelease" /> Pre-release (never marked Latest)</label>
|
|
3508
|
+
</div>
|
|
3509
|
+
<div class="o-cf">
|
|
3510
|
+
<button type="button" class="o-btn o-sm o-r" (click)="draft.set(null)">Cancel</button>
|
|
3511
|
+
@if (!d.id || d.draft) {<button type="button" class="o-btn o-sm" [disabled]="busy() || !d.tag" (click)="save(d, true)" data-testid="release-save-draft">Save draft</button>}
|
|
3512
|
+
<button type="button" class="o-btn o-sm o-dark" [disabled]="busy() || !d.tag" (click)="save(d, false)" data-testid="release-publish">{{ d.id && !d.draft ? 'Save' : 'Publish release' }}</button>
|
|
3513
|
+
</div>
|
|
3514
|
+
</div>
|
|
3515
|
+
}
|
|
3516
|
+
|
|
3517
|
+
@for (r of releases(); track r.id) {
|
|
3518
|
+
<div class="o-card rel" [attr.data-testid]="'release-' + r.tag">
|
|
3519
|
+
<div class="o-ch">
|
|
3520
|
+
<b class="o-md">{{ r.tag }}</b>
|
|
3521
|
+
@if (r.latest) {<span class="o-chip o-grn">Latest</span>}
|
|
3522
|
+
@if (r.draft) {<span class="o-chip o-amb">Draft</span>}
|
|
3523
|
+
@if (r.prerelease) {<span class="o-chip">Pre-release</span>}
|
|
3524
|
+
<span class="o-r o-mono o-sm o-faint">{{ r.short }} · {{ ago(r.publishedAt || r.createdAt) }} · {{ people().name(r.createdBy) }}</span>
|
|
3525
|
+
</div>
|
|
3526
|
+
<div class="o-cb">
|
|
3527
|
+
@if (r.title && r.title !== r.tag) {<h4>{{ r.title }}</h4>}
|
|
3528
|
+
<div class="o-md" style="font-size:13px" [innerHTML]="r.notes ? md(r.notes) : '<p class="o-faint">No notes.</p>'"></div>
|
|
3529
|
+
</div>
|
|
3530
|
+
<div class="o-cf assets">
|
|
3531
|
+
<span>Assets</span>
|
|
3532
|
+
@for (a of r.assets; track a.id) {
|
|
3533
|
+
<button type="button" class="o-lnk o-mono" (click)="download(r, a.id, a.name)" [attr.aria-label]="'Download ' + a.name">{{ a.name }}</button>
|
|
3534
|
+
@if (canWrite()) {<button type="button" class="o-lnk" style="color:var(--o-mut)" (click)="removeAsset(r, a.id)" [attr.aria-label]="'Remove ' + a.name">×</button>}
|
|
3535
|
+
}
|
|
3536
|
+
<button type="button" class="o-lnk" (click)="archive(r, 'zip')">Source (zip)</button>
|
|
3537
|
+
<button type="button" class="o-lnk" (click)="archive(r, 'tar.gz')">Source (tar.gz)</button>
|
|
3538
|
+
@if (canWrite()) {
|
|
3539
|
+
<span class="o-r o-flex" style="gap:10px">
|
|
3540
|
+
<label class="o-lnk" style="cursor:pointer">Add file<input type="file" class="o-sr-only" (change)="upload(r, $event)" [attr.aria-label]="'Add a file to ' + r.tag" /></label>
|
|
3541
|
+
<button type="button" class="o-lnk" (click)="edit(r)" [attr.aria-label]="'Edit ' + r.tag">Edit</button>
|
|
3542
|
+
@if (confirming() === r.id) {
|
|
3543
|
+
<label class="o-flex o-xs"><input type="checkbox" [(ngModel)]="alsoTag" /> and the tag</label>
|
|
3544
|
+
<button type="button" class="o-lnk o-red-t" (click)="remove(r)">Delete</button><button type="button" class="o-lnk" (click)="confirming.set('')">Keep</button>
|
|
3545
|
+
} @else {
|
|
3546
|
+
<button type="button" class="o-lnk" style="color:var(--o-mut)" (click)="confirming.set(r.id); alsoTag = false" [attr.aria-label]="'Delete ' + r.tag">Delete</button>
|
|
3547
|
+
}
|
|
3548
|
+
</span>
|
|
3549
|
+
}
|
|
3550
|
+
</div>
|
|
3551
|
+
</div>
|
|
3552
|
+
} @empty {
|
|
3553
|
+
<div class="o-card"><div class="o-empty">{{ loaded() ? 'No releases yet.' + (canWrite() ? ' Draft one from a tag; its notes can come from the issues it finishes.' : '') : 'Loading…' }}</div></div>
|
|
3554
|
+
}
|
|
3555
|
+
|
|
3556
|
+
<div class="o-card" data-testid="tags">
|
|
3557
|
+
<div class="o-ch"><b>Tags</b><span class="o-r o-faint o-sm">{{ tags().length }}</span></div>
|
|
3558
|
+
@for (t of tags(); track t.name) {
|
|
3559
|
+
<div class="o-row" [attr.data-testid]="'tag-' + t.name">
|
|
3560
|
+
<span class="o-mono o-sm o-b5">{{ t.name }}</span><span class="o-faint o-sm o-mono">{{ t.commit?.short }}</span>
|
|
3561
|
+
<span class="o-r o-faint o-sm">{{ ago(t.commit?.committer?.when) }}</span>
|
|
3562
|
+
@if (canWrite() && !used(t.name)) {<button type="button" class="o-lnk" style="color:var(--o-mut)" (click)="removeTag(t)" [attr.aria-label]="'Delete tag ' + t.name">Delete</button>}
|
|
3563
|
+
</div>
|
|
3564
|
+
} @empty {<div class="o-empty o-sm">No tags yet.</div>}
|
|
3565
|
+
</div>
|
|
3566
|
+
</div>
|
|
3567
|
+
`, isInline: true, styles: [".rel h4{margin:0 0 6px;font-size:15px}.rel .o-md ul{margin:0;padding-left:20px}.assets{display:flex;gap:12px;flex-wrap:wrap;align-items:center}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { 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.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { 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"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
|
|
3568
|
+
}
|
|
3569
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImport: i0, type: ReleasesPage, decorators: [{
|
|
3570
|
+
type: Component,
|
|
3571
|
+
args: [{ selector: 'o-code-releases', imports: [FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: `
|
|
3572
|
+
<div class="o-page o-narrow">
|
|
3573
|
+
<div class="o-ph">
|
|
3574
|
+
<div class="o-col"><span class="o-eyebrow">{{ name() }}</span><h1 class="o-t">Releases and tags</h1></div>
|
|
3575
|
+
@if (canWrite()) {
|
|
3576
|
+
<div class="o-acts">
|
|
3577
|
+
<button type="button" class="o-btn o-sm" (click)="tagging.set(!tagging()); draft.set(null)" data-testid="tag-new">New tag</button>
|
|
3578
|
+
<button type="button" class="o-btn o-sm o-dark" (click)="startRelease()" data-testid="release-new">Draft release</button>
|
|
3579
|
+
</div>
|
|
3580
|
+
}
|
|
3581
|
+
</div>
|
|
3582
|
+
@if (error()) {<div class="o-note o-red" role="alert">{{ error() }}</div>}
|
|
3583
|
+
@if (note()) {<div class="o-note" role="status">{{ note() }}</div>}
|
|
3584
|
+
|
|
3585
|
+
@if (tagging()) {
|
|
3586
|
+
<form class="o-card" (ngSubmit)="createTag()" data-testid="tag-form">
|
|
3587
|
+
<div class="o-ch"><b>New tag</b></div>
|
|
3588
|
+
<div class="o-cb o-flex o-wrap" style="align-items:flex-end">
|
|
3589
|
+
<div class="o-fld"><label for="tg-name">Tag</label><input id="tg-name" class="o-in o-mono" name="tag" placeholder="v1.8.0" [(ngModel)]="tagName" /></div>
|
|
3590
|
+
<div class="o-fld"><label for="tg-from">At</label><input id="tg-from" class="o-in o-mono" name="from" [(ngModel)]="tagFrom" placeholder="main or a commit" /></div>
|
|
3591
|
+
<button type="button" class="o-btn o-sm o-r" (click)="tagging.set(false)">Cancel</button>
|
|
3592
|
+
<button type="submit" class="o-btn o-sm o-dark" [disabled]="busy() || !tagName.trim()">Create tag</button>
|
|
3593
|
+
</div>
|
|
3594
|
+
</form>
|
|
3595
|
+
}
|
|
3596
|
+
|
|
3597
|
+
@if (draft(); as d) {
|
|
3598
|
+
<div class="o-card" data-testid="release-form">
|
|
3599
|
+
<div class="o-ch"><b>{{ d.id ? 'Edit ' + d.tag : 'Draft a release' }}</b></div>
|
|
3600
|
+
<div class="o-cb o-col" style="gap:12px">
|
|
3601
|
+
@if (!d.id) {
|
|
3602
|
+
<div class="o-flex o-wrap" style="align-items:flex-end">
|
|
3603
|
+
<div class="o-fld"><label for="rl-tag">Tag</label>
|
|
3604
|
+
<input id="rl-tag" class="o-in o-mono" list="rl-tags" placeholder="v1.8.0" [ngModel]="d.tag" (ngModelChange)="setTag(d, $event)" data-testid="release-tag" />
|
|
3605
|
+
<datalist id="rl-tags">@for (t of freeTags(); track t.name) {<option [value]="t.name"></option>}</datalist>
|
|
3606
|
+
</div>
|
|
3607
|
+
@if (d.newTag) {<div class="o-fld"><label for="rl-target">New tag at</label><input id="rl-target" class="o-in o-mono" [(ngModel)]="d.target" placeholder="main" /></div>}
|
|
3608
|
+
<span class="o-hint" style="padding-bottom:8px">{{ d.tag ? (d.newTag ? 'A new tag, made when you save.' : 'An existing tag.') : '' }}</span>
|
|
3609
|
+
</div>
|
|
3610
|
+
}
|
|
3611
|
+
<div class="o-fld"><label for="rl-title">Title</label><input id="rl-title" class="o-in" maxlength="255" [(ngModel)]="d.title" placeholder="Defaults to the tag" data-testid="release-title" /></div>
|
|
3612
|
+
<div class="o-fld">
|
|
3613
|
+
<div class="o-flex"><label for="rl-notes">Notes</label>
|
|
3614
|
+
@if (!d.id) {<button type="button" class="o-lnk o-r o-sm" [disabled]="busy() || !d.tag" (click)="generate(d)" data-testid="release-generate">Generate from done issues</button>}
|
|
3615
|
+
</div>
|
|
3616
|
+
<textarea id="rl-notes" class="o-in" rows="7" style="height:auto;padding:8px 10px" [(ngModel)]="d.notes" placeholder="Markdown works." data-testid="release-notes"></textarea>
|
|
3617
|
+
<span class="o-hint">Generated notes list the done issues and merged pull requests since the previous release.</span>
|
|
3618
|
+
</div>
|
|
3619
|
+
<label class="o-flex o-sm"><input type="checkbox" [(ngModel)]="d.prerelease" /> Pre-release (never marked Latest)</label>
|
|
3620
|
+
</div>
|
|
3621
|
+
<div class="o-cf">
|
|
3622
|
+
<button type="button" class="o-btn o-sm o-r" (click)="draft.set(null)">Cancel</button>
|
|
3623
|
+
@if (!d.id || d.draft) {<button type="button" class="o-btn o-sm" [disabled]="busy() || !d.tag" (click)="save(d, true)" data-testid="release-save-draft">Save draft</button>}
|
|
3624
|
+
<button type="button" class="o-btn o-sm o-dark" [disabled]="busy() || !d.tag" (click)="save(d, false)" data-testid="release-publish">{{ d.id && !d.draft ? 'Save' : 'Publish release' }}</button>
|
|
3625
|
+
</div>
|
|
3626
|
+
</div>
|
|
3627
|
+
}
|
|
3628
|
+
|
|
3629
|
+
@for (r of releases(); track r.id) {
|
|
3630
|
+
<div class="o-card rel" [attr.data-testid]="'release-' + r.tag">
|
|
3631
|
+
<div class="o-ch">
|
|
3632
|
+
<b class="o-md">{{ r.tag }}</b>
|
|
3633
|
+
@if (r.latest) {<span class="o-chip o-grn">Latest</span>}
|
|
3634
|
+
@if (r.draft) {<span class="o-chip o-amb">Draft</span>}
|
|
3635
|
+
@if (r.prerelease) {<span class="o-chip">Pre-release</span>}
|
|
3636
|
+
<span class="o-r o-mono o-sm o-faint">{{ r.short }} · {{ ago(r.publishedAt || r.createdAt) }} · {{ people().name(r.createdBy) }}</span>
|
|
3637
|
+
</div>
|
|
3638
|
+
<div class="o-cb">
|
|
3639
|
+
@if (r.title && r.title !== r.tag) {<h4>{{ r.title }}</h4>}
|
|
3640
|
+
<div class="o-md" style="font-size:13px" [innerHTML]="r.notes ? md(r.notes) : '<p class="o-faint">No notes.</p>'"></div>
|
|
3641
|
+
</div>
|
|
3642
|
+
<div class="o-cf assets">
|
|
3643
|
+
<span>Assets</span>
|
|
3644
|
+
@for (a of r.assets; track a.id) {
|
|
3645
|
+
<button type="button" class="o-lnk o-mono" (click)="download(r, a.id, a.name)" [attr.aria-label]="'Download ' + a.name">{{ a.name }}</button>
|
|
3646
|
+
@if (canWrite()) {<button type="button" class="o-lnk" style="color:var(--o-mut)" (click)="removeAsset(r, a.id)" [attr.aria-label]="'Remove ' + a.name">×</button>}
|
|
3647
|
+
}
|
|
3648
|
+
<button type="button" class="o-lnk" (click)="archive(r, 'zip')">Source (zip)</button>
|
|
3649
|
+
<button type="button" class="o-lnk" (click)="archive(r, 'tar.gz')">Source (tar.gz)</button>
|
|
3650
|
+
@if (canWrite()) {
|
|
3651
|
+
<span class="o-r o-flex" style="gap:10px">
|
|
3652
|
+
<label class="o-lnk" style="cursor:pointer">Add file<input type="file" class="o-sr-only" (change)="upload(r, $event)" [attr.aria-label]="'Add a file to ' + r.tag" /></label>
|
|
3653
|
+
<button type="button" class="o-lnk" (click)="edit(r)" [attr.aria-label]="'Edit ' + r.tag">Edit</button>
|
|
3654
|
+
@if (confirming() === r.id) {
|
|
3655
|
+
<label class="o-flex o-xs"><input type="checkbox" [(ngModel)]="alsoTag" /> and the tag</label>
|
|
3656
|
+
<button type="button" class="o-lnk o-red-t" (click)="remove(r)">Delete</button><button type="button" class="o-lnk" (click)="confirming.set('')">Keep</button>
|
|
3657
|
+
} @else {
|
|
3658
|
+
<button type="button" class="o-lnk" style="color:var(--o-mut)" (click)="confirming.set(r.id); alsoTag = false" [attr.aria-label]="'Delete ' + r.tag">Delete</button>
|
|
3659
|
+
}
|
|
3660
|
+
</span>
|
|
3661
|
+
}
|
|
3662
|
+
</div>
|
|
3663
|
+
</div>
|
|
3664
|
+
} @empty {
|
|
3665
|
+
<div class="o-card"><div class="o-empty">{{ loaded() ? 'No releases yet.' + (canWrite() ? ' Draft one from a tag; its notes can come from the issues it finishes.' : '') : 'Loading…' }}</div></div>
|
|
3666
|
+
}
|
|
3667
|
+
|
|
3668
|
+
<div class="o-card" data-testid="tags">
|
|
3669
|
+
<div class="o-ch"><b>Tags</b><span class="o-r o-faint o-sm">{{ tags().length }}</span></div>
|
|
3670
|
+
@for (t of tags(); track t.name) {
|
|
3671
|
+
<div class="o-row" [attr.data-testid]="'tag-' + t.name">
|
|
3672
|
+
<span class="o-mono o-sm o-b5">{{ t.name }}</span><span class="o-faint o-sm o-mono">{{ t.commit?.short }}</span>
|
|
3673
|
+
<span class="o-r o-faint o-sm">{{ ago(t.commit?.committer?.when) }}</span>
|
|
3674
|
+
@if (canWrite() && !used(t.name)) {<button type="button" class="o-lnk" style="color:var(--o-mut)" (click)="removeTag(t)" [attr.aria-label]="'Delete tag ' + t.name">Delete</button>}
|
|
3675
|
+
</div>
|
|
3676
|
+
} @empty {<div class="o-empty o-sm">No tags yet.</div>}
|
|
3677
|
+
</div>
|
|
3678
|
+
</div>
|
|
3679
|
+
`, styles: [".rel h4{margin:0 0 6px;font-size:15px}.rel .o-md ul{margin:0;padding-left:20px}.assets{display:flex;gap:12px;flex-wrap:wrap;align-items:center}\n"] }]
|
|
3680
|
+
}] });
|
|
3681
|
+
/** Newest commit first; tags on commits of the same second by version, highest first (v1.10 after v1.9). */
|
|
3682
|
+
function sortTags(tags) {
|
|
3683
|
+
const when = (t) => Date.parse(t.commit?.committer?.when ?? '') || 0;
|
|
3684
|
+
return [...tags].sort((a, b) => when(b) - when(a) || b.name.localeCompare(a.name, undefined, { numeric: true }));
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3271
3687
|
/*
|
|
3272
3688
|
* @chisquare-tech/origine-ngx-apps-code: the Code module's screens (TRD §6.10). The host app provides
|
|
3273
3689
|
* CodeHost and mounts the pages at /{org}/{project}/code, /code/new, /code/pulls and
|
|
@@ -3278,5 +3694,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.12", ngImpo
|
|
|
3278
3694
|
* Generated bundle index. Do not edit.
|
|
3279
3695
|
*/
|
|
3280
3696
|
|
|
3281
|
-
export { BranchesPage, CodeApi, CodeHost, CommitPage, CommitsPage, CurrentRepo, CurrentRepoPage, DiffView, FilePage, NewRepoPage, People, PullPage, PullsPage, RefPicker, RepoHomePage, RepoPage, RepoSettingsPage, ReposPage, TOKEN_CSS, ago, apiMessage, checkDot, copy, dayLabel, initials, langColour, pair, shortDate, size };
|
|
3697
|
+
export { BranchesPage, CodeApi, CodeHost, CommitPage, CommitsPage, CurrentRepo, CurrentRepoPage, DiffView, FilePage, NewRepoPage, People, PullPage, PullsPage, RefPicker, ReleasesPage, RepoHomePage, RepoPage, RepoSettingsPage, ReposPage, TOKEN_CSS, ago, apiMessage, checkDot, copy, dayLabel, initials, langColour, pair, shortDate, size };
|
|
3282
3698
|
//# sourceMappingURL=chisquare-tech-origine-ngx-apps-code.mjs.map
|