mirror of
https://github.com/gosticks/flashcards-obsidian.git
synced 2025-10-16 12:05:33 +00:00
Adds option to append ID's inline (#35)
* Adds option to append ID's inline * Images in cards can now be stored in any folder that Obsidian recognizes * Removed unnecessary utils functions
This commit is contained in:
parent
69b9984fd7
commit
c14e58272c
3
.gitignore
vendored
3
.gitignore
vendored
@ -23,3 +23,6 @@ move.sh
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
# Gtihub
|
||||
.DS_Store
|
||||
|
||||
134
README.md
134
README.md
@ -1,67 +1,67 @@
|
||||
# Flashcards
|
||||
|
||||
[](https://github.com/reuseman/flashcards-obsidian/releases/latest)
|
||||

|
||||
|
||||
Anki integration for [Obsidian](https://obsidian.md/).
|
||||
|
||||
## Features
|
||||
|
||||
🗃️ Simple flashcards with **#card**
|
||||
🎴 Reversed flashcards with **#card-reverse**
|
||||
📅 Spaced-only cards with **#card-spaced**
|
||||
✍️ Inline style with **Question::Answer**
|
||||
✍️ Inline style reversed with **Question:::Answer**
|
||||
🧠 **Context-aware** mode
|
||||
🏷️ Global and local **tags**
|
||||
🔢 Support for **LaTeX**
|
||||
🖼️ Support for **images**
|
||||
🔗 Support for **Obsidian URI**
|
||||
⚓ Support for **reference to note**
|
||||
📟 Support for **code syntax highlight**
|
||||
|
||||
## How it works?
|
||||
|
||||
The following is a demo where the three main operations are shown:
|
||||
|
||||
1. **Insertion** of cards;
|
||||
2. **Update** of cards;
|
||||
3. **Deletion** of cards.
|
||||
|
||||

|
||||
|
||||
## How to use it?
|
||||
|
||||
The wiki explains in detail [how to use it](https://github.com/reuseman/flashcards-obsidian/wiki).
|
||||
|
||||
## How to install
|
||||
|
||||
1. Install this plugin on Obsidian
|
||||
|
||||
From Obsidian v0.9.8+, you can activate this plugin within Obsidian by doing the following:
|
||||
|
||||
- Open Settings > Third-party plugin
|
||||
- Make sure Safe mode is off
|
||||
- Click Browse community plugins
|
||||
- Search for "**Flashcards**"
|
||||
- Click Install
|
||||
- Once installed, close the community plugins window and activate the newly installed plugin
|
||||
|
||||
2. Install [AnkiConnect](https://ankiweb.net/shared/info/2055492159) on Anki
|
||||
- Tools > Add-ons -> Get Add-ons...
|
||||
- Paste the code **2055492159** > Ok
|
||||
- Select the plugin > Config > Paste the configuration below
|
||||
|
||||
Configuration:
|
||||
|
||||
{
|
||||
"apiKey": null,
|
||||
"apiLogPath": null,
|
||||
"webBindAddress": "127.0.0.1",
|
||||
"webBindPort": 8765,
|
||||
"webCorsOrigin": "http://localhost",
|
||||
"webCorsOriginList": [
|
||||
"http://localhost",
|
||||
"app://obsidian.md"
|
||||
]
|
||||
}
|
||||
# Flashcards
|
||||
|
||||
[](https://github.com/reuseman/flashcards-obsidian/releases/latest)
|
||||

|
||||
|
||||
Anki integration for [Obsidian](https://obsidian.md/).
|
||||
|
||||
## Features
|
||||
|
||||
🗃️ Simple flashcards with **#card**
|
||||
🎴 Reversed flashcards with **#card-reverse**
|
||||
📅 Spaced-only cards with **#card-spaced**
|
||||
✍️ Inline style with **Question::Answer**
|
||||
✍️ Inline style reversed with **Question:::Answer**
|
||||
🧠 **Context-aware** mode
|
||||
🏷️ Global and local **tags**
|
||||
🔢 Support for **LaTeX**
|
||||
🖼️ Support for **images**
|
||||
🔗 Support for **Obsidian URI**
|
||||
⚓ Support for **reference to note**
|
||||
📟 Support for **code syntax highlight**
|
||||
|
||||
## How it works?
|
||||
|
||||
The following is a demo where the three main operations are shown:
|
||||
|
||||
1. **Insertion** of cards;
|
||||
2. **Update** of cards;
|
||||
3. **Deletion** of cards.
|
||||
|
||||

|
||||
|
||||
## How to use it?
|
||||
|
||||
The wiki explains in detail [how to use it](https://github.com/reuseman/flashcards-obsidian/wiki).
|
||||
|
||||
## How to install
|
||||
|
||||
1. Install this plugin on Obsidian
|
||||
|
||||
From Obsidian v0.9.8+, you can activate this plugin within Obsidian by doing the following:
|
||||
|
||||
- Open Settings > Third-party plugin
|
||||
- Make sure Safe mode is off
|
||||
- Click Browse community plugins
|
||||
- Search for "**Flashcards**"
|
||||
- Click Install
|
||||
- Once installed, close the community plugins window and activate the newly installed plugin
|
||||
|
||||
2. Install [AnkiConnect](https://ankiweb.net/shared/info/2055492159) on Anki
|
||||
- Tools > Add-ons -> Get Add-ons...
|
||||
- Paste the code **2055492159** > Ok
|
||||
- Select the plugin > Config > Paste the configuration below
|
||||
|
||||
Configuration:
|
||||
|
||||
{
|
||||
"apiKey": null,
|
||||
"apiLogPath": null,
|
||||
"webBindAddress": "127.0.0.1",
|
||||
"webBindPort": 8765,
|
||||
"webCorsOrigin": "http://localhost",
|
||||
"webCorsOriginList": [
|
||||
"http://localhost",
|
||||
"app://obsidian.md"
|
||||
]
|
||||
}
|
||||
|
||||
2
main.ts
2
main.ts
@ -56,7 +56,7 @@ export default class ObsidianFlashcard extends Plugin {
|
||||
}
|
||||
|
||||
private getDefaultSettings(): ISettings {
|
||||
return { contextAwareMode: true, sourceSupport: false, codeHighlightSupport: false, contextSeparator: " > ", deck: "Default", flashcardsTag: "card", defaultAnkiTag: "obsidian" }
|
||||
return { contextAwareMode: true, sourceSupport: false, codeHighlightSupport: false, inlineID: false, contextSeparator: " > ", deck: "Default", flashcardsTag: "card", defaultAnkiTag: "obsidian" }
|
||||
}
|
||||
|
||||
private generateCards(activeFile: TFile) {
|
||||
|
||||
@ -46,6 +46,6 @@ export class Inlinecard extends Card {
|
||||
}
|
||||
|
||||
public getIdFormat(): string {
|
||||
return "\n^" + this.id.toString()
|
||||
return "^" + this.id.toString()
|
||||
}
|
||||
}
|
||||
@ -55,6 +55,17 @@ export class SettingsTab extends PluginSettingTab {
|
||||
plugin.saveData(plugin.settings)
|
||||
})
|
||||
)
|
||||
new Setting(containerEl)
|
||||
.setName("Inline ID support")
|
||||
.setDesc("Add ID to end of line for inline cards.")
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(plugin.settings.inlineID)
|
||||
.onChange((value) => {
|
||||
plugin.settings.inlineID = value
|
||||
plugin.saveData(plugin.settings)
|
||||
})
|
||||
)
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Default deck")
|
||||
|
||||
@ -38,7 +38,11 @@ export class Regex {
|
||||
this.flashscardsWithTag = new RegExp(str, flags)
|
||||
|
||||
// https://regex101.com/r/Ixtzlv/1
|
||||
str = "( {0,3}[#]{0,6})?(?:(?:[\\t ]*)(?:\\d.|[-+*]|#{1,6}))?(.+?) ?(:{2,3}) ?(.+?)((?: *#[\\p{Letter}-]+)+|$)(?:\\n\\^(\\d{13}))?"
|
||||
if (settings.inlineID){
|
||||
str = "( {0,3}[#]{0,6})?(?:(?:[\\t ]*)(?:\\d.|[-+*]|#{1,6}))?(.+?) ?(:{2,3}) ?(.+?)((?: *#[\\p{Letter}-]+)+)?(?:\\s+\\^(\\d{13})|$)"
|
||||
} else {
|
||||
str = "( {0,3}[#]{0,6})?(?:(?:[\\t ]*)(?:\\d.|[-+*]|#{1,6}))?(.+?) ?(:{2,3}) ?(.+?)((?: *#[\\p{Letter}-]+)+|$)(?:\\n\\^(\\d{13}))?"
|
||||
}
|
||||
this.cardsInlineStyle = new RegExp(str, flags)
|
||||
|
||||
// https://regex101.com/r/HOXF5E/1
|
||||
|
||||
@ -3,9 +3,10 @@ import { App, FileSystemAdapter, FrontMatterCache, Notice, parseFrontMatterEntry
|
||||
import { Parser } from 'src/services/parser'
|
||||
import { ISettings } from 'src/settings'
|
||||
import { Card } from 'src/entities/card'
|
||||
import { arrayBufferToBase64 } from "src/utils"
|
||||
import { arrayBufferToBase64} from "src/utils"
|
||||
import { Regex } from 'src/regex'
|
||||
import { noticeTimeout } from 'src/constants'
|
||||
import { Inlinecard } from 'src/entities/inlinecard'
|
||||
|
||||
|
||||
export class CardsService {
|
||||
@ -43,6 +44,7 @@ export class CardsService {
|
||||
this.totalOffset = 0
|
||||
this.notifications = []
|
||||
let filePath = activeFile.basename
|
||||
let sourcePath = activeFile.path
|
||||
let fileCachedMetadata = this.app.metadataCache.getFileCache(activeFile)
|
||||
let vaultName = this.app.vault.getName()
|
||||
let globalTags: string[] = undefined
|
||||
@ -69,7 +71,7 @@ export class CardsService {
|
||||
|
||||
let cards: Card[] = this.parser.generateFlashcards(this.file, deckName, vaultName, filePath, globalTags)
|
||||
let [cardsToCreate, cardsToUpdate] = this.filterByUpdate(ankiCards, cards)
|
||||
let cardIds : number[] = this.getCardsIds(ankiCards, cards)
|
||||
let cardIds: number[] = this.getCardsIds(ankiCards, cards)
|
||||
let cardsToDelete: number[] = this.parser.getCardsToDelete(this.file)
|
||||
|
||||
console.info("Flashcards: Cards to create")
|
||||
@ -79,7 +81,7 @@ export class CardsService {
|
||||
console.info("Flashcards: Cards to delete")
|
||||
console.info(cardsToDelete)
|
||||
|
||||
this.insertMedias(cards)
|
||||
this.insertMedias(cards, sourcePath)
|
||||
await this.deleteCardsOnAnki(cardsToDelete, ankiBlocks)
|
||||
await this.updateCardsOnAnki(cardsToUpdate)
|
||||
await this.insertCardsOnAnki(cardsToCreate, frontmatter, deckName)
|
||||
@ -116,11 +118,11 @@ export class CardsService {
|
||||
}
|
||||
}
|
||||
|
||||
private async insertMedias(cards: Card[]) {
|
||||
private async insertMedias(cards: Card[], sourcePath: string) {
|
||||
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.generateMediaLinks(cards, sourcePath)
|
||||
await this.anki.storeMediaFiles(cards)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
@ -128,16 +130,15 @@ export class CardsService {
|
||||
}
|
||||
}
|
||||
|
||||
private async generateMediaLinks(cards: Card[]) {
|
||||
private async generateMediaLinks(cards: Card[], sourcePath: string) {
|
||||
if (this.app.vault.adapter instanceof FileSystemAdapter) {
|
||||
// @ts-ignore: Unreachable code error
|
||||
let attachmentsPath = this.app.vault.config.attachmentFolderPath
|
||||
|
||||
for (let card of cards) {
|
||||
for (let media of card.mediaNames) {
|
||||
let file: TFile = this.app.vault.getAbstractFileByPath(attachmentsPath + "/" + media) as TFile
|
||||
let image = this.app.metadataCache.getFirstLinkpathDest(decodeURIComponent(media), sourcePath);
|
||||
try {
|
||||
let binaryMedia = await this.app.vault.readBinary(file)
|
||||
let binaryMedia = await this.app.vault.readBinary(image)
|
||||
card.mediaBase64Encoded.push(arrayBufferToBase64(binaryMedia))
|
||||
} catch (err) {
|
||||
Error("Error: Could not read media")
|
||||
@ -208,6 +209,13 @@ export class CardsService {
|
||||
// if it has been inserted it has an ID too
|
||||
if (card.id !== null && !card.inserted) {
|
||||
let id = card.getIdFormat()
|
||||
if (card instanceof Inlinecard) {
|
||||
if (this.settings.inlineID) {
|
||||
id = " " + id
|
||||
} else {
|
||||
id = "\n" + id
|
||||
}
|
||||
}
|
||||
card.endOffset += this.totalOffset
|
||||
let offset = card.endOffset
|
||||
|
||||
@ -296,7 +304,7 @@ export class CardsService {
|
||||
return [cardsToCreate, cardsToUpdate]
|
||||
}
|
||||
|
||||
public async deckNeedToBeChanged(cardsIds : number[], deckName: string) {
|
||||
public async deckNeedToBeChanged(cardsIds: number[], deckName: string) {
|
||||
let cardsInfo = await this.anki.cardsInfo(cardsIds)
|
||||
console.log("Flashcards: Cards info")
|
||||
console.log(cardsInfo)
|
||||
@ -307,10 +315,10 @@ export class CardsService {
|
||||
return false
|
||||
}
|
||||
|
||||
public getCardsIds(ankiCards: any, generatedCards: Card[]) : number[] {
|
||||
let ids : number[] = []
|
||||
public getCardsIds(ankiCards: any, generatedCards: Card[]): number[] {
|
||||
let ids: number[] = []
|
||||
|
||||
if (ankiCards) {
|
||||
if (ankiCards) {
|
||||
for (let flashcard of generatedCards) {
|
||||
let ankiCard = undefined
|
||||
if (flashcard.inserted) {
|
||||
@ -323,7 +331,7 @@ export class CardsService {
|
||||
return ids
|
||||
}
|
||||
|
||||
public parseGlobalTags(file: String) : string[] {
|
||||
public parseGlobalTags(file: String): string[] {
|
||||
let globalTags: string[] = []
|
||||
|
||||
let tags = file.match(/(?:cards-)?tags: ?(.*)/im)
|
||||
|
||||
@ -2,6 +2,7 @@ export interface ISettings {
|
||||
contextAwareMode: boolean
|
||||
sourceSupport: boolean
|
||||
codeHighlightSupport: boolean
|
||||
inlineID: boolean
|
||||
contextSeparator: string
|
||||
deck: string
|
||||
flashcardsTag: string
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import { Vault, TFile} from 'obsidian';
|
||||
|
||||
export function arrayBufferToBase64(buffer: ArrayBuffer): string {
|
||||
var binary = "";
|
||||
var bytes = new Uint8Array(buffer);
|
||||
@ -43,4 +45,4 @@ export function escapeMarkdown(string: string, skips: string[] = []) {
|
||||
? string
|
||||
: string.replace(replacement[0], replacement[1]);
|
||||
}, string);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user