Skip to content

Commit

Permalink
Add support for Cloze #9
Browse files Browse the repository at this point in the history
  • Loading branch information
[email protected] committed Feb 4, 2022
1 parent 2092d4c commit 2cfdb79
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 19 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand Down
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
4 changes: 2 additions & 2 deletions src/entities/clozecard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -69,6 +69,6 @@ export class Clozecard extends Card {
};

public getIdFormat(): string {
return "^" + this.id.toString() + "\n";
return "\n^" + this.id.toString();
}
}
10 changes: 4 additions & 6 deletions src/regex.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
30 changes: 26 additions & 4 deletions src/services/anki.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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: {
Expand All @@ -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);
}
}
92 changes: 86 additions & 6 deletions src/services/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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>`;
});
}

Expand Down

0 comments on commit 2cfdb79

Please sign in to comment.