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**
|
||||
✍️ Inline style with **Question::Answer**
|
||||
✍️ Inline style reversed with **Question:::Answer**
|
||||
📃 Cloze with **==Highlight==** or **{Curly brackets}** or **{2:Cloze}**
|
||||
🧠 **Context-aware** mode
|
||||
🏷️ Global and local **tags**
|
||||
|
||||
🔢 Support for **LaTeX**
|
||||
🖼️ Support for **images**
|
||||
🎤 Support for **audios**
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "flashcards-obsidian",
|
||||
"name": "Flashcards",
|
||||
"version": "1.5.6",
|
||||
"version": "1.6.0",
|
||||
"minAppVersion": "0.9.17",
|
||||
"description": "Anki integration",
|
||||
"author": "Alex Colucci",
|
||||
|
||||
@ -28,7 +28,7 @@ export class Clozecard extends Card {
|
||||
mediaNames,
|
||||
containsCode
|
||||
);
|
||||
this.modelName = `Obsidian-clozed`;
|
||||
this.modelName = `Obsidian-cloze`;
|
||||
if (fields["Source"]) {
|
||||
this.modelName += sourceDeckExtension;
|
||||
}
|
||||
@ -69,6 +69,6 @@ export class Clozecard extends Card {
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
// 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}))?"
|
||||
// this.cardsClozeWholeLine = new RegExp(str, flags);
|
||||
str = "( {0,3}[#]{0,6})?(?:(?:[\\t ]*)(?:\\d.|[-+*]|#{1,6}))?(.*?(==.+?==|\\{.+?\\}).*?)((?: *#[\\w\\-\\/_]+)+|$)(?:\n\\^(\\d{13}))?"
|
||||
this.cardsClozeWholeLine = new RegExp(str, flags);
|
||||
|
||||
// this.singleClozeCurly = /((?:{)(?:(\d):?)?(.+?)(?:}))/g;
|
||||
// this.singleClozeHighlight = /((?:==)(.+?)(?:==))/g;
|
||||
this.singleClozeCurly = /((?:{)(?:(\d):?)?(.+?)(?:}))/g;
|
||||
this.singleClozeHighlight = /((?:==)(.+?)(?:==))/g;
|
||||
}
|
||||
}
|
||||
@ -233,19 +233,23 @@ export class Anki {
|
||||
}
|
||||
|
||||
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 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 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 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 promptFields = ["Prompt"];
|
||||
let clozeFields = ["Text", "Extra"];
|
||||
if (sourceSupport) {
|
||||
classicFields = classicFields.concat("Source");
|
||||
promptFields = promptFields.concat("Source");
|
||||
clozeFields = clozeFields.concat("Source");
|
||||
}
|
||||
|
||||
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 = {
|
||||
action: "createModel",
|
||||
params: {
|
||||
@ -301,10 +323,10 @@ export class Anki {
|
||||
},
|
||||
};
|
||||
|
||||
return [obsidianBasic, obsidianBasicReversed, obsidianSpaced];
|
||||
return [obsidianBasic, obsidianBasicReversed, obsidianCloze, obsidianSpaced];
|
||||
}
|
||||
|
||||
public async requestPermission() {
|
||||
return await this.invoke("requestPermission", 6);
|
||||
return this.invoke("requestPermission", 6);
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,12 +52,12 @@ export class Parser {
|
||||
cards = cards.concat(
|
||||
this.generateSpacedCards(file, headings, deck, vault, note, globalTags)
|
||||
);
|
||||
// cards = cards.concat(
|
||||
// this.generateClozeCards(file, headings, deck, vault, note, globalTags)
|
||||
// );
|
||||
cards = cards.concat(
|
||||
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 rangesToDiscard = codeBlocks.map(x=>([x.index, x.index+x[0].length]))
|
||||
cards = cards.filter(card => {
|
||||
@ -194,6 +194,87 @@ export class Parser {
|
||||
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(
|
||||
file: string,
|
||||
headings: any,
|
||||
@ -398,8 +479,7 @@ export class Parser {
|
||||
filename
|
||||
)}.md`;
|
||||
const fileRename = rename ? rename : filename;
|
||||
const link = `<a href="${href}">[[${fileRename}]]</a>`;
|
||||
return link;
|
||||
return `<a href="${href}">[[${fileRename}]]</a>`;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user