feat: add "Add comment to Jira" command with editor selection pre-fill

Opens a modal pre-populated with any selected editor text, allowing
users to highlight notes in a non-synced section and post them as a
Jira comment in one step. Supports API v2 (plain text) and v3 (ADF).
This commit is contained in:
slips 2026-04-27 17:11:59 -04:00
parent ad11b3e89a
commit 4958fc6a3c
6 changed files with 112 additions and 1 deletions

View file

@ -3,6 +3,7 @@ import {JiraIssue, JiraTransitionType} from "../interfaces";
import {baseRequest, sanitizeObject} from "./base";
import {Notice} from "obsidian";
import {chunkArray, createLimiter} from "../tools/asyncLimiter";
import {markdownToAdf} from "../tools/markdownToAdf";
/**
@ -257,6 +258,27 @@ export async function addWorkLog(
}
/**
* Add a comment to a Jira issue
*/
export async function addComment(
plugin: JiraPlugin,
issueKey: string,
markdownText: string
): Promise<any> {
let body: any;
if (plugin.settings.connection.apiVersion === "3") {
body = markdownToAdf(markdownText);
} else {
body = markdownText;
}
const payload = JSON.stringify({ body });
const response = await baseRequest(plugin, 'post', `/issue/${issueKey}/comment`, payload);
new Notice(`Comment added to ${issueKey}`);
return response;
}
export async function bulkAddWorkLog(
plugin: JiraPlugin,
worklogs: { issueKey: string, timeSpent: string, startedAt: string, comment: string }[],

View file

@ -0,0 +1,36 @@
import {Editor, MarkdownView, Notice} from "obsidian";
import JiraPlugin from "../main";
import {IssueCommentModal} from "../modals";
import {addComment, validateSettings} from "../api";
import {useTranslations} from "../localization/translator";
const t = useTranslations("commands.add_comment").t;
export function registerAddCommentCommand(plugin: JiraPlugin): void {
plugin.addCommand({
id: "add-comment-jira",
name: t("name"),
editorCheckCallback: (checking: boolean, editor: Editor, view: MarkdownView) => {
if (!validateSettings(plugin)) return false;
if (!view.file) return false;
const frontmatter = plugin.app.metadataCache.getFileCache(view.file)?.frontmatter;
const issueKey = frontmatter?.key;
if (!issueKey) return false;
if (!checking) {
const selectedText = editor.getSelection();
new IssueCommentModal(plugin.app, selectedText, async (commentText: string) => {
try {
await addComment(plugin, issueKey, commentText);
} catch (error) {
new Notice(t("error") + ": " + (error.message || "Unknown error"));
console.error(error);
}
}).open();
}
return true;
},
});
}

View file

@ -1,3 +1,4 @@
export * from './addComment';
export * from './addWorkLogBatch';
export * from './addWorkLogManually';
export * from './batchFetchIssues';

View file

@ -5,7 +5,7 @@ import {
registerUpdateIssueCommand, registerUpdateWorkLogManuallyCommand,
registerGetCurrentIssueCommand, registerUpdateWorkLogBatchCommand,
registerCreateIssueCommand, registerGetIssueCommandWithCustomKey, registerUpdateIssueStatusCommand,
registerBatchFetchIssuesCommand
registerBatchFetchIssuesCommand, registerAddCommentCommand
} from "./commands";
import {transform_string_to_functions_mappings} from "./tools/convertFunctionString";
import {createJiraSyncExtension} from "./postprocessing/livePreview";
@ -36,6 +36,7 @@ export default class JiraPlugin extends Plugin {
registerUpdateWorkLogManuallyCommand(this);
registerUpdateWorkLogBatchCommand(this);
registerAddCommentCommand(this);
// Add settings tab
this.addSettingTab(new JiraSettingTab(this.app, this));

View file

@ -0,0 +1,50 @@
import {App, Modal, Notice, Setting} from "obsidian";
import {useTranslations} from "../localization/translator";
const t = useTranslations("modals.comment").t;
/**
* Modal for adding a comment to a Jira issue.
* Pre-populated with any text that was selected in the editor when the command was invoked.
*/
export class IssueCommentModal extends Modal {
private onSubmit: (commentText: string) => void;
private commentText: string;
constructor(app: App, initialText: string, onSubmit: (commentText: string) => void) {
super(app);
this.commentText = initialText;
this.onSubmit = onSubmit;
}
onOpen() {
new Setting(this.contentEl).setName(t("name")).setHeading();
new Setting(this.contentEl)
.setName(t("body.name"))
.addTextArea((text) => {
text
.setValue(this.commentText)
.onChange((value) => {
this.commentText = value;
});
text.inputEl.rows = 10;
text.inputEl.style.width = "100%";
});
new Setting(this.contentEl)
.addButton((btn) =>
btn
.setButtonText(t("submit"))
.setCta()
.onClick(() => {
if (!this.commentText.trim()) {
new Notice(t("warns.empty"));
return;
}
this.close();
this.onSubmit(this.commentText);
})
);
}
}

View file

@ -1,3 +1,4 @@
export * from "./IssueCommentModal";
export * from "./IssueSearchModal";
export * from "./IssueStatusModal";
export * from "./IssueTypeModal";