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; +} +