ob2qualkard v0.2 test alpha

This commit is contained in:
2026-08-05 21:11:46 -03:00
parent 43a66d604c
commit 7085576313
13 changed files with 308 additions and 209 deletions

View File

@@ -1,114 +1,132 @@
// *************************************************************************
// * (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';
import {
Editor,
MarkdownView,
MarkdownFileInfo,
Modal,
Notice,
Plugin,
} from 'obsidian';
import {
Ob2qualkardSettings,
DEFAULT_SETTINGS,
MyPluginSettings,
SampleSettingTab,
Ob2qualkardSettingTab,
} from './settings';
// Remember to rename these classes and interfaces!
export default class MyPlugin extends Plugin {
settings!: MyPluginSettings;
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>();
async onload() {
await this.loadSettings();
this.addSettingTab(new Ob2qualkardSettingTab(this.app, this));
// This creates an icon in the left ribbon.
this.addRibbonIcon('dice', 'Sample', (_evt: MouseEvent) => {
// Called when the user clicks the icon.
new Notice('This is a notice!');
});
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
const statusBarItemEl = this.addStatusBarItem();
statusBarItemEl.setText('Status bar text');
// This adds a simple command that can be triggered anywhere
this.addCommand({
id: 'open-modal-simple',
name: 'Open modal (simple)',
callback: () => {
new SampleModal(this.app).open();
},
});
// This adds an editor command that can perform some operation on the current editor instance
this.addCommand({
id: 'replace-selected',
name: 'Replace selected content',
editorCallback: (
editor: Editor,
_ctx: MarkdownView | MarkdownFileInfo,
) => {
editor.replaceSelection('Sample editor command');
},
});
// This adds a complex command that can check whether the current state of the app allows execution of the command
this.addCommand({
id: 'open-modal-complex',
name: 'Open modal (complex)',
checkCallback: (checking: boolean) => {
// Conditions to check
const markdownView =
this.app.workspace.getActiveViewOfType(MarkdownView);
if (markdownView) {
// If checking is true, we're simply "checking" if the command can be run.
// If checking is false, then we want to actually perform the operation.
if (!checking) {
new SampleModal(this.app).open();
}
// This command will only show up in Command Palette when the check function returns true
return true;
}
return false;
},
id: 'push-note-to-qualkard',
name: 'Push this note to QualKard',
editorCallback: (editor) => void this.pushNote(editor),
});
// This adds a settings tab so the user can configure various aspects of the plugin
this.addSettingTab(new SampleSettingTab(this.app, this));
// If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
// Using this function will automatically remove the event listener when this plugin is disabled.
this.registerDomEvent(activeDocument, 'click', (_evt: MouseEvent) => {
new Notice('Click');
});
// When registering intervals, this function will automatically clear the interval when the plugin is disabled.
this.registerInterval(
window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000),
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)),
);
}),
);
}
onunload() {}
async loadSettings() {
this.settings = Object.assign(
{},
DEFAULT_SETTINGS,
(await this.loadData()) as Partial<MyPluginSettings>,
(await this.loadData()) as Partial<Ob2qualkardSettings>,
);
}
async saveSettings() {
await this.saveData(this.settings);
}
}
class SampleModal extends Modal {
onOpen() {
const { contentEl } = this;
contentEl.setText('Woah!');
// The board name may have changed; re-resolve its id on the next push.
this.boardCache.clear();
}
onClose() {
const { contentEl } = this;
contentEl.empty();
// 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);
}
}
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;
}
}