ob2qualkard v0.2 test alpha
This commit is contained in:
192
src/main.ts
192
src/main.ts
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user