inital commit
This commit is contained in:
47
src/jiraApi.ts
Executable file
47
src/jiraApi.ts
Executable file
@@ -0,0 +1,47 @@
|
||||
import fetch from "node-fetch";
|
||||
import https from "https";
|
||||
import { Notice } from "obsidian";
|
||||
|
||||
export class JiraApi {
|
||||
constructor(
|
||||
url: string,
|
||||
username: string,
|
||||
password: string,
|
||||
ignoreTLS = true
|
||||
) {
|
||||
this.headers = {
|
||||
Accept: "application/json",
|
||||
Authorization:
|
||||
"Basic " +
|
||||
Buffer.from(`${username}:${password}`).toString("base64"),
|
||||
};
|
||||
this.url = `${url}/rest/api/2/issue/`;
|
||||
this.ignoreTLS = ignoreTLS;
|
||||
}
|
||||
|
||||
private url: string;
|
||||
private headers: any;
|
||||
private ignoreTLS: boolean;
|
||||
|
||||
public async fetchIssue(issueKey: string): Promise<any> {
|
||||
try {
|
||||
const options: any = { headers: this.headers };
|
||||
if (this.ignoreTLS) {
|
||||
options.agent = new https.Agent({ rejectUnauthorized: false });
|
||||
}
|
||||
const response = await fetch(this.url + issueKey, options);
|
||||
if (!response.ok) {
|
||||
const errorDetail = await response.text();
|
||||
throw new Error(
|
||||
`Error fetching issue: ${response.statusText}. Details: ${errorDetail}`
|
||||
);
|
||||
}
|
||||
const data: any = await response.json();
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error fetching issue:", error);
|
||||
new Notice("Error fetching issue: " + error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
136
src/main.ts
Normal file
136
src/main.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import { Editor, Notice, Plugin } from "obsidian";
|
||||
|
||||
import { renderString } from "nunjucks";
|
||||
|
||||
import { JiraSettings, JiraSettingTab, DEFAULT_SETTINGS } from "./settings";
|
||||
import { JiraApi } from "./jiraApi";
|
||||
|
||||
export default class Jira extends Plugin {
|
||||
settings: JiraSettings;
|
||||
statusBarItemEl: HTMLElement;
|
||||
jiraApi: JiraApi;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
this.createJiraApi();
|
||||
|
||||
this.addCommand({
|
||||
id: "create-issue",
|
||||
name: "Create issue",
|
||||
editorCallback: this.createCallback.bind(this),
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "update-issue",
|
||||
name: "Update issue",
|
||||
editorCallback: this.UpdateCallback.bind(this),
|
||||
});
|
||||
|
||||
this.addCommand({
|
||||
id: "update-all-issues",
|
||||
name: "Update all issues",
|
||||
editorCallback: this.UpdateAllCallback.bind(this),
|
||||
});
|
||||
|
||||
this.statusBarItemEl = this.addStatusBarItem();
|
||||
this.statusBarItemEl.addClass("jira-statusbar-btn");
|
||||
|
||||
this.statusBarItemEl.addEventListener("click", () => {
|
||||
const file = this.app.workspace.getActiveFile();
|
||||
|
||||
if (file?.basename) {
|
||||
this.UpdateIssue(file.basename);
|
||||
} else {
|
||||
new Notice("No active file to update.");
|
||||
}
|
||||
});
|
||||
|
||||
this.registerEvent(
|
||||
this.app.workspace.on("active-leaf-change", () => {
|
||||
const file = this.app.workspace.getActiveFile();
|
||||
|
||||
if (file && this.settings.issues.includes(file.basename)) {
|
||||
this.statusBarItemEl.show();
|
||||
this.statusBarItemEl.setText(`Update: ${file.basename}`);
|
||||
} else {
|
||||
this.statusBarItemEl.hide();
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
this.addSettingTab(new JiraSettingTab(this.app, this));
|
||||
}
|
||||
|
||||
private createJiraApi() {
|
||||
this.jiraApi = new JiraApi(
|
||||
this.settings.apiUrl,
|
||||
this.settings.username,
|
||||
this.settings.password,
|
||||
this.settings.ignoreTLS
|
||||
);
|
||||
}
|
||||
|
||||
private async saveIssueToFile(issueKey: string, data: any) {
|
||||
const filePath = `${this.settings.path}/${issueKey}.md`;
|
||||
const content = renderString(this.settings.template, data);
|
||||
await this.app.vault.adapter.write(filePath, content);
|
||||
}
|
||||
|
||||
private async UpdateIssue(issueKey: string) {
|
||||
console.log(`Updating ${issueKey}`);
|
||||
new Notice(`Updating ${issueKey}`);
|
||||
const data = await this.jiraApi.fetchIssue(issueKey);
|
||||
await this.saveIssueToFile(issueKey, data);
|
||||
new Notice(`Issue ${issueKey} updated`);
|
||||
}
|
||||
|
||||
private async UpdateCallback() {
|
||||
const file = this.app.workspace.getActiveFile();
|
||||
if (file && this.settings.issues.includes(file.basename)) {
|
||||
const data = await this.jiraApi.fetchIssue(file.basename);
|
||||
|
||||
await this.saveIssueToFile(file.basename, data);
|
||||
|
||||
new Notice(`Issue ${file.basename} updated`);
|
||||
} else {
|
||||
new Notice("This note is not linked to a Jira issue.");
|
||||
}
|
||||
}
|
||||
|
||||
private async UpdateAllCallback() {
|
||||
for (const issue of this.settings.issues) {
|
||||
this.UpdateIssue(issue);
|
||||
}
|
||||
}
|
||||
|
||||
private async createCallback(editor: Editor) {
|
||||
const issueKey = editor.getSelection();
|
||||
const data = await this.jiraApi.fetchIssue(issueKey);
|
||||
|
||||
await this.saveIssueToFile(issueKey, data);
|
||||
|
||||
editor.replaceSelection(`[[${issueKey}]]`);
|
||||
if (!this.settings.issues.includes(issueKey)) {
|
||||
this.settings.issues.push(issueKey);
|
||||
this.saveSettings();
|
||||
}
|
||||
|
||||
new Notice(`Note ${issueKey} created`);
|
||||
}
|
||||
|
||||
onunload() {}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign(
|
||||
{},
|
||||
DEFAULT_SETTINGS,
|
||||
await this.loadData()
|
||||
);
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
this.createJiraApi();
|
||||
}
|
||||
}
|
||||
190
src/settings.ts
Normal file
190
src/settings.ts
Normal file
@@ -0,0 +1,190 @@
|
||||
import { App, PluginSettingTab, Setting } from "obsidian";
|
||||
|
||||
import Jira from "./main";
|
||||
|
||||
export interface JiraSettings {
|
||||
apiUrl: string;
|
||||
username: string;
|
||||
password: string;
|
||||
path: string;
|
||||
template: string;
|
||||
issues: string[];
|
||||
ignoreTLS: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: JiraSettings = {
|
||||
apiUrl: "https://jira.example.com",
|
||||
username: "",
|
||||
password: "",
|
||||
path: "jira",
|
||||
template: "",
|
||||
issues: [],
|
||||
ignoreTLS: false,
|
||||
};
|
||||
|
||||
export class JiraSettingTab extends PluginSettingTab {
|
||||
plugin: Jira;
|
||||
|
||||
constructor(app: App, plugin: Jira) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl).setName("apiUrl").addText((text) =>
|
||||
text
|
||||
.setValue(this.plugin.settings.apiUrl)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.apiUrl = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new Setting(containerEl).setName("username").addText((text) =>
|
||||
text
|
||||
.setValue(this.plugin.settings.username)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.username = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
new Setting(containerEl).setName("password").addText((text) => {
|
||||
text.setValue(this.plugin.settings.password).onChange(
|
||||
async (value) => {
|
||||
this.plugin.settings.password = value;
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
);
|
||||
text.inputEl.type = "password";
|
||||
|
||||
const toggleBtn = document.createElement("button");
|
||||
toggleBtn.type = "button";
|
||||
toggleBtn.style.marginLeft = "8px";
|
||||
toggleBtn.style.background = "none";
|
||||
toggleBtn.style.border = "none";
|
||||
toggleBtn.style.cursor = "pointer";
|
||||
let visible = false;
|
||||
|
||||
const eyeIcon = `
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M1 12s4-7 11-7 11 7 11 7-4 7-11 7-11-7-11-7z"/>
|
||||
<circle cx="12" cy="12" r="3"/>
|
||||
</svg>
|
||||
`;
|
||||
const eyeOffIcon = `
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M17.94 17.94A10.94 10.94 0 0 1 12 19c-7 0-11-7-11-7a21.81 21.81 0 0 1 5.06-6.06"/>
|
||||
<path d="M1 1l22 22"/>
|
||||
<path d="M9.53 9.53A3 3 0 0 0 12 15a3 3 0 0 0 2.47-5.47"/>
|
||||
<path d="M14.47 14.47A3 3 0 0 1 12 9a3 3 0 0 1-2.47 5.47"/>
|
||||
</svg>
|
||||
`;
|
||||
|
||||
toggleBtn.innerHTML = eyeIcon;
|
||||
|
||||
toggleBtn.onclick = () => {
|
||||
visible = !visible;
|
||||
text.inputEl.type = visible ? "text" : "password";
|
||||
toggleBtn.innerHTML = visible ? eyeOffIcon : eyeIcon;
|
||||
};
|
||||
|
||||
text.inputEl.parentElement?.appendChild(toggleBtn);
|
||||
});
|
||||
new Setting(containerEl).setName("path").addText((text) =>
|
||||
text.setValue(this.plugin.settings.path).onChange(async (value) => {
|
||||
this.plugin.settings.path = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Ignore TLS certificate errors")
|
||||
.setDesc("Disable TLS certificate validation (not secure)")
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.ignoreTLS)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.ignoreTLS = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl).setName("template").addTextArea((text) => {
|
||||
text.setValue(this.plugin.settings.template).onChange(
|
||||
async (value) => {
|
||||
this.plugin.settings.template = value;
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
);
|
||||
text.inputEl.setAttr("rows", 25);
|
||||
text.inputEl.setAttr("cols", 50);
|
||||
});
|
||||
|
||||
const details = document.createElement("details");
|
||||
const summary = document.createElement("summary");
|
||||
summary.textContent = "Issues (expand to view/edit)";
|
||||
details.appendChild(summary);
|
||||
|
||||
const issuesList = document.createElement("ul");
|
||||
this.plugin.settings.issues.forEach((issue, idx) => {
|
||||
const li = document.createElement("li");
|
||||
li.textContent = issue;
|
||||
|
||||
const removeBtn = document.createElement("button");
|
||||
removeBtn.type = "button";
|
||||
removeBtn.innerHTML = `
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none"
|
||||
stroke="red" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="3 6 5 6 21 6"></polyline>
|
||||
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h2a2 2 0 0 1 2 2v2"></path>
|
||||
<line x1="10" y1="11" x2="10" y2="17"></line>
|
||||
<line x1="14" y1="11" x2="14" y2="17"></line>
|
||||
</svg>
|
||||
`;
|
||||
removeBtn.style.background = "none";
|
||||
removeBtn.style.border = "none";
|
||||
removeBtn.style.cursor = "pointer";
|
||||
removeBtn.style.marginLeft = "8px";
|
||||
removeBtn.title = "Remove";
|
||||
|
||||
removeBtn.onclick = async () => {
|
||||
this.plugin.settings.issues.splice(idx, 1);
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
};
|
||||
li.appendChild(removeBtn);
|
||||
|
||||
issuesList.appendChild(li);
|
||||
});
|
||||
details.appendChild(issuesList);
|
||||
|
||||
// Форма для добавления нового issue
|
||||
const addDiv = document.createElement("div");
|
||||
const input = document.createElement("input");
|
||||
input.type = "text";
|
||||
input.placeholder = "Add issue...";
|
||||
addDiv.appendChild(input);
|
||||
|
||||
const addBtn = document.createElement("button");
|
||||
addBtn.textContent = "Add";
|
||||
addBtn.onclick = async () => {
|
||||
const value = input.value.trim();
|
||||
if (value && !this.plugin.settings.issues.includes(value)) {
|
||||
this.plugin.settings.issues.push(value);
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
}
|
||||
input.value = "";
|
||||
};
|
||||
addDiv.appendChild(addBtn);
|
||||
|
||||
details.appendChild(addDiv);
|
||||
|
||||
containerEl.appendChild(details);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user