2026-08-05 21:11:46 -03:00
|
|
|
// *************************************************************************
|
|
|
|
|
// * (C)opyright 2026 by Ruben Carlo Benante *
|
|
|
|
|
// * *
|
|
|
|
|
// * This program is free software; you can redistribute it and/or modify *
|
|
|
|
|
// * it under the terms of the GNU General Public License as published by *
|
|
|
|
|
// * the Free Software Foundation, either version 3 of the License, or *
|
|
|
|
|
// * (at your option) any later version. *
|
|
|
|
|
// * *
|
|
|
|
|
// * This program is distributed in the hope that it will be useful, *
|
|
|
|
|
// * but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
|
|
|
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
|
|
|
|
// * GNU General Public License for more details. *
|
|
|
|
|
// * *
|
|
|
|
|
// * You should have received a copy of the GNU General Public License *
|
|
|
|
|
// * along with this program. If not, see http://www.gnu.org/licenses/. *
|
|
|
|
|
// * *
|
|
|
|
|
// * Contact author at: *
|
|
|
|
|
// * Ruben Carlo Benante *
|
|
|
|
|
// * rcb@beco.cc *
|
|
|
|
|
// *************************************************************************
|
|
|
|
|
|
|
|
|
|
import { Editor, Notice, Plugin } from 'obsidian';
|
|
|
|
|
import { parseQualcardBlocks, selectBlockToPush } from './card';
|
|
|
|
|
import { getProjectIdByName, createTask } from './kanboard';
|
2026-08-05 20:29:39 -03:00
|
|
|
import {
|
2026-08-05 21:11:46 -03:00
|
|
|
Ob2qualkardSettings,
|
2026-08-05 20:29:39 -03:00
|
|
|
DEFAULT_SETTINGS,
|
2026-08-05 21:11:46 -03:00
|
|
|
Ob2qualkardSettingTab,
|
2026-08-05 20:29:39 -03:00
|
|
|
} from './settings';
|
|
|
|
|
|
2026-08-05 21:11:46 -03:00
|
|
|
export default class Ob2qualkardPlugin extends Plugin {
|
|
|
|
|
settings!: Ob2qualkardSettings;
|
|
|
|
|
// Board name -> project id, resolved once per session (cleared on save).
|
|
|
|
|
private boardCache = new Map<string, number>();
|
2026-08-05 20:29:39 -03:00
|
|
|
|
|
|
|
|
async onload() {
|
|
|
|
|
await this.loadSettings();
|
2026-08-05 21:11:46 -03:00
|
|
|
this.addSettingTab(new Ob2qualkardSettingTab(this.app, this));
|
2026-08-05 20:29:39 -03:00
|
|
|
|
|
|
|
|
this.addCommand({
|
2026-08-05 21:11:46 -03:00
|
|
|
id: 'push-note-to-qualkard',
|
|
|
|
|
name: 'Push this note to QualKard',
|
|
|
|
|
editorCallback: (editor) => void this.pushNote(editor),
|
2026-08-05 20:29:39 -03:00
|
|
|
});
|
|
|
|
|
|
2026-08-05 21:11:46 -03:00
|
|
|
this.registerEvent(
|
|
|
|
|
this.app.workspace.on('editor-menu', (menu, editor) => {
|
|
|
|
|
menu.addItem((item) =>
|
|
|
|
|
item
|
|
|
|
|
.setTitle('Push this note to QualKard')
|
|
|
|
|
.setIcon('upload')
|
|
|
|
|
.onClick(() => void this.pushNote(editor)),
|
|
|
|
|
);
|
|
|
|
|
}),
|
2026-08-05 20:29:39 -03:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async loadSettings() {
|
|
|
|
|
this.settings = Object.assign(
|
|
|
|
|
{},
|
|
|
|
|
DEFAULT_SETTINGS,
|
2026-08-05 21:11:46 -03:00
|
|
|
(await this.loadData()) as Partial<Ob2qualkardSettings>,
|
2026-08-05 20:29:39 -03:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async saveSettings() {
|
|
|
|
|
await this.saveData(this.settings);
|
2026-08-05 21:11:46 -03:00
|
|
|
// The board name may have changed; re-resolve its id on the next push.
|
|
|
|
|
this.boardCache.clear();
|
2026-08-05 20:29:39 -03:00
|
|
|
}
|
|
|
|
|
|
2026-08-05 21:11:46 -03:00
|
|
|
// Parse the note, pick the card to push (sole block, or the one under the
|
|
|
|
|
// cursor), validate it, then create the Kanboard task. One-way, no dedup.
|
|
|
|
|
private async pushNote(editor: Editor) {
|
|
|
|
|
const sel = selectBlockToPush(
|
|
|
|
|
parseQualcardBlocks(editor.getValue()),
|
|
|
|
|
editor.getCursor().line,
|
|
|
|
|
);
|
|
|
|
|
if (sel.reason === 'none') {
|
|
|
|
|
new Notice('No qualcard block in this note');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (sel.reason === 'ambiguous') {
|
|
|
|
|
new Notice(
|
|
|
|
|
'Several qualcard blocks; place the cursor inside the one to push',
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const block = sel.block;
|
|
|
|
|
if (!block || !block.front) {
|
|
|
|
|
new Notice('This qualcard block has no prompt (front)');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!block.hasAnswer) {
|
|
|
|
|
new Notice('This qualcard block has no answer, so it was skipped');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!this.settings.apiToken || !this.settings.board) {
|
|
|
|
|
new Notice(
|
|
|
|
|
'Set the API token and study board in settings first',
|
|
|
|
|
);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
const projectId = await this.resolveBoard();
|
|
|
|
|
if (projectId === null) {
|
|
|
|
|
new Notice(`Board "${this.settings.board}" not found`);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const taskId = await createTask(
|
|
|
|
|
this.settings,
|
|
|
|
|
projectId,
|
|
|
|
|
block.front,
|
|
|
|
|
block.back,
|
|
|
|
|
);
|
|
|
|
|
new Notice(`Card created (task #${taskId})`);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
const msg = e instanceof Error ? e.message : 'push failed';
|
|
|
|
|
console.error('ob2qualkard: push failed', e);
|
|
|
|
|
new Notice(msg);
|
|
|
|
|
}
|
2026-08-05 20:29:39 -03:00
|
|
|
}
|
|
|
|
|
|
2026-08-05 21:11:46 -03:00
|
|
|
private async resolveBoard(): Promise<number | null> {
|
|
|
|
|
const name = this.settings.board;
|
|
|
|
|
const cached = this.boardCache.get(name);
|
|
|
|
|
if (cached !== undefined) return cached;
|
|
|
|
|
const id = await getProjectIdByName(this.settings, name);
|
|
|
|
|
if (id !== null) this.boardCache.set(name, id);
|
|
|
|
|
return id;
|
2026-08-05 20:29:39 -03:00
|
|
|
}
|
|
|
|
|
}
|