[
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: akosbalasko\notechie: # Replace with a single Otechie username\ncustom: buymeacoffee.com/akosbalasko\n"
  },
  {
    "path": ".gitignore",
    "content": "# Intellij\r\n*.iml\r\n.idea\r\n\r\n# npm\r\nnode_modules\r\npackage-lock.json\r\n\r\n# build\r\nmain.js\r\n*.js.map\r\n\r\n# obsidian\r\ndata.json\r\n\r\n#wakatime\r\n.wakatime-project"
  },
  {
    "path": "README.md",
    "content": "# Obsidian Zoottelkeeper\r\n\r\n[![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/akosbalasko/zoottelkeeper-obsidian-plugin?style=for-the-badge&sort=semver)](https://github.com/akosbalasko/zoottelkeeper-obsidian-plugin/releases/latest)\r\n![GitHub All Releases](https://img.shields.io/github/downloads/akosbalasko/zoottelkeeper-obsidian-plugin/total?style=for-the-badge)\r\n\r\n## Changes in v.0.18.0\r\n\r\n- Include/Exclude folders improved: absolute paths are required, independently from its first character (it can be '/', or simply just start with the name of the folder within the root path to be included/excluded ).\r\n- Specific character is introduced: if you type '*' at the end of the folder, it means that it AND its subdirectories(recursively) will be included or excluded\r\n\r\n### Example: \r\nAssuming that you have the following directories in the root of your vault: \r\n```\r\nNotes\r\nNotes/Daily\r\nArticles\r\nArticles/Science\r\n```\r\n\r\nIf you would like to include Notes, the Daily within and Articles to be included, but exclude Articles/Science you should set\r\ninclude property to:\r\n```\r\nNotes/* \r\nArticles\r\n```\r\nand exclude property to:\r\n```\r\nArticles/Science\r\n```\r\n\r\n\r\n## What's new in the latest version (v0.17.0)\r\n- Option to set a **template** for index files \r\n- Numbers sorted correctly in index links (bug: https://github.com/akosbalasko/zoottelkeeper-obsidian-plugin/issues/45)\r\n- Frontmatter separator is configurable (by default it's '---' )\r\n## What's new in the latest version (v0.16.1)\r\n\r\nPlenty of new features coming requested by you! Namely, from version 0.16.1 you can:\r\n\r\n- **use sections** ('---' lines) within the index files content, they won't be removed during the content update\r\n- **square brackets** are optional in tags\r\n- **embed child index notes** in the preview ('Embed sub-index content in preview' option in the config)\r\n- **select folders to cover or to be excluded** by the plugin\r\n- trigger the **indexing manually** (see 'Generate index now' button in the config)\r\n- **sort** the index links (ascending or descending) (see 'Index links Order' in the config)\r\n- **emojis** can be set to each row, folders and files can be denoted separately (see 'Emojis' section in the config)\r\n  \r\n## What's new (v0.10.0)\r\n**Customizabe index notes**: From now you can customize your index file, not the whole content will be updated, but the exact list to the notes only (and tags metadata if it is set).\r\n\r\nIn order to achieve this, I added 2 extra autogenerated texts to separate the note list to be updated from the other part of the index file. They appear in Edit mode only. \r\n\r\nThese are: \r\n  - 'Zoottelkeeper: Beginning of the autogenerated index file list' and\r\n  - 'Zoottelkeeper: End of the autogenerated index file list'\r\n  \r\nPlease do not remove them.\r\n\r\n## 1. General Idea\r\nFollowing the idea of Nick Milo and the [LYT](https://www.linkingyourthinking.com/) -concept (Linking Your Thinking), an amazing way to bring structure to your files, folder and thoughts is by using **Maps of Content** (MOCs). Because even though Obsidian is generally built around the idea of being 'beyond' folder structures, you generally still need to have some sort of system to store all those juicy insights.\r\n\r\n## 2. How does it work?\r\nZoottelKeeper watches the followings:\r\n\r\n- _Creation_ of files in rootFolder and any subfolders within \r\n- _Deletion_ of files in rootFolder and any subfolders within \r\n- _Move_ a file among rootFolder to subFolders\r\n- _Move_ a file among subfolders\r\n\r\n### 2.1 Introduction\r\nSo, the idea behind Zoottelkeeper is to help you generate the base form of these maps automatically. It does so by indexing all the files and folders that lay in a folder, thus creating a link from the file to all it's content.\r\n\r\n![image](https://user-images.githubusercontent.com/46029522/126865703-c3a3d12f-a88f-42d1-806a-415d9e1afa53.png)  -->  ![image](https://user-images.githubusercontent.com/46029522/126865758-883888d3-8cf1-496a-aa04-58ae6a4c69a6.png)  --> ![image](https://user-images.githubusercontent.com/46029522/126865823-84272e62-8f4f-417c-8af1-e624a02963be.png)\r\n\r\n**(1)** shows the current folder structure. The plugin generates an index-file in each folder, showing all files and folders it contains. An example list **(2)** is shown for the main folder, but the subfolders contain a similar file. Each of these index-files is tagged **(3)** based on your preferences. This then results in the graph view with \"folders\" **(4)** (it's actually the index-files that are connected, but it looks like folders) and their respective files **(5)**.\r\n\r\n### 2.2 What's actually cool about this?\r\nSo far so good, we've seen that before. The actually nice thing is, if I now move *Folder B* into *Folder A* **(6)**, then the index file will automatically update **(7)**, resulting in the desired graph view **(8)**.\r\n\r\n![image](https://user-images.githubusercontent.com/46029522/126866100-be3717da-cae6-4550-9e52-7719d00e49f7.png)  -->  ![image](https://user-images.githubusercontent.com/46029522/126866120-b2b8d0b1-2334-4be9-88d8-84bb825705a6.png)  -->  ![image](https://user-images.githubusercontent.com/46029522/126866136-ba068748-5698-4ca7-aeff-562ab0c435a0.png)\r\n\r\n### 2.3 Disclaimer and other used plugins\r\nYou might have noticed that you can't see the index files in the folders in view **(1)** and **(6)**, that is because I did not add a prefix to the index-file (so it's automatically named like the folder) and I also use the **Folder Note** plugin, just for the fact that it hides files in folders when they are named like the folder and displays them when you click on the folder (which is super nice for the MOC purpose here too).\r\n\r\n\r\n\r\nPlease note that manually created folder notes may be overwritten by Zottelkeeper, please set up your template using the **Templater** plugin.\r\n\r\n### 2.4 TL;DR\r\nDoes this plugin replace the need to think about structure? No. But it could relief you of the tedious work that has to happen when you just want to allocate files to a broad category and, what's even bigger, it will relief you of the pain to manually go through all the files and change their \"parent-category whenever a topic gets too big or you want to move it somewhere else. Basically all you have to do is save things where they belong and the plugin will map that basic structure out for you. You can then, on top of that, add whatever MOC, index or tag logic you like.\r\n\r\n## 3. Installation and Settings\r\nSimilarly to any other plugins it is downloadable within Obsidian. Then, after enabling it, you will be able to configure Zoottelkeeper in its config interface.\r\n\r\n![image](https://user-images.githubusercontent.com/46029522/126864195-4a8c7dd6-54ca-435e-a0bf-5a6520083609.png)\r\n\r\n### 3.1 Choose your List-Style\r\nThere is three different types of lists for you to choose from:\r\n\t- pure Obsidian links, \r\n\t- list items (dots)\r\n\t- links with checkboxes\r\n\r\n### 3.2 Choose your Index Prefix\r\nDepending on your preferences, you can set any prefix to your index-files (or none at all). (Please note that the prefix must be unique, otherwise, normal notes with the same note name might be recognized as index files, and in this cases they will be updated! \r\n\r\n### 3.3 Enable Meta Tags\r\nYou can choose to add YAML Meta Tags to your automatically generated index-files.\r\n\r\n### 3.4 Set Custom Meta Tags\r\nYou can set one or multiple custom Meta Tags. Since they are displayed in the YAML format, you don't need to add a '#'.\r\nIf you're setting multiple tags please make sure to separate them with commas.\r\n\r\n### 3.5 Additional Things\r\n- The file and the folder are no longer listed in the in the index-file.\r\n\t\r\n---\r\n\r\n### 3.6 Templates\r\n\r\n1. Install templater plugin (https://github.com/SilentVoid13/Templater)\r\n2. In the Templater's settings page:\r\n\r\n   1. Set a template folder\r\n   2. Create a template file (based on the example below) and assign it to your folder handled by Zottelkeeper\r\n   3. Set Templater to be triggered on file creation\r\n\r\n3. In Zoottelkeeper's settings page, specify the full path of your template location like 'templates/zoottel_template.md'. The template can be managed by the **Templater** plugin.\r\n\r\nIn order to prevent the generalization of the Zoottelkeeper's metadata in the real files created, please use the following template as a base, which puts Zoottelkeepers placeholders only if the filename ends with the parent folder's name (so it's an index file/ folder note): \r\n\r\n```markdown\r\n---\r\n\r\ntags: \r\n\r\n---\r\n\r\n<%* if (tp.file.title.endsWith(tp.file.folder())) { %>\r\n\r\n%% Zoottelkeeper: Beginning of the autogenerated index file list  %%\r\n%% Zoottelkeeper: End of the autogenerated index file list  %%\r\n\r\n<%* } %>\r\n\r\nWARNING: PLease make sure that the placeholders (lines starting with %% Zottelkeeper) pasted into Obsidian has double spaces before the ending '%%' characters in each line, otherwise they won't be recognized and therefore the whole index file is going to be regenerated removing the custom texts! \r\n  \r\n```\r\n\r\n## Release notes\r\n\r\n## Appreciation and feedbacks\r\n\r\nAny feedbacks or feature requests are welcome, feel free to [create issues on Zoottelkeeper's repository page](https://github.com/akosbalasko/zoottelkeeper-obsidian-plugin/issues/new)!\r\n\r\n\r\nIf you like the plugin, please let me know by giving a star to it on github: [![GitHub Repo stars](https://img.shields.io/github/stars/akosbalasko/zoottelkeeper-obsidian-plugin?style=social)](https://github.com/akosbalasko/zoottelkeeper-obsidian-plugin/stargazers)\r\n\r\nor you can <a href=\"https://www.buymeacoffee.com/akosbalasko\" target=\"_blank\"><img src=\"https://cdn.buymeacoffee.com/buttons/default-yellow.png\" alt=\"Buy Me A Coffee\" height=\"41\" width=\"174\"></a>\r\n\r\n\r\n## Disclaimer\r\n\r\n**As with every plugin, there is risk of data-loss and I don't give any guarantees or take any responsibility.**"
  },
  {
    "path": "consts.ts",
    "content": "export const ZOOTTELKEEPER_INDEX_LIST_BEGINNING_TEXT='%% Zoottelkeeper: Beginning of the autogenerated index file list  %%'\nexport const ZOOTTELKEEPER_INDEX_LIST_END_TEXT='%% Zoottelkeeper: End of the autogenerated index file list  %%'\n"
  },
  {
    "path": "defaultSettings.ts",
    "content": "import { SortOrder } from 'models';\nimport { IndexItemStyle, ZoottelkeeperPluginSettings } from './interfaces'\n\nexport const DEFAULT_SETTINGS: ZoottelkeeperPluginSettings = {\n\tindexPrefix: '_Index_of_',\n\tindexItemStyle: IndexItemStyle.PureLink,\n\tindexTagValue: 'MOC',\n\tindexTagBoolean: true,\n\tindexTagSeparator: ', ',\n\tindexTagLabel: 'tags',\n\tcleanPathBoolean: true,\n\tfolderEmoji: ':card_index_dividers:',\n\tfileEmoji: ':page_facing_up:',\n\tenableEmojis: false,\n\tfoldersExcluded: '',\n\tfoldersIncluded: '',\n\tsortOrder: SortOrder.ASC,\n\taddSquareBrackets: true,\n\tembedSubIndex: false,\n\ttemplateFile: '',\n\tfrontMatterSeparator: '---',\n};\n"
  },
  {
    "path": "interfaces/GeneralContentOptions.ts",
    "content": "\nimport { TAbstractFile, } from 'obsidian';\n\nexport interface GeneralContentOptions {\n\titems: Array<TAbstractFile>;\n\tinitValue: Array<string>;\n\tfunc: Function;\n}"
  },
  {
    "path": "interfaces/IndexItemStyle.ts",
    "content": "\nexport enum IndexItemStyle {\n\tList = 'list',\n\tCheckbox = 'checkbox',\n\tPureLink='pureLink',\n}\n"
  },
  {
    "path": "interfaces/ZottelkeeperPluginSettings.ts",
    "content": "import { SortOrder } from './../models';\nimport { IndexItemStyle } from './IndexItemStyle';\n\nexport interface ZoottelkeeperPluginSettings {\n\tindexPrefix: string;\n\tindexItemStyle: IndexItemStyle;\n\tindexTagValue: string;\n\tindexTagBoolean: boolean;\n\tindexTagLabel: string;\n\tcleanPathBoolean: boolean;\n\tindexTagSeparator: string;\n\tfolderEmoji: string;\n\tfileEmoji: string;\n\tenableEmojis: boolean;\n\tfoldersIncluded: string;\n\tfoldersExcluded: string;\n  \tsortOrder: SortOrder;\n\taddSquareBrackets: boolean;\n\tembedSubIndex: boolean;\n\ttemplateFile: string;\n\tfrontMatterSeparator: string;\n    [key: string]: any;\n\n}"
  },
  {
    "path": "interfaces/index.ts",
    "content": "export * from './ZottelkeeperPluginSettings';\nexport * from './GeneralContentOptions';\nexport * from './IndexItemStyle';"
  },
  {
    "path": "main.ts",
    "content": "import { App, Modal, debounce, Plugin, PluginSettingTab, Setting, TFile, TAbstractFile, } from 'obsidian';\nimport { IndexItemStyle } from './interfaces/IndexItemStyle';\nimport { GeneralContentOptions, ZoottelkeeperPluginSettings } from './interfaces'\nimport { isInAllowedFolder, isInDisAllowedFolder, updateFrontmatter, updateIndexContent, removeFrontmatter, hasFrontmatter } from './utils'\nimport { DEFAULT_SETTINGS } from './defaultSettings';\nimport * as emoji from 'node-emoji';\nimport { SortOrder } from 'models';\n\nexport default class ZoottelkeeperPlugin extends Plugin {\n\tsettings: ZoottelkeeperPluginSettings;\n\tlastVault: Set<string>;\n\n\ttriggerUpdateIndexFile = debounce(\n\t\tthis.keepTheZooClean.bind(this, false),\n\t\t3000,\n\t\ttrue\n\t);\n\n\tasync onload(): Promise<void> {\n\t\tawait this.loadSettings();\n\t\tthis.app.workspace.onLayoutReady(async () => {\n\t\t\tthis.loadVault();\n\t\t\tconsole.debug(\n\t\t\t\t`Vault in files: ${JSON.stringify(\n\t\t\t\t\tthis.app.vault.getMarkdownFiles().map((f) => f.path)\n\t\t\t\t)}`\n\t\t\t);\n\t\t});\n\t\tthis.registerEvent(\n\t\t\tthis.app.vault.on('create', this.triggerUpdateIndexFile)\n\t\t);\n\t\tthis.registerEvent(\n\t\t\tthis.app.vault.on('delete', this.triggerUpdateIndexFile)\n\t\t);\n\t\tthis.registerEvent(\n\t\t\tthis.app.vault.on('rename', this.triggerUpdateIndexFile)\n\t\t);\n\n\t\tthis.addSettingTab(new ZoottelkeeperPluginSettingTab(this.app, this));\n\t}\n\tloadVault() {\n\t\tthis.lastVault = new Set(\n\t\t\tthis.app.vault.getMarkdownFiles().map((file) => file.path)\n\t\t);\n\t}\n\tasync keepTheZooClean(triggeredManually?: boolean) {\n\t\tconsole.debug('keeping the zoo clean...');\n\t\tif (this.lastVault || triggeredManually) {\n\t\t\tconst vaultFilePathsSet = new Set(\n\t\t\t\tthis.app.vault.getMarkdownFiles().map((file) => file.path)\n\t\t\t);\n\t\t\ttry {\n\t\t\t\t// getting the changed files using symmetric diff\n\n\t\t\t\tlet changedFiles = new Set([\n\t\t\t\t\t...Array.from(vaultFilePathsSet).filter(\n\t\t\t\t\t\t(currentFile) => !this.lastVault.has(currentFile)\n\t\t\t\t\t),\n\t\t\t\t\t...Array.from(this.lastVault).filter(\n\t\t\t\t\t\t(currentVaultFile) => !vaultFilePathsSet.has(currentVaultFile)\n\t\t\t\t\t),\n\t\t\t\t]);\n\t\t\t\tconsole.debug(\n\t\t\t\t\t`changedFiles: ${JSON.stringify(Array.from(changedFiles))}`\n\t\t\t\t);\n\t\t\t\t// getting index files to be updated\n\t\t\t\tconst indexFiles2BUpdated = new Set<string>();\n\n\t\t\t\tfor (const changedFile of Array.from(changedFiles)) {\n\t\t\t\t\tconst indexFilePath = this.getIndexFilePath(changedFile);\n\t\t\t\t\tif (indexFilePath\n\t\t\t\t\t\t&& isInAllowedFolder(this.settings, indexFilePath)\n\t\t\t\t\t\t&& !isInDisAllowedFolder(this.settings, indexFilePath)) {\n\t\t\t\t\t\tindexFiles2BUpdated.add(indexFilePath);\n\t\t\t\t\t}\n\n\t\t\t\t\t// getting the parents' index notes of each changed file in order to update their links as well (hierarhical backlinks)\n\t\t\t\t\tconst parentIndexFilePath = this.getIndexFilePath(\n\t\t\t\t\t\tthis.getParentFolder(changedFile)\n\t\t\t\t\t);\n\t\t\t\t\tif (parentIndexFilePath) indexFiles2BUpdated.add(parentIndexFilePath);\n\t\t\t\t}\n\t\t\t\tconsole.debug(\n\t\t\t\t\t`Index files to be updated: ${JSON.stringify(\n\t\t\t\t\t\tArray.from(indexFiles2BUpdated)\n\t\t\t\t\t)}`\n\t\t\t\t);\n\t\t\t\t\n\t\t\t\tawait this.removeDisallowedFoldersIndexes(indexFiles2BUpdated);\n\t\t\t\t// update index files\n\t\t\t\tfor (const indexFile of Array.from(indexFiles2BUpdated)) {\n\t\t\t\t\tawait this.generateIndexContents(indexFile);\n\t\t\t\t}\n\t\t\t\tawait this.cleanDisallowedFolders();\n\n\t\t\t} catch (e) {}\n\t\t}\n\t\tthis.lastVault = new Set(\n\t\t\tthis.app.vault.getMarkdownFiles().map((file) => file.path)\n\t\t);\n\t}\n\n\tonunload() {\n\t\tconsole.debug('unloading plugin');\n\t}\n\n\tasync loadSettings() {\n\t\tthis.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());\n\t}\n\n\tasync saveSettings() {\n\t\tawait this.saveData(this.settings);\n\t}\n\n\tgenerateIndexContents = async (indexFile: string): Promise<void> => {\n\t\tconst templateFile = this.app.vault.getAbstractFileByPath(this.settings.templateFile);\n\t\tlet currentTemplateContent = '';\n\n\t\tif (templateFile instanceof TFile){\n\t\t\tcurrentTemplateContent = await this.app.vault.cachedRead(templateFile);\n\t\t}\t\n\t\t\n\t\tlet indexTFile =\n\t\t\tthis.app.vault.getAbstractFileByPath(indexFile) ||\n\t\t\t(await this.app.vault.create(indexFile, currentTemplateContent));\n\n\t\tif (indexTFile && indexTFile instanceof TFile)\n\t\t\treturn this.generateIndexContent(indexTFile);\n\t};\n\n\t\n\n\tgenerateGeneralIndexContent = (options: GeneralContentOptions): Array<string> => {\n\t\treturn options.items\n\t\t\t.reduce(\n\t\t\t\t(acc, curr) => {\n\t\t\t\t\tacc.push(options.func(curr.path, this.isFile(curr)));\n\t\t\t\t\treturn acc;\n\t\t\t\t}, options.initValue);\n\n\t}\n\n\tgenerateIndexContent = async (indexTFile: TFile): Promise<void> => {\n\n\t\tlet indexContent;\n\t\t// get subFolders\n\t\t//const subFolders = indexTFile.parent.children.filter(item => !this.isFile(item));\n\t\t//const files = indexTFile.parent.children.filter(item => this.isFile(item));\n\n\t\tconst splitItems = indexTFile.parent.children.reduce(\n\t\t\t(acc,curr) => {\n\t\t\t\tif (this.isFile(curr))\n\t\t\t\t\tacc['files'].push(curr)\n\t\t\t\telse acc['subFolders'].push(curr);\n\t\t\t\treturn acc;\n\t\t\t}, {subFolders: [], files: []}\n\t\t)\n\n\t\tindexContent = this.generateGeneralIndexContent({\n\t\t\titems: splitItems.subFolders,\n\t\t\tfunc: this.generateIndexFolderItem,\n\t\t\tinitValue: [],\n\t\t})\n\t\tindexContent = this.generateGeneralIndexContent({\n\t\t\titems: splitItems.files.filter(file => file.name !== indexTFile.name ),\n\t\t\tfunc: this.generateIndexItem,\n\t\t\tinitValue: indexContent,\n\t\t})\n\n\t\ttry {\n\t\t\tif (indexTFile instanceof TFile){\n\n\t\t\t\tlet currentContent = await this.app.vault.cachedRead(indexTFile);\n\t\t\t\tif (currentContent === ''){\n\t\t\t\t\tconst templateFile = this.app.vault.getAbstractFileByPath(this.settings.templateFile);\n\t\t\t\n\t\t\t\t\tif (templateFile instanceof TFile){\n\t\t\t\t\t\tcurrentContent = await this.app.vault.cachedRead(templateFile);\n\t\t\t\t\t}\t\n\t\t\t\t}\n\t\t\t\tconst updatedFrontmatter = hasFrontmatter(currentContent, this.settings.frontMatterSeparator)\n\t\t\t\t ? updateFrontmatter(this.settings, currentContent)\n\t\t\t\t : '';\n\n\t\t\t\tcurrentContent = removeFrontmatter(currentContent, this.settings.frontMatterSeparator);\n\t\t\t\tconst updatedIndexContent = updateIndexContent(this.settings.sortOrder, currentContent, indexContent);\n\t\t\t\tawait this.app.vault.modify(indexTFile, `${updatedFrontmatter}${updatedIndexContent}`);\n\t\t\t} else {\n\t\t\t\tthrow new Error('Creation index as folder is not supported');\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tconsole.warn('Error during deletion/creation of index files', e);\n\t\t}\n\t};\n\tsetEmojiPrefix = (isFile: boolean): string => {\n\t\treturn this.settings.enableEmojis \n\t\t\t? isFile\n\t\t\t\t? emoji.get(this.settings.fileEmoji)\n\t\t\t\t: emoji.get(this.settings.folderEmoji)\n\t\t\t: '';\n\n\t}\n\n\tgenerateFormattedIndexItem = (path: string, isFile: boolean): string => {\n\t\tconst realFileName = `${path.split('|')[0]}.md`;\n\t\tconst fileAbstrPath = this.app.vault.getAbstractFileByPath(realFileName);\n\t\tconst embedSubIndexCharacter = this.settings.embedSubIndex && this.isIndexFile(fileAbstrPath) ? '!' : '';\n\t\t\n\t\tswitch (this.settings.indexItemStyle) {\n\t\t\tcase IndexItemStyle.PureLink:\n\t\t\t\treturn `${this.setEmojiPrefix(isFile)} ${embedSubIndexCharacter}[[${path}]]`;\n\t\t\tcase IndexItemStyle.List:\n\t\t\t\treturn `- ${this.setEmojiPrefix(isFile)} ${embedSubIndexCharacter}[[${path}]]`;\n\t\t\tcase IndexItemStyle.Checkbox:\n\t\t\t\treturn `- [ ] ${this.setEmojiPrefix(isFile)} ${embedSubIndexCharacter}[[${path}]]`\n\t\t};\n\t}\n\n\tgenerateIndexItem = (path: string, isFile: boolean): string => {\n\t\tlet internalFormattedIndex;\n\t\tif (this.settings.cleanPathBoolean) {\n\t\t\tconst cleanPath = ( path.endsWith(\".md\"))\n\t\t\t\t? path.replace(/\\.md$/,'')\n\t\t\t\t: path;\n\t\t\tconst fileName = cleanPath.split(\"/\").pop();\n\t\t\tinternalFormattedIndex = `${cleanPath}|${fileName}`;\t\t\n\t\t}\n\t\telse {\n\t\t\tinternalFormattedIndex = path;\n\t\t}\n\t\treturn this.generateFormattedIndexItem(internalFormattedIndex, isFile);\n\t}\n\n\tgenerateIndexFolderItem = (path: string, isFile: boolean): string => {\n\t\treturn this.generateIndexItem(this.getInnerIndexFilePath(path), isFile);\n\t}\n\n\tgetInnerIndexFilePath = (folderPath: string): string => {\n\t\tconst folderName = this.getFolderName(folderPath);\n\t\treturn `${folderPath}/${this.settings.indexPrefix}${folderName}.md`;\n\t}\n\tgetIndexFilePath = (filePath: string): string => {\n\t\tconst fileAbstrPath = this.app.vault.getAbstractFileByPath(filePath);\n\n\t\tif (this.isIndexFile(fileAbstrPath)) return null;\n\t\tlet parentPath = this.getParentFolder(filePath);\n\n\t\t// if its parent does not exits, then its a moved subfolder, so it should not be updated\n\t\tconst parentTFolder = this.app.vault.getAbstractFileByPath(parentPath);\n\t\tif (parentPath && parentPath !== '') {\n\t\t\tif (!parentTFolder) return undefined;\n\t\t\tparentPath = `${parentPath}/`;\n\t\t}\n\t\tconst parentName = this.getParentFolderName(filePath);\n\n\t\treturn `${parentPath}${this.settings.indexPrefix}${parentName}.md`;\n\t};\n\n\tremoveDisallowedFoldersIndexes = async (indexFiles: Set<string>): Promise<void> => {\n\t\tfor (const folder of this.settings.foldersExcluded.split('\\n').map(f=> f.trim())){\n\t\t\tconst innerIndex = this.getInnerIndexFilePath(folder);\n\t\t\tindexFiles.delete(innerIndex);\n\t\t}\n\t}\n\n\tcleanDisallowedFolders = async (): Promise<void> => {\n\t\tfor (const folder of this.settings.foldersExcluded.split('\\n').map(f=> f.trim())){\n\t\t\tconst innerIndex = this.getInnerIndexFilePath(folder);\n\t\t\tconst indexTFile = this.app.vault.getAbstractFileByPath(innerIndex);\n\t\t\tawait this.app.vault.delete(indexTFile);\n\t\t}\n\t}\n\n\tgetParentFolder = (filePath: string): string => {\n\t\tconst fileFolderArray = filePath.split('/');\n\t\tfileFolderArray.pop();\n\n\t\treturn fileFolderArray.join('/');\n\t};\n\n\tgetParentFolderName = (filePath: string): string => {\n\t\tconst parentFolder = this.getParentFolder(filePath);\n\t\tconst fileFolderArray = parentFolder.split('/');\n\t\treturn fileFolderArray[0] !== ''\n\t\t\t? fileFolderArray[fileFolderArray.length - 1]\n\t\t\t: this.app.vault.getName();\n\t};\n\n\tgetFolderName = (folderPath: string): string => {\n\t\tconst folderArray = folderPath.split('/');\n\t\treturn (folderArray[0] !== '') ? folderArray[folderArray.length - 1] : this.app.vault.getName();\n\t}\n\n\tisIndexFile = (item: TAbstractFile): boolean => {\n\n\t\treturn this.isFile(item)\n\t\t\t&& (this.settings.indexPrefix === ''\n\t\t\t\t? item.name === item.parent.name\n\t\t\t\t: item.name.startsWith(this.settings.indexPrefix));\n\t}\n\n\tisFile = (item: TAbstractFile): boolean => {\n\t\treturn item instanceof TFile;\n\t}\n\n}\n\nclass ZoottelkeeperPluginModal extends Modal {\n\tconstructor(app: App) {\n\t\tsuper(app);\n\t}\n}\n\nclass ZoottelkeeperPluginSettingTab extends PluginSettingTab {\n\tplugin: ZoottelkeeperPlugin;\n\n\tconstructor(app: App, plugin: ZoottelkeeperPlugin) {\n\t\tsuper(app, plugin);\n\t\tthis.plugin = plugin;\n\t}\n\n\tdisplay(): void {\n\t\tlet { containerEl } = this;\n\n\t\tcontainerEl.empty();\n\t\tcontainerEl.createEl('h2', { text: 'Zoottelkeeper Settings' });\n\t\tcontainerEl.createEl('h3', { text: 'Folder Settings' });\n\t\t\n\t\tnew Setting(containerEl)\n\t\t\t.setName('Folders included')\n\t\t\t.setDesc(\n\t\t\t\t'Specify the folders to be handled by Zoottelkeeper. They must be absolute paths starting from the root vault, one per line, example: Notes/ <enter> Articles/, which will include Notes and Articles folder in the root folder. Empty list means all of the vault will be handled except the excluded folders. \\'*\\' can be added to the end, to include the folder\\'s subdirectories recursively, e.g. Notes/* <enter> Articles/'\n\t\t\t)\n\t\t\t.addTextArea((text) =>\n\t\t\t\ttext\n\t\t\t\t\t.setPlaceholder('')\n\t\t\t\t\t.setValue(this.plugin.settings.foldersIncluded)\n\t\t\t\t\t.onChange(async (value) => {\n\t\t\t\t\t\tthis.plugin.settings.foldersIncluded = value\n\t\t\t\t\t\t\t.replace(/,/g,'\\n')\n\t\t\t\t\t\t\t.split('\\n')\n\t\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t\tfolder=> {\n\t\t\t\t\t\t\t\t\tconst f = folder.trim();\n\t\t\t\t\t\t\t\t\treturn f.startsWith('/')\n\t\t\t\t\t\t\t\t\t\t? f.substring(1)\n\t\t\t\t\t\t\t\t\t\t: f\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.join('\\n');\n\t\t\t\t\t\tawait this.plugin.saveSettings();\n\t\t\t\t\t})\n\t\t\t);\n\t\tnew Setting(containerEl)\n\t\t\t.setName('Folders excluded')\n\t\t\t.setDesc(\n\t\t\t\t'Specify the folders NOT to be handled by Zoottelkeeper. They must be absolute paths starting from the root vault, one per line. Example:  \"Notes/ <enter>  Articles/ \", it will exclude Notes and Articles folder in the root folder. * can be added to the end, to exclude the folder\\'s subdirectories recursively.'\n\t\t\t)\n\t\t\t.addTextArea((text) =>\n\t\t\t\ttext\n\t\t\t\t\t.setPlaceholder('')\n\t\t\t\t\t.setValue(this.plugin.settings.foldersExcluded)\n\t\t\t\t\t.onChange(async (value) => {\n\t\t\t\t\t\tthis.plugin.settings.foldersExcluded = value\n\t\t\t\t\t\t\t.replace(/,/g,'\\n')\n\t\t\t\t\t\t\t.split('\\n')\n\t\t\t\t\t\t\t.map(\n\t\t\t\t\t\t\t\tfolder=> {\n\t\t\t\t\t\t\t\t\tconst f = folder.trim();\n\t\t\t\t\t\t\t\t\treturn f.startsWith('/')\n\t\t\t\t\t\t\t\t\t\t? f.substring(1)\n\t\t\t\t\t\t\t\t\t\t: f\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.join('\\n');;\n\t\t\t\t\t\tawait this.plugin.saveSettings();\n\t\t\t\t\t})\n\t\t\t);\n\t\tnew Setting(containerEl)\n\t\t\t.setName('Trigger indexing')\n\t\t\t.setDesc(\n\t\t\t\t'By pushing this button you can trigger the indexing on folders match your include/exclude criterias currently set.'\n\t\t\t)\n\t\t\t.addButton((btn) => {\n\t\t\t\t\tbtn.setButtonText('Generate index now')\n\t\t\t\t\tbtn.onClick(async () => {\n\t\t\t\t\t\tthis.plugin.lastVault = new Set();\n\t\t\t\t\t\tawait this.plugin.keepTheZooClean(true);\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t);\n\n\n\t\tcontainerEl.createEl('h3', { text: 'General Settings' });\n\t\tnew Setting(containerEl)\n\t\t\t.setName(\"Clean Files\")\n\t\t\t.setDesc(\n\t\t\t\t\"This enables you to only show the files without path and '.md' ending in preview mode.\"\n\t\t\t)\n\t\t\t.addToggle((t) => {\n\t\t\t\tt.setValue(this.plugin.settings.cleanPathBoolean);\n\t\t\t\tt.onChange(async (v) => {\n\t\t\t\t\tthis.plugin.settings.cleanPathBoolean = v;\n\t\t\t\t\tawait this.plugin.saveSettings();\n\t\t\t\t});\n\t\t\t});\n\t\tnew Setting(containerEl)\n\t\t\t.setName('Index links Order')\n\t\t\t.setDesc('Select the order of the links to be sorted in the index files.')\n\t\t\t.addDropdown(async (dropdown) => {\n\t\t\t\tdropdown.addOption(SortOrder.ASC, 'Ascending');\n\t\t\t\tdropdown.addOption(SortOrder.DESC, 'Descending');\n\n\t\t\t\tdropdown.setValue(this.plugin.settings.sortOrder);\n\t\t\t\tdropdown.onChange(async (option) => {\n\t\t\t\t\tthis.plugin.settings.sortOrder = option as SortOrder;\n\t\t\t\t\tawait this.plugin.saveSettings();\n\t\t\t\t});\n\t\t\t});\n\n\t\tnew Setting(containerEl)\n\t\t\t.setName('List Style')\n\t\t\t.setDesc('Select the style of the index-list.')\n\t\t\t.addDropdown(async (dropdown) => {\n\t\t\t\tdropdown.addOption(IndexItemStyle.PureLink, 'Pure Obsidian link');\n\t\t\t\tdropdown.addOption(IndexItemStyle.List, 'Listed link');\n\t\t\t\tdropdown.addOption(IndexItemStyle.Checkbox, 'Checkboxed link');\n\n\t\t\t\tdropdown.setValue(this.plugin.settings.indexItemStyle);\n\t\t\t\tdropdown.onChange(async (option) => {\n\t\t\t\t\tconsole.debug('Chosen index item style: ' + option);\n\t\t\t\t\tthis.plugin.settings.indexItemStyle = option as IndexItemStyle;\n\t\t\t\t\tawait this.plugin.saveSettings();\n\t\t\t\t});\n\t\t\t});\n\t\tnew Setting(containerEl)\n\t\t\t.setName('Embed sub-index content in preview')\n\t\t\t.setDesc(\n\t\t\t\t\"If you enable this, the plugin will embed the sub-index content in preview mode.\"\n\t\t\t)\n\t\t\t.addToggle((t) => {\n\t\t\t\tt.setValue(this.plugin.settings.embedSubIndex);\n\t\t\t\tt.onChange(async (v) => {\n\t\t\t\t\tthis.plugin.settings.embedSubIndex = v;\n\t\t\t\t\tawait this.plugin.saveSettings();\n\t\t\t\t});\n\t\t\t});\n\n\t\t// index prefix\n\t\tnew Setting(containerEl)\n\t\t\t.setName('Index Prefix')\n\t\t\t.setDesc(\n\t\t\t\t'Per default the file is named after your folder, but you can prefix it here.'\n\t\t\t)\n\t\t\t.addText((text) =>\n\t\t\t\ttext\n\t\t\t\t\t.setPlaceholder('')\n\t\t\t\t\t.setValue(this.plugin.settings.indexPrefix)\n\t\t\t\t\t.onChange(async (value) => {\n\t\t\t\t\t\tconsole.debug('Index prefix: ' + value);\n\t\t\t\t\t\tthis.plugin.settings.indexPrefix = value;\n\t\t\t\t\t\tawait this.plugin.saveSettings();\n\t\t\t\t\t})\n\t\t\t);\n\t\tnew Setting(containerEl)\n\t\t\t.setName('Template file')\n\t\t\t.setDesc(\n\t\t\t\t'Set your template file\\'s absolute path like \"templates/zoottel_template.md\"'\n\t\t\t)\n\t\t\t.addText((text) =>\n\t\t\t\ttext\n\t\t\t\t\t.setPlaceholder('')\n\t\t\t\t\t.setValue(this.plugin.settings.templateFile)\n\t\t\t\t\t.onChange(async (value) => {\n\t\t\t\t\t\tconsole.debug('Template file: ' + value);\n\t\t\t\t\t\tthis.plugin.settings.templateFile = value;\n\t\t\t\t\t\tawait this.plugin.saveSettings();\n\t\t\t\t\t})\n\t\t\t);\n\t\tnew Setting(containerEl)\n\t\t\t.setName('Frontmatter separator')\n\t\t\t.setDesc('It specifies the separator string generated before and after the frontmatter, by default its ---')\n\t\t\t.addText((text) =>\n\t\t\t\ttext\n\t\t\t\t\t.setPlaceholder('')\n\t\t\t\t\t.setValue(this.plugin.settings.frontMatterSeparator)\n\t\t\t\t\t.onChange(async (value) => {\n\t\t\t\t\t\tthis.plugin.settings.frontMatterSeparator = value;\n\t\t\t\t\t\tawait this.plugin.saveSettings();\n\t\t\t\t\t})\n\t\t\t);\n\t\tcontainerEl.createEl('h4', { text: 'Meta Tags' });\n\n\t\t// Enabling Meta Tags\n\t\tnew Setting(containerEl)\n\t\t\t.setName('Enable Meta Tags')\n\t\t\t.setDesc(\n\t\t\t\t\"You can add Meta Tags at the top of your index-file. This is useful when you're using the index files as MOCs.\"\n\t\t\t)\n\t\t\t.addToggle((t) => {\n\t\t\t\tt.setValue(this.plugin.settings.indexTagBoolean);\n\t\t\t\tt.onChange(async (v) => {\n\t\t\t\t\tthis.plugin.settings.indexTagBoolean = v;\n\t\t\t\t\tawait this.plugin.saveSettings();\n\t\t\t\t});\n\t\t\t});\n\n\t\t// setting the meta tag value\n\t\tconst metaTagsSetting = new Setting(containerEl)\n\t\t\t.setName('Set Meta Tags')\n\t\t\t.setDesc(\n\t\t\t\t'You can add one or multiple tags to your index-files! There is no need to use \"#\", just use the exact value of the tags\\' separator specified below between the tags.'\n\t\t\t)\n\t\t\t.addText((text) =>\n\t\t\t\ttext\n\t\t\t\t\t.setPlaceholder('moc')\n\t\t\t\t\t.setValue(this.plugin.settings.indexTagValue)\n\t\t\t\t\t.onChange(async (value) => {\n\t\t\t\t\t\tthis.plugin.settings.indexTagValue = value;\n\t\t\t\t\t\tawait this.plugin.saveSettings();\n\t\t\t\t\t})\n\t\t\t);\n\t\tnew Setting(containerEl)\n\t\t\t.setName('Set the tag\\'s label in frontmatter')\n\t\t\t.setDesc(\n\t\t\t\t'Please specify the label of the tags in frontmatter (the text before the colon ):'\n\t\t\t)\n\t\t\t.addText((text) =>\n\t\t\t\ttext\n\t\t\t\t\t.setPlaceholder('tags')\n\t\t\t\t\t.setValue(this.plugin.settings.indexTagLabel)\n\t\t\t\t\t.onChange(async (value) => {\n\t\t\t\t\t\tthis.plugin.settings.indexTagLabel = value;\n\t\t\t\t\t\tawait this.plugin.saveSettings();\n\t\t\t\t\t})\n\t\t\t);\n\t\tnew Setting(containerEl)\n\t\t\t.setName('Set the tag\\'s separator in Frontmatter')\n\t\t\t.setDesc(\n\t\t\t\t'Please specify the separator characters that distinguish the tags in Frontmatter:'\n\t\t\t)\n\t\t\t.addText((text) =>\n\t\t\t\ttext\n\t\t\t\t\t.setPlaceholder(', ')\n\t\t\t\t\t.setValue(this.plugin.settings.indexTagSeparator)\n\t\t\t\t\t.onChange(async (value) => {\n\t\t\t\t\t\tthis.plugin.settings.indexTagSeparator = value;\n\t\t\t\t\t\tawait this.plugin.saveSettings();\n\t\t\t\t\t})\n\t\t\t);\n\t\tnew Setting(containerEl)\n\t\t\t.setName('Add square brackets around each tags')\n\t\t\t.setDesc(\n\t\t\t\t\"If you enable this, the plugin will put square brackets around the tags set.\"\n\t\t\t)\n\t\t\t.addToggle((t) => {\n\t\t\t\tt.setValue(this.plugin.settings.addSquareBrackets);\n\t\t\t\tt.onChange(async (v) => {\n\t\t\t\t\tthis.plugin.settings.addSquareBrackets = v;\n\t\t\t\t\tawait this.plugin.saveSettings();\n\t\t\t\t});\n\t\t\t});\n\t\t\tcontainerEl.createEl('h4', { text: 'Emojis' });\n\n\t\t\t// Enabling Meta Tags\n\t\t\tnew Setting(containerEl)\n\t\t\t\t.setName('Enable Emojis')\n\t\t\t\t.setDesc(\"You can set an emoji at the beginning of each index item depending on its type (file or folder). If multiple emojis matches, the first one will be stored.\"\n\t\t\t\t)\n\t\t\t\t.addToggle((t) => {\n\t\t\t\t\tt.setValue(this.plugin.settings.enableEmojis);\n\t\t\t\t\tt.onChange(async (v) => {\n\t\t\t\t\t\tthis.plugin.settings.enableEmojis = v;\n\t\t\t\t\t\tawait this.plugin.saveSettings();\n\t\t\t\t\t});\n\t\t\t\t});\n\n\n\t\t\tlet emojiFolderDesc = 'Set an emoji for folders:'\n\t\t\tif (this.plugin.settings.folderEmoji){\n\t\t\t\tconst setFolderEmoji = emoji.search(this.plugin.settings.folderEmoji);\n\t\t\t\temojiFolderDesc = `Matching Options:${setFolderEmoji[0].emoji} (${setFolderEmoji[0].key})`;\n\t\t\t}\n\t\t\tconst emojiForFoldersSetting = new Setting(containerEl)\n\t\t\t\t.setName('Emojis')\n\t\t\t\t.setDesc(emojiFolderDesc)\n\t\t\t\t.addText((text) =>\n\t\t\t\t\ttext.setPlaceholder('card_index_dividers')\n\t\t\t\t\t\t.setValue(this.plugin.settings.folderEmoji.replace(/:/g, ''))\n\t\t\t\t\t\t.onChange(async (value) => {\n\t\t\t\t\t\t\tif (value !== ''){\n\t\t\t\t\t\t\t\tconst emojiOptions = emoji.search(value);\n\t\t\t\t\t\t\t\temojiForFoldersSetting.setDesc(`Matching Options:${emojiOptions.map(emojOp => emojOp.emoji + \"(\"+emojOp.key+\")\")}`)\n\t\t\t\t\t\t\t\tif (emojiOptions.length > 0){\n\t\t\t\t\t\t\t\t\tthis.plugin.settings.folderEmoji = `:${emojiOptions[0].key}:`;\n\t\t\t\t\t\t\t\t\tawait this.plugin.saveSettings();\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\temojiForFoldersSetting.setDesc(\n\t\t\t\t\t\t\t\t\t'Set an emoji for folders:'\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t}\n\t\t\t\t\t}));\n\t\t\t\tlet emojiFileDesc = 'Set an emoji for files:'\n\t\t\t\tif (this.plugin.settings.fileEmoji){\n\t\t\t\t\tconst setFileEmoji = emoji.search(this.plugin.settings.fileEmoji);\n\t\t\t\t\temojiFileDesc = `Matching Options:${setFileEmoji[0].emoji} (${setFileEmoji[0].key})`;\n\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\tconst emojiForFilesSetting = new Setting(containerEl)\n\t\t\t\t\t.setName('Emojis')\n\t\t\t\t\t.setDesc(emojiFileDesc)\n\t\t\t\t\t.addText((text) =>\n\t\t\t\t\t\ttext.setPlaceholder('page_facing_up')\n\t\t\t\t\t\t\t.setValue(this.plugin.settings.fileEmoji.replace(/:/g, ''))\n\t\t\t\t\t\t\t.onChange(async (value) => {\n\t\t\t\t\t\t\t\tif (value !== ''){\n\t\t\t\t\t\t\t\t\tconst emojiOptions = emoji.search(value);\n\t\t\t\t\t\t\t\t\temojiForFilesSetting.setDesc(`Matching Options:${emojiOptions.map(emojOp => emojOp.emoji + \"(\"+emojOp.key+\")\")}`)\n\t\t\t\t\t\t\t\t\tif (emojiOptions.length > 0){\n\t\t\t\t\t\t\t\t\t\tthis.plugin.settings.fileEmoji = `:${emojiOptions[0].key}:`;\n\t\t\t\t\t\t\t\t\t\tawait this.plugin.saveSettings();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\temojiForFilesSetting.setDesc(\n\t\t\t\t\t\t\t\t\t\t'Set an emoji for files:'\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})\n\t\t\t);\n\t\t\t\t\n\t}\n}\n"
  },
  {
    "path": "manifest.json",
    "content": "{\r\n\t\"id\": \"zoottelkeeper-obsidian-plugin\",\r\n\t\"name\": \"Zoottelkeeper Plugin\",\r\n\t\"version\": \"0.18.0\",\r\n\t\"minAppVersion\": \"0.12.1\",\r\n\t\"description\": \"This plugin automatically creates, maintains and tags MOCs for all your folders.\",\r\n\t\"author\": \"Akos Balasko, Micha Brugger\",\r\n\t\"authorUrl\": \"https://github.com/akosbalasko, https://github.com/michabrugger\",\r\n\t\"isDesktopOnly\": false\r\n}\r\n"
  },
  {
    "path": "models/index.ts",
    "content": "export * from './sortOrder';\n"
  },
  {
    "path": "models/sortOrder.ts",
    "content": "export enum SortOrder {\n    'ASC'= 'asc',\n    'DESC' = 'desc'\n}"
  },
  {
    "path": "package.json",
    "content": "{\n\t\t\"name\": \"zoottelkeeper-obsidian-plugin\",\n\t\t\"version\": \"0.18.0\",\n\t\t\"description\": \"This plugin automatically creates, maintains and tags MOCs for all your folders.\",\n\t\t\"main\": \"main.js\",\n\t\t\"scripts\": {\n\t\t\t\t\"dev\": \"rollup --config rollup.config.js -w\",\n\t\t\t\t\"build\": \"rollup --config rollup.config.js --environment BUILD:production\"\n\t\t},\n\t\t\"keywords\": [\n\t\t\t\t\"zettelkasten\",\n\t\t\t\t\"obsidian.md\",\n\t\t\t\t\"obsidian-plugin\"\n\t\t],\n\t\t\"author\": \"Akos Balasko, Micha Brugger\",\n\t\t\"license\": \"MIT\",\n\t\t\"devDependencies\": {\n\t\t\t\t\"@rollup/plugin-commonjs\": \"18.0.0\",\n\t\t\t\t\"@rollup/plugin-json\": \"4.1.0\",\n\t\t\t\t\"@rollup/plugin-node-resolve\": \"11.2.1\",\n\t\t\t\t\"@rollup/plugin-typescript\": \"8.2.1\",\n\t\t\t\t\"@types/node\": \"14.14.37\",\n\t\t\t\t\"@types/node-emoji\": \"1.8.1\",\n\t\t\t\t\"obsidian\": \"0.12.0\",\n\t\t\t\t\"rollup\": \"2.32.1\",\n\t\t\t\t\"tslib\": \"2.2.0\",\n\t\t\t\t\"typescript\": \"4.2.4\"\n\t\t},\n\t\t\"dependencies\": {\n\t\t\t\t\"node-emoji\": \"1.11.0\"\n\t\t}\n}\n"
  },
  {
    "path": "rollup.config.js",
    "content": "import typescript from '@rollup/plugin-typescript';\r\nimport json from '@rollup/plugin-json';\r\nimport {nodeResolve} from '@rollup/plugin-node-resolve';\r\nimport commonjs from '@rollup/plugin-commonjs';\r\n\r\nconst isProd = (process.env.BUILD === 'production');\r\n\r\nconst banner = \r\n`/*\r\nTHIS IS A GENERATED/BUNDLED FILE BY ROLLUP\r\nif you want to view the source visit the plugins github repository\r\n*/\r\n`;\r\n\r\nexport default {\r\n  input: 'main.ts',\r\n  output: {\r\n    dir: '.',\r\n    sourcemap: 'inline',\r\n    sourcemapExcludeSources: isProd,\r\n    format: 'cjs',\r\n    exports: 'default',\r\n    banner,\r\n  },\r\n  external: ['obsidian'],\r\n  plugins: [\r\n    typescript(),\r\n    nodeResolve({browser: true}),\r\n    commonjs(),\r\n    json(),\r\n  ]\r\n};"
  },
  {
    "path": "styles.css",
    "content": ""
  },
  {
    "path": "tsconfig.json",
    "content": "{\r\n\t\"compilerOptions\": {\r\n\t\t\"baseUrl\": \".\",\r\n\t\t\"inlineSourceMap\": true,\r\n\t\t\"inlineSources\": true,\r\n\t\t\"module\": \"ESNext\",\r\n\t\t\"target\": \"es2018\",\r\n\t\t\"allowJs\": true,\r\n\t\t\"noImplicitAny\": true,\r\n\t\t\"moduleResolution\": \"node\",\r\n\t\t\"importHelpers\": true,\r\n\t\t\"lib\": [\"dom\", \"es5\", \"scripthost\", \"es2015\"]\r\n\t},\r\n\t\"include\": [\"**/*.ts\"]\r\n}\r\n"
  },
  {
    "path": "utils/cleanDisallowedFolders.ts",
    "content": ""
  },
  {
    "path": "utils/getFrontmatter.ts",
    "content": "import { hasFrontmatter } from './hasFrontmatter';\n\nexport const getFrontmatter = (content: string, separator: string): string => {\n    return hasFrontmatter(content, separator)\n        ? `${separator}${content.split(separator)[1]}${separator}`\n        : ''\n}"
  },
  {
    "path": "utils/hasFrontmatter.ts",
    "content": "\n\nexport const hasFrontmatter = (content: string, separator: string): boolean => {\n    return (content.trim().startsWith(separator) && content.split(separator).length > 1);\n}"
  },
  {
    "path": "utils/index.ts",
    "content": "export * from './updateFrontmatter';\nexport * from './updateIndexContent';\nexport * from './removeFrontmatter';\nexport * from './hasFrontmatter';\nexport * from './getFrontmatter';\nexport * from './isInSpecificFolder';"
  },
  {
    "path": "utils/isInSpecificFolder.ts",
    "content": "import { ZoottelkeeperPluginSettings } from \"../interfaces\"\n\nexport const isInAllowedFolder = (settings: ZoottelkeeperPluginSettings, indexFilePath: string): boolean => {\n    return settings.foldersIncluded === '' || isInSpecificFolder(settings, indexFilePath, 'foldersIncluded');\n\n}\n\nexport const isInDisAllowedFolder = (settings: ZoottelkeeperPluginSettings, indexFilePath: string): boolean => {\n    return isInSpecificFolder(settings, indexFilePath, 'foldersExcluded');\n}\n\nexport const isInSpecificFolder = (settings: ZoottelkeeperPluginSettings, indexFilePath: string, folderType: string): boolean => {\n\n    return !!settings[folderType].replace(/,/g,'\\n').split('\\n').find((folder: any) => {\n        return folder.endsWith('*') \n            ? indexFilePath.startsWith(folder.slice(0, -1).trim())\n            : indexFilePath.split(folder).length > 1 && !indexFilePath.split(folder)[1].includes('/');\n        })\n}\n\n"
  },
  {
    "path": "utils/removeFrontmatter.ts",
    "content": "export const removeFrontmatter = (content: string, separator: string): string => {\n    return (content.startsWith(separator)&& content.split(separator).length > 1)\n        ? content.split(separator).slice(2).join(separator)\n        : content\n}\n"
  },
  {
    "path": "utils/updateFrontmatter.ts",
    "content": "import { ZoottelkeeperPluginSettings } from '../interfaces';\nimport { getFrontmatter } from './getFrontmatter'; \n\nexport const updateFrontmatter = (settings: ZoottelkeeperPluginSettings, currentContent: string): string => {\n\n\n    if (!settings.indexTagBoolean)\n        return getFrontmatter(currentContent, settings.frontMatterSeparator);\n    \n    let currentFrontmatterWithoutSep = `${currentContent.split(settings.frontMatterSeparator)[1]}`;\n    \n    if (currentFrontmatterWithoutSep === '')\n        return ''\n    else {\n        let tagLine = currentFrontmatterWithoutSep.split('\\n').find(elem => elem.split(':')[0]=== settings.indexTagLabel);\n        if (!tagLine && settings.indexTagValue && settings.indexTagBoolean){\n            tagLine = 'tags:';\n            currentFrontmatterWithoutSep = `${currentFrontmatterWithoutSep}${tagLine}\\n`;\n        }\n\n        const taglist = tagLine.split(':')[1].trim();\n        const indexTags = settings.indexTagSeparator\n            ? settings.indexTagValue.split(settings.indexTagSeparator) \n            : [settings.indexTagValue];\n\n        let updatedTaglist = taglist.replace(/\\[|\\]/g,'')\n        for (const indexTag of indexTags) {\n            if (!taglist.includes(indexTag)) {\n                updatedTaglist =  !settings.indexTagSeparator \n                || (updatedTaglist.split(settings.indexTagSeparator).length >= 1 \n                    && updatedTaglist.split(settings.indexTagSeparator)[0]!== '')\n                    ? `${updatedTaglist}${settings.indexTagSeparator}${indexTag}`\n                    : indexTag;\n            }\n        }\n        if (settings.addSquareBrackets)\n            updatedTaglist = `[${updatedTaglist}]`;\n        const updatedTagLine = `tags: ${updatedTaglist}`;\n        const regex = new RegExp(tagLine.replace(/\\[/g,'\\\\[').replace(/\\]/g,'\\\\]'), 'g');\n\n        return settings.indexTagBoolean\n            ? `${settings.frontMatterSeparator}${currentFrontmatterWithoutSep.replace(regex,updatedTagLine )}${settings.frontMatterSeparator}`\n            : `${settings.frontMatterSeparator}${currentFrontmatterWithoutSep}${settings.frontMatterSeparator}`;\n    }\n}"
  },
  {
    "path": "utils/updateIndexContent.ts",
    "content": "import { SortOrder } from './../models';\nimport {\n    ZOOTTELKEEPER_INDEX_LIST_BEGINNING_TEXT,\n    ZOOTTELKEEPER_INDEX_LIST_END_TEXT\n} from './../consts'\n\nexport const updateIndexContent = (sortOrder: SortOrder, currentContent: string, indexContent: Array<string>): string => {\n    \n    \n    const intro = currentContent.split(ZOOTTELKEEPER_INDEX_LIST_BEGINNING_TEXT)[0];\n    const outro = currentContent.split(ZOOTTELKEEPER_INDEX_LIST_END_TEXT);\t\n\n    indexContent = indexContent.sort(function (a, b) {\n        return a.localeCompare(b, undefined, {numeric: true});\n      });\n    if(sortOrder === SortOrder.DESC)\n      indexContent.reverse();\n    const content =   (currentContent === intro || currentContent === outro[0])\n    ? `${ZOOTTELKEEPER_INDEX_LIST_BEGINNING_TEXT}\\n${indexContent.join('\\n')}\\n${ZOOTTELKEEPER_INDEX_LIST_END_TEXT}\\n`\n    : `${intro}${ZOOTTELKEEPER_INDEX_LIST_BEGINNING_TEXT}\\n${indexContent.join('\\n')}\\n${ZOOTTELKEEPER_INDEX_LIST_END_TEXT}${outro[1]}`\n    return content;\n\n}\n\n"
  },
  {
    "path": "versions.json",
    "content": "{\n\t\"0.5.2\": \"0.12.1\",\n\t\"0.5.3\": \"0.12.1\",\n\t\"0.6.0\": \"0.12.1\",\n\t\"0.8.0\": \"0.12.1\",\n\t\"0.9.0\": \"0.12.1\",\n\t\"0.10.0\": \"0.12.1\",\n\t\"0.10.1\": \"0.12.1\",\n\t\"0.10.2\": \"0.12.1\",\n\t\"0.10.3\": \"0.12.1\",\n\t\"0.16.0\": \"0.12.1\",\n\t\"0.16.1\": \"0.12.1\",\n\t\"0.16.2\": \"0.12.1\",\n\t\"0.17.0\": \"0.12.1\",\n\t\"0.17.1\": \"0.12.1\",\n\t\"0.17.2\": \"0.12.1\",\n\t\"0.17.3\": \"0.12.1\",\n\t\"0.18.0\": \"0.12.1\"\n}"
  }
]