Implement full attachment CRUD for issues and comments, restricted to open issues only. Attachments are stored locally and mirrored to the Gitea tracker via new `mirror_attachment_to_gitea` and `set_gitea_asset_id` functions. Add `gitea_asset_id` column to the attachments table, extend `IssueForm` and `IssueCommentForm` with `attachment_uids` field, and expose new API endpoints for listing, adding, and deleting issue attachments. Update agent configuration to include web research tools, and document the new capability in README, AGENTS.md, and CLAUDE.md.
72 lines
2.4 KiB
JavaScript
72 lines
2.4 KiB
JavaScript
// retoor <retoor@molodetz.nl>
|
|
|
|
import { Http } from "./Http.js";
|
|
import { JobPoller } from "./JobPoller.js";
|
|
|
|
export class IssueReporter {
|
|
constructor() {
|
|
this.active = false;
|
|
document.addEventListener("submit", (event) => {
|
|
const form = event.target.closest("form[data-issue-create]");
|
|
if (!form) return;
|
|
event.preventDefault();
|
|
this.submit(form);
|
|
});
|
|
}
|
|
|
|
async submit(form) {
|
|
if (this.active) return;
|
|
const title = form.querySelector("[name='title']").value.trim();
|
|
const description = form.querySelector("[name='description']").value.trim();
|
|
if (!title || !description) {
|
|
window.app.toast.show("Title and description are required", { type: "error" });
|
|
return;
|
|
}
|
|
const uidsInput = form.querySelector("[name='attachment_uids']");
|
|
const attachmentUids = uidsInput ? uidsInput.value : "";
|
|
this.active = true;
|
|
const button = form.querySelector("button[type='submit']");
|
|
if (button) {
|
|
button.disabled = true;
|
|
button.classList.add("is-loading");
|
|
}
|
|
window.app.toast.show("Filing your report and improving it with AI...", {
|
|
type: "info",
|
|
ms: 5000,
|
|
});
|
|
try {
|
|
const job = await Http.sendForm(form.action, {
|
|
title,
|
|
description,
|
|
attachment_uids: attachmentUids,
|
|
});
|
|
await this.poll(job.status_url);
|
|
} catch (error) {
|
|
window.app.toast.show("Could not submit the report", { type: "error" });
|
|
} finally {
|
|
this.active = false;
|
|
if (button) {
|
|
button.disabled = false;
|
|
button.classList.remove("is-loading");
|
|
}
|
|
}
|
|
}
|
|
|
|
poll(statusUrl) {
|
|
return JobPoller.run(statusUrl, {
|
|
onDone: (status) => {
|
|
window.app.toast.show("Issue report filed", { type: "success" });
|
|
window.location.href = status.issue_url || "/issues";
|
|
},
|
|
onFailed: (status) => {
|
|
window.app.toast.show(
|
|
"Report failed: " + (status.error || "unknown error"),
|
|
{ type: "error" },
|
|
);
|
|
},
|
|
onTimeout: () =>
|
|
window.app.toast.show("Report is still processing", { type: "info" }),
|
|
});
|
|
}
|
|
}
|