Repository: st3v3nmw/obsidian-spaced-repetition (ts)

Issue:
[FEAT] Do not auto scroll to the top of the flashcard after clicking to reveal the answer
For longer cloze deletion cards, it is rather annoying to have to scroll back down again after having done so in the first place when selecting to read the answer. Preserving the current scroll position would be very convenient.

Pull request description:
[FIX] Not scroll back to top after review
Fixes #966 

Scroll back after review card done. Required for longer cards or small screens. 

Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index b8afae5..0000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,34 +0,0 @@
----
-name: Bug report
-about: Create a bug report to help us improve the plugin.
-title: "[BUG]"
-labels: bug
-assignees: ""
----
-
-**Describe the bug**
-A clear and concise description of what the bug is.
-
-**To Reproduce**
-Steps to reproduce the behavior:
-
-1. Go to '...'
-2. Click on '....'
-3. Scroll down to '....'
-4. See error
-
-**Expected behavior**
-A clear and concise description of what you expected to happen.
-
-**Screenshots**
-If applicable, add screenshots to help explain the bug. This could be the logs in the developer console (`CTRL` + `SHIFT` + `i`).
-
-**Versions (please complete the following information):**
-
--   OS: [e.g. iOS]
--   Obsidian version: [e.g. v0.12.4]
--   Plugin version: [e.g. v1.4.9]
--   If on desktop, Installer version: [run `this.navigator.appVersion` on the developer console, e.g. `5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) obsidian/0.12.3 Chrome/89.0.4389.128 Electron/12.0.6 Safari/537.36"`]
-
-**Additional context**
-Add any other context about the problem here (e.g. the markdown producing the error).
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000..c5fefeb
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,91 @@
+name: Bug report
+description: Create a bug report to help us improve the plugin.
+title: "[Bug]: "
+labels: ["bug"]
+
+body:
+    - type: textarea
+      id: bug-description
+      attributes:
+          label: Describe the bug
+          description: A clear and concise description of what the bug is.
+          placeholder: Tell us what happened that you didn't expect [...]
+      validations:
+          required: true
+    - type: textarea
+      id: bug-reproduce-steps
+      attributes:
+          label: To Reproduce
+          description: Steps to reproduce the behavior
+          placeholder: |
+              1. Go to '...'
+              2. Click on '...' 
+              3. Scroll down to '...' 
+              4. See error"
+      validations:
+          required: true
+    - type: textarea
+      id: expected-behavior
+      attributes:
+          label: Expected behavior
+          description: A clear and concise description of what you expected to happen.
+          placeholder: Tell us what you think should have happened...
+      validations:
+          required: true
+    - type: textarea
+      id: screenshots
+      attributes:
+          label: Screenshots
+          description: If applicable, add screenshots to help explain the bug. This could be the logs in the developer console (`CTRL` + `SHIFT` + `i`).
+          placeholder: Paste image here
+      validations:
+          required: false
+    - type: input
+      id: operating-system
+      attributes:
+          label: OS of your device
+          placeholder: e.g. Win10
+      validations:
+          required: true
+    - type: input
+      id: obsidian-version
+      attributes:
+          label: Obsidian version
+          placeholder: e.g. v0.12.4
+      validations:
+          required: true
+    - type: input
+      id: plugin-version
+      attributes:
+          label: Plugin version
+          placeholder: "e.g. v1.4.9"
+      validations:
+          required: true
+    - type: input
+      id: installer-version
+      attributes:
+          label: Installer version
+          description: If on desktop [run `this.navigator.appVersion` on the developer console, e.g. `5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) obsidian/0.12.3 Chrome/89.0.4389.128 Electron/12.0.6 Safari/537.36"`]
+          placeholder: "e.g. v1.4.9"
+      validations:
+          required: false
+    - type: textarea
+      id: context
+      attributes:
+          label: Additional context
+          description: Add any other context about the problem here (e.g. the markdown producing the error).
+          placeholder: Explain any additional context [...]
+      validations:
+          required: false
+    - type: textarea
+      id: config-files
+      attributes:
+          label: Config file
+          description: |
+              Drag & drop your config file here [VAULT/.obsidian/plugins/obsidian-spaced-repetition/data.json]
+              Drag & drop your violated file containing your error here
+          placeholder: |
+              data.json
+              violated_file.md
+      validations:
+          required: false
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index 4f153ba..0000000
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,16 +0,0 @@
----
-name: Feature request
-about: Suggest an idea for this plugin.
-title: "[FEAT]"
-labels: enhancement
-assignees: ""
----
-
-**Is your feature request related to a problem? Please describe.**
-A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
-
-**Describe the solution you'd like**
-A clear and concise description of what you want to happen.
-
-**Additional context**
-Add any other context or screenshots about the feature request here.
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000..79a8dfa
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,30 @@
+name: Feature request
+description: Suggest an idea for this plugin.
+title: "[FEAT]: "
+labels: ["enhancement"]
+
+body:
+    - type: textarea
+      id: problem-description
+      attributes:
+          label: Is your feature request related to a problem? Please describe.
+          description: A clear and concise description of what the problem is.
+          placeholder: I'm always frustrated when [...]
+      validations:
+          required: false
+    - type: textarea
+      id: solution-description
+      attributes:
+          label: Describe the solution you'd like
+          description: A clear and concise description of what you want to happen.
+          placeholder: I wanted that [...]
+      validations:
+          required: true
+    - type: textarea
+      id: context
+      attributes:
+          label: Additional context
+          description: Add any other context or screenshots about the feature request here.
+          placeholder: Explain any additional context [...]
+      validations:
+          required: false
diff --git a/docs/zh/algorithms.md b/docs/zh/algorithms.md
new file mode 100644
index 0000000..8155269
--- /dev/null
+++ b/docs/zh/algorithms.md
@@ -0,0 +1,30 @@
+# 算法
+
+## SM-2
+
+!!! 警告
+
+    该条目长时间未更新,
+    请注意阅读 [源代码](https://github.com/st3v3nmw/obsidian-spaced-repetition/blob/master/src/scheduling.ts).
+
+(除 PageRanks 之外，卡片复习采用相同规划算法)
+
+-   该算法为 [Anki](https://faqs.ankiweb.net/what-spaced-repetition-algorithm.html) 所采用的基于 [SM-2 算法](https://www.supermemo.com/en/archives1990-2015/english/ol/sm2) 的变种。
+-   使用三级打分制，即在复习阶段自评对某个概念的掌握程度为`困难`，`记得`或`简单`。
+-   初始熟练度会根据链接笔记的平均熟练度、当前笔记的重要性和基本熟练度进行加权（使用 最大外链因子）。
+    -   `当存在外链时: 初始熟练度 = (1 - 链接加权) * 基础熟练度 + 链接加权 * 外链平均熟练度`
+        -   `链接加权 = 最大外链因子 * min(1.0, log(外链数目 + 0.5) / log(64))` (以自适应不同情况)
+    -   不同概念/笔记的优先级由 PageRank 算法设定（笔记之间存在轻重缓急）
+        -   大多数情况下基础概念/笔记具有更高优先级
+-   当用户对某个概念/笔记的自评为：
+    -   简单, 熟练度增加 `20` 复习间隔更新为 `原复习间隔 * 更新后熟练度 / 100 * 1.3` (1.3 是简单奖励)
+    -   记得, 熟练度不变，复习间隔更新为 `原复习间隔 * old_ease / 100`
+    -   困难, 熟练度降低 `20`，复习间隔更新为 `原复习间隔 * 0.5`
+        -   `0.5` 可在设置中更改
+        -   `最小熟练度 = 130`
+    -   当复习间隔不小于 `8` 天时
+        -   `间隔 += 随机取值({-扰动, 0, +扰动})`
+            -   设定 `扰动 = 向上取整(0.05 * 间隔)`
+            -   [Anki 文档](https://faqs.ankiweb.net/what-spaced-repetition-algorithm.html):
+                > "[...] Anki 还会加入少量的随机扰动，以防止同时出现且评级相同的卡片获得相同的复习周期，导致其它们是在同一天被复习。"
+-   复习规划信息将被存储于笔记的yaml front matter部分
diff --git a/docs/zh/contributing.md b/docs/zh/contributing.md
new file mode 100644
index 0000000..bc89ca0
--- /dev/null
+++ b/docs/zh/contributing.md
@@ -0,0 +1,156 @@
+# Contributing
+
+First off, thanks for wanting to contribute to the Spaced Repetition plugin!
+
+## Bug Reports & Feature Requests
+
+-   Check the [roadmap](https://github.com/st3v3nmw/obsidian-spaced-repetition/projects/2/) for upcoming features & fixes.
+-   Raise an issue [here](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/) if you have a feature request or a bug report.
+-   Visit the [discussions](https://github.com/st3v3nmw/obsidian-spaced-repetition/discussions/) section for Q&A help, feedback, and general discussion.
+
+## Translating
+
+### Steps
+
+To help translate the plugin to your language:
+
+1. Fork the [repository](https://github.com/st3v3nmw/obsidian-spaced-repetition).
+2. Copy the entries from `src/lang/locale/en.ts` to the proper file in `src/lang/locale/` (i.e. `fr.ts` for French, or `sw.ts` for Swahili). The locale codes are [IETF language tags](https://en.wikipedia.org/wiki/IETF_language_tag).
+3. Translate,
+4. Then open a pull request,
+
+### Example
+
+Sample `en.ts` file:
+
+```typescript
+// English
+
+export default {
+    EASY: "Easy",
+    SHOW_ANSWER: "Show Answer",
+    DAYS_STR_IVL: "${interval} days",
+    CHECK_ALGORITHM_WIKI:
+        'For more information, check the <a href="${algo_url}">algorithm implementation</a>.',
+};
+```
+
+Equivalent `sw.ts` file:
+
+```typescript
+// Swahili
+
+export default {
+    EASY: "Rahisi",
+    SHOW_ANSWER: "Onyesha Jibu",
+    DAYS_STR_IVL: "Siku ${interval}",
+    CHECK_ALGORITHM_WIKI:
+        'Kwa habari zaidi, angalia <a href="${algo_url}">utekelezaji wa algorithm</a>.',
+};
+```
+
+<sub><sup>A part of that last one is uhh, Google translated, I have a working understanding of Swahili but not enough to write computerese lol.</sup></sub>
+
+Please note that:
+
+1. Only the strings(templates) on the right of the key should be translated.
+2. Text inside `${}` isn't translated. This is used to replace variables in code. For instance, if interval = 4, it becomes `4 days` in English & `Siku 4` in Swahili. Quite nifty if you ask me.
+
+## Code
+
+1. Make your changes.
+2. Run `pnpm dev` to test the changes inside Obsidian.
+3. You could create symbolic links between the build files and the Obsidian vault, example:
+
+    ```bash
+    # remove existing files in the Obsidian vault
+    rm ~/notes/.obsidian/plugins/obsidian-spaced-repetition/main.js ~/notes/.obsidian/plugins/obsidian-spaced-repetition/manifest.json ~/notes/.obsidian/plugins/obsidian-spaced-repetition/styles.css
+    # use absolute paths
+    ln -s /home/stephen/obsidian-spaced-repetition/build/main.js /home/stephen/notes/.obsidian/plugins/obsidian-spaced-repetition
+    ln -s /home/stephen/obsidian-spaced-repetition/manifest.json /home/stephen/notes/.obsidian/plugins/obsidian-spaced-repetition
+    ln -s /home/stephen/obsidian-spaced-repetition/styles.css /home/stephen/notes/.obsidian/plugins/obsidian-spaced-repetition
+    ```
+
+    - This can be coupled with the [Hot Reload plugin](https://github.com/pjeby/hot-reload)
+
+4. Document the "user-facing" changes e.g. new feature, UI change, etc.
+5. If your "business logic" is properly decoupled from Obsidian APIs, write some unit tests.
+    - This project uses [jest](https://jestjs.io/), tests are stored in `tests/`.
+    - `pnpm test`
+6. Add your change to the `[Unreleased]` section of the changelog (`docs/changelog.md`).
+    - The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), TL;DR:
+        - `Added` for new features.
+        - `Changed` for changes in existing functionality.
+        - `Deprecated` for soon-to-be removed features.
+        - `Removed` for now removed features.
+        - `Fixed` for any bug fixes.
+        - `Security` in case of vulnerabilities.
+    - You can also append a link to your GitHub profile, example:
+        - `Make flashcard text selectable [@st3v3nmw](https://github.com/st3v3nmw)`
+7. Before pushing your changes, run the linter: `pnpm lint`
+    - Format the code in case any warnings are raised: `pnpm format`
+8. Open the pull request.
+
+## Documentation
+
+The documentation consists of Markdown files which [MkDocs](https://www.mkdocs.org/) converts to static web pages.
+Specifically, this project uses [MkDocs Material](https://squidfunk.github.io/mkdocs-material/getting-started/).
+
+These files reside in `docs/` in the respective language's folder. For instance, English docs are located in `docs/en/`.
+
+The docs are served on [https://www.stephenmwangi.com/obsidian-spaced-repetition/](https://www.stephenmwangi.com/obsidian-spaced-repetition/).
+
+For small changes, you can simply open an pull request for merging (against the `master` branch).
+The changes will be live once a new [release](https://github.com/st3v3nmw/obsidian-spaced-repetition/releases) is made.
+
+For larger diffs, it's important that you check how your docs look like as explained below.
+
+### Viewing Docs Locally
+
+#### Initial Setup
+
+1. Create a virtual environment: `python3 -m venv venv`
+2. Activate it: `. venv/bin/activate`
+3. Install the required dependencies: `pip install -r requirements.txt`
+
+#### Viewing
+
+1. Activate the virtual environment: `. venv/bin/activate`
+2. Serve the docs: `mkdocs serve`
+3. View your documentation locally on [http://127.0.0.1:8000/obsidian-spaced-repetition/](http://127.0.0.1:8000/obsidian-spaced-repetition/), any changes you make will reflect on the browser instantly.
+
+### Translating Documentation
+
+1. Create a folder for your language in `docs/` if it doesn't exist. Use the language codes provided [here](https://squidfunk.github.io/mkdocs-material/setup/changing-the-language/#site-language).
+2. Add the code from (1) to the MkDocs configuration (`mkdocs.yml` - `plugins.i18n.languages`).
+3. Copy the files from the English (`en`) folder into the new folder.
+4. Translate then open a pull request.
+
+## Maintenance
+
+### Releases
+
+Example using `v1.9.2`:
+
+1. Create a new branch: `git switch -c release-v1.9.2`
+2. Bump the plugin version in `manifest.json` and `package.json` (following [Semantic Versioning](https://semver.org/spec/v2.0.0.html)).
+    - Semantic Versioning TL;DR, given a version number `MAJOR.MINOR.PATCH`, increment the:
+        - `MAJOR` version when you make incompatible API changes
+        - `MINOR` version when you add functionality in a backwards compatible manner
+        - `PATCH` version when you make backwards compatible bug fixes
+    - If the new version uses new Obsidian APIs, update `minAppVersion` and `versions.json` to reflect this.
+3. Run `pnpm changelog` to update the CHANGELOG.
+4. Commit and push the changes:
+
+    ```bash
+    git add .
+    git commit -m "Bump version to v1.9.2"
+    git push --set-upstream origin release-v1.9.2
+    ```
+
+5. Open and merge the PR into `master`.
+6. Locally, switch back to `master` and pull the changes: `git switch master && git pull`
+7. Create a git tag with the version: `git tag 1.9.2`
+8. Push the tag: `git push --tags`. <br> You're all set! [This GitHub action](https://github.com/st3v3nmw/obsidian-spaced-repetition/blob/master/.github/workflows/release.yml) should pick it up, create a release, publish it, and update the live documentation.
+
+[^1]: Check the Obsidian Tasks project which has [excellent contribution guidelines](https://github.com/obsidian-tasks-group/obsidian-tasks/blob/main/CONTRIBUTING.md).
diff --git a/docs/zh/flashcards.md b/docs/zh/flashcards.md
new file mode 100644
index 0000000..273293f
--- /dev/null
+++ b/docs/zh/flashcards.md
@@ -0,0 +1,240 @@
+# 卡片
+
+## 新建卡片
+
+[Piotr Wozniak的知识标准化二十守则](https://supermemo.guru/wiki/20_rules_of_knowledge_formulation) 是创建复习卡片时的良好入门指南。
+
+### 单行基础卡片 (Remnote风格)
+
+问题和答案以 `::` 分隔（可在设置中更改）。
+
+```markdown
+这是问题::这是答案
+```
+
+### 单行双向卡片
+
+创建 `正:::反` 与其反向卡片 `反:::正`.
+
+问题和答案以 `:::` 分隔（可在设置中更改）。
+
+```markdown
+这是问题:::这是答案
+```
+
+注意：初次复习时插件会同时展示正向和反向卡片。
+如果打开 **将关联卡片隐藏至下一天？** 将仅展示正向卡片。
+
+### 多行基础卡片
+
+卡片的正反面以 `?` 分隔（可在设置中更改）。
+
+```markdown
+多行卡片的正面
+?
+多行卡片的反面
+```
+
+只要正反面字段都在 `?` 的作用域内，卡片内容可以跨越多行:
+
+```markdown
+顾名思义
+多行卡片的内容
+可以跨越多行
+?
+这也包括
+卡片的反面
+```
+
+### 多行双向卡片
+
+创建 `正??反` 与其反向卡片 `反??正`.
+
+卡片的正反面以 `??` 分隔（可在设置中更改）。
+
+```markdown
+多行卡片的正面
+??
+多行卡片的反面
+```
+
+只要正反面字段都在 `??` 的作用域内，卡片内容可以跨越多行:
+
+```markdown
+顾名思义
+多行卡片的内容
+可以跨越多行
+??
+这也包括
+卡片的反面
+```
+
+注意：其隐藏机制同单行双向卡片
+
+### 填空卡片
+
+你可以轻松使用 `==高亮==` ，`**加粗**` ，或 `{{花括号}}` 创建挖空卡片.
+
+该特性可在设置中开关。
+
+暂不支持 Anki 风格的 `{{c1:This text}} would {{c2:generate}} {{c1:2 cards}}` 挖空语法。该特性正在 [计划中](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/93/)。
+
+## 卡组
+
+![Screenshot from 2021-06-05 19-28-24](https://user-images.githubusercontent.com/43380836/120922211-78603400-c6d0-11eb-9d09-bdd5df1c9112.png)
+
+卡组名称右边的绿色和蓝色数字分别表示到期卡片和新卡片数目。
+
+### 使用 Obsidian 标签
+
+1. 在设置中设定制卡标签 (默认为 `#flashcards`)。
+2. 将您想要制卡的笔记打上该标签。
+
+#### 标签层级
+
+注意 `#flashcards` 可以匹配嵌套标签例如 `#flashcards/subdeck/subdeck`.
+
+#### 单个文件包含多个标签
+
+单一文件中可以包含不同卡组的多个卡片内容。
+
+这是因为一个标签的作用域直到下一个标签出现才会结束。
+
+例如：
+
+```markdown
+#flashcards/deckA
+Question1 (in deckA)::Answer1
+Question2 (also in deckA)::Answer2
+Question3 (also in deckA)::Answer3
+
+#flashcards/deckB
+Question4 (in deckB)::Answer4
+Question5 (also in deckB)::Answer5
+
+#flashcards/deckC
+Question6 (in deckC)::Answer6
+```
+
+#### 多个卡组包含同一卡片
+
+通常情况下一张卡片只会出现在一个卡组。然而某些时候，一张卡片无法被恰当地归入单一卡组的层级结构中。
+
+这种情况下，卡片可以被标记为归属为多个卡组。比如下面这张卡片属于三个卡组。
+
+```markdown
+#flashcards/language/words #flashcards/trivia #flashcards/learned-from-tv
+A group of cats is called a::clowder
+```
+
+注意，在上面的例子中所有标签必须位于一行并以空格隔开。
+
+#### 作用于特定问答的卡片
+
+位于卡片内容同一行开头处的标签是「仅限当前问答」的。
+
+例如：
+
+```markdown
+#flashcards/deckA
+Question1 (in deckA)::Answer1
+Question2 (also in deckA)::Answer2
+Question3 (also in deckA)::Answer3
+
+#flashcards/deckB Question4 (in deckB)::Answer4
+
+Question6 (in deckA)::Answer6
+```
+
+此处 `Question6` 将出现在 `deckA` 但不会出现于 `deckB` 因 `deckB` 是仅作用于 `Question4` 的标签。
+
+### 使用目录结构
+
+插件将自动遍历目录结构并依次创建卡组和子卡组，例如 `Folder/sub-folder/sub-sub-folder` ⇔ `Deck/sub-deck/sub-sub-deck`。
+
+这是使用标签指定卡组的替代方案，可以在设置中打开。
+
+## 复习
+
+制卡完成后即可在左边栏中点击图标开始复习。当一张卡片被复习后，将会被附上一个包含下一次复习时间、复习间隔和熟练度的HTML注释。
+
+```
+<!--SR:!2021-08-20,13,290-->
+```
+
+HTML注释在笔记预览页面中不可见。对于单行卡片，你可以在设置中选择让这个标签位于同一行还是另起一行。放置在同一行可以防止破坏markdown的列表渲染效果。
+
+注意您可以按 `S` 跳过一张卡片（大小写不敏感）。
+
+!!! 提示
+
+    如果您在移动设备上遇到了悬浮框尺寸的问题，进入设置并将 _Flashcard Height Percentage_ 和 _Flashcard Width Percentage_
+    设为 100% 以适应屏幕。
+
+### 快速复习
+
+你可以在快速复习中使用如下快捷键：
+
+-   `Space/Enter` => 显示答案
+-   `0` => 重置进度 (等价于 Anki 中的 `Again`)
+-   `1` => 标记为 `Hard`
+-   `2` 或 `Space` => 标记为 `Good`
+-   `3` => 标记为 `Easy`
+
+### 上下文
+
+如用于制卡的部分位于笔记标题之下，则卡片中会附加一个上下文标题。
+
+例如：
+
+```markdown
+#flashcards
+
+# Trivia
+
+## Capitals
+
+### Africa
+
+Kenya::Nairobi
+
+### North America
+
+Canada::Ottawa
+```
+
+卡片 `Kenya::Nairobi` 将会被附上 `Trivia > Capitals > Africa` 作为上下文标题而卡片 `Canada::Ottawa` 将会被附上 `Trivia > Capitals > North America` 作为上下文标题。
+
+### 删除卡片
+
+要删除一个卡片，只需删除复习规划标签和卡片相关文本。
+
+### 忽略卡片
+
+你可以使用诸如 `<!--Card text <!--SR:2021-08-20,13,290--> -->` 的HTML标签来将其从复习队列中移除。你可以随时移除该标签。
+
+## 集中复习
+
+当前仅支持使用 集中复习此笔记中的卡片 命令。将复习所有卡组中来自该笔记的卡片。
+
+## 数据统计
+
+统计页面可以使用 `View Statistics` 命令打开。
+
+### 预估
+
+计算将要到期的卡片数量。
+
+<img src="https://raw.githubusercontent.com/st3v3nmw/obsidian-spaced-repetition/master/assets/stats_forecast.png" />
+
+### 复习间隔
+
+统计卡片再次出现的时间间隔。
+
+### 熟练度
+
+统计卡片熟练度。
+
+### 卡片类型
+
+统计卡片类型：新卡片，较新卡片, 熟悉卡片（复习间隔超过一个月）。
diff --git a/docs/zh/index.md b/docs/zh/index.md
new file mode 100644
index 0000000..d27ab16
--- /dev/null
+++ b/docs/zh/index.md
@@ -0,0 +1,53 @@
+# Obsidian Spaced Repetition
+
+<img src="https://img.shields.io/github/downloads/st3v3nmw/obsidian-spaced-repetition/total" /> <img src="https://img.shields.io/github/downloads/st3v3nmw/obsidian-spaced-repetition/latest/total?style=flat-square" /> <img src="https://img.shields.io/github/manifest-json/v/st3v3nmw/obsidian-spaced-repetition?style=flat-square" />
+
+Fight the forgetting curve & note aging by reviewing flashcards & notes using spaced repetition on Obsidian.md
+
+-   阅读 [文档](https://www.stephenmwangi.com/obsidian-spaced-repetition/).
+-   查看新特性和故障修复 [规划](https://github.com/st3v3nmw/obsidian-spaced-repetition/projects/2/)
+-   如果您有新建议或故障报告，请提出 [请求](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/)
+-   访问 [讨论](https://github.com/st3v3nmw/obsidian-spaced-repetition/discussions/) 板块以获取问答帮助，意见反馈
+-   感谢Obsidian社区 😄 的贡献，本插件已支持 _Arabic / العربية, Chinese (Simplified) / 简体中文, Chinese (Traditional) / 繁體中文, Czech / čeština, German / Deutsch, Italian / Italiano, Korean / 한국어, Japanese / 日本語, Polish / Polski, Portuguese (Brazil) / Português do Brasil, Spanish / Español, and Russian / русский_
+    -   如果您愿意提供翻译上的帮助，请阅读 [翻译指南](https://www.stephenmwangi.com/obsidian-spaced-repetition/contributing/#translating_1).
+
+## 视频演示
+
+<video controls>
+  <source src="https://user-images.githubusercontent.com/43380836/115256965-5d455f00-a138-11eb-988f-27ba29f328a0.mp4" type="video/mp4">
+</video>
+
+## 安装
+
+在Obsidian搜索社区插件-`Spaced Repetition`.
+
+### 手动安装
+
+在您的Obsidian仓库中的 `.obsidian/plugins` 下创建 `obsidian-spaced-repetition` 目录。将 [最新发布的软件包](https://github.com/st3v3nmw/obsidian-spaced-repetition/releases) 解压并移动 `main.js`, `manifest.json` 和 `styles.css` 到该目录下。
+
+## 相关资源
+
+### YouTube 教程
+
+#### 卡片
+
+-   [PRODUCTIVELY Learning New Things Using Obsidian by @FromSergio](https://youtu.be/DwSNZEW6jCU)
+
+#### 笔记
+
+##### 渐进式写作
+
+-   [Obsidian: inbox review with spaced repetition by @aviskase](https://youtu.be/zG5r7QIY_TM)
+-   [Разгребатель инбокса заметок как у Andy Matuschak в Obsidian by @YuliyaBagriy_ru](https://youtu.be/CF6SSHB74cs)
+
+### 间隔重复系统
+
+-   [How to Remember Anything Forever-Ish by Nicky Case](https://ncase.me/remember/)
+-   [Spaced Repetition for Efficient Learning by Gwern](https://www.gwern.net/Spaced-repetition/)
+-   [20 rules of knowledge formulation by Dr. Piotr Wozniak](https://supermemo.guru/wiki/20_rules_of_knowledge_formulation)
+
+### 赞助
+
+<a href='https://ko-fi.com/M4M44DEN6' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://cdn.ko-fi.com/cdn/kofi3.png?v=2' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>
+
+<a href="https://jb.gg/OpenSourceSupport" target="_blank"><img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" height='128' style='border:0px;height:128px;' alt="JetBrains Logo (Main) logo."></a>
diff --git a/docs/zh/notes.md b/docs/zh/notes.md
new file mode 100644
index 0000000..fb9ad80
--- /dev/null
+++ b/docs/zh/notes.md
@@ -0,0 +1,71 @@
+# 笔记
+
+-   笔记应当具有原子性：说清楚**一个**概念；
+-   笔记之间应当高度关联；
+-   先理解，后复习；
+-   善用 [费曼学习法](https://fs.blog/2021/02/feynman-learning-technique/)
+
+## 开始使用
+
+为需要复习的笔记添加 `#review` 标签。你可以在插件设置中修改此默认标签（也可以使用多个标签）
+
+## 新笔记
+
+新笔记将展示在右栏的 `新` （复习序列）中，如图：
+
+<img src="https://raw.githubusercontent.com/st3v3nmw/obsidian-spaced-repetition/master/assets/new_notes.png" />
+
+## 复习
+
+打开笔记即可复习。在菜单中选择 `复习: 简单`，`复习: 记得` 或 `复习: 较难`。 选择 `简单`，`记得` 还是 `较难` 取决于你对复习材料的理解程度。
+
+<img src="https://raw.githubusercontent.com/st3v3nmw/obsidian-spaced-repetition/master/assets/more_options.png" />
+
+在文件上右击可以调出相同选项：
+
+<img src="https://raw.githubusercontent.com/st3v3nmw/obsidian-spaced-repetition/master/assets/file_context_menu.png" />
+
+笔记将被添加到复习队列中：
+
+<img src="https://raw.githubusercontent.com/st3v3nmw/obsidian-spaced-repetition/master/assets/scheduled.png" />
+
+### 快速复习
+
+我们提供快速进入复习模式的命令。你可以在 `设置 -> 快捷键` 中定制快捷键。这可以使您直接开始复习。
+
+### 复习设置
+
+可供定制的选项包括：
+
+-   随机打开笔记或按照优先级排序
+-   在复习完成后是否自动打开下一个笔记
+
+## 复习计划
+
+位于状态栏底部的 `复习: N 卡片已到期` 显示您今天需要复习的卡片数目（今日卡片 + 逾期卡片）。点击可打开一张卡片开始复习。
+
+您也可以使用 `打开一个笔记开始复习` 命令。
+
+## 复习序列
+
+-   每日复习条目将按照优先级排序 (PageRank)
+
+## 渐进式写作
+
+阅读 `@aviskase` 的 [介绍](https://github.com/st3v3nmw/obsidian-spaced-repetition/issues/15)
+
+视频资源：
+
+-   英文： [Obsidian: inbox review with spaced repetition](https://youtu.be/zG5r7QIY_TM)
+-   俄文： [Yuliya Bagriy - Разгребатель инбокса заметок как у Andy Matuschak в Obsidian](https://www.youtube.com/watch?v=CF6SSHB74cs)
+
+### 概要
+
+Andy Matuschak 在 [写作素材库中引入间隔重复系统](https://notes.andymatuschak.org/z7iCjRziX6V6unNWL81yc2dJicpRw2Cpp9MfQ).
+
+简而言之，可以进行四种操作 (此处 `x < y`):
+
+-   跳过笔记 (增加 `x` 天的复习间隔) == 标记为 `记得`
+-   已阅，觉得有用 (降低复习间隔) == 标记为 `较难`
+-   已阅，觉得没用 (增加 `y` 天的复习间隔) == 标记为 `简单`
+-   转换为 evergreen 笔记 （中止使用间隔重复系统）
diff --git a/mkdocs.yml b/mkdocs.yml
index 375c0f5..6537cdb 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -42,6 +42,7 @@ plugins:
           docs_structure: folder
           languages:
               en: English
+              zh: 简体中文
           default_language: en
 
 markdown_extensions:
diff --git a/src/gui/FlashcardReviewView.tsx b/src/gui/FlashcardReviewView.tsx
index cb6024b..5921cae 100644
--- a/src/gui/FlashcardReviewView.tsx
+++ b/src/gui/FlashcardReviewView.tsx
@@ -137,6 +137,8 @@ export class FlashcardReviewView {
             this._currentNote.filePath,
         );
         await wrapper.renderMarkdownWrapper(this._currentCard.front, this.content);
+        // Set scroll position back to top
+        this.content.scrollTop = 0;
 
         // Setup response buttons
         this._resetResponseButtons();
diff --git a/src/main.ts b/src/main.ts
index a8c3c27..fac5237 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -41,6 +41,7 @@ import { NoteEaseCalculator } from "./NoteEaseCalculator";
 import { DeckTreeStatsCalculator } from "./DeckTreeStatsCalculator";
 import { NoteEaseList } from "./NoteEaseList";
 import { QuestionPostponementList } from "./QuestionPostponementList";
+import { isEqualOrSubPath } from "./util/utils";
 
 interface PluginData {
     settings: SRSettings;
@@ -379,7 +380,7 @@ export default class SRPlugin extends Plugin {
         for (const noteFile of notes) {
             if (
                 this.data.settings.noteFoldersToIgnore.some((folder) =>
-                    noteFile.path.startsWith(folder),
+                    isEqualOrSubPath(noteFile.path, folder),
                 )
             ) {
                 continue;
@@ -567,7 +568,11 @@ export default class SRPlugin extends Plugin {
             fileCachedData.frontmatter || {};
 
         const tags = getAllTags(fileCachedData) || [];
-        if (this.data.settings.noteFoldersToIgnore.some((folder) => note.path.startsWith(folder))) {
+        if (
+            this.data.settings.noteFoldersToIgnore.some((folder) =>
+                isEqualOrSubPath(note.path, folder),
+            )
+        ) {
             new Notice(t("NOTE_IN_IGNORED_FOLDER"));
             return;
         }
diff --git a/src/util/RenderMarkdownWrapper.ts b/src/util/RenderMarkdownWrapper.ts
index 8b82f76..9e18784 100644
--- a/src/util/RenderMarkdownWrapper.ts
+++ b/src/util/RenderMarkdownWrapper.ts
@@ -27,7 +27,7 @@ export class RenderMarkdownWrapper {
     ): Promise<void> {
         if (recursiveDepth > 4) return;
 
-        MarkdownRenderer.renderMarkdown(markdownString, containerEl, this.notePath, this.plugin);
+        MarkdownRenderer.render(this.app, markdownString, containerEl, this.notePath, this.plugin);
 
         containerEl.findAll(".internal-embed").forEach((el) => {
             const link = this.parseLink(el.getAttribute("src"));
diff --git a/src/util/utils.ts b/src/util/utils.ts
index 6e4bfef..6882c5e 100644
--- a/src/util/utils.ts
+++ b/src/util/utils.ts
@@ -1,5 +1,6 @@
 import moment from "moment";
 import { Moment } from "moment";
+import { normalize, sep } from "path";
 import { PREFERRED_DATE_FORMAT, YAML_FRONT_MATTER_REGEX } from "src/constants";
 
 type Hex = number;
@@ -96,6 +97,38 @@ export function stringTrimStart(str: string): [string, string] {
     return [ws, trimmed];
 }
 
+/**
+ * Checks a path is equal or a subpath of the other rootPath
+ *
+ * @param toCheck The path to check it is equal or a subpath of path.
+ * @param rootPath The ref path to check the other is equal to or a subpath of this.
+ * @tutorial
+ * rootPath = "root/sub/sub2"
+ * if toCheck = "notRoot/..." -> false
+ * if toCheck = "root" -> true
+ * if toCheck = "root/sub" -> true
+ * if toCheck = "root/s" -> false
+ */
+export function isEqualOrSubPath(toCheck: string, rootPath: string): boolean {
+    const rootPathSections = normalize(rootPath.toLowerCase())
+        .replaceAll(/(\\|\/)/g, sep)
+        .split(sep)
+        .filter((p) => p !== "");
+    const pathSections = normalize(toCheck.toLowerCase())
+        .replaceAll(/(\\|\/)/g, sep)
+        .split(sep)
+        .filter((p) => p !== "");
+    if (pathSections.length < rootPathSections.length) {
+        return false;
+    }
+    for (let i = 0; i < rootPathSections.length; i++) {
+        if (rootPathSections[i] !== pathSections[i]) {
+            return false;
+        }
+    }
+    return true;
+}
+
 //
 // This returns [frontmatter, content]
 //


Test patch:
diff --git a/tests/unit/util/utils.test.ts b/tests/unit/util/utils.test.ts
index bcfcf46..89dd3c3 100644
--- a/tests/unit/util/utils.test.ts
+++ b/tests/unit/util/utils.test.ts
@@ -2,6 +2,7 @@ import { YAML_FRONT_MATTER_REGEX } from "src/constants";
 import {
     extractFrontmatter,
     findLineIndexOfSearchStringIgnoringWs,
+    isEqualOrSubPath,
     literalStringReplace,
 } from "src/util/utils";
 
@@ -243,3 +244,113 @@ describe("findLineIndexOfSearchStringIgnoringWs", () => {
         expect(findLineIndexOfSearchStringIgnoringWs(lines, "??")).toEqual(2);
     });
 });
+
+describe("isEqualOrSubPath", () => {
+    const winSep = "\\";
+    const linSep = "/";
+    const root = "root";
+    const sub_1 = "plugins";
+    const sub_2 = "obsidian-spaced-repetition";
+    const sub_3 = "data";
+    const noMatch = "notRoot";
+    const caseMatch = "Root";
+
+    describe("Windows", () => {
+        const sep = winSep;
+        const rootPath = root + sep + sub_1;
+
+        test("Upper and lower case letters", () => {
+            expect(isEqualOrSubPath(caseMatch, root)).toBe(true);
+            expect(isEqualOrSubPath(caseMatch.toUpperCase(), root)).toBe(true);
+        });
+
+        test("Seperator auto correction", () => {
+            expect(isEqualOrSubPath(root + winSep + sub_1, rootPath)).toBe(true);
+            expect(isEqualOrSubPath(root + winSep + sub_1 + winSep, rootPath)).toBe(true);
+
+            expect(isEqualOrSubPath(root + linSep + sub_1, rootPath)).toBe(true);
+            expect(isEqualOrSubPath(root + linSep + sub_1 + linSep, rootPath)).toBe(true);
+        });
+
+        test("Differnent path", () => {
+            expect(isEqualOrSubPath(noMatch, rootPath)).toBe(false);
+            expect(isEqualOrSubPath(noMatch + sep, rootPath)).toBe(false);
+            expect(isEqualOrSubPath(noMatch + sep + sub_1, rootPath)).toBe(false);
+            expect(isEqualOrSubPath(noMatch + sep + sub_1 + sep + sub_2, rootPath)).toBe(false);
+        });
+
+        test("Partially Match path", () => {
+            expect(isEqualOrSubPath("roo", rootPath)).toBe(false);
+            expect(isEqualOrSubPath("roo" + sep, rootPath)).toBe(false);
+            expect(isEqualOrSubPath(root + sep + "plug", rootPath)).toBe(false);
+            expect(isEqualOrSubPath(root + sep + "plug" + sep, rootPath)).toBe(false);
+        });
+
+        test("Same path", () => {
+            expect(isEqualOrSubPath(rootPath, rootPath)).toBe(true);
+        });
+
+        test("Subpath", () => {
+            expect(isEqualOrSubPath(root, rootPath)).toBe(false);
+            expect(isEqualOrSubPath(root + sep, rootPath)).toBe(false);
+            expect(isEqualOrSubPath(root + sep + sub_1, rootPath)).toBe(true);
+            expect(isEqualOrSubPath(rootPath, rootPath + sep)).toBe(true);
+            expect(isEqualOrSubPath(rootPath + sep, rootPath)).toBe(true);
+            expect(isEqualOrSubPath(root + sep + sub_1 + sep, rootPath)).toBe(true);
+            expect(isEqualOrSubPath(root + sep + sub_1 + sep + sub_2, rootPath)).toBe(true);
+            expect(isEqualOrSubPath(root + sep + sub_1 + sep + sub_2 + sep, rootPath)).toBe(true);
+            expect(isEqualOrSubPath(root + sep + sub_1 + sep + sub_2 + sep + sub_3, rootPath)).toBe(
+                true,
+            );
+        });
+    });
+    describe("Linux", () => {
+        const sep = linSep;
+        const rootPath = root + sep + sub_1;
+
+        test("Upper and lower case letters", () => {
+            expect(isEqualOrSubPath(caseMatch, root)).toBe(true);
+            expect(isEqualOrSubPath(caseMatch.toUpperCase(), root)).toBe(true);
+        });
+
+        test("Seperator auto correction", () => {
+            expect(isEqualOrSubPath(root + winSep + sub_1, rootPath)).toBe(true);
+            expect(isEqualOrSubPath(root + winSep + sub_1 + winSep, rootPath)).toBe(true);
+
+            expect(isEqualOrSubPath(root + linSep + sub_1, rootPath)).toBe(true);
+            expect(isEqualOrSubPath(root + linSep + sub_1 + linSep, rootPath)).toBe(true);
+        });
+
+        test("Differnent path", () => {
+            expect(isEqualOrSubPath(noMatch, rootPath)).toBe(false);
+            expect(isEqualOrSubPath(noMatch + sep, rootPath)).toBe(false);
+            expect(isEqualOrSubPath(noMatch + sep + sub_1, rootPath)).toBe(false);
+            expect(isEqualOrSubPath(noMatch + sep + sub_1 + sep + sub_2, rootPath)).toBe(false);
+        });
+
+        test("Partially Match path", () => {
+            expect(isEqualOrSubPath("roo", rootPath)).toBe(false);
+            expect(isEqualOrSubPath("roo" + sep, rootPath)).toBe(false);
+            expect(isEqualOrSubPath(root + sep + "plug", rootPath)).toBe(false);
+            expect(isEqualOrSubPath(root + sep + "plug" + sep, rootPath)).toBe(false);
+        });
+
+        test("Same path", () => {
+            expect(isEqualOrSubPath(rootPath, rootPath)).toBe(true);
+        });
+
+        test("Subpath", () => {
+            expect(isEqualOrSubPath(root, rootPath)).toBe(false);
+            expect(isEqualOrSubPath(root + sep, rootPath)).toBe(false);
+            expect(isEqualOrSubPath(root + sep + sub_1, rootPath)).toBe(true);
+            expect(isEqualOrSubPath(rootPath, rootPath + sep)).toBe(true);
+            expect(isEqualOrSubPath(rootPath + sep, rootPath)).toBe(true);
+            expect(isEqualOrSubPath(root + sep + sub_1 + sep, rootPath)).toBe(true);
+            expect(isEqualOrSubPath(root + sep + sub_1 + sep + sub_2, rootPath)).toBe(true);
+            expect(isEqualOrSubPath(root + sep + sub_1 + sep + sub_2 + sep, rootPath)).toBe(true);
+            expect(isEqualOrSubPath(root + sep + sub_1 + sep + sub_2 + sep + sub_3, rootPath)).toBe(
+                true,
+            );
+        });
+    });
+});