diff --git a/manifest-beta.json b/manifest-beta.json
index 9f57a04b..896967bf 100644
--- a/manifest-beta.json
+++ b/manifest-beta.json
@@ -1,7 +1,7 @@
{
"id": "obsidian-spaced-repetition",
"name": "Spaced Repetition",
- "version": "1.13-beta.8",
+ "version": "1.13-beta.9",
"minAppVersion": "0.15.4",
"description": "Fight the forgetting curve by reviewing flashcards & entire notes.",
"author": "Stephen Mwangi",
diff --git a/src/gui/Tabs.ts b/src/gui/Tabs.ts
new file mode 100644
index 00000000..c771ac6d
--- /dev/null
+++ b/src/gui/Tabs.ts
@@ -0,0 +1,171 @@
+/*
+ * 'Shell commands' plugin for Obsidian.
+ * Copyright (C) 2021 - 2023 Jarkko Linnanvirta
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.0 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ * Contact the author (Jarkko Linnanvirta): https://github.com/Taitava/
+ */
+
+import {setIcon} from "obsidian";
+
+export interface Tab {
+ title: string;
+ icon: string;
+ content_generator: (container_element: HTMLElement) => Promise;
+}
+
+export interface TabStructure {
+ header: HTMLElement,
+ active_tab_id: string,
+ buttons: {
+ [key: string]: HTMLElement,
+ }
+ contentContainers: {
+ [key: string]: HTMLElement,
+ },
+ contentGeneratorPromises: {
+ [key: string]: Promise,
+ }
+}
+
+export interface Tabs {
+ [key: string]: Tab;
+}
+
+interface TabContentContainers {
+ [key: string]: HTMLElement,
+}
+
+interface TabButtons {
+ [key: string]: HTMLElement,
+}
+
+export function createTabs(container_element: HTMLElement, tabs: Tabs, activateTabId: string): TabStructure {
+ const tab_header = container_element.createEl("div", {attr: {class: "sr-tab-header"}});
+ const tab_content_containers: TabContentContainers = {};
+ const tab_buttons: TabButtons = {};
+ const tab_structure: TabStructure = {
+ header: tab_header,
+ active_tab_id: Object.keys(tabs)[0] as string, // Indicate that the first tab is active. This does not affect what tab is active in practise, it just reports the active tab.
+ buttons: tab_buttons,
+ contentContainers: tab_content_containers,
+ contentGeneratorPromises: {},
+ };
+ let first_button: HTMLElement | undefined;
+ for (const tab_id in tabs) {
+ const tab = tabs[tab_id];
+
+ // Create button
+ const button = tab_header.createEl("button", {
+ attr: {
+ class: "sr-tab-header-button",
+ activateTab: "sr-tab-" + tab_id,
+ },
+ });
+ button.onclick = function (event: MouseEvent) {
+ const tab_button = this as HTMLElement; // Use 'this' instead of event.target because this way we'll always get a button element, not an element inside the button (i.e. an icon).
+
+ // Hide all tab contents and get the max dimensions
+ let max_width = 0;
+ let max_height = 0;
+ const tab_header = tab_button.parentElement;
+ if (null === tab_header) {
+ throw new Error("Tab header is missing. Did not get a parent from tab button.");
+ }
+ const container_element = tab_header.parentElement;
+ if (null === container_element) {
+ throw new Error("Container element is missing. Did not get a parent from tab header.");
+ }
+ const tab_contents = container_element.findAll("div.sr-tab-content"); // Do not get all tab contents that exist, because there might be multiple tab systems open at the same time.
+ const is_main_settings_modal = container_element.hasClass("vertical-tab-content");
+ for (const index in tab_contents) {
+ const tab_content = tab_contents[index];
+
+ // Get the maximum tab dimensions so that all tabs can have the same dimensions.
+ // But don't do it if this is the main settings modal
+ if (!is_main_settings_modal) {
+ tab_content.addClass("sr-tab-active"); // Need to make the tab visible temporarily in order to get the dimensions.
+ if (tab_content.offsetHeight > max_height) {
+ max_height = tab_content.offsetHeight;
+ }
+ if (tab_content.offsetWidth > max_width) {
+ max_width = tab_content.offsetWidth;
+ }
+ }
+
+ // Finally hide the tab
+ tab_content.removeClass("sr-tab-active");
+ }
+
+ // Remove active status from all buttons
+ const adjacent_tab_buttons = tab_header.findAll(".sr-tab-header-button"); // Do not get all tab buttons that exist, because there might be multiple tab systems open at the same time.
+ for (const index in adjacent_tab_buttons) {
+ const tab_button = adjacent_tab_buttons[index];
+ tab_button.removeClass("sr-tab-active");
+ }
+
+ // Activate the clicked tab
+ tab_button.addClass("sr-tab-active");
+ const activateTabAttribute: Attr | null = tab_button.attributes.getNamedItem("activateTab");
+ if (null === activateTabAttribute) {
+ throw new Error("Tab button has no 'activateTab' HTML attribute! Murr!");
+ }
+ const activate_tab_id = activateTabAttribute.value;
+ const tab_content: HTMLElement | null = document.getElementById(activate_tab_id);
+ if (null === tab_content) {
+ throw new Error("No tab content was found with activate_tab_id '"+activate_tab_id+"'! Hmph!");
+ }
+ tab_content.addClass("sr-tab-active");
+
+ // Mark the clicked tab as active in TabStructure (just to report which tab is currently active)
+ tab_structure.active_tab_id = activate_tab_id.replace(/^sr-tab-/, ""); // Remove "sr-tab" prefix.
+
+ // Focus an element (if a focusable element is present)
+ tab_content.find(".sr-focus-element-on-tab-opening")?.focus(); // ? = If not found, do nothing.
+
+ // Apply the max dimensions to this tab
+ // But don't do it if this is the main settings modal
+ if (!is_main_settings_modal) {
+ tab_content.style.width = max_width + "px";
+ tab_content.style.height = max_height + "px";
+ }
+
+ // Do nothing else (I don't know if this is needed or not)
+ event.preventDefault();
+ };
+ if (tab.icon)
+ setIcon(button, tab.icon);
+
+ button.insertAdjacentText("beforeend", " " + tab.title);
+ tab_buttons[tab_id] = button;
+
+ // Create content container
+ tab_content_containers[tab_id] = container_element.createEl("div", {attr: {class: "sr-tab-content", id: "sr-tab-" + tab_id}});
+
+ // Generate content
+ tab_structure.contentGeneratorPromises[tab_id] = tab.content_generator(tab_content_containers[tab_id]);
+
+ // Memorize the first tab's button
+ if (undefined === first_button) {
+ first_button = button;
+ }
+ }
+
+ // Open a tab.
+ tab_buttons[activateTabId].click();
+
+ // Return the TabStructure
+ return tab_structure;
+}
+
diff --git a/src/lang/locale/ar.ts b/src/lang/locale/ar.ts
index d4dee92e..990f0d55 100644
--- a/src/lang/locale/ar.ts
+++ b/src/lang/locale/ar.ts
@@ -52,9 +52,16 @@ export default {
// settings.ts
SETTINGS_HEADER: "Spaced Repetition Plugin - Settings",
CHECK_WIKI: '.wiki لمزيد من المعلومات ، تحقق من',
+ GITHUB_DISCUSSIONS: 'Visit the discussions section for Q&A help, feedback, and general discussion.',
+ GITHUB_ISSUES: 'Raise an issue here if you have a feature request or a bug-report.',
+ GITHUB_SOURCE_CODE: 'Project source code available on GitHub',
+ CODE_CONTRIBUTION_INFO: 'Information on code contributions',
+ TRANSLATION_CONTRIBUTION_INFO: 'Information on translating the plugin to your language',
+ PROJECT_CONTRIBUTIONS: 'Raise an issue here if you have a feature request or a bug-report',
FOLDERS_TO_IGNORE: "مجلدات لتجاهلها",
FOLDERS_TO_IGNORE_DESC:
- "Templates Meta/Scripts : أدخل مسارات المجلد مفصولة بواسطة سطور جديدة,مثال",
+ `Templates Meta/Scripts.
+Note that this setting is common to both Flashcards and Notes. : أدخل مسارات المجلد مفصولة بواسطة سطور جديدة,مثال`,
FLASHCARDS: "البطاقات",
FLASHCARD_EASY_LABEL: "نص الزر سهل",
FLASHCARD_GOOD_LABEL: "نص الزر جيد",
diff --git a/src/lang/locale/cz.ts b/src/lang/locale/cz.ts
index ebece0a5..354c7997 100644
--- a/src/lang/locale/cz.ts
+++ b/src/lang/locale/cz.ts
@@ -50,11 +50,18 @@ export default {
YEARS_STR_IVL_MOBILE: "${interval}r",
// settings.ts
- SETTINGS_HEADER: "Spaced Repetition Plugin - Nastavení",
+ SETTINGS_HEADER: "Spaced Repetition - Nastavení",
CHECK_WIKI: 'Pro více informací jděte na wiki.',
+ GITHUB_DISCUSSIONS: 'Visit the discussions section for Q&A help, feedback, and general discussion.',
+ GITHUB_ISSUES: 'Raise an issue here if you have a feature request or a bug-report.',
+ GITHUB_SOURCE_CODE: 'Project source code available on GitHub',
+ CODE_CONTRIBUTION_INFO: 'Information on code contributions',
+ TRANSLATION_CONTRIBUTION_INFO: 'Information on translating the plugin to your language',
+ PROJECT_CONTRIBUTIONS: 'Raise an issue here if you have a feature request or a bug-report',
FOLDERS_TO_IGNORE: "Ignorované složky",
FOLDERS_TO_IGNORE_DESC:
- "Zadejte cesty ke složkám oddělené odřádkováním napříkad. Šablony Meta/Scripts",
+ `Zadejte cesty ke složkám oddělené odřádkováním napříkad. Šablony Meta/Scripts.
+Note that this setting is common to both Flashcards and Notes.`,
FLASHCARDS: "Kartičky",
FLASHCARD_EASY_LABEL: "Easy Button Text",
FLASHCARD_GOOD_LABEL: "Good Button Text",
diff --git a/src/lang/locale/de.ts b/src/lang/locale/de.ts
index d4e142d9..0dac438b 100644
--- a/src/lang/locale/de.ts
+++ b/src/lang/locale/de.ts
@@ -56,11 +56,18 @@ export default {
YEARS_STR_IVL_MOBILE: "${interval}j",
// settings.ts
- SETTINGS_HEADER: "Spaced Repetition Plugin - Einstellungen",
+ SETTINGS_HEADER: "Spaced Repetition - Einstellungen",
CHECK_WIKI: 'Weitere Informationen gibt es im Wiki (english).',
+ GITHUB_DISCUSSIONS: 'Visit the discussions section for Q&A help, feedback, and general discussion.',
+ GITHUB_ISSUES: 'Raise an issue here if you have a feature request or a bug-report.',
+ GITHUB_SOURCE_CODE: 'Project source code available on GitHub',
+ CODE_CONTRIBUTION_INFO: 'Information on code contributions',
+ TRANSLATION_CONTRIBUTION_INFO: 'Information on translating the plugin to your language',
+ PROJECT_CONTRIBUTIONS: 'Raise an issue here if you have a feature request or a bug-report',
FOLDERS_TO_IGNORE: "Ausgeschlossene Ordner",
FOLDERS_TO_IGNORE_DESC:
- "Mehrere Ordner mit Zeilenumbrüchen getrennt angeben. Bsp. OrdnerA[Zeilenumbruch]OrdnerB/Unterordner",
+ `Mehrere Ordner mit Zeilenumbrüchen getrennt angeben. Bsp. OrdnerA[Zeilenumbruch]OrdnerB/Unterordner.
+Note that this setting is common to both Flashcards and Notes.`,
FLASHCARDS: "Lernkarten",
FLASHCARD_EASY_LABEL: "Einfach Knopf Text",
FLASHCARD_GOOD_LABEL: "Gut Knopf Text",
diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts
index 2112229c..42cc4218 100644
--- a/src/lang/locale/en.ts
+++ b/src/lang/locale/en.ts
@@ -50,10 +50,17 @@ export default {
YEARS_STR_IVL_MOBILE: "${interval}y",
// settings.ts
- SETTINGS_HEADER: "Spaced Repetition Plugin - Settings",
+ SETTINGS_HEADER: "Spaced Repetition - Settings",
CHECK_WIKI: 'For more information, check the wiki.',
+ GITHUB_DISCUSSIONS: 'Visit the discussions section for Q&A help, feedback, and general discussion.',
+ GITHUB_ISSUES: 'Raise an issue here if you have a feature request or a bug-report.',
+ GITHUB_SOURCE_CODE: 'Project source code available on GitHub',
+ CODE_CONTRIBUTION_INFO: 'Information on code contributions',
+ TRANSLATION_CONTRIBUTION_INFO: 'Information on translating the plugin to your language',
+ PROJECT_CONTRIBUTIONS: 'Raise an issue here if you have a feature request or a bug-report',
FOLDERS_TO_IGNORE: "Folders to ignore",
- FOLDERS_TO_IGNORE_DESC: "Enter folder paths separated by newlines i.e. Templates Meta/Scripts",
+ FOLDERS_TO_IGNORE_DESC: `Enter folder paths separated by newlines e.g. Templates Meta/Scripts.
+Note that this setting is common to both Flashcards and Notes.`,
FLASHCARDS: "Flashcards",
FLASHCARD_EASY_LABEL: "Easy Button Text",
FLASHCARD_GOOD_LABEL: "Good Button Text",
diff --git a/src/lang/locale/es.ts b/src/lang/locale/es.ts
index 337105ad..b6af1115 100644
--- a/src/lang/locale/es.ts
+++ b/src/lang/locale/es.ts
@@ -52,9 +52,16 @@ export default {
// settings.ts
SETTINGS_HEADER: "Extensión de Repetición Espaciada - Ajustes",
CHECK_WIKI: 'Para más información revisa la wiki.',
+ GITHUB_DISCUSSIONS: 'Visit the discussions section for Q&A help, feedback, and general discussion.',
+ GITHUB_ISSUES: 'Raise an issue here if you have a feature request or a bug-report.',
+ GITHUB_SOURCE_CODE: 'Project source code available on GitHub',
+ CODE_CONTRIBUTION_INFO: 'Information on code contributions',
+ TRANSLATION_CONTRIBUTION_INFO: 'Information on translating the plugin to your language',
+ PROJECT_CONTRIBUTIONS: 'Raise an issue here if you have a feature request or a bug-report',
FOLDERS_TO_IGNORE: "Directorios a ignorar",
FOLDERS_TO_IGNORE_DESC:
- "Escriba las rutas de los directorios separadas por saltos de línea, por ejemplo, Plantillas Extra/Guiones",
+ `Escriba las rutas de los directorios separadas por saltos de línea, por ejemplo, Plantillas Extra/Guiones.
+Note that this setting is common to both Flashcards and Notes.`,
FLASHCARDS: "Tarjetas de Memorización",
FLASHCARD_EASY_LABEL: "Texto del botón: Fácil",
FLASHCARD_GOOD_LABEL: "Texto del botón: Bien",
diff --git a/src/lang/locale/it.ts b/src/lang/locale/it.ts
index 33ddff94..54f930b9 100644
--- a/src/lang/locale/it.ts
+++ b/src/lang/locale/it.ts
@@ -53,9 +53,16 @@ export default {
// settings.ts
SETTINGS_HEADER: "Plugin per ripetizione spaziata - Impostazioni",
CHECK_WIKI: 'Per maggiori informazioni, rivolgersi alla wiki.',
+ GITHUB_DISCUSSIONS: 'Visit the discussions section for Q&A help, feedback, and general discussion.',
+ GITHUB_ISSUES: 'Raise an issue here if you have a feature request or a bug-report.',
+ GITHUB_SOURCE_CODE: 'Project source code available on GitHub',
+ CODE_CONTRIBUTION_INFO: 'Information on code contributions',
+ TRANSLATION_CONTRIBUTION_INFO: 'Information on translating the plugin to your language',
+ PROJECT_CONTRIBUTIONS: 'Raise an issue here if you have a feature request or a bug-report',
FOLDERS_TO_IGNORE: "Cartelle da ignorare",
FOLDERS_TO_IGNORE_DESC:
- "Inserisci i percorsi delle cartelle separati da a capo, per esempio, Templates Meta/Scripts",
+ `Inserisci i percorsi delle cartelle separati da a capo, per esempio, Templates Meta/Scripts.
+Note that this setting is common to both Flashcards and Notes.`,
FLASHCARDS: "Schede",
FLASHCARD_EASY_LABEL: "Testo del bottone facile",
FLASHCARD_GOOD_LABEL: "Testo del bottone buono",
diff --git a/src/lang/locale/ja.ts b/src/lang/locale/ja.ts
index 96bbbcfd..f8be4435 100644
--- a/src/lang/locale/ja.ts
+++ b/src/lang/locale/ja.ts
@@ -51,11 +51,18 @@ export default {
YEARS_STR_IVL_MOBILE: "${interval}y",
// settings.ts
- SETTINGS_HEADER: "Spaced Repetition Plugin - 設定",
+ SETTINGS_HEADER: "Spaced Repetition - 設定",
CHECK_WIKI: '詳細についてはwikiを確認してください。',
+ GITHUB_DISCUSSIONS: 'Visit the discussions section for Q&A help, feedback, and general discussion.',
+ GITHUB_ISSUES: 'Raise an issue here if you have a feature request or a bug-report.',
+ GITHUB_SOURCE_CODE: 'Project source code available on GitHub',
+ CODE_CONTRIBUTION_INFO: 'Information on code contributions',
+ TRANSLATION_CONTRIBUTION_INFO: 'Information on translating the plugin to your language',
+ PROJECT_CONTRIBUTIONS: 'Raise an issue here if you have a feature request or a bug-report',
FOLDERS_TO_IGNORE: "無視するフォルダ",
FOLDERS_TO_IGNORE_DESC:
- 'フォルダパスを改行で区切って入力してください。"Templates Meta/Scripts" のようなスペースによる区切りでの書き方は無効です。',
+ `フォルダパスを改行で区切って入力してください。"Templates Meta/Scripts" のようなスペースによる区切りでの書き方は無効です。.
+Note that this setting is common to both Flashcards and Notes.`,
FLASHCARDS: "フラッシュカード",
FLASHCARD_EASY_LABEL: "Easy Button Text",
FLASHCARD_GOOD_LABEL: "Good Button Text",
diff --git a/src/lang/locale/ko.ts b/src/lang/locale/ko.ts
index 4f1e6206..2b4efd47 100644
--- a/src/lang/locale/ko.ts
+++ b/src/lang/locale/ko.ts
@@ -50,11 +50,18 @@ export default {
YEARS_STR_IVL_MOBILE: "${interval}y",
// settings.ts
- SETTINGS_HEADER: "Spaced Repetition Plugin - 설정",
+ SETTINGS_HEADER: "Spaced Repetition - 설정",
CHECK_WIKI: '더 많은 정보를 원하시면, wiki를 확인해주세요.',
+ GITHUB_DISCUSSIONS: 'Visit the discussions section for Q&A help, feedback, and general discussion.',
+ GITHUB_ISSUES: 'Raise an issue here if you have a feature request or a bug-report.',
+ GITHUB_SOURCE_CODE: 'Project source code available on GitHub',
+ CODE_CONTRIBUTION_INFO: 'Information on code contributions',
+ TRANSLATION_CONTRIBUTION_INFO: 'Information on translating the plugin to your language',
+ PROJECT_CONTRIBUTIONS: 'Raise an issue here if you have a feature request or a bug-report',
FOLDERS_TO_IGNORE: "무시할 폴더들",
FOLDERS_TO_IGNORE_DESC:
- "폴더 경로를 빈 줄로 구분해서 입력해주세요. 'Templates Meta/Scripts' 와 같이 입력하는 것은 유효하지 않습니다.",
+ `폴더 경로를 빈 줄로 구분해서 입력해주세요. 'Templates Meta/Scripts' 와 같이 입력하는 것은 유효하지 않습니다.
+Note that this setting is common to both Flashcards and Notes.`,
FLASHCARDS: "플래시카드",
FLASHCARD_EASY_LABEL: "Easy Button Text",
FLASHCARD_GOOD_LABEL: "Good Button Text",
diff --git a/src/lang/locale/pl.ts b/src/lang/locale/pl.ts
index 3d3cf47d..f46a5140 100644
--- a/src/lang/locale/pl.ts
+++ b/src/lang/locale/pl.ts
@@ -52,9 +52,16 @@ export default {
// settings.ts
SETTINGS_HEADER: "Spaced Repetition - Ustawienia",
CHECK_WIKI: 'Aby uzyskać więcej informacji, sprawdź wiki.',
+ GITHUB_DISCUSSIONS: 'Visit the discussions section for Q&A help, feedback, and general discussion.',
+ GITHUB_ISSUES: 'Raise an issue here if you have a feature request or a bug-report.',
+ GITHUB_SOURCE_CODE: 'Project source code available on GitHub',
+ CODE_CONTRIBUTION_INFO: 'Information on code contributions',
+ TRANSLATION_CONTRIBUTION_INFO: 'Information on translating the plugin to your language',
+ PROJECT_CONTRIBUTIONS: 'Raise an issue here if you have a feature request or a bug-report',
FOLDERS_TO_IGNORE: "Foldery do zignorowania",
FOLDERS_TO_IGNORE_DESC:
- "Wprowadź ścieżki folderów oddzielone nowymi liniami, np. Szablony Meta/Scripts",
+ `Wprowadź ścieżki folderów oddzielone nowymi liniami, np. Szablony Meta/Scripts.
+Note that this setting is common to both Flashcards and Notes.`,
FLASHCARDS: "Fiszki",
FLASHCARD_EASY_LABEL: "Tekst przycisku Łatwe",
FLASHCARD_GOOD_LABEL: "Tekst przycisku Średnio trudne",
diff --git a/src/lang/locale/pt-br.ts b/src/lang/locale/pt-br.ts
index 7656563f..4606a14e 100644
--- a/src/lang/locale/pt-br.ts
+++ b/src/lang/locale/pt-br.ts
@@ -52,11 +52,18 @@ export default {
YEARS_STR_IVL_MOBILE: "${interval}a",
// settings.ts
- SETTINGS_HEADER: "Plugin Spaced Repetition - Configuração",
+ SETTINGS_HEADER: "Spaced Repetition - Configuração",
CHECK_WIKI: 'Para mais informações, cheque a wiki.',
+ GITHUB_DISCUSSIONS: 'Visit the discussions section for Q&A help, feedback, and general discussion.',
+ GITHUB_ISSUES: 'Raise an issue here if you have a feature request or a bug-report.',
+ GITHUB_SOURCE_CODE: 'Project source code available on GitHub',
+ CODE_CONTRIBUTION_INFO: 'Information on code contributions',
+ TRANSLATION_CONTRIBUTION_INFO: 'Information on translating the plugin to your language',
+ PROJECT_CONTRIBUTIONS: 'Raise an issue here if you have a feature request or a bug-report',
FOLDERS_TO_IGNORE: "Pastas para ignorar",
FOLDERS_TO_IGNORE_DESC:
- "Insira o caminho das pastas separado por quebras de linha ex: Templates Meta/Scripts",
+ `Insira o caminho das pastas separado por quebras de linha ex: Templates Meta/Scripts.
+Note that this setting is common to both Flashcards and Notes.`,
FLASHCARDS: "Flashcards",
FLASHCARD_EASY_LABEL: "Texto do Botão de Fácil",
FLASHCARD_GOOD_LABEL: "Texto do Botão de OK",
diff --git a/src/lang/locale/ru.ts b/src/lang/locale/ru.ts
index 6a05f222..de63e69d 100644
--- a/src/lang/locale/ru.ts
+++ b/src/lang/locale/ru.ts
@@ -61,9 +61,16 @@ export default {
// settings.ts
SETTINGS_HEADER: "Плагин Spaced Repetition - Настройки",
CHECK_WIKI: 'Для дополнительной информации посетите: wiki.',
+ GITHUB_DISCUSSIONS: 'Visit the discussions section for Q&A help, feedback, and general discussion.',
+ GITHUB_ISSUES: 'Raise an issue here if you have a feature request or a bug-report.',
+ GITHUB_SOURCE_CODE: 'Project source code available on GitHub',
+ CODE_CONTRIBUTION_INFO: 'Information on code contributions',
+ TRANSLATION_CONTRIBUTION_INFO: 'Information on translating the plugin to your language',
+ PROJECT_CONTRIBUTIONS: 'Raise an issue here if you have a feature request or a bug-report',
FOLDERS_TO_IGNORE: "Игнорируемые папки",
FOLDERS_TO_IGNORE_DESC:
- "Укажите пути папок, каждый на своей строке, например: Templates Meta/Scripts",
+ `Укажите пути папок, каждый на своей строке, например: Templates Meta/Scripts.
+Note that this setting is common to both Flashcards and Notes.`,
FLASHCARDS: "Карточки",
FLASHCARD_EASY_LABEL: 'Текст кнопки "Легко"',
FLASHCARD_GOOD_LABEL: 'Текст кнопки "Нормально"',
diff --git a/src/lang/locale/zh-cn.ts b/src/lang/locale/zh-cn.ts
index d34d01ca..9fa5ef5a 100644
--- a/src/lang/locale/zh-cn.ts
+++ b/src/lang/locale/zh-cn.ts
@@ -52,8 +52,15 @@ export default {
// settings.ts
SETTINGS_HEADER: "间隔重复插件 - 设置",
CHECK_WIKI: '了解更多, 请点击wiki.',
+ GITHUB_DISCUSSIONS: 'Visit the discussions section for Q&A help, feedback, and general discussion.',
+ GITHUB_ISSUES: 'Raise an issue here if you have a feature request or a bug-report.',
+ GITHUB_SOURCE_CODE: 'Project source code available on GitHub',
+ CODE_CONTRIBUTION_INFO: 'Information on code contributions',
+ TRANSLATION_CONTRIBUTION_INFO: 'Information on translating the plugin to your language',
+ PROJECT_CONTRIBUTIONS: 'Raise an issue here if you have a feature request or a bug-report',
FOLDERS_TO_IGNORE: "忽略此文件夹",
- FOLDERS_TO_IGNORE_DESC: "输入文件夹路径,用新建行分隔,例如:Templates Meta/Scripts",
+ FOLDERS_TO_IGNORE_DESC: `输入文件夹路径,用新建行分隔,例如:Templates Meta/Scripts.
+Note that this setting is common to both Flashcards and Notes.`,
FLASHCARDS: "卡片",
FLASHCARD_EASY_LABEL: "“简单”按钮文本",
FLASHCARD_GOOD_LABEL: "“记得”按钮文本",
diff --git a/src/lang/locale/zh-tw.ts b/src/lang/locale/zh-tw.ts
index 6b2dd287..75c80de6 100644
--- a/src/lang/locale/zh-tw.ts
+++ b/src/lang/locale/zh-tw.ts
@@ -52,8 +52,15 @@ export default {
// settings.ts
SETTINGS_HEADER: "間隔重複外掛 - 設定",
CHECK_WIKI: '瞭解更多, 請點選wiki.',
+ GITHUB_DISCUSSIONS: 'Visit the discussions section for Q&A help, feedback, and general discussion.',
+ GITHUB_ISSUES: 'Raise an issue here if you have a feature request or a bug-report.',
+ GITHUB_SOURCE_CODE: 'Project source code available on GitHub',
+ CODE_CONTRIBUTION_INFO: 'Information on code contributions',
+ TRANSLATION_CONTRIBUTION_INFO: 'Information on translating the plugin to your language',
+ PROJECT_CONTRIBUTIONS: 'Raise an issue here if you have a feature request or a bug-report',
FOLDERS_TO_IGNORE: "忽略此資料夾",
- FOLDERS_TO_IGNORE_DESC: "輸入資料夾路徑(用換行字元分隔),例如:Templates Meta/Scripts",
+ FOLDERS_TO_IGNORE_DESC: `輸入資料夾路徑(用換行字元分隔),例如:Templates Meta/Scripts.
+Note that this setting is common to both Flashcards and Notes.`,
FLASHCARDS: "卡片",
FLASHCARD_EASY_LABEL: "簡單按鈕文字",
FLASHCARD_GOOD_LABEL: "記得按鈕文字",
diff --git a/src/settings.ts b/src/settings.ts
index ed7eab01..1c359a34 100644
--- a/src/settings.ts
+++ b/src/settings.ts
@@ -2,6 +2,7 @@ import { Notice, PluginSettingTab, Setting, App, Platform } from "obsidian";
import type SRPlugin from "src/main";
import { t } from "src/lang/helpers";
import { isEqualOrSubPath } from "./util/utils";
+import { TabStructure, createTabs } from "./gui/Tabs";
export interface SRSettings {
// flashcards
@@ -159,6 +160,7 @@ function applySettingsUpdate(callback: () => void): void {
export class SRSettingTab extends PluginSettingTab {
private plugin: SRPlugin;
+ private tab_structure: TabStructure;
constructor(app: App, plugin: SRPlugin) {
super(app, plugin);
@@ -176,33 +178,59 @@ export class SRSettingTab extends PluginSettingTab {
containerEl.empty();
- const header = containerEl.createEl("h1", { text: `${t("SETTINGS_HEADER")}` });
+ const header = containerEl.createEl("h4", { text: `${t("SETTINGS_HEADER")}` });
header.addClass("sr-centered");
- containerEl.createDiv().innerHTML = t("CHECK_WIKI", {
- wiki_url: "https://www.stephenmwangi.com/obsidian-spaced-repetition/",
- });
+ this.tab_structure = createTabs(
+ containerEl,
+ {
+ "main-flashcards": {
+ title: t("FLASHCARDS"),
+ icon: null, // "SpacedRepIcon",
+ content_generator: (container_element: HTMLElement) => this.tabFlashcards(container_element),
+ },
+ "main-notes": {
+ title: t("NOTES"),
+ icon: null, // "note-glyph",
+ content_generator: (container_element: HTMLElement) => this.tabNotes(container_element),
+ },
+ "main-algorithm": {
+ title: "Algorithm",
+ icon: null, // "dot-network",
+ content_generator: (container_element: HTMLElement) => this.tabAlgorithm(container_element),
+ },
+ "main-ui-preferences": {
+ title: t("UI_PREFERENCES"),
+ icon: null, // "presentation",
+ content_generator: (container_element: HTMLElement) => this.tabUiPreferences(container_element),
+ },
+ "main-developer": {
+ title: "Developer",
+ icon: null, // "code-glyph",
+ content_generator: (container_element: HTMLElement) => this.tabDeveloper(container_element),
+ },
+ "main-help": {
+ title: "Help",
+ icon: null, // "help",
+ content_generator: (container_element: HTMLElement) => this.tabHelp(container_element),
+ },
+ },
+ this.last_position.tab_name,
+ );
- new Setting(containerEl)
- .setName(t("FOLDERS_TO_IGNORE"))
- .setDesc(t("FOLDERS_TO_IGNORE_DESC"))
- .addTextArea((text) =>
- text
- .setValue(this.plugin.data.settings.noteFoldersToIgnore.join("\n"))
- .onChange((value) => {
- applySettingsUpdate(async () => {
- this.plugin.data.settings.noteFoldersToIgnore = value
- .split(/\n+/)
- .map((v) => v.trim())
- .filter((v) => v);
- await this.plugin.savePluginData();
- });
- }),
- );
+ // KEEP THIS AFTER CREATING ALL ELEMENTS:
+ // Scroll to the position when the settings modal was last open, but do it after content generating has finished.
+ // In practise, shell command previews may take some time to appear.
+ this.tab_structure.contentGeneratorPromises[this.tab_structure.active_tab_id].then(() => {
+ this.rememberLastPosition(containerEl);
+ });
+ }
- containerEl.createEl("h3", { text: `${t("FLASHCARDS")}` });
+ private async tabFlashcards(containerEl: HTMLElement): Promise {
- new Setting(containerEl)
+ containerEl.createEl("h3", { text: `Tags & Folders` });
+ {
+ new Setting(containerEl)
.setName(t("FLASHCARD_TAGS"))
.setDesc(t("FLASHCARD_TAGS_DESC"))
.addTextArea((text) =>
@@ -216,221 +244,316 @@ export class SRSettingTab extends PluginSettingTab {
}),
);
- new Setting(containerEl)
- .setName(t("CONVERT_FOLDERS_TO_DECKS"))
- .setDesc(t("CONVERT_FOLDERS_TO_DECKS_DESC"))
- .addToggle((toggle) =>
- toggle
- .setValue(this.plugin.data.settings.convertFoldersToDecks)
- .onChange(async (value) => {
- this.plugin.data.settings.convertFoldersToDecks = value;
- await this.plugin.savePluginData();
- }),
+ new Setting(containerEl)
+ .setName(t("CONVERT_FOLDERS_TO_DECKS"))
+ .setDesc(t("CONVERT_FOLDERS_TO_DECKS_DESC"))
+ .addToggle((toggle) =>
+ toggle
+ .setValue(this.plugin.data.settings.convertFoldersToDecks)
+ .onChange(async (value) => {
+ this.plugin.data.settings.convertFoldersToDecks = value;
+ await this.plugin.savePluginData();
+ }),
);
+ this.createSetting_FoldersToIgnore(containerEl);
+ }
+
+ containerEl.createEl("h3", { text: `Flashcard Review` });
+ {
+ new Setting(containerEl)
+ .setName(t("BURY_SIBLINGS_TILL_NEXT_DAY"))
+ .setDesc(t("BURY_SIBLINGS_TILL_NEXT_DAY_DESC"))
+ .addToggle((toggle) =>
+ toggle
+ .setValue(this.plugin.data.settings.burySiblingCards)
+ .onChange(async (value) => {
+ this.plugin.data.settings.burySiblingCards = value;
+ await this.plugin.savePluginData();
+ }),
+ );
+
+ new Setting(containerEl)
+ .setName(t("REVIEW_CARD_ORDER_WITHIN_DECK"))
+ .addDropdown((dropdown) =>
+ dropdown
+ .addOptions({
+ NewFirstSequential: t("REVIEW_CARD_ORDER_NEW_FIRST_SEQUENTIAL"),
+ DueFirstSequential: t("REVIEW_CARD_ORDER_DUE_FIRST_SEQUENTIAL"),
+ NewFirstRandom: t("REVIEW_CARD_ORDER_NEW_FIRST_RANDOM"),
+ DueFirstRandom: t("REVIEW_CARD_ORDER_DUE_FIRST_RANDOM"),
+ EveryCardRandomDeckAndCard: t("REVIEW_CARD_ORDER_RANDOM_DECK_AND_CARD"),
+ })
+ .setValue(this.plugin.data.settings.flashcardCardOrder)
+ .onChange(async (value) => {
+ this.plugin.data.settings.flashcardCardOrder = value;
+ await this.plugin.savePluginData();
- new Setting(containerEl)
- .setName(t("INLINE_SCHEDULING_COMMENTS"))
- .setDesc(t("INLINE_SCHEDULING_COMMENTS_DESC"))
- .addToggle((toggle) =>
- toggle
- .setValue(this.plugin.data.settings.cardCommentOnSameLine)
- .onChange(async (value) => {
- this.plugin.data.settings.cardCommentOnSameLine = value;
- await this.plugin.savePluginData();
- }),
- );
+ // Need to redisplay as changing this setting affects the "deck order" setting
+ this.display();
+ }),
+ );
- new Setting(containerEl)
- .setName(t("BURY_SIBLINGS_TILL_NEXT_DAY"))
- .setDesc(t("BURY_SIBLINGS_TILL_NEXT_DAY_DESC"))
- .addToggle((toggle) =>
- toggle
- .setValue(this.plugin.data.settings.burySiblingCards)
+ const deckOrderEnabled: boolean =
+ this.plugin.data.settings.flashcardCardOrder != "EveryCardRandomDeckAndCard";
+ new Setting(containerEl).setName(t("REVIEW_DECK_ORDER")).addDropdown((dropdown) =>
+ dropdown
+ .addOptions(
+ deckOrderEnabled
+ ? {
+ PrevDeckComplete_Sequential: t(
+ "REVIEW_DECK_ORDER_PREV_DECK_COMPLETE_SEQUENTIAL",
+ ),
+ PrevDeckComplete_Random: t(
+ "REVIEW_DECK_ORDER_PREV_DECK_COMPLETE_RANDOM",
+ ),
+ }
+ : {
+ EveryCardRandomDeckAndCard: t(
+ "REVIEW_DECK_ORDER_RANDOM_DECK_AND_CARD",
+ ),
+ },
+ )
+ .setValue(
+ deckOrderEnabled
+ ? this.plugin.data.settings.flashcardDeckOrder
+ : "EveryCardRandomDeckAndCard",
+ )
+ .setDisabled(!deckOrderEnabled)
.onChange(async (value) => {
- this.plugin.data.settings.burySiblingCards = value;
+ this.plugin.data.settings.flashcardDeckOrder = value;
await this.plugin.savePluginData();
}),
);
+ }
- new Setting(containerEl)
- .setName(t("SHOW_CARD_CONTEXT"))
- .setDesc(t("SHOW_CARD_CONTEXT_DESC"))
- .addToggle((toggle) =>
+ containerEl.createEl("h3", { text: `Flashcard Separators` });
+ {
+ new Setting(containerEl).setName(t("CONVERT_HIGHLIGHTS_TO_CLOZES")).addToggle((toggle) =>
toggle
- .setValue(this.plugin.data.settings.showContextInCards)
+ .setValue(this.plugin.data.settings.convertHighlightsToClozes)
.onChange(async (value) => {
- this.plugin.data.settings.showContextInCards = value;
+ this.plugin.data.settings.convertHighlightsToClozes = value;
await this.plugin.savePluginData();
}),
);
- new Setting(containerEl)
- .setName(t("CARD_MODAL_HEIGHT_PERCENT"))
- .setDesc(t("CARD_MODAL_SIZE_PERCENT_DESC"))
- .addSlider((slider) =>
- slider
- .setLimits(10, 100, 5)
- .setValue(this.plugin.data.settings.flashcardHeightPercentage)
- .setDynamicTooltip()
- .onChange(async (value) => {
- this.plugin.data.settings.flashcardHeightPercentage = value;
- await this.plugin.savePluginData();
- }),
- )
- .addExtraButton((button) => {
- button
- .setIcon("reset")
- .setTooltip(t("RESET_DEFAULT"))
- .onClick(async () => {
- this.plugin.data.settings.flashcardHeightPercentage =
- DEFAULT_SETTINGS.flashcardHeightPercentage;
- await this.plugin.savePluginData();
- this.display();
- });
- });
-
- new Setting(containerEl)
- .setName(t("CARD_MODAL_WIDTH_PERCENT"))
- .setDesc(t("CARD_MODAL_SIZE_PERCENT_DESC"))
- .addSlider((slider) =>
- slider
- .setLimits(10, 100, 5)
- .setValue(this.plugin.data.settings.flashcardWidthPercentage)
- .setDynamicTooltip()
- .onChange(async (value) => {
- this.plugin.data.settings.flashcardWidthPercentage = value;
- await this.plugin.savePluginData();
- }),
- )
- .addExtraButton((button) => {
- button
- .setIcon("reset")
- .setTooltip(t("RESET_DEFAULT"))
- .onClick(async () => {
- this.plugin.data.settings.flashcardWidthPercentage =
- DEFAULT_SETTINGS.flashcardWidthPercentage;
- await this.plugin.savePluginData();
- this.display();
- });
- });
-
- new Setting(this.containerEl)
- .setName(t("REVIEW_CARD_ORDER_WITHIN_DECK"))
- .addDropdown((dropdown) =>
- dropdown
- .addOptions({
- NewFirstSequential: t("REVIEW_CARD_ORDER_NEW_FIRST_SEQUENTIAL"),
- DueFirstSequential: t("REVIEW_CARD_ORDER_DUE_FIRST_SEQUENTIAL"),
- NewFirstRandom: t("REVIEW_CARD_ORDER_NEW_FIRST_RANDOM"),
- DueFirstRandom: t("REVIEW_CARD_ORDER_DUE_FIRST_RANDOM"),
- EveryCardRandomDeckAndCard: t("REVIEW_CARD_ORDER_RANDOM_DECK_AND_CARD"),
- })
- .setValue(this.plugin.data.settings.flashcardCardOrder)
+ new Setting(containerEl).setName(t("CONVERT_BOLD_TEXT_TO_CLOZES")).addToggle((toggle) =>
+ toggle
+ .setValue(this.plugin.data.settings.convertBoldTextToClozes)
.onChange(async (value) => {
- this.plugin.data.settings.flashcardCardOrder = value;
+ this.plugin.data.settings.convertBoldTextToClozes = value;
await this.plugin.savePluginData();
-
- // Need to redisplay as changing this setting affects the "deck order" setting
- this.display();
}),
);
- const deckOrderEnabled: boolean =
- this.plugin.data.settings.flashcardCardOrder != "EveryCardRandomDeckAndCard";
- new Setting(this.containerEl).setName(t("REVIEW_DECK_ORDER")).addDropdown((dropdown) =>
- dropdown
- .addOptions(
- deckOrderEnabled
- ? {
- PrevDeckComplete_Sequential: t(
- "REVIEW_DECK_ORDER_PREV_DECK_COMPLETE_SEQUENTIAL",
- ),
- PrevDeckComplete_Random: t(
- "REVIEW_DECK_ORDER_PREV_DECK_COMPLETE_RANDOM",
- ),
- }
- : {
- EveryCardRandomDeckAndCard: t(
- "REVIEW_DECK_ORDER_RANDOM_DECK_AND_CARD",
- ),
- },
+ new Setting(containerEl)
+ .setName(t("CONVERT_CURLY_BRACKETS_TO_CLOZES"))
+ .addToggle((toggle) =>
+ toggle
+ .setValue(this.plugin.data.settings.convertCurlyBracketsToClozes)
+ .onChange(async (value) => {
+ this.plugin.data.settings.convertCurlyBracketsToClozes = value;
+ await this.plugin.savePluginData();
+ }),
+ );
+
+ new Setting(containerEl)
+ .setName(t("INLINE_CARDS_SEPARATOR"))
+ .setDesc(t("FIX_SEPARATORS_MANUALLY_WARNING"))
+ .addText((text) =>
+ text
+ .setValue(this.plugin.data.settings.singleLineCardSeparator)
+ .onChange((value) => {
+ applySettingsUpdate(async () => {
+ this.plugin.data.settings.singleLineCardSeparator = value;
+ await this.plugin.savePluginData();
+ });
+ }),
)
- .setValue(
- deckOrderEnabled
- ? this.plugin.data.settings.flashcardDeckOrder
- : "EveryCardRandomDeckAndCard",
+ .addExtraButton((button) => {
+ button
+ .setIcon("reset")
+ .setTooltip(t("RESET_DEFAULT"))
+ .onClick(async () => {
+ this.plugin.data.settings.singleLineCardSeparator =
+ DEFAULT_SETTINGS.singleLineCardSeparator;
+ await this.plugin.savePluginData();
+ this.display();
+ });
+ });
+
+ new Setting(containerEl)
+ .setName(t("INLINE_REVERSED_CARDS_SEPARATOR"))
+ .setDesc(t("FIX_SEPARATORS_MANUALLY_WARNING"))
+ .addText((text) =>
+ text
+ .setValue(this.plugin.data.settings.singleLineReversedCardSeparator)
+ .onChange((value) => {
+ applySettingsUpdate(async () => {
+ this.plugin.data.settings.singleLineReversedCardSeparator = value;
+ await this.plugin.savePluginData();
+ });
+ }),
)
- .setDisabled(!deckOrderEnabled)
- .onChange(async (value) => {
- this.plugin.data.settings.flashcardDeckOrder = value;
- await this.plugin.savePluginData();
- }),
- );
+ .addExtraButton((button) => {
+ button
+ .setIcon("reset")
+ .setTooltip(t("RESET_DEFAULT"))
+ .onClick(async () => {
+ this.plugin.data.settings.singleLineReversedCardSeparator =
+ DEFAULT_SETTINGS.singleLineReversedCardSeparator;
+ await this.plugin.savePluginData();
+ this.display();
+ });
+ });
+
+ new Setting(containerEl)
+ .setName(t("MULTILINE_CARDS_SEPARATOR"))
+ .setDesc(t("FIX_SEPARATORS_MANUALLY_WARNING"))
+ .addText((text) =>
+ text
+ .setValue(this.plugin.data.settings.multilineCardSeparator)
+ .onChange((value) => {
+ applySettingsUpdate(async () => {
+ this.plugin.data.settings.multilineCardSeparator = value;
+ await this.plugin.savePluginData();
+ });
+ }),
+ )
+ .addExtraButton((button) => {
+ button
+ .setIcon("reset")
+ .setTooltip(t("RESET_DEFAULT"))
+ .onClick(async () => {
+ this.plugin.data.settings.multilineCardSeparator =
+ DEFAULT_SETTINGS.multilineCardSeparator;
+ await this.plugin.savePluginData();
+ this.display();
+ });
+ });
+
+ new Setting(containerEl)
+ .setName(t("MULTILINE_REVERSED_CARDS_SEPARATOR"))
+ .setDesc(t("FIX_SEPARATORS_MANUALLY_WARNING"))
+ .addText((text) =>
+ text
+ .setValue(this.plugin.data.settings.multilineReversedCardSeparator)
+ .onChange((value) => {
+ applySettingsUpdate(async () => {
+ this.plugin.data.settings.multilineReversedCardSeparator = value;
+ await this.plugin.savePluginData();
+ });
+ }),
+ )
+ .addExtraButton((button) => {
+ button
+ .setIcon("reset")
+ .setTooltip(t("RESET_DEFAULT"))
+ .onClick(async () => {
+ this.plugin.data.settings.multilineReversedCardSeparator =
+ DEFAULT_SETTINGS.multilineReversedCardSeparator;
+ await this.plugin.savePluginData();
+ this.display();
+ });
+ });
+ }
- new Setting(containerEl).setName(t("CONVERT_HIGHLIGHTS_TO_CLOZES")).addToggle((toggle) =>
- toggle
- .setValue(this.plugin.data.settings.convertHighlightsToClozes)
- .onChange(async (value) => {
- this.plugin.data.settings.convertHighlightsToClozes = value;
- await this.plugin.savePluginData();
- }),
- );
+ containerEl.createEl("h3", { text: `Storage of Scheduling Data` });
+ {
+ new Setting(containerEl)
+ .setName(t("INLINE_SCHEDULING_COMMENTS"))
+ .setDesc(t("INLINE_SCHEDULING_COMMENTS_DESC"))
+ .addToggle((toggle) =>
+ toggle
+ .setValue(this.plugin.data.settings.cardCommentOnSameLine)
+ .onChange(async (value) => {
+ this.plugin.data.settings.cardCommentOnSameLine = value;
+ await this.plugin.savePluginData();
+ }),
+ );
+ }
+ }
- new Setting(containerEl).setName(t("CONVERT_BOLD_TEXT_TO_CLOZES")).addToggle((toggle) =>
+ private async tabNotes(containerEl: HTMLElement): Promise {
+ containerEl.createEl("br");
+ new Setting(containerEl).setName(t("REVIEW_PANE_ON_STARTUP")).addToggle((toggle) =>
toggle
- .setValue(this.plugin.data.settings.convertBoldTextToClozes)
+ .setValue(this.plugin.data.settings.enableNoteReviewPaneOnStartup)
.onChange(async (value) => {
- this.plugin.data.settings.convertBoldTextToClozes = value;
+ this.plugin.data.settings.enableNoteReviewPaneOnStartup = value;
await this.plugin.savePluginData();
}),
);
new Setting(containerEl)
- .setName(t("CONVERT_CURLY_BRACKETS_TO_CLOZES"))
+ .setName(t("TAGS_TO_REVIEW"))
+ .setDesc(t("TAGS_TO_REVIEW_DESC"))
+ .addTextArea((text) =>
+ text
+ .setValue(this.plugin.data.settings.tagsToReview.join(" "))
+ .onChange((value) => {
+ applySettingsUpdate(async () => {
+ this.plugin.data.settings.tagsToReview = value.split(/\s+/);
+ await this.plugin.savePluginData();
+ });
+ }),
+ );
+
+ this.createSetting_FoldersToIgnore(containerEl);
+
+ new Setting(containerEl)
+ .setName(t("OPEN_RANDOM_NOTE"))
+ .setDesc(t("OPEN_RANDOM_NOTE_DESC"))
.addToggle((toggle) =>
toggle
- .setValue(this.plugin.data.settings.convertCurlyBracketsToClozes)
+ .setValue(this.plugin.data.settings.openRandomNote)
.onChange(async (value) => {
- this.plugin.data.settings.convertCurlyBracketsToClozes = value;
+ this.plugin.data.settings.openRandomNote = value;
await this.plugin.savePluginData();
}),
);
+ new Setting(containerEl).setName(t("AUTO_NEXT_NOTE")).addToggle((toggle) =>
+ toggle.setValue(this.plugin.data.settings.autoNextNote).onChange(async (value) => {
+ this.plugin.data.settings.autoNextNote = value;
+ await this.plugin.savePluginData();
+ }),
+ );
+
new Setting(containerEl)
- .setName(t("INLINE_CARDS_SEPARATOR"))
- .setDesc(t("FIX_SEPARATORS_MANUALLY_WARNING"))
- .addText((text) =>
- text
- .setValue(this.plugin.data.settings.singleLineCardSeparator)
- .onChange((value) => {
- applySettingsUpdate(async () => {
- this.plugin.data.settings.singleLineCardSeparator = value;
- await this.plugin.savePluginData();
- });
- }),
- )
- .addExtraButton((button) => {
- button
- .setIcon("reset")
- .setTooltip(t("RESET_DEFAULT"))
- .onClick(async () => {
- this.plugin.data.settings.singleLineCardSeparator =
- DEFAULT_SETTINGS.singleLineCardSeparator;
+ .setName(t("DISABLE_FILE_MENU_REVIEW_OPTIONS"))
+ .setDesc(t("DISABLE_FILE_MENU_REVIEW_OPTIONS_DESC"))
+ .addToggle((toggle) =>
+ toggle
+ .setValue(this.plugin.data.settings.disableFileMenuReviewOptions)
+ .onChange(async (value) => {
+ this.plugin.data.settings.disableFileMenuReviewOptions = value;
await this.plugin.savePluginData();
- this.display();
- });
- });
+ }),
+ );
new Setting(containerEl)
- .setName(t("INLINE_REVERSED_CARDS_SEPARATOR"))
- .setDesc(t("FIX_SEPARATORS_MANUALLY_WARNING"))
+ .setName(t("MAX_N_DAYS_REVIEW_QUEUE"))
.addText((text) =>
text
- .setValue(this.plugin.data.settings.singleLineReversedCardSeparator)
+ .setValue(this.plugin.data.settings.maxNDaysNotesReviewQueue.toString())
.onChange((value) => {
applySettingsUpdate(async () => {
- this.plugin.data.settings.singleLineReversedCardSeparator = value;
- await this.plugin.savePluginData();
+ const numValue: number = Number.parseInt(value);
+ if (!isNaN(numValue)) {
+ if (numValue < 1) {
+ new Notice(t("MIN_ONE_DAY"));
+ text.setValue(
+ this.plugin.data.settings.maxNDaysNotesReviewQueue.toString(),
+ );
+ return;
+ }
+
+ this.plugin.data.settings.maxNDaysNotesReviewQueue = numValue;
+ await this.plugin.savePluginData();
+ } else {
+ new Notice(t("VALID_NUMBER_WARNING"));
+ }
});
}),
)
@@ -439,64 +562,112 @@ export class SRSettingTab extends PluginSettingTab {
.setIcon("reset")
.setTooltip(t("RESET_DEFAULT"))
.onClick(async () => {
- this.plugin.data.settings.singleLineReversedCardSeparator =
- DEFAULT_SETTINGS.singleLineReversedCardSeparator;
+ this.plugin.data.settings.maxNDaysNotesReviewQueue =
+ DEFAULT_SETTINGS.maxNDaysNotesReviewQueue;
await this.plugin.savePluginData();
this.display();
});
});
+ }
+ private async createSetting_FoldersToIgnore(containerEl: HTMLElement): Promise {
new Setting(containerEl)
- .setName(t("MULTILINE_CARDS_SEPARATOR"))
- .setDesc(t("FIX_SEPARATORS_MANUALLY_WARNING"))
- .addText((text) =>
+ .setName(t("FOLDERS_TO_IGNORE"))
+ .setDesc(t("FOLDERS_TO_IGNORE_DESC"))
+ .addTextArea((text) =>
text
- .setValue(this.plugin.data.settings.multilineCardSeparator)
+ .setValue(this.plugin.data.settings.noteFoldersToIgnore.join("\n"))
.onChange((value) => {
applySettingsUpdate(async () => {
- this.plugin.data.settings.multilineCardSeparator = value;
+ this.plugin.data.settings.noteFoldersToIgnore = value
+ .split(/\n+/)
+ .map((v) => v.trim())
+ .filter((v) => v);
await this.plugin.savePluginData();
});
}),
- )
- .addExtraButton((button) => {
- button
- .setIcon("reset")
- .setTooltip(t("RESET_DEFAULT"))
- .onClick(async () => {
- this.plugin.data.settings.multilineCardSeparator =
- DEFAULT_SETTINGS.multilineCardSeparator;
- await this.plugin.savePluginData();
- this.display();
- });
- });
+ );
+ }
+
+ private async tabUiPreferences(containerEl: HTMLElement): Promise {
+ containerEl.createEl("h3", { text: `Flashcards` });
new Setting(containerEl)
- .setName(t("MULTILINE_REVERSED_CARDS_SEPARATOR"))
- .setDesc(t("FIX_SEPARATORS_MANUALLY_WARNING"))
- .addText((text) =>
- text
- .setValue(this.plugin.data.settings.multilineReversedCardSeparator)
- .onChange((value) => {
- applySettingsUpdate(async () => {
- this.plugin.data.settings.multilineReversedCardSeparator = value;
+ .setName(t("INITIALLY_EXPAND_SUBDECKS_IN_TREE"))
+ .setDesc(t("INITIALLY_EXPAND_SUBDECKS_IN_TREE_DESC"))
+ .addToggle((toggle) =>
+ toggle
+ .setValue(this.plugin.data.settings.initiallyExpandAllSubdecksInTree)
+ .onChange(async (value) => {
+ this.plugin.data.settings.initiallyExpandAllSubdecksInTree = value;
+ await this.plugin.savePluginData();
+ }),
+ );
+
+ new Setting(containerEl)
+ .setName(t("SHOW_CARD_CONTEXT"))
+ .setDesc(t("SHOW_CARD_CONTEXT_DESC"))
+ .addToggle((toggle) =>
+ toggle
+ .setValue(this.plugin.data.settings.showContextInCards)
+ .onChange(async (value) => {
+ this.plugin.data.settings.showContextInCards = value;
+ await this.plugin.savePluginData();
+ }),
+ );
+
+ new Setting(containerEl)
+ .setName(t("CARD_MODAL_HEIGHT_PERCENT"))
+ .setDesc(t("CARD_MODAL_SIZE_PERCENT_DESC"))
+ .addSlider((slider) =>
+ slider
+ .setLimits(10, 100, 5)
+ .setValue(this.plugin.data.settings.flashcardHeightPercentage)
+ .setDynamicTooltip()
+ .onChange(async (value) => {
+ this.plugin.data.settings.flashcardHeightPercentage = value;
+ await this.plugin.savePluginData();
+ }),
+ )
+ .addExtraButton((button) => {
+ button
+ .setIcon("reset")
+ .setTooltip(t("RESET_DEFAULT"))
+ .onClick(async () => {
+ this.plugin.data.settings.flashcardHeightPercentage =
+ DEFAULT_SETTINGS.flashcardHeightPercentage;
await this.plugin.savePluginData();
+ this.display();
});
- }),
- )
- .addExtraButton((button) => {
- button
- .setIcon("reset")
- .setTooltip(t("RESET_DEFAULT"))
- .onClick(async () => {
- this.plugin.data.settings.multilineReversedCardSeparator =
- DEFAULT_SETTINGS.multilineReversedCardSeparator;
- await this.plugin.savePluginData();
- this.display();
- });
- });
+ });
+
+ new Setting(containerEl)
+ .setName(t("CARD_MODAL_WIDTH_PERCENT"))
+ .setDesc(t("CARD_MODAL_SIZE_PERCENT_DESC"))
+ .addSlider((slider) =>
+ slider
+ .setLimits(10, 100, 5)
+ .setValue(this.plugin.data.settings.flashcardWidthPercentage)
+ .setDynamicTooltip()
+ .onChange(async (value) => {
+ this.plugin.data.settings.flashcardWidthPercentage = value;
+ await this.plugin.savePluginData();
+ }),
+ )
+ .addExtraButton((button) => {
+ button
+ .setIcon("reset")
+ .setTooltip(t("RESET_DEFAULT"))
+ .onClick(async () => {
+ this.plugin.data.settings.flashcardWidthPercentage =
+ DEFAULT_SETTINGS.flashcardWidthPercentage;
+ await this.plugin.savePluginData();
+ this.display();
+ });
+ });
- new Setting(containerEl)
+ containerEl.createEl("h3", { text: `Flashcards & Notes` });
+ new Setting(containerEl)
.setName(t("FLASHCARD_EASY_LABEL"))
.setDesc(t("FLASHCARD_EASY_DESC"))
.addText((text) =>
@@ -564,118 +735,13 @@ export class SRSettingTab extends PluginSettingTab {
this.display();
});
});
+ }
- containerEl.createEl("h3", { text: `${t("NOTES")}` });
-
- new Setting(containerEl).setName(t("REVIEW_PANE_ON_STARTUP")).addToggle((toggle) =>
- toggle
- .setValue(this.plugin.data.settings.enableNoteReviewPaneOnStartup)
- .onChange(async (value) => {
- this.plugin.data.settings.enableNoteReviewPaneOnStartup = value;
- await this.plugin.savePluginData();
- }),
- );
-
- new Setting(containerEl)
- .setName(t("TAGS_TO_REVIEW"))
- .setDesc(t("TAGS_TO_REVIEW_DESC"))
- .addTextArea((text) =>
- text
- .setValue(this.plugin.data.settings.tagsToReview.join(" "))
- .onChange((value) => {
- applySettingsUpdate(async () => {
- this.plugin.data.settings.tagsToReview = value.split(/\s+/);
- await this.plugin.savePluginData();
- });
- }),
- );
-
- new Setting(containerEl)
- .setName(t("OPEN_RANDOM_NOTE"))
- .setDesc(t("OPEN_RANDOM_NOTE_DESC"))
- .addToggle((toggle) =>
- toggle
- .setValue(this.plugin.data.settings.openRandomNote)
- .onChange(async (value) => {
- this.plugin.data.settings.openRandomNote = value;
- await this.plugin.savePluginData();
- }),
- );
-
- new Setting(containerEl).setName(t("AUTO_NEXT_NOTE")).addToggle((toggle) =>
- toggle.setValue(this.plugin.data.settings.autoNextNote).onChange(async (value) => {
- this.plugin.data.settings.autoNextNote = value;
- await this.plugin.savePluginData();
- }),
- );
-
- new Setting(containerEl)
- .setName(t("DISABLE_FILE_MENU_REVIEW_OPTIONS"))
- .setDesc(t("DISABLE_FILE_MENU_REVIEW_OPTIONS_DESC"))
- .addToggle((toggle) =>
- toggle
- .setValue(this.plugin.data.settings.disableFileMenuReviewOptions)
- .onChange(async (value) => {
- this.plugin.data.settings.disableFileMenuReviewOptions = value;
- await this.plugin.savePluginData();
- }),
- );
-
- new Setting(containerEl)
- .setName(t("MAX_N_DAYS_REVIEW_QUEUE"))
- .addText((text) =>
- text
- .setValue(this.plugin.data.settings.maxNDaysNotesReviewQueue.toString())
- .onChange((value) => {
- applySettingsUpdate(async () => {
- const numValue: number = Number.parseInt(value);
- if (!isNaN(numValue)) {
- if (numValue < 1) {
- new Notice(t("MIN_ONE_DAY"));
- text.setValue(
- this.plugin.data.settings.maxNDaysNotesReviewQueue.toString(),
- );
- return;
- }
-
- this.plugin.data.settings.maxNDaysNotesReviewQueue = numValue;
- await this.plugin.savePluginData();
- } else {
- new Notice(t("VALID_NUMBER_WARNING"));
- }
- });
- }),
- )
- .addExtraButton((button) => {
- button
- .setIcon("reset")
- .setTooltip(t("RESET_DEFAULT"))
- .onClick(async () => {
- this.plugin.data.settings.maxNDaysNotesReviewQueue =
- DEFAULT_SETTINGS.maxNDaysNotesReviewQueue;
- await this.plugin.savePluginData();
- this.display();
- });
- });
-
- containerEl.createEl("h3", { text: `${t("UI_PREFERENCES")}` });
+ private async tabAlgorithm(containerEl: HTMLElement): Promise {
- new Setting(containerEl)
- .setName(t("INITIALLY_EXPAND_SUBDECKS_IN_TREE"))
- .setDesc(t("INITIALLY_EXPAND_SUBDECKS_IN_TREE_DESC"))
- .addToggle((toggle) =>
- toggle
- .setValue(this.plugin.data.settings.initiallyExpandAllSubdecksInTree)
- .onChange(async (value) => {
- this.plugin.data.settings.initiallyExpandAllSubdecksInTree = value;
- await this.plugin.savePluginData();
- }),
- );
-
- containerEl.createEl("h3", { text: `${t("ALGORITHM")}` });
- containerEl.createDiv().innerHTML = t("CHECK_ALGORITHM_WIKI", {
- algo_url: "https://www.stephenmwangi.com/obsidian-spaced-repetition/algorithms/",
- });
+ containerEl.createEl("p").insertAdjacentHTML("beforeend", t("CHECK_ALGORITHM_WIKI", {
+ algo_url: "https://www.stephenmwangi.com/obsidian-spaced-repetition/",
+ }));
new Setting(containerEl)
.setName(t("BASE_EASE"))
@@ -833,6 +899,9 @@ export class SRSettingTab extends PluginSettingTab {
this.display();
});
});
+ }
+
+ private async tabDeveloper(containerEl: HTMLElement): Promise {
containerEl.createEl("h3", { text: `${t("LOGGING")}` });
new Setting(containerEl).setName(t("DISPLAY_DEBUG_INFO")).addToggle((toggle) =>
@@ -841,5 +910,75 @@ export class SRSettingTab extends PluginSettingTab {
await this.plugin.savePluginData();
}),
);
+ containerEl.createEl("h3", { text: `Contributing` });
+ containerEl.createEl("p").insertAdjacentHTML("beforeend", t("GITHUB_SOURCE_CODE", {
+ github_project_url: "https://github.com/st3v3nmw/obsidian-spaced-repetition",
+ }));
+ containerEl.createEl("p").insertAdjacentHTML("beforeend", t("CODE_CONTRIBUTION_INFO", {
+ code_contribution_url: "https://www.stephenmwangi.com/obsidian-spaced-repetition/contributing/#code",
+ }));
+ containerEl.createEl("p").insertAdjacentHTML("beforeend", t("TRANSLATION_CONTRIBUTION_INFO", {
+ translation_contribution_url: "https://www.stephenmwangi.com/obsidian-spaced-repetition/contributing/#translating",
+ }));
+
+ }
+
+ private async tabHelp(containerEl: HTMLElement): Promise {
+
+ // Documentation link & GitHub links
+ containerEl.createEl("p").insertAdjacentHTML("beforeend", t("CHECK_WIKI", {
+ wiki_url: "https://www.stephenmwangi.com/obsidian-spaced-repetition/",
+ }));
+
+ containerEl.createEl("p").insertAdjacentHTML("beforeend", t("GITHUB_DISCUSSIONS", {
+ discussions_url: "https://github.com/st3v3nmw/obsidian-spaced-repetition/discussions/",
+ }));
+
+ containerEl.createEl("p").insertAdjacentHTML("beforeend", t("GITHUB_ISSUES", {
+ issues_url: "https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/",
+ }));
+/*
+ // Documentation link & GitHub links
+ containerEl.createEl("hr").insertAdjacentHTML("beforeend");
+
+ // Copyright notice
+ const copyright_paragraph = containerEl.createEl("p");
+ copyright_paragraph.addClass("sr-small-font");
+ copyright_paragraph.insertAdjacentHTML("beforeend", `
+ Shell commands plugin Copyright © 2021 - 2023 Jarkko Linnanvirta. This program comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. See more information in the license: GNU GPL-3.0.
+ `); */
+ }
+
+ private last_position: {
+ scroll_position: number;
+ tab_name: string;
+ } = {
+ scroll_position: 0,
+ tab_name: "main-flashcards",
+ };
+ private rememberLastPosition(container_element: HTMLElement) {
+ const last_position = this.last_position;
+
+ // Go to last position now
+ this.tab_structure.buttons[last_position.tab_name].click();
+ // window.setTimeout(() => { // Need to delay the scrolling a bit. Without this, something else would override scrolling and scroll back to 0.
+ container_element.scrollTo({
+ top: this.last_position.scroll_position,
+ behavior: "auto",
+ });
+ // }, 0); // 'timeout' can be 0 ms, no need to wait any longer.
+ // I guess there's no need for setTimeout() anymore, as rememberLastPosition() is now called after waiting for asynchronous tab content generating is finished.
+ // TODO: Remove the commented code after a while.
+
+ // Listen to changes
+ container_element.addEventListener("scroll", (event) => {
+ this.last_position.scroll_position = container_element.scrollTop;
+ });
+ for (const tab_name in this.tab_structure.buttons) {
+ const button = this.tab_structure.buttons[tab_name];
+ button.onClickEvent((event: MouseEvent) => {
+ last_position.tab_name = tab_name;
+ });
+ }
}
}
diff --git a/styles.css b/styles.css
index f532c01c..8e655ed4 100644
--- a/styles.css
+++ b/styles.css
@@ -314,3 +314,58 @@ body:not(.native-scrollbars) #sr-modal .modal-close-button {
appearance: menulist;
border-right: 8px solid transparent;
}
+
+/*
+ * Tab elements
+ * This CSS is copied from https://github.com/Taitava/obsidian-shellcommands
+ * Jarkko Linnanvirta https://github.com/Taitava comments below...
+ * - Renamed classes
+ *
+ * This CSS is copied 2021-10-21 from https://www.w3schools.com/howto/howto_js_tabs.asp
+ * Modifications:
+ * - Renamed classes
+ * - Added tab icons.
+ * - Changed colors.
+ * - Changed/removed borders.
+ * - Removed button transition.
+ * - Changed button border-radiuses
+ * - Added margin-right rule to .sr-tab-header-button .
+ */
+
+/* Style the tab */
+.sr-tab-header {
+ border-bottom: 6px solid var(--background-modifier-border);
+}
+
+/* Style the buttons that are used to open the tab content */
+button.sr-tab-header-button {
+ background-color: unset;
+ border: none;
+ box-shadow: none; /* Remove a "border" that came via Obsidian 0.16.0. */
+ outline: none;
+ cursor: pointer;
+ padding: 14px 16px;
+ margin-right: 6px; /* Reduced margin. Obsidian's default margin-right for button is 12px (0 for other margins). */
+ border-radius: 10px 10px 0 0; /* 0 0 = No border-radius at bottom */
+}
+
+/* Create an active/current tablink class */
+button.sr-tab-header-button.sr-tab-active,
+button.sr-tab-header-button:hover {
+ background-color: var(--background-modifier-border);
+}
+
+.sr-tab-header-button svg {
+ vertical-align: middle; /* Not middle but close enough. */
+}
+
+/* Style the tab content */
+.sr-tab-content {
+ display: none;
+ padding: 6px 12px;
+}
+
+.sr-tab-content.sr-tab-active {
+ display: block;
+}
+