mirror of
https://github.com/gosticks/flashcards-obsidian.git
synced 2025-10-16 12:05:33 +00:00
Add support for code syntax highlight
This commit is contained in:
parent
e5de6b32aa
commit
7dc7915c1f
@ -14,7 +14,8 @@ Anki integration for [Obsidian](https://obsidian.md/).
|
||||
🏷️ Global and local **tags**
|
||||
🔢 Support for **LaTeX**
|
||||
🖼️ Support for **images**
|
||||
🔗 **Obsidian URI** support
|
||||
🔗 Support for **Obsidian URI**
|
||||
📟 Support for **Code syntax highlight**
|
||||
|
||||
## How it works?
|
||||
|
||||
|
||||
2
main.ts
2
main.ts
@ -56,7 +56,7 @@ export default class ObsidianFlashcard extends Plugin {
|
||||
}
|
||||
|
||||
private getDefaultSettings(): ISettings {
|
||||
return { contextAwareMode: true, contextSeparator: " > ", deck: "Default", flashcardsTag: "card" }
|
||||
return { contextAwareMode: true, codeHighlightSupport: true, contextSeparator: " > ", deck: "Default", flashcardsTag: "card" }
|
||||
}
|
||||
|
||||
private generateCards(activeFile: TFile) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1,3 +1,4 @@
|
||||
import { codeDeckExtension } from 'src/constants'
|
||||
import { arraysEqual } from 'src/utils'
|
||||
|
||||
export abstract class Card {
|
||||
@ -12,9 +13,11 @@ export abstract class Card {
|
||||
mediaNames: string[]
|
||||
mediaBase64Encoded: string[]
|
||||
oldTags: string[]
|
||||
containsCode: boolean
|
||||
modelName: string
|
||||
|
||||
// TODO set "obsidian as optional in the settings", this means that the tag should be outside
|
||||
constructor(id: number, deckName: string, initialContent: string, fields: Record<string, string>, reversed: boolean, endOffset: number, tags: string[], inserted: boolean, mediaNames: string[]) {
|
||||
constructor(id: number, deckName: string, initialContent: string, fields: Record<string, string>, reversed: boolean, endOffset: number, tags: string[], inserted: boolean, mediaNames: string[], containsCode: boolean = false) {
|
||||
this.id = id
|
||||
this.deckName = deckName
|
||||
this.initialContent = initialContent
|
||||
@ -27,6 +30,8 @@ export abstract class Card {
|
||||
this.mediaNames = mediaNames
|
||||
this.mediaBase64Encoded = []
|
||||
this.oldTags = []
|
||||
this.containsCode = containsCode
|
||||
this.modelName = ""
|
||||
}
|
||||
|
||||
abstract toString(): string
|
||||
@ -35,6 +40,11 @@ export abstract class Card {
|
||||
abstract getIdFormat(): string
|
||||
|
||||
match(card: any): boolean {
|
||||
// TODO not supported currently
|
||||
// if (this.modelName !== card.modelName) {
|
||||
// return false
|
||||
// }
|
||||
|
||||
let fields = Object.entries(card.fields)
|
||||
for (let field of fields) {
|
||||
let fieldName = field[0]
|
||||
@ -45,4 +55,8 @@ export abstract class Card {
|
||||
|
||||
return arraysEqual(card.tags, this.tags)
|
||||
}
|
||||
|
||||
getCodeDeckNameExtension() {
|
||||
return this.containsCode ? codeDeckExtension : ""
|
||||
}
|
||||
}
|
||||
@ -1,15 +1,16 @@
|
||||
import { Card } from "src/entities/card";
|
||||
|
||||
export class Flashcard extends Card {
|
||||
constructor(id: number = -1, deckName: string, initialContent: string, fields: Record<string, string>, reversed: boolean, endOffset: number, tags: string[] = [], inserted: boolean = false, mediaNames: string[]) {
|
||||
super(id, deckName, initialContent, fields, reversed, endOffset, tags, inserted, mediaNames)
|
||||
constructor(id: number = -1, deckName: string, initialContent: string, fields: Record<string, string>, reversed: boolean, endOffset: number, tags: string[] = [], inserted: boolean = false, mediaNames: string[], containsCode: boolean) {
|
||||
super(id, deckName, initialContent, fields, reversed, endOffset, tags, inserted, mediaNames, containsCode)
|
||||
let codeExtension = this.getCodeDeckNameExtension()
|
||||
this.modelName = this.reversed ? `Obsidian-basic-reversed${codeExtension}` : `Obsidian-basic${codeExtension}`
|
||||
}
|
||||
|
||||
public getCard(update: boolean = false): object {
|
||||
let modelName = this.reversed ? "Obsidian-basic-reversed" : "Obsidian-basic"
|
||||
let card: any = {
|
||||
"deckName": this.deckName,
|
||||
"modelName": modelName,
|
||||
"modelName": this.modelName,
|
||||
"fields": {
|
||||
"Front": this.fields["Front"],
|
||||
"Back": this.fields["Back"]
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import { Card } from "src/entities/card";
|
||||
|
||||
export class Inlinecard extends Card {
|
||||
constructor(id: number = -1, deckName: string, initialContent: string, fields: Record<string, string>, reversed: boolean, endOffset: number, tags: string[] = [], inserted: boolean = false, mediaNames: string[]) {
|
||||
super(id, deckName, initialContent, fields, reversed, endOffset, tags, inserted, mediaNames)
|
||||
constructor(id: number = -1, deckName: string, initialContent: string, fields: Record<string, string>, reversed: boolean, endOffset: number, tags: string[] = [], inserted: boolean = false, mediaNames: string[], containsCode: boolean) {
|
||||
super(id, deckName, initialContent, fields, reversed, endOffset, tags, inserted, mediaNames, containsCode)
|
||||
let codeExtension = this.getCodeDeckNameExtension()
|
||||
this.modelName = `Obsidian-basic${codeExtension}`
|
||||
}
|
||||
|
||||
public getCard(update: boolean = false): object {
|
||||
let modelName = "Obsidian-basic"
|
||||
let card: any = {
|
||||
"deckName": this.deckName,
|
||||
"modelName": modelName,
|
||||
"modelName": this.modelName,
|
||||
"fields": {
|
||||
"Front": this.fields["Front"],
|
||||
"Back": this.fields["Back"]
|
||||
|
||||
@ -1,15 +1,17 @@
|
||||
import { Card } from "src/entities/card";
|
||||
|
||||
export class Spacedcard extends Card {
|
||||
constructor(id: number = -1, deckName: string, initialContent: string, fields: Record<string, string>, reversed: boolean, endOffset: number, tags: string[] = [], inserted: boolean = false, mediaNames: string[]) {
|
||||
super(id, deckName, initialContent, fields, reversed, endOffset, tags, inserted, mediaNames)
|
||||
constructor(id: number = -1, deckName: string, initialContent: string, fields: Record<string, string>, reversed: boolean, endOffset: number, tags: string[] = [], inserted: boolean = false, mediaNames: string[], containsCode: boolean) {
|
||||
super(id, deckName, initialContent, fields, reversed, endOffset, tags, inserted, mediaNames, containsCode)
|
||||
let codeExtension = this.getCodeDeckNameExtension()
|
||||
this.modelName = `Obsidian-spaced${codeExtension}`
|
||||
}
|
||||
|
||||
public getCard(update: boolean = false): object {
|
||||
let modelName = "Obsidian-spaced"
|
||||
|
||||
let card: any = {
|
||||
"deckName": this.deckName,
|
||||
"modelName": modelName,
|
||||
"modelName": this.modelName,
|
||||
"fields": {
|
||||
"Prompt": this.fields["Prompt"],
|
||||
},
|
||||
|
||||
@ -32,6 +32,18 @@ export class SettingsTab extends PluginSettingTab {
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Code highlight support")
|
||||
.setDesc("Add highlight of the code in Anki.")
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(plugin.settings.codeHighlightSupport)
|
||||
.onChange((value) => {
|
||||
plugin.settings.codeHighlightSupport = value
|
||||
plugin.saveData(plugin.settings)
|
||||
})
|
||||
)
|
||||
new Setting(containerEl)
|
||||
.setName("Default deck")
|
||||
.setDesc("The name of the default deck where the cards will be added when not specified.")
|
||||
@ -64,5 +76,6 @@ export class SettingsTab extends PluginSettingTab {
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ export class Regex {
|
||||
headingsRegex: RegExp
|
||||
wikiImageLinks: RegExp
|
||||
markdownImageLinks: RegExp
|
||||
codeBlock: RegExp
|
||||
cardsDeckLine: RegExp
|
||||
cardsToDelete: RegExp
|
||||
flashscardsWithTag: RegExp
|
||||
@ -21,6 +22,7 @@ export class Regex {
|
||||
// Supported images https://publish.obsidian.md/help/How+to/Embed+files
|
||||
this.wikiImageLinks = /!\[\[(.*\.(?:png|jpg|jpeg|gif|bmp|svg|tiff))\]\]/gim
|
||||
this.markdownImageLinks = /!\[\]\((.*\.(?:png|jpg|jpeg|gif|bmp|svg|tiff))\)/gim
|
||||
this.codeBlock = /<code\b[^>]*>(.*?)<\/code>/gims
|
||||
|
||||
this.cardsDeckLine = /cards-deck: [\w\d]+/gi
|
||||
this.cardsToDelete = /^\s*(?:\n)(?:\^(\d{13}))(?:\n\s*?)?/gm
|
||||
|
||||
@ -1,13 +1,24 @@
|
||||
import { Card } from 'src/entities/card';
|
||||
import { codeScript, highlightjsBase64, hihglightjsInitBase64, highlightCssBase64, codeDeckExtension } from 'src/constants'
|
||||
|
||||
export class Anki {
|
||||
public async createModels() {
|
||||
|
||||
public async createModels(codeHighlightSupport: boolean) {
|
||||
let css = ".card {\r\n font-family: arial;\r\n font-size: 20px;\r\n text-align: center;\r\n color: black;\r\n background-color: white;\r\n}\r\n\r\n.tag::before {\r\n\tcontent: \"#\";\r\n}\r\n\r\n.tag {\r\n color: white;\r\n background-color: #9F2BFF;\r\n border: none;\r\n font-size: 11px;\r\n font-weight: bold;\r\n padding: 1px 8px;\r\n margin: 0px 3px;\r\n text-align: center;\r\n text-decoration: none;\r\n cursor: pointer;\r\n border-radius: 14px;\r\n display: inline;\r\n vertical-align: middle;\r\n}\r\n"
|
||||
let front = "{{Front}}\r\n<p class=\"tags\">{{Tags}}<\/p>\r\n\r\n<script>\r\n var tagEl = document.querySelector(\'.tags\');\r\n var tags = tagEl.innerHTML.split(\' \');\r\n var html = \'\';\r\n tags.forEach(function(tag) {\r\n\tif (tag) {\r\n\t var newTag = \'<span class=\"tag\">\' + tag + \'<\/span>\';\r\n html += newTag;\r\n \t tagEl.innerHTML = html;\r\n\t}\r\n });\r\n \r\n<\/script>"
|
||||
let frontReversed = "{{Back}}\r\n<p class=\"tags\">{{Tags}}<\/p>\r\n\r\n<script>\r\n var tagEl = document.querySelector(\'.tags\');\r\n var tags = tagEl.innerHTML.split(\' \');\r\n var html = \'\';\r\n tags.forEach(function(tag) {\r\n\tif (tag) {\r\n\t var newTag = \'<span class=\"tag\">\' + tag + \'<\/span>\';\r\n html += newTag;\r\n \t tagEl.innerHTML = html;\r\n\t}\r\n });\r\n \r\n<\/script>"
|
||||
let prompt = "{{Prompt}}\r\n<p class=\"tags\">🧠spaced {{Tags}}<\/p>\r\n\r\n<script>\r\n var tagEl = document.querySelector(\'.tags\');\r\n var tags = tagEl.innerHTML.split(\' \');\r\n var html = \'\';\r\n tags.forEach(function(tag) {\r\n\tif (tag) {\r\n\t var newTag = \'<span class=\"tag\">\' + tag + \'<\/span>\';\r\n html += newTag;\r\n \t tagEl.innerHTML = html;\r\n\t}\r\n });\r\n \r\n<\/script>"
|
||||
let front = `{{Front}}\r\n<p class=\"tags\">{{Tags}}<\/p>\r\n\r\n<script>\r\n var tagEl = document.querySelector(\'.tags\');\r\n var tags = tagEl.innerHTML.split(\' \');\r\n var html = \'\';\r\n tags.forEach(function(tag) {\r\n\tif (tag) {\r\n\t var newTag = \'<span class=\"tag\">\' + tag + \'<\/span>\';\r\n html += newTag;\r\n \t tagEl.innerHTML = html;\r\n\t}\r\n });\r\n \r\n<\/script>`
|
||||
let frontReversed = `{{Back}}\r\n<p class=\"tags\">{{Tags}}<\/p>\r\n\r\n<script>\r\n var tagEl = document.querySelector(\'.tags\');\r\n var tags = tagEl.innerHTML.split(\' \');\r\n var html = \'\';\r\n tags.forEach(function(tag) {\r\n\tif (tag) {\r\n\t var newTag = \'<span class=\"tag\">\' + tag + \'<\/span>\';\r\n html += newTag;\r\n \t tagEl.innerHTML = html;\r\n\t}\r\n });\r\n \r\n<\/script>`
|
||||
let prompt = `{{Prompt}}\r\n<p class=\"tags\">🧠spaced {{Tags}}<\/p>\r\n\r\n<script>\r\n var tagEl = document.querySelector(\'.tags\');\r\n var tags = tagEl.innerHTML.split(\' \');\r\n var html = \'\';\r\n tags.forEach(function(tag) {\r\n\tif (tag) {\r\n\t var newTag = \'<span class=\"tag\">\' + tag + \'<\/span>\';\r\n html += newTag;\r\n \t tagEl.innerHTML = html;\r\n\t}\r\n });\r\n \r\n<\/script>`
|
||||
|
||||
let models = this.getModels(front, frontReversed, prompt, css)
|
||||
if (codeHighlightSupport) {
|
||||
css = ".card {\r\n font-family: arial;\r\n font-size: 20px;\r\n text-align: center;\r\n color: black;\r\n background-color: white;\r\n}\r\n\r\n.tag::before {\r\n\tcontent: \"#\";\r\n}\r\n\r\n.tag {\r\n color: white;\r\n background-color: #9F2BFF;\r\n border: none;\r\n font-size: 11px;\r\n font-weight: bold;\r\n padding: 1px 8px;\r\n margin: 0px 3px;\r\n text-align: center;\r\n text-decoration: none;\r\n cursor: pointer;\r\n border-radius: 14px;\r\n display: inline;\r\n vertical-align: middle;\r\n}\r\n"
|
||||
front = `{{Front}}\r\n<p class=\"tags\">{{Tags}}<\/p>\r\n\r\n<script>\r\n var tagEl = document.querySelector(\'.tags\');\r\n var tags = tagEl.innerHTML.split(\' \');\r\n var html = \'\';\r\n tags.forEach(function(tag) {\r\n\tif (tag) {\r\n\t var newTag = \'<span class=\"tag\">\' + tag + \'<\/span>\';\r\n html += newTag;\r\n \t tagEl.innerHTML = html;\r\n\t}\r\n });\r\n \r\n<\/script>\r\n${codeScript}\r\n`
|
||||
frontReversed = `{{Back}}\r\n<p class=\"tags\">{{Tags}}<\/p>\r\n\r\n<script>\r\n var tagEl = document.querySelector(\'.tags\');\r\n var tags = tagEl.innerHTML.split(\' \');\r\n var html = \'\';\r\n tags.forEach(function(tag) {\r\n\tif (tag) {\r\n\t var newTag = \'<span class=\"tag\">\' + tag + \'<\/span>\';\r\n html += newTag;\r\n \t tagEl.innerHTML = html;\r\n\t}\r\n });\r\n \r\n<\/script>\r\n${codeScript}\r\n`
|
||||
prompt = `{{Prompt}}\r\n<p class=\"tags\">🧠spaced {{Tags}}<\/p>\r\n\r\n<script>\r\n var tagEl = document.querySelector(\'.tags\');\r\n var tags = tagEl.innerHTML.split(\' \');\r\n var html = \'\';\r\n tags.forEach(function(tag) {\r\n\tif (tag) {\r\n\t var newTag = \'<span class=\"tag\">\' + tag + \'<\/span>\';\r\n html += newTag;\r\n \t tagEl.innerHTML = html;\r\n\t}\r\n });\r\n \r\n<\/script>\r\n${codeScript}\r\n`
|
||||
|
||||
models = models.concat(this.getModels(front, frontReversed, prompt, css, codeDeckExtension))
|
||||
}
|
||||
|
||||
return this.invoke("multi", 6, { "actions": models })
|
||||
}
|
||||
|
||||
@ -34,6 +45,37 @@ export class Anki {
|
||||
}
|
||||
}
|
||||
|
||||
public async storeCodeHighlightMedias() {
|
||||
let fileExists = await this.invoke(
|
||||
"retrieveMediaFile",
|
||||
6,
|
||||
{
|
||||
"filename": "_highlightInit.js"
|
||||
})
|
||||
|
||||
if (!fileExists) {
|
||||
let highlightjs = {
|
||||
"action": "storeMediaFile", "params": {
|
||||
"filename": "_highlight.js",
|
||||
"data": highlightjsBase64
|
||||
}
|
||||
}
|
||||
let highlightjsInit = {
|
||||
"action": "storeMediaFile", "params": {
|
||||
"filename": "_highlightInit.js",
|
||||
"data": hihglightjsInitBase64
|
||||
}
|
||||
}
|
||||
let highlightjcss = {
|
||||
"action": "storeMediaFile", "params": {
|
||||
"filename": "_highlight.css",
|
||||
"data": highlightCssBase64
|
||||
}
|
||||
}
|
||||
return this.invoke("multi", 6, { "actions": [highlightjs, highlightjsInit, highlightjcss] })
|
||||
}
|
||||
}
|
||||
|
||||
public async addCards(cards: Card[]): Promise<number[]> {
|
||||
let notes: any = []
|
||||
|
||||
@ -121,7 +163,7 @@ export class Anki {
|
||||
return actions
|
||||
}
|
||||
|
||||
private invoke(action: string, version: number, params = {}): any {
|
||||
private invoke(action: string, version: number = 6, params = {}): any {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.addEventListener('error', () => reject('failed to issue request'));
|
||||
@ -151,11 +193,11 @@ export class Anki {
|
||||
});
|
||||
}
|
||||
|
||||
private getModels(front: string, frontReversed: string, prompt: string, css: string): object[] {
|
||||
private getModels(front: string, frontReversed: string, prompt: string, css: string, extension: string = ""): object[] {
|
||||
let obsidianBasic = {
|
||||
"action": "createModel",
|
||||
"params": {
|
||||
"modelName": "Obsidian-basic",
|
||||
"modelName": `Obsidian-basic${extension}`,
|
||||
"inOrderFields": ["Front", "Back"],
|
||||
"css": css,
|
||||
"cardTemplates": [
|
||||
@ -171,7 +213,7 @@ export class Anki {
|
||||
let obsidianBasicReversed = {
|
||||
"action": "createModel",
|
||||
"params": {
|
||||
"modelName": "Obsidian-basic-reversed",
|
||||
"modelName": `Obsidian-basic-reversed${extension}`,
|
||||
"inOrderFields": ["Front", "Back"],
|
||||
"css": css,
|
||||
"cardTemplates": [
|
||||
@ -192,7 +234,7 @@ export class Anki {
|
||||
let obsidianSpaced = {
|
||||
"action": "createModel",
|
||||
"params": {
|
||||
"modelName": "Obsidian-spaced",
|
||||
"modelName": `Obsidian-spaced${extension}`,
|
||||
"inOrderFields": ["Prompt"],
|
||||
"css": css,
|
||||
"cardTemplates": [
|
||||
|
||||
@ -3,7 +3,6 @@ import { App, FileSystemAdapter, FrontMatterCache, Notice, parseFrontMatterEntry
|
||||
import { Parser } from 'src/services/parser'
|
||||
import { ISettings } from 'src/settings'
|
||||
import { Card } from 'src/entities/card'
|
||||
import { Flashcard } from 'src/entities/flashcard'
|
||||
import { arrayBufferToBase64 } from "src/utils"
|
||||
import { Regex } from 'src/regex'
|
||||
import { noticeTimeout } from 'src/constants'
|
||||
@ -57,7 +56,8 @@ export class CardsService {
|
||||
}
|
||||
|
||||
try {
|
||||
await this.anki.createModels()
|
||||
await this.anki.storeCodeHighlightMedias()
|
||||
await this.anki.createModels(this.settings.codeHighlightSupport)
|
||||
await this.anki.createDeck(deckName)
|
||||
this.file = await this.app.vault.read(activeFile)
|
||||
// TODO with empty check that does not call ankiCards line
|
||||
|
||||
@ -17,6 +17,7 @@ export class Parser {
|
||||
this.htmlConverter.setOption("simplifiedAutoLink", true)
|
||||
this.htmlConverter.setOption("tables", true)
|
||||
this.htmlConverter.setOption("tasks", true)
|
||||
this.htmlConverter.setOption("ghCodeBlocks", true)
|
||||
}
|
||||
|
||||
public generateFlashcards(file: string, deck: string, vault: string, globalTags: string[] = []): Flashcard[] {
|
||||
@ -106,8 +107,9 @@ export class Parser {
|
||||
let id: number = match[5] ? Number(match[5]) : -1
|
||||
let inserted: boolean = match[5] ? true : false
|
||||
let fields = { "Prompt": prompt }
|
||||
let containsCode = this.containsCode([prompt])
|
||||
|
||||
let card = new Spacedcard(id, deck, originalPrompt, fields, reversed, endingLine, tags, inserted, imagesMedia)
|
||||
let card = new Spacedcard(id, deck, originalPrompt, fields, reversed, endingLine, tags, inserted, imagesMedia, containsCode)
|
||||
cards.push(card)
|
||||
}
|
||||
|
||||
@ -141,8 +143,9 @@ export class Parser {
|
||||
let id: number = match[5] ? Number(match[5]) : -1
|
||||
let inserted: boolean = match[5] ? true : false
|
||||
let fields = { "Front": question, "Back": answer }
|
||||
let containsCode = this.containsCode([question, answer])
|
||||
|
||||
let card = new Inlinecard(id, deck, originalQuestion, fields, reversed, endingLine, tags, inserted, imagesMedia)
|
||||
let card = new Inlinecard(id, deck, originalQuestion, fields, reversed, endingLine, tags, inserted, imagesMedia, containsCode)
|
||||
cards.push(card)
|
||||
}
|
||||
|
||||
@ -173,21 +176,31 @@ export class Parser {
|
||||
let id: number = match[6] ? Number(match[6]) : -1
|
||||
let inserted: boolean = match[6] ? true : false
|
||||
let fields = { "Front": question, "Back": answer }
|
||||
let containsCode = this.containsCode([question, answer])
|
||||
|
||||
let card = new Flashcard(id, deck, originalQuestion, fields, reversed, endingLine, tags, inserted, imagesMedia)
|
||||
let card = new Flashcard(id, deck, originalQuestion, fields, reversed, endingLine, tags, inserted, imagesMedia, containsCode)
|
||||
cards.push(card)
|
||||
}
|
||||
|
||||
return cards
|
||||
}
|
||||
|
||||
public containsCode(str: string[]): boolean {
|
||||
for (let s of str) {
|
||||
if (s.match(this.regex.codeBlock)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public getCardsToDelete(file: string): number[] {
|
||||
// Find block IDs with no content above it
|
||||
return [...file.matchAll(this.regex.cardsToDelete)].map((match) => { return Number(match[1]) })
|
||||
}
|
||||
|
||||
private parseLine(str: string, vaultName: string) {
|
||||
return this.mathToAnki(this.substituteObsidianLinks(this.substituteImageLinks(str), vaultName))
|
||||
return this.mathToAnki(this.htmlConverter.makeHtml(this.substituteObsidianLinks(this.substituteImageLinks(str), vaultName)))
|
||||
}
|
||||
|
||||
private getImageLinks(str: string) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export interface ISettings {
|
||||
contextAwareMode: boolean
|
||||
codeHighlightSupport: boolean
|
||||
contextSeparator: string
|
||||
deck: string
|
||||
flashcardsTag: string
|
||||
|
||||
Loading…
Reference in New Issue
Block a user