Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add custom cloze tokens #607

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Fight the forgetting curve & note aging by reviewing flashcards & notes using sp
- [Single-line reversed style](https://www.stephenmwangi.com/obsidian-spaced-repetition/flashcards/#single-line-reversed) (`Question:::Answer`)
- [Multi-line style](https://www.stephenmwangi.com/obsidian-spaced-repetition/flashcards/#multi-line-basic) (Separated by `?`)
- [Multi-line reversed style](https://www.stephenmwangi.com/obsidian-spaced-repetition/flashcards/#multi-line-reversed) (Separated by `??`)
- [Cloze cards](https://www.stephenmwangi.com/obsidian-spaced-repetition/flashcards/#cloze-cards) (`==highlight==` your cloze deletions!, `**bolded text**`, or `{{text in curly braces}}`)
- [Cloze cards](https://www.stephenmwangi.com/obsidian-spaced-repetition/flashcards/#cloze-cards) (`==highlight==` your cloze deletions!, `**bolded text**`, `{{text in curly braces}}`, or `{=([text in user defined tokens}=])`)
- [Card context - automatic titles based on headings](https://www.stephenmwangi.com/obsidian-spaced-repetition/flashcards/#context) (i.e. `Note title > Heading 1 > Subheading`)
- Rich text support in flashcards (inherited from Obsidian)
- Images, Audio, & Video
Expand Down
2 changes: 1 addition & 1 deletion docs/en/flashcards.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Note: The behaviour is same as single line reversed.

### Cloze cards

You can easily add cloze deletion cards using `==highlights==`, `**bolded text**`, or `{{text in curly braces}}`.
You can easily add cloze deletion cards using `==highlights==`, `**bolded text**`, `{{text in curly braces}}`, or `{=([text in user defined tokens}=])`.

These can be turned on or off in settings.

Expand Down
2 changes: 2 additions & 0 deletions src/NoteQuestionParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ export class NoteQuestionParser {
settings.convertHighlightsToClozes,
settings.convertBoldTextToClozes,
settings.convertCurlyBracketsToClozes,
settings.clozeOpeningToken,
settings.clozeClosingToken,
);
return result;
}
Expand Down
21 changes: 19 additions & 2 deletions src/QuestionType.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CardType } from "./Question";
import { SRSettings } from "./settings";
import { escapeRegexString } from "./util/utils";

export class CardFrontBack {
front: string;
Expand Down Expand Up @@ -93,6 +94,18 @@ class QuestionType_Cloze implements IQuestionTypeHandler {
if (settings.convertCurlyBracketsToClozes) {
siblings.push(...questionText.matchAll(/{{(.*?)}}/gm));
}
if (settings.clozeOpeningToken !== "" && settings.clozeClosingToken !== "") {
siblings.push(
...questionText.matchAll(
new RegExp(
`${escapeRegexString(settings.clozeOpeningToken)}.*?${escapeRegexString(
settings.clozeClosingToken,
)}`,
"gm",
),
),
);
}
siblings.sort((a, b) => {
if (a.index < b.index) {
return -1;
Expand All @@ -117,7 +130,9 @@ class QuestionType_Cloze implements IQuestionTypeHandler {
.replace(/==/gm, "")
.replace(/\*\*/gm, "")
.replace(/{{/gm, "")
.replace(/}}/gm, "");
.replace(/}}/gm, "")
.replaceAll(settings.clozeOpeningToken, "")
.replaceAll(settings.clozeClosingToken, "");
back =
questionText.substring(0, deletionStart) +
QuestionType_ClozeUtil.renderClozeBack(
Expand All @@ -128,7 +143,9 @@ class QuestionType_Cloze implements IQuestionTypeHandler {
.replace(/==/gm, "")
.replace(/\*\*/gm, "")
.replace(/{{/gm, "")
.replace(/}}/gm, "");
.replace(/}}/gm, "")
.replaceAll(settings.clozeOpeningToken, "")
.replaceAll(settings.clozeClosingToken, "");
result.push(new CardFrontBack(front, back));
}

Expand Down
3 changes: 3 additions & 0 deletions src/lang/locale/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ export default {
CONVERT_HIGHLIGHTS_TO_CLOZES: "Convert ==hightlights== to clozes?",
CONVERT_BOLD_TEXT_TO_CLOZES: "Convert **bolded text** to clozes?",
CONVERT_CURLY_BRACKETS_TO_CLOZES: "Convert {{curly brackets}} to clozes?",
CLOZE_OPENING_TOKEN: "Opening token for clozes",
CLOZE_CLOSING_TOKEN: "Closing token for clozes",
CLOZE_TOKEN_DISABLED: "(Disabled)",
INLINE_CARDS_SEPARATOR: "فاصل من أجل البطاقات المضمنة",
FIX_SEPARATORS_MANUALLY_WARNING:
"ضع في حسابك أنه بعد تغيير هذا ، يجب عليك تعديل أي بطاقات لديك بالفعل يدويًا",
Expand Down
3 changes: 3 additions & 0 deletions src/lang/locale/cz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ export default {
CONVERT_HIGHLIGHTS_TO_CLOZES: "Převést ==zvýraznění== na clozes?",
CONVERT_BOLD_TEXT_TO_CLOZES: "Převést **tučný text** na clozes?",
CONVERT_CURLY_BRACKETS_TO_CLOZES: "Převést {{složené závorky}} na clozes?",
CLOZE_OPENING_TOKEN: "Opening token for clozes",
CLOZE_CLOSING_TOKEN: "Closing token for clozes",
CLOZE_TOKEN_DISABLED: "(Disabled)",
INLINE_CARDS_SEPARATOR: "Oddělovač pro inline kartičky",
FIX_SEPARATORS_MANUALLY_WARNING:
"Pozor. Jakmile toto změníte, budete muset ručně upravit všechny existující kartičky.",
Expand Down
3 changes: 3 additions & 0 deletions src/lang/locale/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ export default {
CONVERT_BOLD_TEXT_TO_CLOZES: "**Fettgedruckten** Text in Lückentextkarten umwandeln?",
CONVERT_CURLY_BRACKETS_TO_CLOZES:
"{{Geschweifte Klammern}} Text in Lückentextkarten umwandeln?",
CLOZE_OPENING_TOKEN: "Opening token for clozes",
CLOZE_CLOSING_TOKEN: "Closing token for clozes",
CLOZE_TOKEN_DISABLED: "(Disabled)",
INLINE_CARDS_SEPARATOR: "Trennzeichen für einzeilige Lernkarten",
FIX_SEPARATORS_MANUALLY_WARNING:
"Wenn diese Einstellung geändert wird, dann müssen die entsprechenden Lernkarten manuell angepasst werden.",
Expand Down
3 changes: 3 additions & 0 deletions src/lang/locale/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ export default {
CONVERT_HIGHLIGHTS_TO_CLOZES: "Convert ==hightlights== to clozes?",
CONVERT_BOLD_TEXT_TO_CLOZES: "Convert **bolded text** to clozes?",
CONVERT_CURLY_BRACKETS_TO_CLOZES: "Convert {{curly brackets}} to clozes?",
CLOZE_OPENING_TOKEN: "Opening token for clozes",
CLOZE_CLOSING_TOKEN: "Closing token for clozes",
CLOZE_TOKEN_DISABLED: "(Disabled)",
INLINE_CARDS_SEPARATOR: "Separator for inline flashcards",
FIX_SEPARATORS_MANUALLY_WARNING:
"Note that after changing this you have to manually edit any flashcards you already have.",
Expand Down
3 changes: 3 additions & 0 deletions src/lang/locale/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,9 @@ export default {
CONVERT_HIGHLIGHTS_TO_CLOZES: "¿Convertir ==resaltados== a deletreo de huecos?",
CONVERT_BOLD_TEXT_TO_CLOZES: "¿Convertir **texto en negrita** a deletreo de huecos?",
CONVERT_CURLY_BRACKETS_TO_CLOZES: "¿Convertir {{llaves rizadas}} a deletreo de huecos?",
CLOZE_OPENING_TOKEN: "Opening token for clozes",
CLOZE_CLOSING_TOKEN: "Closing token for clozes",
CLOZE_TOKEN_DISABLED: "(Disabled)",
INLINE_CARDS_SEPARATOR: "Separador de tarjetas de memorización en línea",
FIX_SEPARATORS_MANUALLY_WARNING:
"Note que después de cambiar este ajuste, tendrá que cambiar manualmente todas las notas que tenga.",
Expand Down
3 changes: 3 additions & 0 deletions src/lang/locale/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ export default {
CONVERT_HIGHLIGHTS_TO_CLOZES: "==ハイライト==を穴埋めとして使用しますか?",
CONVERT_BOLD_TEXT_TO_CLOZES: "**ボールド体**を穴埋めとして使用しますか?",
CONVERT_CURLY_BRACKETS_TO_CLOZES: "{{中括弧}}を穴埋めとして使用しますか?",
CLOZE_OPENING_TOKEN: "Opening token for clozes",
CLOZE_CLOSING_TOKEN: "Closing token for clozes",
CLOZE_TOKEN_DISABLED: "(Disabled)",
INLINE_CARDS_SEPARATOR: "インラインフラッシュカードに使用するセパレーター",
FIX_SEPARATORS_MANUALLY_WARNING:
"このオプションを変更する場合には、作成済みのフラッシュカードを手動で編集し直す必要があることに注意してください。",
Expand Down
3 changes: 3 additions & 0 deletions src/lang/locale/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@ export default {
CONVERT_HIGHLIGHTS_TO_CLOZES: "==hightlights== 를 빈 칸 채우기로 전환하시겠습니까?",
CONVERT_BOLD_TEXT_TO_CLOZES: "**bolded text** 를 빈 칸 채우기로 전환하시겠습니까?",
CONVERT_CURLY_BRACKETS_TO_CLOZES: "{{curly brackets}} 를 빈 칸 채우기로 전환하시겠습니까?",
CLOZE_OPENING_TOKEN: "Opening token for clozes",
CLOZE_CLOSING_TOKEN: "Closing token for clozes",
CLOZE_TOKEN_DISABLED: "(Disabled)",
INLINE_CARDS_SEPARATOR: "인라인 플래시카드 구분자",
FIX_SEPARATORS_MANUALLY_WARNING:
"주의: 이 옵션을 수정한 후에는 이미 작성된 플래시카드를 수동으로 수정해야 함을 주의하십시오.",
Expand Down
3 changes: 3 additions & 0 deletions src/lang/locale/pt-br.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ export default {
CONVERT_HIGHLIGHTS_TO_CLOZES: "Converter ==marca-texto== em omissões?",
CONVERT_BOLD_TEXT_TO_CLOZES: "Converter **texto em negrito** em omissões?",
CONVERT_CURLY_BRACKETS_TO_CLOZES: "Converter {{chaves}} em omissões?",
CLOZE_OPENING_TOKEN: "Opening token for clozes",
CLOZE_CLOSING_TOKEN: "Closing token for clozes",
CLOZE_TOKEN_DISABLED: "(Disabled)",
INLINE_CARDS_SEPARATOR: "Separador para flashcards inline",
FIX_SEPARATORS_MANUALLY_WARNING:
"Note que depois de mudar isso você vai ter que manualmente mudar quaisquer flashcards que você tenha.",
Expand Down
3 changes: 3 additions & 0 deletions src/lang/locale/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ export default {
CONVERT_BOLD_TEXT_TO_CLOZES: "Конвертировать **жирный текст** в пропуски (пример: [...])?",
CONVERT_CURLY_BRACKETS_TO_CLOZES:
"Конвертировать {{фигурные скобки}} в пропуски (пример: [...])?",
CLOZE_OPENING_TOKEN: "Opening token for clozes",
CLOZE_CLOSING_TOKEN: "Closing token for clozes",
CLOZE_TOKEN_DISABLED: "(Disabled)",
INLINE_CARDS_SEPARATOR: "Разделитель для внутристрочных карточек",
FIX_SEPARATORS_MANUALLY_WARNING:
"Внимание! После изменения этого вам придётся вручную редактировать уже существующие карточки",
Expand Down
3 changes: 3 additions & 0 deletions src/lang/locale/zh-cn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ export default {
CONVERT_HIGHLIGHTS_TO_CLOZES: "将 ==高亮== 转换为完形填空?",
CONVERT_BOLD_TEXT_TO_CLOZES: "将 **粗体** 转换为完形填空?",
CONVERT_CURLY_BRACKETS_TO_CLOZES: "将 {{大括号}} 转换为完形填空?",
CLOZE_OPENING_TOKEN: "完形填空的开始符",
CLOZE_CLOSING_TOKEN: "完形填空的结束符",
CLOZE_TOKEN_DISABLED: "(已禁用)",
INLINE_CARDS_SEPARATOR: "单行卡片的分隔符",
FIX_SEPARATORS_MANUALLY_WARNING: "注意:更改此选项后你将需要自行更改已存在卡片的分隔符。",
INLINE_REVERSED_CARDS_SEPARATOR: "单行翻转卡片的分隔符",
Expand Down
3 changes: 3 additions & 0 deletions src/lang/locale/zh-tw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ export default {
CONVERT_HIGHLIGHTS_TO_CLOZES: "將 ==高亮== 轉換為填空克漏字?",
CONVERT_BOLD_TEXT_TO_CLOZES: "將 **粗體** 轉換為填空克漏字?",
CONVERT_CURLY_BRACKETS_TO_CLOZES: "將 {{大括號}} 轉換為填空克漏字?",
CLOZE_OPENING_TOKEN: "填空克漏字的開始字元",
CLOZE_CLOSING_TOKEN: "填空克漏字的結束字元",
CLOZE_TOKEN_DISABLED: "(已禁用)",
INLINE_CARDS_SEPARATOR: "單行卡片的分隔字元",
FIX_SEPARATORS_MANUALLY_WARNING: "注意:更改此選項後你將需要自行更改已存在卡片的分隔字元。",
INLINE_REVERSED_CARDS_SEPARATOR: "單行反轉卡片的分隔字元",
Expand Down
13 changes: 12 additions & 1 deletion src/parser.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CardType } from "./Question";
import { escapeRegexString } from "./util/utils";

/**
* Returns flashcards found in `text`
Expand All @@ -19,6 +20,8 @@ export function parse(
convertHighlightsToClozes: boolean,
convertBoldTextToClozes: boolean,
convertCurlyBracketsToClozes: boolean,
clozeOpeningToken: string,
clozeClosingToken: string,
): [CardType, string, number][] {
let cardText = "";
const cards: [CardType, string, number][] = [];
Expand Down Expand Up @@ -67,7 +70,15 @@ export function parse(
cardType === null &&
((convertHighlightsToClozes && /==.*?==/gm.test(currentLine)) ||
(convertBoldTextToClozes && /\*\*.*?\*\*/gm.test(currentLine)) ||
(convertCurlyBracketsToClozes && /{{.*?}}/gm.test(currentLine)))
(convertCurlyBracketsToClozes && /{{.*?}}/gm.test(currentLine)) ||
(clozeOpeningToken !== "" &&
clozeClosingToken !== "" &&
new RegExp(
`${escapeRegexString(clozeOpeningToken)}.*?${escapeRegexString(
clozeClosingToken,
)}`,
"gm",
).test(currentLine)))
) {
cardType = CardType.Cloze;
lineNo = i;
Expand Down
56 changes: 56 additions & 0 deletions src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export interface SRSettings {
convertHighlightsToClozes: boolean;
convertBoldTextToClozes: boolean;
convertCurlyBracketsToClozes: boolean;
clozeOpeningToken: string;
clozeClosingToken: string;
singleLineCardSeparator: string;
singleLineReversedCardSeparator: string;
multilineCardSeparator: string;
Expand Down Expand Up @@ -59,6 +61,8 @@ export const DEFAULT_SETTINGS: SRSettings = {
convertHighlightsToClozes: true,
convertBoldTextToClozes: false,
convertCurlyBracketsToClozes: false,
clozeOpeningToken: "",
clozeClosingToken: "",
singleLineCardSeparator: "::",
singleLineReversedCardSeparator: ":::",
multilineCardSeparator: "?",
Expand Down Expand Up @@ -280,6 +284,58 @@ export class SRSettingTab extends PluginSettingTab {
}),
);

new Setting(containerEl)
.setName(t("CLOZE_OPENING_TOKEN"))
.setDesc(t("FIX_SEPARATORS_MANUALLY_WARNING"))
.addText((text) =>
text
.setPlaceholder(t("CLOZE_TOKEN_DISABLED"))
.setValue(this.plugin.data.settings.clozeOpeningToken)
.onChange((value) => {
applySettingsUpdate(async () => {
this.plugin.data.settings.clozeOpeningToken = value;
await this.plugin.savePluginData();
});
}),
)
.addExtraButton((button) => {
button
.setIcon("reset")
.setTooltip(t("RESET_DEFAULT"))
.onClick(async () => {
this.plugin.data.settings.clozeOpeningToken =
DEFAULT_SETTINGS.clozeOpeningToken;
await this.plugin.savePluginData();
this.display();
});
});

new Setting(containerEl)
.setName(t("CLOZE_CLOSING_TOKEN"))
.setDesc(t("FIX_SEPARATORS_MANUALLY_WARNING"))
.addText((text) =>
text
.setPlaceholder(t("CLOZE_TOKEN_DISABLED"))
.setValue(this.plugin.data.settings.clozeClosingToken)
.onChange((value) => {
applySettingsUpdate(async () => {
this.plugin.data.settings.clozeClosingToken = value;
await this.plugin.savePluginData();
});
}),
)
.addExtraButton((button) => {
button
.setIcon("reset")
.setTooltip(t("RESET_DEFAULT"))
.onClick(async () => {
this.plugin.data.settings.clozeClosingToken =
DEFAULT_SETTINGS.clozeClosingToken;
await this.plugin.savePluginData();
this.display();
});
});

new Setting(containerEl)
.setName(t("INLINE_CARDS_SEPARATOR"))
.setDesc(t("FIX_SEPARATORS_MANUALLY_WARNING"))
Expand Down
63 changes: 53 additions & 10 deletions tests/unit/parser.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { parse } from "src/parser";
import { CardType } from "src/Question";

const defaultArgs: [string, string, string, string, boolean, boolean, boolean] = [
const defaultArgs: [string, string, string, string, boolean, boolean, boolean, string, string] = [
"::",
":::",
"?",
"??",
true,
true,
true,
"{=",
"=}",
];

test("Test parsing of single line basic cards", () => {
Expand Down Expand Up @@ -120,9 +122,9 @@ test("Test parsing of cloze cards", () => {
expect(parse("lorem ipsum ==p\ndolor won==", ...defaultArgs)).toEqual([]);
expect(parse("lorem ipsum ==dolor won=", ...defaultArgs)).toEqual([]);
// ==highlights== turned off
expect(parse("cloze ==deletion== test", "::", ":::", "?", "??", false, true, false)).toEqual(
[],
);
expect(
parse("cloze ==deletion== test", "::", ":::", "?", "??", false, true, false, "", ""),
).toEqual([]);

// **bolded**
expect(parse("cloze **deletion** test", ...defaultArgs)).toEqual([
Expand Down Expand Up @@ -151,13 +153,53 @@ test("Test parsing of cloze cards", () => {
expect(parse("lorem ipsum **p\ndolor won**", ...defaultArgs)).toEqual([]);
expect(parse("lorem ipsum **dolor won*", ...defaultArgs)).toEqual([]);
// **bolded** turned off
expect(parse("cloze **deletion** test", "::", ":::", "?", "??", true, false, false)).toEqual(
[],
);
expect(
parse("cloze **deletion** test", "::", ":::", "?", "??", true, false, false, "", ""),
).toEqual([]);

// {=custom=}
expect(parse("cloze {=deletion=} test", ...defaultArgs)).toEqual([
[CardType.Cloze, "cloze {=deletion=} test", 0],
]);
expect(parse("cloze {=deletion=} test\n<!--SR:2021-08-11,4,270-->", ...defaultArgs)).toEqual([
[CardType.Cloze, "cloze {=deletion=} test\n<!--SR:2021-08-11,4,270-->", 0],
]);
expect(parse("cloze {=deletion=} test <!--SR:2021-08-11,4,270-->", ...defaultArgs)).toEqual([
[CardType.Cloze, "cloze {=deletion=} test <!--SR:2021-08-11,4,270-->", 0],
]);
expect(parse("{=this=} is a {=deletion=}\n", ...defaultArgs)).toEqual([
[CardType.Cloze, "{=this=} is a {=deletion=}", 0],
]);
expect(
parse(
"some text before\n\na deletion on\nsuch {=wow=}\n\n" +
"many text\nsuch surprise {=wow=} more {=text=}\nsome text after\n\nHmm",
...defaultArgs,
),
).toEqual([
[CardType.Cloze, "a deletion on\nsuch {=wow=}", 3],
[CardType.Cloze, "many text\nsuch surprise {=wow=} more {=text=}\nsome text after", 6],
]);
expect(parse("srdf {=", ...defaultArgs)).toEqual([]);
expect(parse("=} srdf", ...defaultArgs)).toEqual([]);
expect(parse("lorem ipsum {=p\ndolor won=}", ...defaultArgs)).toEqual([]);
expect(parse("lorem ipsum {=dolor won=", ...defaultArgs)).toEqual([]);
// {=custom=} turned off
expect(
parse("cloze {=deletion=} test", "::", ":::", "?", "??", true, true, true, "", ""),
).toEqual([]);
expect(
parse("cloze {=deletion=} test", "::", ":::", "?", "??", true, true, true, "{=", ""),
).toEqual([]);
expect(
parse("cloze {=deletion=} test", "::", ":::", "?", "??", true, true, true, "", "=}"),
).toEqual([]);

// both
expect(parse("cloze **deletion** test ==another deletion==!", ...defaultArgs)).toEqual([
[CardType.Cloze, "cloze **deletion** test ==another deletion==!", 0],
// all
expect(
parse("cloze **deletion** test ==another deletion== {=custom deletion=}!", ...defaultArgs),
).toEqual([
[CardType.Cloze, "cloze **deletion** test ==another deletion== {=custom deletion=}!", 0],
]);
});

Expand Down Expand Up @@ -265,4 +307,5 @@ test("Test not parsing cards in HTML comments", () => {
).toEqual([]);
expect(parse("<!--cloze ==deletion== test-->", ...defaultArgs)).toEqual([]);
expect(parse("<!--cloze **deletion** test-->", ...defaultArgs)).toEqual([]);
expect(parse("<!--cloze {=deletion=} test-->", ...defaultArgs)).toEqual([]);
});