diff --git a/main.ts b/main.ts index e98193f..94b295d 100644 --- a/main.ts +++ b/main.ts @@ -58,14 +58,14 @@ export default class ObsidianFlashcard extends Plugin { this.addCommand({ id: "anki-test", name: "Anki", callback: () => { - let anki: Anki = new Anki() - anki.storeMedia(this.app.vault).then(res => { - console.log("ok") - console.log(res) - }).catch(err => { - console.log("err") - console.log(err) - }) + // let anki: Anki = new Anki() + // anki.storeMedia(this.app.vault).then(res => { + // console.log("ok") + // console.log(res) + // }).catch(err => { + // console.log("err") + // console.log(err) + // }) } }) diff --git a/src/cards-service.ts b/src/cards-service.ts index 1a3c16b..399d060 100644 --- a/src/cards-service.ts +++ b/src/cards-service.ts @@ -4,6 +4,7 @@ import { Parser } from 'src/parser' import { Settings } from 'src/settings' import { Card } from 'src/entities/card' import { Flashcard } from 'src/entities/flashcard' +import { arrayBufferToBase64 } from "src/utils" export class CardsService { // TODO right now you do not check for cards that when inserted/updated gives back null as ID @@ -27,7 +28,6 @@ export class CardsService { public async execute(activeFile: TFile): Promise { // TODO add note-type to Anki - // TODO check media problem try { await this.anki.ping() } catch (err) { @@ -60,16 +60,7 @@ export class CardsService { let [cardsToCreate, cardsToUpdate] = this.filterByUpdate(ankiCards, cards) let cardsToDelete: number[] = this.parser.getCardsToDelete(this.file) - try { - // Currently the media are created for every run, this is not a problem since Anki APIs overwrite the file - // A more efficient way would be to keep track of the medias saved - await this.generateMediaLinks(cards) - await this.anki.storeMediaFiles(cards) - } catch (err) { - console.error(err) - Error("Error: Could not upload medias") - } - + this.insertMedias(cards) await this.deleteCardsOnAnki(cardsToDelete, ankiBlocks) await this.updateCardsOnAnki(cardsToUpdate) await this.insertCardsOnAnki(cardsToCreate, frontmatter, deckName) @@ -94,14 +85,16 @@ export class CardsService { } } - private arrayBufferToBase64(buffer: ArrayBuffer): string { - var binary = ''; - var bytes = new Uint8Array(buffer); - var len = bytes.byteLength; - for (var i = 0; i < len; i++) { - binary += String.fromCharCode(bytes[i]); + private async insertMedias(cards: Card[]) { + try { + // Currently the media are created for every run, this is not a problem since Anki APIs overwrite the file + // A more efficient way would be to keep track of the medias saved + await this.generateMediaLinks(cards) + await this.anki.storeMediaFiles(cards) + } catch (err) { + console.error(err) + Error("Error: Could not upload medias") } - return window.btoa(binary); } private async generateMediaLinks(cards: Card[]) { @@ -114,7 +107,7 @@ export class CardsService { let file: TFile = this.app.vault.getAbstractFileByPath(attachmentsPath + "/" + media) as TFile try { let binaryMedia = await this.app.vault.readBinary(file) - card.mediaBase64Encoded.push(this.arrayBufferToBase64(binaryMedia)) + card.mediaBase64Encoded.push(arrayBufferToBase64(binaryMedia)) } catch (err) { Error("Error: Could not read media") } @@ -231,7 +224,6 @@ export class CardsService { private getAnkiIDs(blocks: RegExpMatchArray[]): number[] { let IDs: number[] = [] - for (let b of blocks) { IDs.push(Number(b[1])) } @@ -260,7 +252,6 @@ export class CardsService { } } } else { - console.log("No cards in Anki, I am going to create all of them") cardsToCreate = [...generatedCards] } diff --git a/src/parser.ts b/src/parser.ts index c207498..ee77dba 100644 --- a/src/parser.ts +++ b/src/parser.ts @@ -3,7 +3,7 @@ import { Spaced } from "./entities/spaced" import { Cloze } from './entities/cloze'; import { Settings } from 'src/settings'; import * as showdown from 'showdown'; - +import { wikiImageLinks, markdownImageLinks } from "src/utils"; export class Parser { private settings: Settings @@ -65,7 +65,7 @@ export class Parser { public getCardsToDelete(file: string): number[] { // Find block IDs with no content above it - let regex: RegExp = /^\s*(?:\n)(?:\^(\d{13}))(?:\n\s*?)?/gm + const regex: RegExp = /^\s*(?:\n)(?:\^(\d{13}))(?:\n\s*?)?/gm return [...file.matchAll(regex)].map((match) => { return Number(match[1]) }) } @@ -79,24 +79,26 @@ export class Parser { if (contextAware) { // https://regex101.com/r/agSp9X/4 - headings = [...file.matchAll(/^ {0,3}(#{1,6}) +([^\n]+?) ?((?: *#\S+)*) *$/gim)] + const headingsRegex = /^ {0,3}(#{1,6}) +([^\n]+?) ?((?: *#\S+)*) *$/gim + headings = [...file.matchAll(headingsRegex)] } for (let match of matches) { let reversed: boolean = match[3].trim().toLowerCase() === "#flashcard-reverse" let headingLevel = match[1].trim().length !== 0 ? match[1].length : -1 - // Match.index - 1 because otherwise in the context there will be even match[1], i.e. the question + // Match.index - 1 because otherwise in the context there will be even match[1], i.e. the question itself let context = contextAware ? this.getContext(headings, match.index - 1, headingLevel) : "" let originalQuestion = match[2].trim() let question = contextAware ? [...context, match[2].trim()].join(`${this.settings.contextSeparator}`) : match[2].trim() let answer = match[5].trim() - - let imagesMedia: string[] = this.substituteImageLinks(question) - imagesMedia = imagesMedia.concat(this.substituteImageLinks(answer)) - + let imagesMedia: string[] = this.getImageLinks(question) + imagesMedia = imagesMedia.concat(this.getImageLinks(answer)) + question = this.substituteImageLinks(question) + answer = this.substituteImageLinks(answer) question = this.mathToAnki(this.htmlConverter.makeHtml(question)) answer = this.mathToAnki(this.htmlConverter.makeHtml(answer)) + let endingLine = match.index + match[0].length let tags: string[] = this.parseTags(match[4], globalTags) let id: number = match[6] ? Number(match[6]) : -1 @@ -110,13 +112,9 @@ export class Parser { return flashcards } - private substituteImageLinks(str: string): string[] { - // Supported images https://publish.obsidian.md/help/How+to/Embed+files - let wikiLinks = /!\[\[(.*\.(?:png|jpg|jpeg|gif|bmp|svg|tiff))\]\]/gim - let markdownLinks = /!\[\]\((.*\.(?:png|jpg|jpeg|gif|bmp|svg|tiff))\)/gim - - let wikiMatches = str.matchAll(wikiLinks) - let markdownMatches = str.matchAll(markdownLinks) + private getImageLinks(str: string) { + let wikiMatches = str.matchAll(wikiImageLinks) + let markdownMatches = str.matchAll(markdownImageLinks) let links: string[] = [] for (let wikiMatch of wikiMatches) { @@ -127,15 +125,16 @@ export class Parser { links.push(decodeURIComponent(markdownMatch[1])) } - str.replace(wikiLinks, "") - // TODO markdown link maybe is not correct, it should be decoded (remove the 20%) - str.replace(markdownLinks, "") - - console.log("Generated links:") - console.log(links) return links } + private substituteImageLinks(str: string): string { + str = str.replace(wikiImageLinks, "") + str = str.replace(markdownImageLinks, "") + + return str + } + private generateSpacedCards(file: string): Spaced[] { let spaced: Spaced[] = [] diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..e5b125b --- /dev/null +++ b/src/utils.ts @@ -0,0 +1,13 @@ +// Supported images https://publish.obsidian.md/help/How+to/Embed+files +export const wikiImageLinks = /!\[\[(.*\.(?:png|jpg|jpeg|gif|bmp|svg|tiff))\]\]/gim +export const markdownImageLinks = /!\[\]\((.*\.(?:png|jpg|jpeg|gif|bmp|svg|tiff))\)/gim + +export function arrayBufferToBase64(buffer: ArrayBuffer): string { + var binary = ''; + var bytes = new Uint8Array(buffer); + var len = bytes.byteLength; + for (var i = 0; i < len; i++) { + binary += String.fromCharCode(bytes[i]); + } + return window.btoa(binary); +}