Skip to content

Commit 6d1c009

Browse files
author
Pseudonium
authored
Merge pull request #158 from Pseudonium/develop
Tag integration and Folder settings
2 parents b22931e + 24cb244 commit 6d1c009

9 files changed

Lines changed: 207 additions & 28 deletions

File tree

main.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export default class MyPlugin extends Plugin {
1919
CUSTOM_REGEXPS: {},
2020
FILE_LINK_FIELDS: {},
2121
CONTEXT_FIELDS: {},
22+
FOLDER_DECKS: {},
23+
FOLDER_TAGS: {},
2224
Syntax: {
2325
"Begin Note": "START",
2426
"End Note": "END",
@@ -38,6 +40,7 @@ export default class MyPlugin extends Plugin {
3840
"CurlyCloze": false,
3941
"CurlyCloze - Highlights to Clozes": false,
4042
"ID Comments": true,
43+
"Add Obsidian Tags": false,
4144
}
4245
}
4346
/*Making settings from scratch, so need note types*/

manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "obsidian-to-anki-plugin",
33
"name": "Obsidian_to_Anki",
4-
"version": "3.3.5",
4+
"version": "3.4.0",
55
"minAppVersion": "0.9.20",
66
"description": "This is an Anki integration plugin! Designed for efficient bulk exporting.",
77
"author": "Pseudonium",

src/file.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,15 @@ export class AllFile extends AbstractFile {
258258

259259
add_spans_to_ignore() {
260260
this.ignore_spans = []
261+
this.ignore_spans.push(...spans(this.data.FROZEN_REGEXP, this.file))
262+
const deck_result = this.file.match(this.data.DECK_REGEXP)
263+
if (deck_result) {
264+
this.ignore_spans.push([deck_result.index, deck_result.index + deck_result[0].length])
265+
}
266+
const tag_result = this.file.match(this.data.TAG_REGEXP)
267+
if (tag_result) {
268+
this.ignore_spans.push([tag_result.index, tag_result.index + tag_result[0].length])
269+
}
261270
this.ignore_spans.push(...spans(this.data.NOTE_REGEXP, this.file))
262271
this.ignore_spans.push(...spans(this.data.INLINE_REGEXP, this.file))
263272
this.ignore_spans.push(...spans(c.OBS_INLINE_MATH_REGEXP, this.file))

src/files-manager.ts

Lines changed: 70 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*Class for managing a list of files, and their Anki requests.*/
2-
import { ParsedSettings } from './interfaces/settings-interface'
3-
import { App, TFile, CachedMetadata, FileSystemAdapter, Notice } from 'obsidian'
2+
import { ParsedSettings, FileData } from './interfaces/settings-interface'
3+
import { App, TFile, TFolder, TAbstractFile, CachedMetadata, FileSystemAdapter, Notice } from 'obsidian'
44
import { AllFile } from './file'
55
import * as AnkiConnect from './anki'
66
import { basename } from 'path'
@@ -73,41 +73,93 @@ export class FileManager {
7373
return "obsidian://open?vault=" + encodeURIComponent(this.data.vault_name) + String.raw`&file=` + encodeURIComponent(file.path)
7474
}
7575

76-
async initialiseFiles() {
77-
await this.genAllFiles()
78-
let files_changed: Array<AllFile> = []
79-
let obfiles_changed: TFile[] = []
80-
for (let index in this.ownFiles) {
81-
const i = parseInt(index)
82-
let file = this.ownFiles[i]
83-
if (!(this.file_hashes.hasOwnProperty(file.path) && file.getHash() === this.file_hashes[file.path])) {
84-
//Indicates it's changed or new
85-
console.info("Scanning ", file.path, "as it's changed or new.")
86-
file.scanFile()
87-
files_changed.push(file)
88-
obfiles_changed.push(this.files[i])
76+
getFolderPathList(file: TFile): TFolder[] {
77+
let result: TFolder[] = []
78+
let abstractFile: TAbstractFile = file
79+
while (abstractFile.hasOwnProperty('parent')) {
80+
result.push(abstractFile.parent)
81+
abstractFile = abstractFile.parent
82+
}
83+
result.pop() // Removes top-level vault
84+
return result
85+
}
86+
87+
getDefaultDeck(file: TFile, folder_path_list: TFolder[]): string {
88+
let folder_decks = this.data.folder_decks
89+
for (let folder of folder_path_list) {
90+
// Loops over them from innermost folder
91+
if (folder_decks[folder.path] !== "") {
92+
return folder_decks[folder.path]
8993
}
9094
}
91-
this.ownFiles = files_changed
92-
this.files = obfiles_changed
95+
// If no decks specified
96+
return this.data.template.deckName
97+
}
98+
99+
getDefaultTags(file: TFile, folder_path_list: TFolder[]): string[] {
100+
let folder_tags = this.data.folder_tags
101+
let tags_list: string[] = []
102+
for (let folder of folder_path_list) {
103+
// Loops over them from innermost folder
104+
if (folder_tags[folder.path] !== "") {
105+
tags_list.push(...folder_tags[folder.path].split(" "))
106+
}
107+
}
108+
tags_list.push(...this.data.template.tags)
109+
return tags_list
110+
}
111+
112+
dataToFileData(file: TFile): FileData {
113+
const folder_path_list: TFolder[] = this.getFolderPathList(file)
114+
let result: FileData = JSON.parse(JSON.stringify(this.data))
115+
//Lost regexp, so have to get them back
116+
result.FROZEN_REGEXP = this.data.FROZEN_REGEXP
117+
result.DECK_REGEXP = this.data.DECK_REGEXP
118+
result.TAG_REGEXP = this.data.TAG_REGEXP
119+
result.NOTE_REGEXP = this.data.NOTE_REGEXP
120+
result.INLINE_REGEXP = this.data.INLINE_REGEXP
121+
result.EMPTY_REGEXP = this.data.EMPTY_REGEXP
122+
result.template.deckName = this.getDefaultDeck(file, folder_path_list)
123+
result.template.tags = this.getDefaultTags(file, folder_path_list)
124+
return result
93125
}
94126

95127
async genAllFiles() {
96128
for (let file of this.files) {
97129
const content: string = await this.app.vault.read(file)
98130
const cache: CachedMetadata = this.app.metadataCache.getCache(file.path)
131+
const file_data = this.dataToFileData(file)
99132
this.ownFiles.push(
100133
new AllFile(
101134
content,
102135
file.path,
103136
this.data.add_file_link ? this.getUrl(file) : "",
104-
this.data,
137+
file_data,
105138
cache
106139
)
107140
)
108141
}
109142
}
110143

144+
async initialiseFiles() {
145+
await this.genAllFiles()
146+
let files_changed: Array<AllFile> = []
147+
let obfiles_changed: TFile[] = []
148+
for (let index in this.ownFiles) {
149+
const i = parseInt(index)
150+
let file = this.ownFiles[i]
151+
if (!(this.file_hashes.hasOwnProperty(file.path) && file.getHash() === this.file_hashes[file.path])) {
152+
//Indicates it's changed or new
153+
console.info("Scanning ", file.path, "as it's changed or new.")
154+
file.scanFile()
155+
files_changed.push(file)
156+
obfiles_changed.push(this.files[i])
157+
}
158+
}
159+
this.ownFiles = files_changed
160+
this.files = obfiles_changed
161+
}
162+
111163
async requests_1() {
112164
let requests: AnkiConnect.AnkiConnectRequest[] = []
113165
let temp: AnkiConnect.AnkiConnectRequest[] = []

src/interfaces/settings-interface.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { AnkiConnectNote } from './note-interface'
44
export interface PluginSettings {
55
CUSTOM_REGEXPS: Record<string, string>,
66
FILE_LINK_FIELDS: Record<string, string>,
7-
CONTEXT_FIELDS: Record<string, string>
7+
CONTEXT_FIELDS: Record<string, string>,
8+
FOLDER_DECKS: Record<string, string>,
9+
FOLDER_TAGS: Record<string, string>,
810
Syntax: {
911
"Begin Note": string,
1012
"End Note": string,
@@ -24,6 +26,7 @@ export interface PluginSettings {
2426
"CurlyCloze": boolean,
2527
"CurlyCloze - Highlights to Clozes": boolean,
2628
"ID Comments": boolean,
29+
"Add Obsidian Tags": boolean
2730
}
2831
}
2932

@@ -48,8 +51,11 @@ export interface FileData {
4851
highlights_to_cloze: boolean
4952
comment: boolean
5053
add_context: boolean
54+
add_obs_tags: boolean
5155
}
5256

5357
export interface ParsedSettings extends FileData {
5458
add_file_link: boolean
59+
folder_decks: Record<string, string>
60+
folder_tags: Record<string, string>
5561
}

src/note.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const TAG_PREFIX:string = "Tags: "
1212
export const TAG_SEP:string = " "
1313
export const ID_REGEXP_STR: string = String.raw`\n?(?:<!--)?(?:ID: (\d+).*)`
1414
export const TAG_REGEXP_STR: string = String.raw`(Tags: .*)`
15+
const OBS_TAG_REGEXP: RegExp = /#(\w+)/g
1516

1617
const ANKI_CLOZE_REGEXP: RegExp = /{{c\d+::[\s\S]+?}}/
1718
export const CLOZE_ERROR: number = 42
@@ -86,6 +87,14 @@ abstract class AbstractNote {
8687
const context_field = data.context_fields[this.note_type]
8788
template["fields"][context_field] += context
8889
}
90+
if (data.add_obs_tags) {
91+
for (let key in template["fields"]) {
92+
for (let match of template["fields"][key].matchAll(OBS_TAG_REGEXP)) {
93+
this.tags.push(match[1])
94+
}
95+
template["fields"][key] = template["fields"][key].replace(OBS_TAG_REGEXP, "")
96+
}
97+
}
8998
template["tags"].push(...this.tags)
9099
template["deckName"] = deck
91100
return {note: template, identifier: this.identifier}

src/setting-to-data.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export async function settingToData(app: App, settings: PluginSettings, fields_d
1313
result.custom_regexps = settings.CUSTOM_REGEXPS
1414
result.file_link_fields = settings.FILE_LINK_FIELDS
1515
result.context_fields = settings.CONTEXT_FIELDS
16+
result.folder_decks = settings.FOLDER_DECKS
17+
result.folder_tags = settings.FOLDER_TAGS
1618
result.template = {
1719
deckName: settings.Defaults.Deck,
1820
modelName: "",
@@ -39,6 +41,7 @@ export async function settingToData(app: App, settings: PluginSettings, fields_d
3941
result.add_file_link = settings.Defaults["Add File Link"]
4042
result.comment = settings.Defaults["ID Comments"]
4143
result.add_context = settings.Defaults["Add Context"]
44+
result.add_obs_tags = settings.Defaults["Add Obsidian Tags"]
4245

4346
return result
4447
}

src/settings.ts

Lines changed: 104 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { PluginSettingTab, Setting, Notice } from 'obsidian'
1+
import { PluginSettingTab, Setting, Notice, TFolder } from 'obsidian'
22
import * as AnkiConnect from './anki'
33

44
const defaultDescs = {
@@ -9,7 +9,8 @@ const defaultDescs = {
99
"Add Context": "Append 'context' for the card, in the form of path > heading > heading etc, to the field specified in the table.",
1010
"CurlyCloze": "Convert {cloze deletions} -> {{c1::cloze deletions}} on note types that have a 'Cloze' in their name.",
1111
"CurlyCloze - Highlights to Clozes": "Convert ==highlights== -> {highlights} to be processed by CurlyCloze.",
12-
"ID Comments": "Wrap note IDs in a HTML comment."
12+
"ID Comments": "Wrap note IDs in a HTML comment.",
13+
"Add Obsidian Tags": "Interpret #tags in the fields of a note as Anki tags, removing them from the note text in Anki."
1314
}
1415

1516
export class SettingsTab extends PluginSettingTab {
@@ -94,13 +95,11 @@ export class SettingsTab extends PluginSettingTab {
9495
context_field.controlEl.className += " anki-center"
9596
}
9697

97-
setup_table() {
98+
create_collapsible(name: string) {
9899
let {containerEl} = this;
99-
const plugin = (this as any).plugin
100-
containerEl.createEl('h3', {text: 'Note type settings'})
101100
let div = containerEl.createEl('div', {cls: "collapsible-item"})
102101
div.innerHTML = `
103-
<div class="collapsible-item-self"><div class="collapsible-item-collapse collapse-icon anki-rotated"><svg viewBox="0 0 100 100" width="8" height="8" class="right-triangle"><path fill="currentColor" stroke="currentColor" d="M94.9,20.8c-1.4-2.5-4.1-4.1-7.1-4.1H12.2c-3,0-5.7,1.6-7.1,4.1c-1.3,2.4-1.2,5.2,0.2,7.6L43.1,88c1.5,2.3,4,3.7,6.9,3.7 s5.4-1.4,6.9-3.7l37.8-59.6C96.1,26,96.2,23.2,94.9,20.8L94.9,20.8z"></path></svg></div><div class="collapsible-item-inner"></div><header >Note Type Table</header></div>
102+
<div class="collapsible-item-self"><div class="collapsible-item-collapse collapse-icon anki-rotated"><svg viewBox="0 0 100 100" width="8" height="8" class="right-triangle"><path fill="currentColor" stroke="currentColor" d="M94.9,20.8c-1.4-2.5-4.1-4.1-7.1-4.1H12.2c-3,0-5.7,1.6-7.1,4.1c-1.3,2.4-1.2,5.2,0.2,7.6L43.1,88c1.5,2.3,4,3.7,6.9,3.7 s5.4-1.4,6.9-3.7l37.8-59.6C96.1,26,96.2,23.2,94.9,20.8L94.9,20.8z"></path></svg></div><div class="collapsible-item-inner"></div><header>${name}</header></div>
104103
`
105104
div.addEventListener('click', function () {
106105
this.classList.toggle("active")
@@ -113,6 +112,13 @@ export class SettingsTab extends PluginSettingTab {
113112
content.style.display = "block"
114113
}
115114
})
115+
}
116+
117+
setup_note_table() {
118+
let {containerEl} = this;
119+
const plugin = (this as any).plugin
120+
containerEl.createEl('h3', {text: 'Note type settings'})
121+
this.create_collapsible("Note Type Table")
116122
let note_type_table = containerEl.createEl('table', {cls: "anki-settings-table"})
117123
let head = note_type_table.createTHead()
118124
let header_row = head.insertRow()
@@ -176,6 +182,10 @@ export class SettingsTab extends PluginSettingTab {
176182
if (!(plugin.settings["Defaults"].hasOwnProperty("CurlyCloze - Highlights to Clozes"))) {
177183
plugin.settings["Defaults"]["CurlyCloze - Highlights to Clozes"] = false
178184
}
185+
// To account for new add obsidian tags
186+
if (!(plugin.settings["Defaults"].hasOwnProperty("Add Obsidian Tags"))) {
187+
plugin.settings["Defaults"]["Add Obsidian Tags"] = false
188+
}
179189
for (let key of Object.keys(plugin.settings["Defaults"])) {
180190
// To account for removal of regex setting
181191
if (key === "Regex") {
@@ -230,12 +240,97 @@ export class SettingsTab extends PluginSettingTab {
230240
}
231241
}
232242

243+
get_folders(): TFolder[] {
244+
const app = (this as any).plugin.app
245+
let folder_list: TFolder[] = [app.vault.getRoot()]
246+
for (let folder of folder_list) {
247+
let filtered_list: TFolder[] = folder.children.filter((element) => element.hasOwnProperty("children")) as TFolder[]
248+
folder_list.push(...filtered_list)
249+
}
250+
return folder_list.slice(1) //Removes initial vault folder
251+
}
252+
253+
setup_folder_deck(folder: TFolder, row_cells: HTMLCollection) {
254+
const plugin = (this as any).plugin
255+
let folder_decks = plugin.settings.FOLDER_DECKS
256+
if (!(folder_decks.hasOwnProperty(folder.path))) {
257+
folder_decks[folder.path] = ""
258+
}
259+
let folder_deck = new Setting(row_cells[1] as HTMLElement)
260+
.addText(
261+
text => text.setValue(folder_decks[folder.path])
262+
.onChange((value) => {
263+
plugin.settings.FOLDER_DECKS[folder.path] = value
264+
plugin.saveAllData()
265+
})
266+
)
267+
folder_deck.settingEl = row_cells[1] as HTMLElement
268+
folder_deck.infoEl.remove()
269+
folder_deck.controlEl.className += " anki-center"
270+
}
271+
272+
setup_folder_tag(folder: TFolder, row_cells: HTMLCollection) {
273+
const plugin = (this as any).plugin
274+
let folder_tags = plugin.settings.FOLDER_TAGS
275+
if (!(folder_tags.hasOwnProperty(folder.path))) {
276+
folder_tags[folder.path] = ""
277+
}
278+
let folder_tag = new Setting(row_cells[2] as HTMLElement)
279+
.addText(
280+
text => text.setValue(folder_tags[folder.path])
281+
.onChange((value) => {
282+
plugin.settings.FOLDER_TAGS[folder.path] = value
283+
plugin.saveAllData()
284+
})
285+
)
286+
folder_tag.settingEl = row_cells[2] as HTMLElement
287+
folder_tag.infoEl.remove()
288+
folder_tag.controlEl.className += " anki-center"
289+
}
290+
291+
setup_folder_table() {
292+
let {containerEl} = this;
293+
const plugin = (this as any).plugin
294+
const folder_list = this.get_folders()
295+
containerEl.createEl('h3', {text: 'Folder settings'})
296+
this.create_collapsible("Folder Table")
297+
let folder_table = containerEl.createEl('table', {cls: "anki-settings-table"})
298+
let head = folder_table.createTHead()
299+
let header_row = head.insertRow()
300+
for (let header of ["Folder", "Folder Deck", "Folder Tags"]) {
301+
let th = document.createElement("th")
302+
th.appendChild(document.createTextNode(header))
303+
header_row.appendChild(th)
304+
}
305+
let main_body = folder_table.createTBody()
306+
if (!(plugin.settings.hasOwnProperty("FOLDER_DECKS"))) {
307+
plugin.settings.FOLDER_DECKS = {}
308+
}
309+
if (!(plugin.settings.hasOwnProperty("FOLDER_TAGS"))) {
310+
plugin.settings.FOLDER_TAGS = {}
311+
}
312+
for (let folder of folder_list) {
313+
let row = main_body.insertRow()
314+
315+
row.insertCell()
316+
row.insertCell()
317+
row.insertCell()
318+
319+
let row_cells = row.children
320+
321+
row_cells[0].innerHTML = folder.path
322+
this.setup_folder_deck(folder, row_cells)
323+
this.setup_folder_tag(folder, row_cells)
324+
}
325+
326+
}
327+
233328
setup_buttons() {
234329
let {containerEl} = this
235330
const plugin = (this as any).plugin
236331
let action_buttons = containerEl.createEl('h3', {text: 'Actions'})
237332
new Setting(action_buttons)
238-
.setName("Regenerate Table")
333+
.setName("Regenerate Note Type Table")
239334
.setDesc("Connect to Anki to regenerate the table with new note types, or get rid of deleted note types.")
240335
.addButton(
241336
button => {
@@ -304,7 +399,8 @@ export class SettingsTab extends PluginSettingTab {
304399
containerEl.empty()
305400
containerEl.createEl('h2', {text: 'Obsidian_to_Anki settings'})
306401
containerEl.createEl('a', {text: 'For more information check the wiki', href: "https://github.com/Pseudonium/Obsidian_to_Anki/wiki"})
307-
this.setup_table()
402+
this.setup_note_table()
403+
this.setup_folder_table()
308404
this.setup_syntax()
309405
this.setup_defaults()
310406
this.setup_buttons()

0 commit comments

Comments
 (0)