diff --git a/main.ts b/main.ts index 1d20152..87ddce3 100644 --- a/main.ts +++ b/main.ts @@ -32,11 +32,11 @@ export default class AnkiSynchronizer extends Plugin { this.noteTypeState.set(parseInt(key), noteTypeState[key]); } } - await this.configureUI(); + this.configureUI(); console.log(locale.onLoad); } - async configureUI() { + configureUI() { // Add import note types command this.addCommand({ id: 'import', @@ -58,8 +58,8 @@ export default class AnkiSynchronizer extends Plugin { } // Save data to local file - async save() { - await this.saveData({ + save() { + return this.saveData({ version: version, settings: this.settings, noteState: Object.fromEntries(this.noteState), @@ -73,39 +73,42 @@ export default class AnkiSynchronizer extends Plugin { } // Retrieve template information from Obsidian core plugin "Templates" - async getTemplatePath() { + getTemplatePath() { const templatesPlugin = (this.app as any).internalPlugins?.plugins['templates']; if (!templatesPlugin?.enabled) { new Notice(locale.templatesNotEnabledNotice); - return undefined; + throw new Error("Cannot get template path!"); } return normalizePath(templatesPlugin.instance.options.folder); } async importNoteTypes() { new Notice(locale.importStartNotice); - const templatesPath = await this.getTemplatePath(); - if (!templatesPath) return; + const templatesPath = this.getTemplatePath(); this.noteTypeState.setTemplatePath(templatesPath); const noteTypesAndIds = await this.anki.noteTypesAndIds(); - if (noteTypesAndIds instanceof AnkiError || noteTypesAndIds instanceof Error) return; + if (noteTypesAndIds instanceof AnkiError) { + new Notice(locale.importFaliureNotice); + return; + } const noteTypes = Object.keys(noteTypesAndIds); const noteTypeFields = await this.anki.multi<{ modelName: string }, string[]>('modelFieldNames', noteTypes.map(s => ({ modelName: s }))); - if (noteTypeFields instanceof AnkiError || noteTypeFields instanceof Error) return; + if (noteTypeFields instanceof AnkiError) { + new Notice(locale.importFaliureNotice); + return; + } const state = new Map(noteTypes.map((name, index) => [noteTypesAndIds[name], { name: name, fieldNames: noteTypeFields[index] }])); - console.log('Note type data retrieved from Anki:'); - console.log(state); + console.log(`Retrieved note type data from Anki`, state); await this.noteTypeState.change(state); await this.save(); new Notice(locale.importSuccessNotice); } async synchronize() { - const templatesPath = await this.getTemplatePath(); - if (!templatesPath) return; + const templatesPath = this.getTemplatePath(); new Notice(locale.synchronizeStartNotice); const allFiles = this.app.vault.getMarkdownFiles(); const state = new Map(); @@ -117,7 +120,12 @@ export default class AnkiSynchronizer extends Plugin { const note = Note.validateNote((file as TAbstractFile).path, content, this.noteTypeState); if (!note) continue; if (note.nid === 0) { // new file - await this.noteState.handleAddNote(note); + const nid = await this.noteState.handleAddNote(note); + if (nid === undefined) { + new Notice(locale.synchronizeAddNoteFaliureNotice(file.basename)); + continue; + } + note.nid = nid; this.app.vault.modify(file, note.dump()); } state.set(note.nid, [note.digest(), note]); diff --git a/manifest.json b/manifest.json index 8fcbfdb..cd2e22d 100644 --- a/manifest.json +++ b/manifest.json @@ -1,7 +1,7 @@ { "id": "obsidian-anki-synchronizer", "name": "Anki Synchronizer", - "version": "0.0.5", + "version": "0.0.6", "minAppVersion": "0.14.0", "description": "This is a plugin for exporting Obsidian contents to Anki.", "author": "Songchen Tan", diff --git a/package.json b/package.json index eb0ee77..1d43a58 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "obsidian-anki-synchronizer", - "version": "0.0.5", + "version": "0.0.6", "description": "This is a plugin for exporting Obsidian contents to Anki.", "main": "main.js", "scripts": { diff --git a/src/anki.ts b/src/anki.ts index 7021b66..9e36715 100644 --- a/src/anki.ts +++ b/src/anki.ts @@ -13,13 +13,7 @@ interface Response { result: R } -export class AnkiError { - error: string - - constructor(error: string) { - this.error = error - } -} +export class AnkiError extends Error {} export interface Note { deckName: string, @@ -35,7 +29,7 @@ export interface Note { class Anki { private port = 8765; - async invoke(action: string, params: P): Promise { + async invoke(action: string, params: P): Promise { type requestType = Request

; type responseType = Response; const request = { @@ -51,7 +45,7 @@ class Anki { return data.result; } catch (error) { new Notice(locale.synchronizeAnkiConnectUnavailableNotice); - return new Error("Bad Anki Connection"); + throw error; } } diff --git a/src/lang.ts b/src/lang.ts index 02e5226..cf5cf47 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -8,10 +8,15 @@ interface Locale { importCommandName: string, importStartNotice: string, importSuccessNotice: string, + importFaliureNotice: string, synchronizeStartNotice: string, synchronizeSuccessNotice: string, synchronizeBadAnkiConnectNotice: string, synchronizeAnkiConnectUnavailableNotice: string, + synchronizeAddNoteFaliureNotice: (filename: string) => string, + synchronizeChangeDeckFaliureNotice: (filename: string) => string, + synchronizeUpdateFieldsFaliureNotice: (filename: string) => string, + synchronizeUpdateTagsFaliureNotice: (filename: string) => string, settingTabHeader: string, settingRenderName: string, settingRenderDescription: string, @@ -25,10 +30,15 @@ const en: Locale = { importCommandName: 'Import Note Types', importStartNotice: 'Importing note types from Anki...', importSuccessNotice: 'Successfully imported note types from Anki!', + importFaliureNotice: 'Cannot import note types from Anki!', synchronizeStartNotice: 'Synchronizing to Anki...', synchronizeSuccessNotice: 'Successfully synchronized to Anki!', synchronizeBadAnkiConnectNotice: `Bad version of AnkiConnect`, synchronizeAnkiConnectUnavailableNotice: `Anki is not opened or AnkiConnect is not installed!`, + synchronizeAddNoteFaliureNotice: (s) => `Cannot add note for ${s}`, + synchronizeChangeDeckFaliureNotice: (filename: string) => `Cannot change deck for ${filename}`, + synchronizeUpdateFieldsFaliureNotice: (filename: string) => `Cannot update fields for ${filename}`, + synchronizeUpdateTagsFaliureNotice: (filename: string) => `Cannot update tags for ${filename}`, settingTabHeader: 'Anki Synchronizer Settings', settingRenderName: 'Render', settingRenderDescription: 'Whether to render markdown before importing to Anki or not.', @@ -42,10 +52,15 @@ const zh_cn: Locale = { importCommandName: '导入笔记类型', importStartNotice: '正在从 Anki 导入笔记类型……', importSuccessNotice: '已成功为 Anki 导入笔记类型!', + importFaliureNotice: '无法从 Anki 导入笔记类型!', synchronizeStartNotice: '正在与 Anki 同步笔记……', synchronizeSuccessNotice: '已成功与 Anki 同步笔记!', synchronizeBadAnkiConnectNotice: 'Anki Connect 版本不匹配!', synchronizeAnkiConnectUnavailableNotice: 'Anki 未打开或 Anki Connect 未安装!', + synchronizeAddNoteFaliureNotice: (s) => `无法向 Anki 添加笔记 ${s}`, + synchronizeChangeDeckFaliureNotice: (filename: string) => `无法改变 ${filename} 的牌组`, + synchronizeUpdateFieldsFaliureNotice: (filename: string) => `无法更新 ${filename} 的字段`, + synchronizeUpdateTagsFaliureNotice: (filename: string) => `无法更新 ${filename} 的标签`, settingTabHeader: 'Anki 同步设置', settingRenderName: '渲染', settingRenderDescription: '是否在导入时将 Markdown 渲染为 HTML' diff --git a/src/state.ts b/src/state.ts index 13cd597..644fc93 100644 --- a/src/state.ts +++ b/src/state.ts @@ -1,8 +1,9 @@ import AnkiSynchronizer from 'main'; import { Notice, TFile } from 'obsidian'; import Note, { FrontMatter } from 'src/note'; -import Anki, { AnkiError } from './anki'; +import Anki from './anki'; import Formatter from './format'; +import locale from './lang'; abstract class State extends Map { protected plugin: AnkiSynchronizer; @@ -69,12 +70,10 @@ export class NoteTypeState extends State { value.fieldNames.map(x => pseudoFields[x] = '\n\n'); const templateNote = new Note(templatePath, value.name, pseudoFrontMatter, pseudoFields); const maybeTemplate = this.plugin.app.vault.getAbstractFileByPath(templatePath); - if (maybeTemplate === null) { - this.plugin.app.vault.create(templatePath, templateNote.dump()) - } else if (maybeTemplate instanceof TFile) { - this.plugin.app.vault.modify(maybeTemplate as TFile, templateNote.dump()); + if (maybeTemplate !== null) { + await this.plugin.app.vault.modify(maybeTemplate as TFile, templateNote.dump()); } else { - new Notice("Bad template type"); + await this.plugin.app.vault.create(templatePath, templateNote.dump()); } console.log(`Created template ${templatePath}`); } @@ -114,38 +113,44 @@ export class NoteState extends State { return; } const { cards } = notesInfoResponse[0]; - console.log(`Changing deck for ${note.title()}`); - console.log(cards, deck); - const changeDeckResponse = await this.anki.changeDeck(cards, deck); - if (changeDeckResponse instanceof AnkiError) { - console.log(changeDeckResponse, ', try creating'); - await this.anki.createDeck(deck); - await this.anki.changeDeck(cards, deck); - } else if (changeDeckResponse instanceof Error) { - return; + console.log(`Changing deck for ${note.title()}`, deck); + let changeDeckResponse = await this.anki.changeDeck(cards, deck); + if (changeDeckResponse === null) return; + + // if the supposed deck does not exist, create it + if (changeDeckResponse.message.contains('deck was not found')) { + console.log(changeDeckResponse.message, ', try creating'); + const createDeckResponse = await this.anki.createDeck(deck); + if (createDeckResponse === null) { + changeDeckResponse = await this.anki.changeDeck(cards, deck); + if (changeDeckResponse === null) return; + } } + + new Notice(locale.synchronizeChangeDeckFaliureNotice(note.title())); } async updateFields(key: number, current: NoteDigest, value: NoteDigest, note: Note) { const fields = this.formatter.format(note.fields); - console.log(`Updating fields for ${note.title()}`) - console.log(note.nid, fields); - await this.anki.updateFields(note.nid, fields); + console.log(`Updating fields for ${note.title()}`, fields); + const updateFieldsResponse = await this.anki.updateFields(note.nid, fields); + if (updateFieldsResponse === null) return; + new Notice(locale.synchronizeUpdateFieldsFaliureNotice(note.title())); } async updateTags(key: number, current: NoteDigest, nextValue: NoteDigest, note: Note) { const tagsToAdd = note.tags.filter(x => !current.tags.contains(x)); const tagsToRemove = current.tags.filter(x => !note.tags.contains(x)); + let addTagsResponse = null, removeTagsResponse = null; if (tagsToAdd.length) { - console.log(`Adding tags for ${note.title()}`); - console.log(tagsToAdd); - await this.anki.addTagsToNotes([note.nid], tagsToAdd); + console.log(`Adding tags for ${note.title()}`, tagsToAdd); + addTagsResponse = await this.anki.addTagsToNotes([note.nid], tagsToAdd); } if (tagsToRemove.length) { - console.log(`Removing tags for ${note.title()}`); - console.log(tagsToRemove); - await this.anki.removeTagsFromNotes([note.nid], tagsToRemove); + console.log(`Removing tags for ${note.title()}`, tagsToRemove); + removeTagsResponse = await this.anki.removeTagsFromNotes([note.nid], tagsToRemove); } + if (addTagsResponse || removeTagsResponse) new Notice(locale.synchronizeUpdateTagsFaliureNotice(note.title())); } delete(key: number) { @@ -160,20 +165,22 @@ export class NoteState extends State { fields: this.formatter.format(note.fields), tags: note.tags }; - console.log(`Adding note for ${note.title()}`); - console.log(ankiNote); + console.log(`Adding note for ${note.title()}`, ankiNote); let idOrError = await this.anki.addNote(ankiNote); - if (idOrError instanceof AnkiError) { - // if the supposed deck does not exist, create it - console.log(idOrError.error, ', try creating'); - await this.anki.createDeck(ankiNote.deckName); - idOrError = await this.anki.addNote(ankiNote); - if (typeof idOrError !== 'number') { - return; + if (typeof idOrError === 'number') { + return idOrError; + } + + // if the supposed deck does not exist, create it + if (idOrError.message.contains('deck was not found')) { + console.log(idOrError.message, ', try creating'); + const nullOrError = await this.anki.createDeck(ankiNote.deckName); + if (nullOrError === null) { + idOrError = await this.anki.addNote(ankiNote); + if (typeof idOrError === 'number') { + return idOrError; + } } - } else if (idOrError instanceof Error) { - return; } - note.nid = idOrError; } } diff --git a/versions.json b/versions.json index 35c3080..b9a3163 100644 --- a/versions.json +++ b/versions.json @@ -3,5 +3,6 @@ "0.0.2": "0.14.0", "0.0.3": "0.14.0", "0.0.4": "0.14.0", - "0.0.5": "0.14.0" + "0.0.5": "0.14.0", + "0.0.6": "0.14.0" } \ No newline at end of file