mirror of
https://github.com/gosticks/flashcards-obsidian.git
synced 2025-10-16 12:05:33 +00:00
Add support for Cloze #9
This commit is contained in:
parent
2092d4c1ad
commit
2cfdb79fff
@ -13,8 +13,10 @@ Anki integration for [Obsidian](https://obsidian.md/).
|
|||||||
📅 Spaced-only cards with **#card-spaced** or **#card/spaced**
|
📅 Spaced-only cards with **#card-spaced** or **#card/spaced**
|
||||||
✍️ Inline style with **Question::Answer**
|
✍️ Inline style with **Question::Answer**
|
||||||
✍️ Inline style reversed with **Question:::Answer**
|
✍️ Inline style reversed with **Question:::Answer**
|
||||||
|
📃 Cloze with **==Highlight==** or **{Curly brackets}** or **{2:Cloze}**
|
||||||
🧠 **Context-aware** mode
|
🧠 **Context-aware** mode
|
||||||
🏷️ Global and local **tags**
|
🏷️ Global and local **tags**
|
||||||
|
|
||||||
🔢 Support for **LaTeX**
|
🔢 Support for **LaTeX**
|
||||||
🖼️ Support for **images**
|
🖼️ Support for **images**
|
||||||
🎤 Support for **audios**
|
🎤 Support for **audios**
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"id": "flashcards-obsidian",
|
"id": "flashcards-obsidian",
|
||||||
"name": "Flashcards",
|
"name": "Flashcards",
|
||||||
"version": "1.5.6",
|
"version": "1.6.0",
|
||||||
"minAppVersion": "0.9.17",
|
"minAppVersion": "0.9.17",
|
||||||
"description": "Anki integration",
|
"description": "Anki integration",
|
||||||
"author": "Alex Colucci",
|
"author": "Alex Colucci",
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export class Clozecard extends Card {
|
|||||||
mediaNames,
|
mediaNames,
|
||||||
containsCode
|
containsCode
|
||||||
);
|
);
|
||||||
this.modelName = `Obsidian-clozed`;
|
this.modelName = `Obsidian-cloze`;
|
||||||
if (fields["Source"]) {
|
if (fields["Source"]) {
|
||||||
this.modelName += sourceDeckExtension;
|
this.modelName += sourceDeckExtension;
|
||||||
}
|
}
|
||||||
@ -69,6 +69,6 @@ export class Clozecard extends Card {
|
|||||||
};
|
};
|
||||||
|
|
||||||
public getIdFormat(): string {
|
public getIdFormat(): string {
|
||||||
return "^" + this.id.toString() + "\n";
|
return "\n^" + this.id.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
src/regex.ts
10
src/regex.ts
@ -80,13 +80,11 @@ export class Regex {
|
|||||||
this.cardsSpacedStyle = new RegExp(str, flags);
|
this.cardsSpacedStyle = new RegExp(str, flags);
|
||||||
|
|
||||||
// https://regex101.com/r/cgtnLf/1
|
// https://regex101.com/r/cgtnLf/1
|
||||||
// str = "(?:.*?(==(.*?)==).*)(?:\n?^(d{13}))?";
|
|
||||||
// this.clozeHighlight = /((==)(.*?)(==))/gm;
|
|
||||||
|
|
||||||
// str = "( {0,3}[#]{0,6})?(?:(?:[\t ]*)(?:\\d.|[-+*]|#{1,6}))?(.*?(==.+?==|{.+?}).*?)((?: *#[\\w-]+)+|$)(?:\n\\^(?:\\d{13}))?"
|
str = "( {0,3}[#]{0,6})?(?:(?:[\\t ]*)(?:\\d.|[-+*]|#{1,6}))?(.*?(==.+?==|\\{.+?\\}).*?)((?: *#[\\w\\-\\/_]+)+|$)(?:\n\\^(\\d{13}))?"
|
||||||
// this.cardsClozeWholeLine = new RegExp(str, flags);
|
this.cardsClozeWholeLine = new RegExp(str, flags);
|
||||||
|
|
||||||
// this.singleClozeCurly = /((?:{)(?:(\d):?)?(.+?)(?:}))/g;
|
this.singleClozeCurly = /((?:{)(?:(\d):?)?(.+?)(?:}))/g;
|
||||||
// this.singleClozeHighlight = /((?:==)(.+?)(?:==))/g;
|
this.singleClozeHighlight = /((?:==)(.+?)(?:==))/g;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -233,19 +233,23 @@ export class Anki {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const css =
|
const 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';
|
'.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 .cloze { font-weight: bold; color: blue;}.nightMode .cloze { color: lightblue;}';
|
||||||
const 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>${codeScriptContent}`;
|
const 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>${codeScriptContent}`;
|
||||||
const back = `{{FrontSide}}\n\n<hr id=answer>\n\n{{Back}}${sourceFieldContent}`;
|
const back = `{{FrontSide}}\n\n<hr id=answer>\n\n{{Back}}${sourceFieldContent}`;
|
||||||
const 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>${codeScriptContent}`;
|
const 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>${codeScriptContent}`;
|
||||||
const backReversed = `{{FrontSide}}\n\n<hr id=answer>\n\n{{Front}}${sourceFieldContent}`;
|
const backReversed = `{{FrontSide}}\n\n<hr id=answer>\n\n{{Front}}${sourceFieldContent}`;
|
||||||
const 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>${codeScriptContent}`;
|
const 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>${codeScriptContent}`;
|
||||||
const promptBack = `{{FrontSide}}\n\n<hr id=answer>🧠 Review done.${sourceFieldContent}`;
|
const promptBack = `{{FrontSide}}\n\n<hr id=answer>🧠 Review done.${sourceFieldContent}`;
|
||||||
|
const clozeFront = `{{cloze:Text}}\n\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>${codeScriptContent}`;
|
||||||
|
const clozeBack = `{{cloze:Text}}\n\n<br>{{Extra}}${sourceFieldContent}<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>${codeScriptContent}`;
|
||||||
|
|
||||||
let classicFields = ["Front", "Back"];
|
let classicFields = ["Front", "Back"];
|
||||||
let promptFields = ["Prompt"];
|
let promptFields = ["Prompt"];
|
||||||
|
let clozeFields = ["Text", "Extra"];
|
||||||
if (sourceSupport) {
|
if (sourceSupport) {
|
||||||
classicFields = classicFields.concat("Source");
|
classicFields = classicFields.concat("Source");
|
||||||
promptFields = promptFields.concat("Source");
|
promptFields = promptFields.concat("Source");
|
||||||
|
clozeFields = clozeFields.concat("Source");
|
||||||
}
|
}
|
||||||
|
|
||||||
const obsidianBasic = {
|
const obsidianBasic = {
|
||||||
@ -285,6 +289,24 @@ export class Anki {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const obsidianCloze = {
|
||||||
|
action: "createModel",
|
||||||
|
params: {
|
||||||
|
modelName: `Obsidian-cloze${sourceExtension}${codeExtension}`,
|
||||||
|
inOrderFields: clozeFields,
|
||||||
|
css: css,
|
||||||
|
isCloze: true,
|
||||||
|
cardTemplates: [
|
||||||
|
{
|
||||||
|
Name: "Cloze",
|
||||||
|
Front: clozeFront,
|
||||||
|
Back: clozeBack,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
const obsidianSpaced = {
|
const obsidianSpaced = {
|
||||||
action: "createModel",
|
action: "createModel",
|
||||||
params: {
|
params: {
|
||||||
@ -301,10 +323,10 @@ export class Anki {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return [obsidianBasic, obsidianBasicReversed, obsidianSpaced];
|
return [obsidianBasic, obsidianBasicReversed, obsidianCloze, obsidianSpaced];
|
||||||
}
|
}
|
||||||
|
|
||||||
public async requestPermission() {
|
public async requestPermission() {
|
||||||
return await this.invoke("requestPermission", 6);
|
return this.invoke("requestPermission", 6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,12 +52,12 @@ export class Parser {
|
|||||||
cards = cards.concat(
|
cards = cards.concat(
|
||||||
this.generateSpacedCards(file, headings, deck, vault, note, globalTags)
|
this.generateSpacedCards(file, headings, deck, vault, note, globalTags)
|
||||||
);
|
);
|
||||||
// cards = cards.concat(
|
cards = cards.concat(
|
||||||
// this.generateClozeCards(file, headings, deck, vault, note, globalTags)
|
this.generateClozeCards(file, headings, deck, vault, note, globalTags)
|
||||||
// );
|
);
|
||||||
|
|
||||||
|
|
||||||
// Filter out cards that are fully inside a code block
|
// Filter out cards that are fully inside a code block (that's needed for the )
|
||||||
const codeBlocks = [...file.matchAll(this.regex.obsidianCodeBlock)];
|
const codeBlocks = [...file.matchAll(this.regex.obsidianCodeBlock)];
|
||||||
const rangesToDiscard = codeBlocks.map(x=>([x.index, x.index+x[0].length]))
|
const rangesToDiscard = codeBlocks.map(x=>([x.index, x.index+x[0].length]))
|
||||||
cards = cards.filter(card => {
|
cards = cards.filter(card => {
|
||||||
@ -194,6 +194,87 @@ export class Parser {
|
|||||||
return cards;
|
return cards;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private generateClozeCards(
|
||||||
|
file: string,
|
||||||
|
headings: any,
|
||||||
|
deck: string,
|
||||||
|
vault: string,
|
||||||
|
note: string,
|
||||||
|
globalTags: string[] = []
|
||||||
|
) {
|
||||||
|
const contextAware = this.settings.contextAwareMode;
|
||||||
|
const cards: Clozecard[] = [];
|
||||||
|
const matches = [...file.matchAll(this.regex.cardsClozeWholeLine)];
|
||||||
|
|
||||||
|
for (const match of matches) {
|
||||||
|
const reversed = false;
|
||||||
|
let headingLevel = -1;
|
||||||
|
if (match[1]) {
|
||||||
|
headingLevel =
|
||||||
|
match[1].trim().length !== 0 ? match[1].trim().length : -1;
|
||||||
|
}
|
||||||
|
// Match.index - 1 because otherwise in the context there will be even match[1], i.e. the question itself
|
||||||
|
const context = contextAware
|
||||||
|
? this.getContext(headings, match.index - 1, headingLevel)
|
||||||
|
: "";
|
||||||
|
|
||||||
|
|
||||||
|
// Replace the curly in the line with Anki syntax
|
||||||
|
let clozeText = match[2].replace(this.regex.singleClozeCurly, (match, g1, g2, g3) => {
|
||||||
|
console.log("group1 :" + g1);
|
||||||
|
console.log("group2 :" + g2);
|
||||||
|
console.log("group3 :" + g3);
|
||||||
|
if (g2) {
|
||||||
|
return `{{c${g2}::${g3}}}`;
|
||||||
|
} else {
|
||||||
|
return `{{c1::${g3}}}`;
|
||||||
|
}
|
||||||
|
} );
|
||||||
|
|
||||||
|
// Replace the highlight clozes in the line with Anki syntax
|
||||||
|
clozeText = clozeText.replace(this.regex.singleClozeHighlight, "{{c1::$2}}");
|
||||||
|
|
||||||
|
const originalLine = match[2].trim();
|
||||||
|
// Add context
|
||||||
|
clozeText = contextAware
|
||||||
|
? [...context, clozeText.trim()].join(
|
||||||
|
`${this.settings.contextSeparator}`
|
||||||
|
)
|
||||||
|
: clozeText.trim();
|
||||||
|
let medias: string[] = this.getImageLinks(clozeText);
|
||||||
|
medias = medias.concat(this.getAudioLinks(clozeText));
|
||||||
|
clozeText = this.parseLine(clozeText, vault);
|
||||||
|
|
||||||
|
const initialOffset = match.index;
|
||||||
|
const endingLine = match.index + match[0].length;
|
||||||
|
const tags: string[] = this.parseTags(match[4], globalTags);
|
||||||
|
const id: number = match[5] ? Number(match[5]) : -1;
|
||||||
|
const inserted: boolean = match[5] ? true : false;
|
||||||
|
const fields: any = { Text: clozeText, Extra: "" };
|
||||||
|
if (this.settings.sourceSupport) {
|
||||||
|
fields["Source"] = note;
|
||||||
|
}
|
||||||
|
const containsCode = this.containsCode([clozeText]);
|
||||||
|
|
||||||
|
const card = new Clozecard(
|
||||||
|
id,
|
||||||
|
deck,
|
||||||
|
originalLine,
|
||||||
|
fields,
|
||||||
|
reversed,
|
||||||
|
initialOffset,
|
||||||
|
endingLine,
|
||||||
|
tags,
|
||||||
|
inserted,
|
||||||
|
medias,
|
||||||
|
containsCode
|
||||||
|
);
|
||||||
|
cards.push(card);
|
||||||
|
}
|
||||||
|
|
||||||
|
return cards;
|
||||||
|
}
|
||||||
|
|
||||||
private generateInlineCards(
|
private generateInlineCards(
|
||||||
file: string,
|
file: string,
|
||||||
headings: any,
|
headings: any,
|
||||||
@ -398,8 +479,7 @@ export class Parser {
|
|||||||
filename
|
filename
|
||||||
)}.md`;
|
)}.md`;
|
||||||
const fileRename = rename ? rename : filename;
|
const fileRename = rename ? rename : filename;
|
||||||
const link = `<a href="${href}">[[${fileRename}]]</a>`;
|
return `<a href="${href}">[[${fileRename}]]</a>`;
|
||||||
return link;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user