+93
-1
@@ -42,8 +42,10 @@ interface GiteaBranchResponse {
|
||||
interface GiteaIssueResponse {
|
||||
number?: number;
|
||||
title?: string;
|
||||
body?: string;
|
||||
state?: string;
|
||||
html_url?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
user?: {
|
||||
login?: string;
|
||||
@@ -51,14 +53,29 @@ interface GiteaIssueResponse {
|
||||
comments?: number;
|
||||
}
|
||||
|
||||
interface GiteaIssueCommentResponse {
|
||||
id?: number;
|
||||
body?: string;
|
||||
html_url?: string;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
user?: {
|
||||
login?: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface GiteaLabelResponse {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface GiteaAttachmentResponse {
|
||||
id?: number;
|
||||
name?: string;
|
||||
browser_download_url?: string;
|
||||
download_url?: string;
|
||||
size?: number;
|
||||
created_at?: string;
|
||||
}
|
||||
|
||||
export interface CreatedGiteaIssue {
|
||||
@@ -69,16 +86,36 @@ export interface CreatedGiteaIssue {
|
||||
export interface GiteaIssueSummary {
|
||||
number: number;
|
||||
title: string;
|
||||
body: string;
|
||||
state: string;
|
||||
webUrl: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
author: string;
|
||||
comments: number;
|
||||
}
|
||||
|
||||
export interface GiteaIssueAttachment {
|
||||
id: number;
|
||||
name: string;
|
||||
downloadUrl: string;
|
||||
size: number;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface GiteaIssueComment {
|
||||
id: number;
|
||||
body: string;
|
||||
webUrl: string;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
author: string;
|
||||
}
|
||||
|
||||
export interface GiteaIssueDetail {
|
||||
issue: GiteaIssueSummary;
|
||||
comments: GiteaIssueComment[];
|
||||
attachments: GiteaIssueAttachment[];
|
||||
}
|
||||
|
||||
export interface GiteaRepoSummary {
|
||||
@@ -174,14 +211,66 @@ export async function getGiteaIssues(owner: string, repo: string): Promise<Gitea
|
||||
.map((issue) => ({
|
||||
number: issue.number ?? 0,
|
||||
title: issue.title || "Untitled issue",
|
||||
body: issue.body || "",
|
||||
state: issue.state || "unknown",
|
||||
webUrl: issue.html_url || `${giteaConfig.baseUrl.replace(/\/+$/, "")}/${owner}/${repo}/issues/${issue.number}`,
|
||||
createdAt: issue.created_at || "",
|
||||
updatedAt: issue.updated_at || "",
|
||||
author: issue.user?.login || "unknown",
|
||||
comments: issue.comments ?? 0
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getGiteaIssueDetail(owner: string, repo: string, issueNumber: number): Promise<GiteaIssueDetail> {
|
||||
const client = createGiteaClient();
|
||||
const [issueResponse, commentsResponse, attachmentsResponse] = await Promise.all([
|
||||
client.get<GiteaIssueResponse>(`/repos/${owner}/${repo}/issues/${issueNumber}`),
|
||||
client.get<GiteaIssueCommentResponse[]>(`/repos/${owner}/${repo}/issues/${issueNumber}/comments`, {
|
||||
params: {
|
||||
limit: 100,
|
||||
page: 1
|
||||
}
|
||||
}),
|
||||
client.get<GiteaAttachmentResponse[]>(`/repos/${owner}/${repo}/issues/${issueNumber}/assets`, {
|
||||
params: {
|
||||
limit: 100,
|
||||
page: 1
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
const issue = issueResponse.data;
|
||||
|
||||
return {
|
||||
issue: {
|
||||
number: issue.number ?? issueNumber,
|
||||
title: issue.title || "Untitled issue",
|
||||
body: issue.body || "",
|
||||
state: issue.state || "unknown",
|
||||
webUrl: issue.html_url || `${giteaConfig.baseUrl.replace(/\/+$/, "")}/${owner}/${repo}/issues/${issueNumber}`,
|
||||
createdAt: issue.created_at || "",
|
||||
updatedAt: issue.updated_at || "",
|
||||
author: issue.user?.login || "unknown",
|
||||
comments: issue.comments ?? commentsResponse.data.length
|
||||
},
|
||||
comments: commentsResponse.data.map((comment) => ({
|
||||
id: comment.id ?? 0,
|
||||
body: comment.body || "",
|
||||
webUrl: comment.html_url || "",
|
||||
createdAt: comment.created_at || "",
|
||||
updatedAt: comment.updated_at || "",
|
||||
author: comment.user?.login || "unknown"
|
||||
})),
|
||||
attachments: attachmentsResponse.data.map((attachment) => ({
|
||||
id: attachment.id ?? 0,
|
||||
name: attachment.name || "attachment",
|
||||
downloadUrl: attachment.browser_download_url || attachment.download_url || "",
|
||||
size: attachment.size ?? 0,
|
||||
createdAt: attachment.created_at || ""
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
export async function uploadGiteaIssueAttachment(
|
||||
owner: string,
|
||||
repo: string,
|
||||
@@ -199,8 +288,11 @@ export async function uploadGiteaIssueAttachment(
|
||||
);
|
||||
|
||||
return {
|
||||
id: response.data.id ?? 0,
|
||||
name: response.data.name || fileName,
|
||||
downloadUrl: response.data.browser_download_url || ""
|
||||
downloadUrl: response.data.browser_download_url || response.data.download_url || "",
|
||||
size: response.data.size ?? file.size,
|
||||
createdAt: response.data.created_at || ""
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+125
-2
@@ -8,7 +8,7 @@ import {
|
||||
type AppView,
|
||||
type ScreenshotAttachment
|
||||
} from "./store";
|
||||
import type { GiteaIssueSummary, GiteaRepoSummary } from "./api";
|
||||
import type { GiteaIssueAttachment, GiteaIssueComment, GiteaIssueSummary, GiteaRepoSummary } from "./api";
|
||||
import type { KnownUrl, LocalContext, LocalWorkspace } from "./localContext";
|
||||
|
||||
const status = document.querySelector<HTMLElement>("#status");
|
||||
@@ -60,6 +60,22 @@ function formatSha(value: string): string {
|
||||
return value ? value.slice(0, 8) : "unknown";
|
||||
}
|
||||
|
||||
function formatBytes(value: number): string {
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (value < 1024) {
|
||||
return `${value} B`;
|
||||
}
|
||||
|
||||
if (value < 1024 * 1024) {
|
||||
return `${Math.round(value / 102.4) / 10} KB`;
|
||||
}
|
||||
|
||||
return `${Math.round(value / 1024 / 102.4) / 10} MB`;
|
||||
}
|
||||
|
||||
function allUrls(): KnownUrl[] {
|
||||
const state = appStore.getState();
|
||||
const deletedBases = new Set(state.deletedUrlBases);
|
||||
@@ -294,6 +310,60 @@ function renderAttachmentList(items: string[], emptyText: string): string {
|
||||
`;
|
||||
}
|
||||
|
||||
function renderBodyText(body: string, emptyText: string): string {
|
||||
const trimmed = body.trim();
|
||||
if (!trimmed) {
|
||||
return `<p class="muted">${escapeHtml(emptyText)}</p>`;
|
||||
}
|
||||
|
||||
return `<pre class="issue-body-text">${escapeHtml(trimmed)}</pre>`;
|
||||
}
|
||||
|
||||
function renderIssueAttachments(attachments: GiteaIssueAttachment[]): string {
|
||||
if (!attachments.length) {
|
||||
return `<p class="muted">No issue attachments.</p>`;
|
||||
}
|
||||
|
||||
return `
|
||||
<ul class="issue-attachment-list">
|
||||
${attachments
|
||||
.map(
|
||||
(attachment) => `
|
||||
<li>
|
||||
<a href="${attachment.downloadUrl}" target="_blank" rel="noreferrer">${escapeHtml(attachment.name)}</a>
|
||||
${attachment.size ? `<span>${escapeHtml(formatBytes(attachment.size))}</span>` : ""}
|
||||
</li>
|
||||
`
|
||||
)
|
||||
.join("")}
|
||||
</ul>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderIssueComments(comments: GiteaIssueComment[]): string {
|
||||
if (!comments.length) {
|
||||
return `<p class="muted">No comments yet.</p>`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="issue-comment-chain">
|
||||
${comments
|
||||
.map(
|
||||
(comment) => `
|
||||
<article class="issue-comment">
|
||||
<div class="issue-comment-meta">
|
||||
<span>${escapeHtml(comment.author)}</span>
|
||||
<a href="${comment.webUrl}" target="_blank" rel="noreferrer">${escapeHtml(formatDate(comment.createdAt))}</a>
|
||||
</div>
|
||||
${renderBodyText(comment.body, "Empty comment.")}
|
||||
</article>
|
||||
`
|
||||
)
|
||||
.join("")}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderScreenshotList(screenshots: ScreenshotAttachment[], deleteAction: "issue" | "comment"): string {
|
||||
if (!screenshots.length) {
|
||||
return `<span class="muted">No screenshots captured.</span>`;
|
||||
@@ -393,6 +463,41 @@ function renderIssueCommentForm(issue: GiteaIssueSummary): string {
|
||||
`;
|
||||
}
|
||||
|
||||
function renderSelectedIssueDetail(): string {
|
||||
const state = appStore.getState();
|
||||
const detail = state.selectedIssueDetail;
|
||||
|
||||
if (state.isLoadingIssueDetail && !detail) {
|
||||
return `<div class="empty-state">Loading issue details...</div>`;
|
||||
}
|
||||
|
||||
if (!detail) {
|
||||
return `<div class="empty-state">Issue details are not loaded yet.</div>`;
|
||||
}
|
||||
|
||||
return `
|
||||
<article class="item-card issue-detail-card">
|
||||
<div class="section-heading inline-heading">
|
||||
<span>Original description</span>
|
||||
<button id="refresh-issue-detail" class="secondary-button compact-button" type="button" ${state.isLoadingIssueDetail ? "disabled" : ""}>
|
||||
${state.isLoadingIssueDetail ? "Loading..." : "Refresh"}
|
||||
</button>
|
||||
</div>
|
||||
${renderBodyText(detail.issue.body, "No description.")}
|
||||
|
||||
<div class="section-heading inline-heading">
|
||||
<span>Attachments</span>
|
||||
</div>
|
||||
${renderIssueAttachments(detail.attachments)}
|
||||
|
||||
<div class="section-heading inline-heading">
|
||||
<span>Comments</span>
|
||||
</div>
|
||||
${renderIssueComments(detail.comments)}
|
||||
</article>
|
||||
`;
|
||||
}
|
||||
|
||||
function renderTargetIssues(): string {
|
||||
const state = appStore.getState();
|
||||
const selectedIssue = state.targetIssues.find((issue) => issue.number === state.selectedIssueNumber) ?? null;
|
||||
@@ -406,7 +511,7 @@ function renderTargetIssues(): string {
|
||||
</button>
|
||||
</div>
|
||||
${state.isLoadingTargetIssues && !state.targetIssues.length ? `<div class="empty-state">Loading issues...</div>` : renderIssueRows(state.targetIssues, state.selectedIssueNumber)}
|
||||
${selectedIssue ? renderIssueCommentForm(selectedIssue) : ""}
|
||||
${selectedIssue ? `${renderSelectedIssueDetail()}${renderIssueCommentForm(selectedIssue)}` : ""}
|
||||
</section>
|
||||
`;
|
||||
}
|
||||
@@ -591,6 +696,10 @@ function bindViewActions(): void {
|
||||
void appStore.getState().loadTargetIssues(true);
|
||||
});
|
||||
|
||||
viewContent?.querySelector<HTMLButtonElement>("#refresh-issue-detail")?.addEventListener("click", () => {
|
||||
void appStore.getState().loadSelectedIssueDetail(true);
|
||||
});
|
||||
|
||||
viewContent?.querySelector<HTMLTextAreaElement>("#issue-comment-content")?.addEventListener("input", (event) => {
|
||||
appStore.getState().setIssueCommentDraft({
|
||||
content: (event.target as HTMLTextAreaElement).value
|
||||
@@ -702,6 +811,7 @@ function render(): void {
|
||||
renderChrome();
|
||||
renderView();
|
||||
ensureTargetIssuesLoaded();
|
||||
ensureSelectedIssueDetailLoaded();
|
||||
}
|
||||
|
||||
function ensureTargetIssuesLoaded(): void {
|
||||
@@ -719,6 +829,19 @@ function ensureTargetIssuesLoaded(): void {
|
||||
}
|
||||
}
|
||||
|
||||
function ensureSelectedIssueDetailLoaded(): void {
|
||||
const state = appStore.getState();
|
||||
|
||||
if (
|
||||
state.activeView === "add-issue" &&
|
||||
state.selectedIssueNumber &&
|
||||
state.selectedIssueDetailNumber !== state.selectedIssueNumber &&
|
||||
!state.isLoadingIssueDetail
|
||||
) {
|
||||
void appStore.getState().loadSelectedIssueDetail();
|
||||
}
|
||||
}
|
||||
|
||||
viewButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
const view = button.dataset.view as AppView | undefined;
|
||||
|
||||
+79
-4
@@ -6,10 +6,12 @@ import {
|
||||
deleteGiteaIssue,
|
||||
ensureGiteaLabel,
|
||||
getCurrentGiteaUser,
|
||||
getGiteaIssueDetail,
|
||||
getGiteaIssues,
|
||||
getReposOrderedByLastCommit,
|
||||
giteaConfig,
|
||||
type GiteaIssueAttachment,
|
||||
type GiteaIssueDetail,
|
||||
type GiteaIssueSummary,
|
||||
type GiteaRepoSummary,
|
||||
type GiteaUser,
|
||||
@@ -66,8 +68,11 @@ interface AppState {
|
||||
targetIssues: GiteaIssueSummary[];
|
||||
issueListRepoFullName: string;
|
||||
selectedIssueNumber: number | null;
|
||||
selectedIssueDetail: GiteaIssueDetail | null;
|
||||
selectedIssueDetailNumber: number | null;
|
||||
isLoadingData: boolean;
|
||||
isLoadingTargetIssues: boolean;
|
||||
isLoadingIssueDetail: boolean;
|
||||
isTestingConnection: boolean;
|
||||
isCapturingScreenshot: boolean;
|
||||
isCapturingIssueCommentScreenshot: boolean;
|
||||
@@ -87,6 +92,7 @@ interface AppState {
|
||||
deleteIssueCommentScreenshot: (id: string) => void;
|
||||
selectIssue: (issueNumber: number) => void;
|
||||
loadTargetIssues: (force?: boolean) => Promise<void>;
|
||||
loadSelectedIssueDetail: (force?: boolean) => Promise<void>;
|
||||
markIssueReady: (issueNumber: number) => Promise<void>;
|
||||
deleteIssue: (issueNumber: number) => Promise<void>;
|
||||
captureIssueCommentScreenshot: () => Promise<void>;
|
||||
@@ -419,8 +425,11 @@ export const appStore = createStore<AppState>()((set, get) => ({
|
||||
targetIssues: [],
|
||||
issueListRepoFullName: "",
|
||||
selectedIssueNumber: null,
|
||||
selectedIssueDetail: null,
|
||||
selectedIssueDetailNumber: null,
|
||||
isLoadingData: false,
|
||||
isLoadingTargetIssues: false,
|
||||
isLoadingIssueDetail: false,
|
||||
isTestingConnection: false,
|
||||
isCapturingScreenshot: false,
|
||||
isCapturingIssueCommentScreenshot: false,
|
||||
@@ -487,14 +496,22 @@ export const appStore = createStore<AppState>()((set, get) => ({
|
||||
}
|
||||
})),
|
||||
selectIssue: (issueNumber) =>
|
||||
set({
|
||||
set((state) => ({
|
||||
selectedIssueNumber: issueNumber,
|
||||
selectedIssueDetail:
|
||||
state.selectedIssueDetailNumber === issueNumber
|
||||
? state.selectedIssueDetail
|
||||
: null,
|
||||
selectedIssueDetailNumber:
|
||||
state.selectedIssueDetailNumber === issueNumber
|
||||
? state.selectedIssueDetailNumber
|
||||
: null,
|
||||
issueCommentDraft: {
|
||||
content: "",
|
||||
screenshots: [],
|
||||
files: []
|
||||
}
|
||||
}),
|
||||
})),
|
||||
loadTargetIssues: async (force = false) => {
|
||||
const state = appStore.getState();
|
||||
const target = getTargetRepo(state);
|
||||
@@ -503,7 +520,9 @@ export const appStore = createStore<AppState>()((set, get) => ({
|
||||
set({
|
||||
targetIssues: [],
|
||||
issueListRepoFullName: "",
|
||||
selectedIssueNumber: null
|
||||
selectedIssueNumber: null,
|
||||
selectedIssueDetail: null,
|
||||
selectedIssueDetailNumber: null
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -525,6 +544,14 @@ export const appStore = createStore<AppState>()((set, get) => ({
|
||||
selectedIssueNumber:
|
||||
current.selectedIssueNumber && targetIssues.some((issue) => issue.number === current.selectedIssueNumber)
|
||||
? current.selectedIssueNumber
|
||||
: null,
|
||||
selectedIssueDetail:
|
||||
current.selectedIssueNumber && targetIssues.some((issue) => issue.number === current.selectedIssueNumber)
|
||||
? current.selectedIssueDetail
|
||||
: null,
|
||||
selectedIssueDetailNumber:
|
||||
current.selectedIssueNumber && targetIssues.some((issue) => issue.number === current.selectedIssueNumber)
|
||||
? current.selectedIssueDetailNumber
|
||||
: null
|
||||
}));
|
||||
} catch (error) {
|
||||
@@ -534,6 +561,41 @@ export const appStore = createStore<AppState>()((set, get) => ({
|
||||
set({ isLoadingTargetIssues: false });
|
||||
}
|
||||
},
|
||||
loadSelectedIssueDetail: async (force = false) => {
|
||||
const state = appStore.getState();
|
||||
const target = getTargetRepo(state);
|
||||
const issueNumber = state.selectedIssueNumber;
|
||||
|
||||
if (!target || !issueNumber) {
|
||||
set({
|
||||
selectedIssueDetail: null,
|
||||
selectedIssueDetailNumber: null
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (!force && state.selectedIssueDetailNumber === issueNumber && state.selectedIssueDetail) {
|
||||
return;
|
||||
}
|
||||
|
||||
set({
|
||||
isLoadingIssueDetail: true,
|
||||
selectedIssueDetailNumber: issueNumber
|
||||
});
|
||||
|
||||
try {
|
||||
const selectedIssueDetail = await getGiteaIssueDetail(target.owner, target.repo, issueNumber);
|
||||
set({
|
||||
selectedIssueDetail,
|
||||
selectedIssueDetailNumber: issueNumber
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Could not load issue details.";
|
||||
setStatus(set, message, "error");
|
||||
} finally {
|
||||
set({ isLoadingIssueDetail: false });
|
||||
}
|
||||
},
|
||||
markIssueReady: async (issueNumber) => {
|
||||
const target = getTargetRepo(appStore.getState());
|
||||
|
||||
@@ -549,6 +611,7 @@ export const appStore = createStore<AppState>()((set, get) => ({
|
||||
const readyLabel = await ensureGiteaLabel(target.owner, target.repo, "ready");
|
||||
await addGiteaIssueLabels(target.owner, target.repo, issueNumber, [readyLabel.id]);
|
||||
await appStore.getState().loadTargetIssues(true);
|
||||
await appStore.getState().loadSelectedIssueDetail(true);
|
||||
setStatus(set, `Applied ready label to issue #${issueNumber}.`, "success");
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Could not apply the ready label.";
|
||||
@@ -573,6 +636,8 @@ export const appStore = createStore<AppState>()((set, get) => ({
|
||||
set((state) => ({
|
||||
targetIssues: state.targetIssues.filter((issue) => issue.number !== issueNumber),
|
||||
selectedIssueNumber: state.selectedIssueNumber === issueNumber ? null : state.selectedIssueNumber,
|
||||
selectedIssueDetail: state.selectedIssueNumber === issueNumber ? null : state.selectedIssueDetail,
|
||||
selectedIssueDetailNumber: state.selectedIssueNumber === issueNumber ? null : state.selectedIssueDetailNumber,
|
||||
issueCommentDraft:
|
||||
state.selectedIssueNumber === issueNumber
|
||||
? {
|
||||
@@ -689,6 +754,7 @@ export const appStore = createStore<AppState>()((set, get) => ({
|
||||
}
|
||||
});
|
||||
await appStore.getState().loadTargetIssues(true);
|
||||
await appStore.getState().loadSelectedIssueDetail(true);
|
||||
setStatus(set, `Commented on issue #${selectedIssueNumber}.`, "success");
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Could not comment on the Gitea issue.";
|
||||
@@ -755,6 +821,7 @@ export const appStore = createStore<AppState>()((set, get) => ({
|
||||
selectedIssueNumber: issue.number
|
||||
});
|
||||
await appStore.getState().loadTargetIssues(true);
|
||||
await appStore.getState().loadSelectedIssueDetail(true);
|
||||
setStatus(set, `Created issue #${issue.number} in ${target.fullName}.`, "success");
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Could not create the Gitea issue.";
|
||||
@@ -786,7 +853,15 @@ export const appStore = createStore<AppState>()((set, get) => ({
|
||||
selectedIssueNumber:
|
||||
activeBaseUrl !== currentBaseUrl
|
||||
? null
|
||||
: get().selectedIssueNumber
|
||||
: get().selectedIssueNumber,
|
||||
selectedIssueDetail:
|
||||
activeBaseUrl !== currentBaseUrl
|
||||
? null
|
||||
: get().selectedIssueDetail,
|
||||
selectedIssueDetailNumber:
|
||||
activeBaseUrl !== currentBaseUrl
|
||||
? null
|
||||
: get().selectedIssueDetailNumber
|
||||
});
|
||||
},
|
||||
loadData: async () => {
|
||||
|
||||
@@ -533,6 +533,79 @@ input[readonly] {
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.issue-detail-card {
|
||||
gap: 8px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.inline-heading {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.issue-body-text {
|
||||
max-height: 180px;
|
||||
margin: 0;
|
||||
overflow: auto;
|
||||
border: 1px solid #edf2f7;
|
||||
border-radius: 6px;
|
||||
padding: 6px;
|
||||
background: #fbfdff;
|
||||
color: #243b53;
|
||||
font-family: inherit;
|
||||
font-size: 11px;
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.issue-attachment-list {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
margin: 0;
|
||||
padding-left: 16px;
|
||||
color: #52616f;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.issue-attachment-list li {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.issue-attachment-list a {
|
||||
color: #0b6bcb;
|
||||
font-weight: 750;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.issue-attachment-list span {
|
||||
margin-left: 4px;
|
||||
color: #829ab1;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.issue-comment-chain {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.issue-comment {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.issue-comment-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
color: #52616f;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.issue-comment-meta a {
|
||||
color: #0b6bcb;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.target-summary {
|
||||
display: grid;
|
||||
gap: 2px;
|
||||
|
||||
Reference in New Issue
Block a user