├── styles.css ├── .npmrc ├── .eslintignore ├── .prettierrc.yaml ├── .editorconfig ├── versions.json ├── manifest.json ├── .gitignore ├── tsconfig.json ├── version-bump.mjs ├── .eslintrc ├── src ├── Clients │ ├── ITfsClient.ts │ ├── AzureDevopsClient.ts │ └── JiraClient.ts ├── Task.ts └── VaultHelper.ts ├── package.json ├── LICENSE ├── esbuild.config.mjs ├── README.md ├── main.ts └── yarn.lock /styles.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | tag-version-prefix="" -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | npm node_modules 2 | build -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | # Prettier (Code Formatter) Configuration 2 | printWidth: 120 3 | singleQuote: true 4 | useTabs: false 5 | tabWidth: 2 6 | semi: true 7 | bracketSpacing: true 8 | endOfLine: auto -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | insert_final_newline = true 8 | indent_style = tab 9 | indent_size = 4 10 | tab_width = 4 11 | -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.0": "0.12.0", 3 | "1.0.1": "0.12.0", 4 | "1.0.2": "0.12.0", 5 | "1.0.3": "0.12.0", 6 | "1.0.4": "0.12.0", 7 | "1.0.5": "0.12.0", 8 | "1.1.0": "0.12.0", 9 | "1.2.0": "0.12.0", 10 | "1.3.0": "0.12.0", 11 | "1.3.1": "0.12.0", 12 | "1.3.2": "0.12.0", 13 | "1.4.0": "0.12.0" 14 | } -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-agile-task-notes", 3 | "name": "Agile Task Notes", 4 | "version": "1.4.0", 5 | "minAppVersion": "0.12.0", 6 | "description": "Import your tasks from your TFS (Azure or Jira) to take notes on them and make todo-lists!", 7 | "author": "BoxThatBeat", 8 | "authorUrl": "https://github.com/BoxThatBeat", 9 | "isDesktopOnly": false 10 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | 11 | # Don't include the compiled main.js file in the repo. 12 | # They should be uploaded to GitHub releases instead. 13 | main.js 14 | 15 | # Exclude sourcemaps 16 | *.map 17 | 18 | # obsidian 19 | data.json 20 | 21 | # Exclude macOS Finder (System Explorer) View States 22 | .DS_Store 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ES6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "isolatedModules": true, 13 | "strictNullChecks": true, 14 | "lib": [ 15 | "DOM", 16 | "ES5", 17 | "ES6", 18 | "ES7" 19 | ] 20 | }, 21 | "include": [ 22 | "**/*.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /version-bump.mjs: -------------------------------------------------------------------------------- 1 | import { readFileSync, writeFileSync } from "fs"; 2 | 3 | const targetVersion = process.env.npm_package_version; 4 | 5 | // read minAppVersion from manifest.json and bump version to target version 6 | let manifest = JSON.parse(readFileSync("manifest.json", "utf8")); 7 | const { minAppVersion } = manifest; 8 | manifest.version = targetVersion; 9 | writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t")); 10 | 11 | // update versions.json with target version and minAppVersion from manifest.json 12 | let versions = JSON.parse(readFileSync("versions.json", "utf8")); 13 | versions[targetVersion] = minAppVersion; 14 | writeFileSync("versions.json", JSON.stringify(versions, null, "\t")); 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "env": { "node": true }, 5 | "plugins": [ 6 | "@typescript-eslint" 7 | ], 8 | "extends": [ 9 | "eslint:recommended", 10 | "plugin:@typescript-eslint/eslint-recommended", 11 | "plugin:@typescript-eslint/recommended" 12 | ], 13 | "parserOptions": { 14 | "sourceType": "module" 15 | }, 16 | "rules": { 17 | "no-unused-vars": "off", 18 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 19 | "@typescript-eslint/ban-ts-comment": "off", 20 | "no-prototype-builtins": "off", 21 | "@typescript-eslint/no-empty-function": "off" 22 | } 23 | } -------------------------------------------------------------------------------- /src/Clients/ITfsClient.ts: -------------------------------------------------------------------------------- 1 | import AgileTaskNotesPlugin, { AgileTaskNotesPluginSettingTab } from 'main'; 2 | import { App } from 'obsidian'; 3 | 4 | /** 5 | * An interface describing a TFS backend implementation 6 | */ 7 | export interface ITfsClient { 8 | /** 9 | * The title of the client in string format 10 | */ 11 | clientName: string; 12 | 13 | /** 14 | * Creates all the user's assigned tasks in the current sprint as markdown notes with the globally defined template 15 | * Also creates the Kanban board that links to the new files 16 | * @param settings - The plugin settings 17 | * @public 18 | */ 19 | update(settings: any): Promise; 20 | 21 | /** 22 | * Creates all the required UI elements for this client's settings 23 | * @param container - The HTML container to build off of 24 | * @param plugin - The plugin itself 25 | * @public 26 | */ 27 | setupSettings(container: HTMLElement, plugin: AgileTaskNotesPlugin, settingsTab: AgileTaskNotesPluginSettingTab): any; 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-agile-task-notes", 3 | "version": "1.4.0", 4 | "description": "Automated grabbing of tasks from TFS (AzureDevops or Jira)", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs", 8 | "build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production", 9 | "version": "node version-bump.mjs && git add manifest.json versions.json" 10 | }, 11 | "keywords": [], 12 | "author": "BoxThatBeat", 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/BoxThatBeat/obsidian-agile-task-notes" 16 | }, 17 | "license": "MIT", 18 | "devDependencies": { 19 | "@types/node": "^16.11.6", 20 | "@typescript-eslint/eslint-plugin": "5.29.0", 21 | "@typescript-eslint/parser": "5.29.0", 22 | "builtin-modules": "3.3.0", 23 | "esbuild": "0.14.47", 24 | "obsidian": "latest", 25 | "tslib": "2.4.0", 26 | "typescript": "4.7.4" 27 | }, 28 | "dependencies": { 29 | "sanitizer": "^0.1.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Task.ts: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Simple data class that allows for generalization of a task from any TFS 4 | * Optional fields are only available in Azure for now. 5 | * @public 6 | */ 7 | export class Task { 8 | public id: string; 9 | public state: string; 10 | public title: string; 11 | public type: string; 12 | public assignedTo: string; 13 | public link: string; 14 | public desc: string; 15 | public criteria?: string; 16 | public testScenarios?: string; 17 | public dueDate?: string; 18 | public tags?: string; 19 | 20 | constructor(id: string, state: string, title: string, type: string, assignedTo: string, link: string, desc: string, criteria?: string, testScenarios?: string, dueDate?: string, tags?: string) { 21 | this.id = id; 22 | this.state = state; 23 | this.title = title; 24 | this.type = type; 25 | this.assignedTo = assignedTo; 26 | this.link = link; 27 | this.desc = desc; 28 | this.criteria = criteria; 29 | this.testScenarios = testScenarios; 30 | this.dueDate = dueDate; 31 | this.tags = tags; 32 | } 33 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Aaron Buitenwerf 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from 'builtin-modules' 4 | 5 | const banner = 6 | `/* 7 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 8 | if you want to view the source, please visit the github repository of this plugin 9 | */ 10 | `; 11 | 12 | const prod = (process.argv[2] === 'production'); 13 | 14 | esbuild.build({ 15 | banner: { 16 | js: banner, 17 | }, 18 | entryPoints: ['main.ts'], 19 | bundle: true, 20 | external: [ 21 | 'obsidian', 22 | 'electron', 23 | '@codemirror/autocomplete', 24 | '@codemirror/closebrackets', 25 | '@codemirror/collab', 26 | '@codemirror/commands', 27 | '@codemirror/comment', 28 | '@codemirror/fold', 29 | '@codemirror/gutter', 30 | '@codemirror/highlight', 31 | '@codemirror/history', 32 | '@codemirror/language', 33 | '@codemirror/lint', 34 | '@codemirror/matchbrackets', 35 | '@codemirror/panel', 36 | '@codemirror/rangeset', 37 | '@codemirror/rectangular-selection', 38 | '@codemirror/search', 39 | '@codemirror/state', 40 | '@codemirror/stream-parser', 41 | '@codemirror/text', 42 | '@codemirror/tooltip', 43 | '@codemirror/view', 44 | '@lezer/common', 45 | '@lezer/highlight', 46 | '@lezer/lr', 47 | ...builtins], 48 | format: 'cjs', 49 | watch: !prod, 50 | target: 'es2016', 51 | logLevel: "info", 52 | sourcemap: prod ? false : 'inline', 53 | treeShaking: true, 54 | outfile: 'main.js', 55 | }).catch(() => process.exit(1)); 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Stay on top of your tasks with Agile-Task-Notes! 3 | 4 | Import your tasks from your TFS to take notes on them and make todo-lists! 5 | This plugin currently supports these TFS systems: {**Jira**, **Azure Devops**} 6 | 7 | ### Kanban Board generation: 8 | ![Kanban](https://user-images.githubusercontent.com/28713093/187089414-e6c6788c-d2e2-428f-bb8e-ed3c9edc21c5.gif) 9 | 10 | ### Task notes generation: 11 | ![OpenLinks](https://user-images.githubusercontent.com/28713093/187089532-7c4f665d-f5c3-4729-918f-8bdba97f4739.gif) 12 | 13 | ### Task todo lists: 14 | ![Todo](https://user-images.githubusercontent.com/28713093/187089536-6789cd8f-e503-470f-a1bd-016d95df20bc.gif) 15 | 16 | 17 | ## Features: 18 | - Generates local copy of Kanban board with only tasks assigned to you for easy task navigation in Obsidian 19 | - Automatically creates all your tasks as files where you can add notes and todo lists for your tasks 20 | - Customize starter content of the generated task notes in settings 21 | 22 | ## Important: 23 | This plugin works best with these other community plugins (I take no credit for these great plugins): 24 | - \"Kanban\" by mgmeyers 25 | - \"Checklist\" by delashum (Less important but works nicely alongside this plugin) 26 | If Kanban is not installed, there will be no UI for the Kanban board. However, the board generation can be toggled in settings. 27 | 28 | **Warning**: The settings are NOT encrypted, they are stored in plain text, so put your API key/ Personal Access Token in at your own risk 29 | 30 | ## Usage 31 | There are 3 options for updating your tasks from TFS: 32 | - Using the Update Interval setting to grab updates every x minutes automatically 33 | - Using the left-hand button 34 | - Using the command palette "Update Current Sprint" 35 | 36 | Notes: 37 | - The generated kanban board for the sprint is destroyed and replaced each time updates are pulled from TFS. This has the following implications: 38 | - Any manual changes to the kanban board of the current sprint will be deleted on each update of the board 39 | - The Time Interval setting should not be too low since when the kanban board note is openned when it is updated, it will close since it is deleted and replaced 40 | - Please make backups of task notes since there may be bugs in this code and they could be removed. 41 | 42 | ## Installation 43 | 44 | ### From within Obsidian 45 | From Obsidian v0.9.8, you can activate this plugin within Obsidian by doing the following: 46 | - Open Settings > Third-party plugin 47 | - Make sure Restricted mode is **off** 48 | - Click Browse community plugins 49 | - Search for this plugin 50 | - Click Install 51 | - Once installed, close the community plugins window and activate the newly installed plugin 52 | #### Updates 53 | You can follow the same procedure to update the plugin 54 | 55 | ### From GitHub 56 | - Download the Latest Release from the Releases section of the GitHub Repository 57 | - Extract the plugin folder from the zip to your vault's plugins folder: `/.obsidian/plugins/` 58 | Note: On some machines the `.obsidian` folder may be hidden. On MacOS you should be able to press `Command+Shift+Dot` to show the folder in Finder. 59 | - Reload Obsidian 60 | - If prompted about Safe Mode, you can disable safe mode and enable the plugin. 61 | Otherwise head to Settings, third-party plugins, make sure safe mode is off and 62 | enable the plugin from there. 63 | 64 | ## Development 65 | 66 | If you want to contribute to development and/or just customize it with your own 67 | tweaks, you can do the following: 68 | - Clone this repo. 69 | - `npm i` or `yarn` to install dependencies 70 | - `npm run build` to compile. 71 | - Copy `manifest.json`, `main.js` and `styles.css` to a subfolder of your plugins 72 | folder (e.g, `/.obsidian/plugins//`) 73 | - Reload obsidian to see changes 74 | 75 | Alternately, you can clone the repo directly into your plugins folder and once 76 | dependencies are installed use `npm run dev` to start compilation in watch mode. 77 | You may have to reload obsidian (`ctrl+R`) to see changes. 78 | 79 | Note: feel free to add a new TFS backend that the plugin does not currently support and make a pull request. Simply follow the example of the current TfsClient implmentations and add it to the list of implementations in main.ts 80 | 81 | ## Pricing 82 | This plugin is free to enjoy! However, if you wish to support my work, I would really appretiate it. You can do so here: 83 | 84 | [BuyMeACoffee](https://www.buymeacoffee.com/BoxThatBeat) 85 | 86 | -------------------------------------------------------------------------------- /src/VaultHelper.ts: -------------------------------------------------------------------------------- 1 | import { App, Notice, TFile } from 'obsidian'; 2 | import { Task } from './Task'; 3 | 4 | export class VaultHelper { 5 | private static BOARD_TEMPLATE_START: string = '---\n\nkanban-plugin: basic\n\n---\n\n'; 6 | private static BOARD_TEMPLATE_END: string = '\n%% kanban:settings\n```\n{"kanban-plugin":"basic"}\n```%%"'; 7 | 8 | /** 9 | * Logs an error and notifies user that an error occured 10 | * @param error - The error message to log 11 | * @public 12 | */ 13 | public static logError(error: string): void { 14 | console.log(error); 15 | new Notice('Error occured, see console logs for details. (ctrl+shift+i) to open'); 16 | } 17 | 18 | /** 19 | * Creates all folders in a given path if they are non-existant 20 | * @param path - The path of folders to creates 21 | * @public 22 | */ 23 | public static createFolders(path: string, app: App): void { 24 | if (app.vault.getAbstractFileByPath(path) == null) { 25 | app.vault.createFolder(path).catch((err) => console.log(err)); 26 | } 27 | } 28 | 29 | /** 30 | * Creates all folders for all given paths if they are non-existent 31 | * @param paths - The list of paths of folders to creates 32 | * @public 33 | */ 34 | public static createFoldersFromList(paths: string[], app: App): void { 35 | paths.forEach((path) => this.createFolders(path, app)); 36 | } 37 | 38 | /** 39 | * Will return a filehandle if the provided id is in the folder of the provided path 40 | * @param path - The vault path to search in 41 | * @param id - The string to search for in the path folder 42 | * @public 43 | */ 44 | public static getFileByTaskId(path: string, id: string, app: App): TFile | undefined { 45 | const files = app.vault.getMarkdownFiles(); 46 | 47 | const projectPath = path.slice(0, path.lastIndexOf('/')); // Remove the specific sprint since files can be in old sprints 48 | 49 | for (let i = 0; i < files.length; i++) { 50 | let filePath = files[i].path; 51 | if (filePath.startsWith(projectPath) && filePath.contains(id)) { 52 | return files[i]; 53 | } 54 | } 55 | 56 | return undefined; 57 | } 58 | 59 | /** 60 | * Creates all task notes given the provided array of Tasks" 61 | * @param path - The path to create each task at 62 | * @param tasks - An array of Tasks 63 | * @public 64 | */ 65 | public static createTaskNotes( 66 | path: string, 67 | tasks: Array, 68 | template: string, 69 | notename: string, 70 | app: App 71 | ): Promise[] { 72 | let promisesToCreateNotes: Promise[] = []; 73 | tasks.forEach((task) => { 74 | if (this.getFileByTaskId(path, task.id, app) == undefined) { 75 | promisesToCreateNotes.push(this.createTaskNote(path, task, template, notename, app)); 76 | } 77 | }); 78 | 79 | return promisesToCreateNotes; 80 | } 81 | 82 | /** 83 | * Builds up a markdown file that represents a Kanban board for the sprint. Utilizes the format for the Kanban plugin" 84 | * @param path - The path to create each task at 85 | * @param tasks - An array of Tasks 86 | * @param columns - An array of column names to match state of the tasks with 87 | * @param prefix - The prefix to add to the kanban board name 88 | * @public 89 | */ 90 | public static createKanbanBoard( 91 | path: string, 92 | tasks: Array, 93 | columns: Array, 94 | prefix: string, 95 | teamLeaderMode: boolean, 96 | app: App 97 | ): Promise { 98 | const filename = `${prefix}-Board`; 99 | const filepath = path + `/${filename}.md`; 100 | 101 | let boardMD = this.BOARD_TEMPLATE_START; 102 | 103 | // Create Kanban board with specified columns matching the state of each task 104 | columns.forEach((column: string) => { 105 | boardMD += '## '; 106 | boardMD += column; 107 | boardMD += '\n'; 108 | 109 | tasks.forEach((task: Task) => { 110 | if (task.state === column) { 111 | let file = this.getFileByTaskId(path, task.id, app); 112 | if (file != undefined) { 113 | if (teamLeaderMode) { 114 | boardMD += `- [ ] [[${file.basename}]] \n ${task.assignedTo} \n ${task.title}\n`; 115 | } else { 116 | boardMD += `- [ ] [[${file.basename}]] \n ${task.title}\n`; 117 | } 118 | } 119 | } 120 | }); 121 | 122 | boardMD += '\n'; 123 | }); 124 | 125 | boardMD += this.BOARD_TEMPLATE_END; 126 | 127 | return app.vault.adapter.write(filepath, boardMD); 128 | } 129 | 130 | private static async createTaskNote( 131 | path: string, 132 | task: Task, 133 | template: string, 134 | notename: string, 135 | app: App 136 | ): Promise { 137 | let filename = notename 138 | .replace(/{{TASK_ID}}/g, task.id) 139 | .replace(/{{TASK_STATE}}/g, task.state) 140 | .replace(/{{TASK_TYPE}}/g, task.type.replace(/ /g, '')) 141 | .replace(/{{TASK_ASSIGNEDTO}}/g, task.assignedTo); 142 | 143 | const filepath = path + `/${filename}.md`; 144 | 145 | let content = template 146 | .replace(/{{TASK_ID}}/g, task.id) 147 | .replace(/{{TASK_TITLE}}/g, task.title) 148 | .replace(/{{TASK_STATE}}/g, task.state) 149 | .replace(/{{TASK_TYPE}}/g, task.type.replace(/ /g, '')) 150 | .replace(/{{TASK_ASSIGNEDTO}}/g, task.assignedTo) 151 | .replace(/{{TASK_LINK}}/g, task.link); 152 | 153 | if (task.dueDate != null) { 154 | content = content.replace(/{{TASK_DUEDATE}}/g, task.dueDate); 155 | } else { 156 | content = content.replace(/{{TASK_DUEDATE}}/g, ''); 157 | } 158 | 159 | if (task.tags != null) { 160 | content = content.replace(/{{TASK_TAGS}}/g, task.tags); 161 | } else { 162 | content = content.replace(/{{TASK_TAGS}}/g, ''); 163 | } 164 | 165 | if (task.desc != null) { 166 | content = content.replace(/{{TASK_DESCRIPTION}}/g, task.desc); 167 | } else { 168 | content = content.replace(/{{TASK_DESCRIPTION}}/g, ''); 169 | } 170 | 171 | if (task.criteria != null) { 172 | content = content.replace(/{{TASK_CRITERIA}}/g, task.criteria); 173 | } else { 174 | content = content.replace(/{{TASK_CRITERIA}}/g, ''); 175 | } 176 | 177 | if (task.testScenarios != null) { 178 | content = content.replace(/{{TASK_TESTS}}/g, task.testScenarios); 179 | } else { 180 | content = content.replace(/{{TASK_TESTS}}/g, ''); 181 | } 182 | 183 | return app.vault.create(filepath, content); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /main.ts: -------------------------------------------------------------------------------- 1 | import { App, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian'; 2 | import { AzureDevopsClient, AzureDevopsSettings, AZURE_DEVOPS_DEFAULT_SETTINGS } from 'src/Clients/AzureDevopsClient'; 3 | import { ITfsClient } from './src/Clients/ITfsClient'; 4 | import { JiraClient, JiraSettings, JIRA_DEFAULT_SETTINGS } from './src/Clients/JiraClient'; 5 | 6 | export interface AgileTaskNotesSettings { 7 | selectedTfsClient: string; 8 | targetFolder: string; 9 | noteTemplate: string; 10 | noteName: string; 11 | intervalMinutes: number; 12 | createKanban: boolean; 13 | teamLeaderMode: boolean; 14 | azureDevopsSettings: AzureDevopsSettings; 15 | jiraSettings: JiraSettings; 16 | } 17 | 18 | const DEFAULT_SETTINGS: AgileTaskNotesSettings = { 19 | selectedTfsClient: 'AzureDevops', 20 | targetFolder: '', 21 | noteTemplate: 22 | '# {{TASK_TITLE}}\n#{{TASK_TYPE}}\n\nid: {{TASK_ID}}\nstate: {{TASK_STATE}}\nAssignedTo: {{TASK_ASSIGNEDTO}}\n\nLink: {{TASK_LINK}}\n\n{{TASK_DESCRIPTION}}\n\n#todo:\n- [ ] Create todo list\n- [ ] \n\n## Notes:\n', 23 | noteName: '{{TASK_TYPE}} - {{TASK_ID}}', 24 | intervalMinutes: 0, 25 | createKanban: true, 26 | teamLeaderMode: false, 27 | azureDevopsSettings: AZURE_DEVOPS_DEFAULT_SETTINGS, 28 | jiraSettings: JIRA_DEFAULT_SETTINGS, 29 | }; 30 | 31 | export default class AgileTaskNotesPlugin extends Plugin { 32 | settings: AgileTaskNotesSettings; 33 | 34 | tfsClientImplementations: { [key: string]: ITfsClient } = {}; 35 | 36 | async onload() { 37 | // Add TFS backend implmentations 38 | const azureDevopsClient: ITfsClient = new AzureDevopsClient(this.app); 39 | const jiraClient: ITfsClient = new JiraClient(this.app); 40 | 41 | this.tfsClientImplementations[azureDevopsClient.clientName] = azureDevopsClient; 42 | this.tfsClientImplementations[jiraClient.clientName] = jiraClient; 43 | 44 | await this.loadSettings(); 45 | 46 | // This creates an icon in the left ribbon for updating boards. 47 | this.addRibbonIcon('dice', 'Update TFS Tasks', () => { 48 | this.tfsClientImplementations[this.settings.selectedTfsClient].update(this.settings); 49 | new Notice('Updated current tasks successfully!'); 50 | }); 51 | 52 | this.addCommand({ 53 | id: 'update-tfs-tasks', 54 | name: 'Update TFS Tasks', 55 | callback: () => { 56 | this.tfsClientImplementations[this.settings.selectedTfsClient].update(this.settings); 57 | new Notice('Updated current tasks successfully!'); 58 | }, 59 | }); 60 | 61 | this.addSettingTab(new AgileTaskNotesPluginSettingTab(this.app, this)); 62 | 63 | if (this.settings.intervalMinutes > 0) { 64 | this.registerInterval( 65 | window.setInterval( 66 | () => this.tfsClientImplementations[this.settings.selectedTfsClient].update(this.settings), 67 | this.settings.intervalMinutes * 60000 68 | ) 69 | ); 70 | } 71 | } 72 | 73 | async loadSettings() { 74 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 75 | } 76 | 77 | async saveSettings() { 78 | await this.saveData(this.settings); 79 | } 80 | } 81 | 82 | export class AgileTaskNotesPluginSettingTab extends PluginSettingTab { 83 | plugin: AgileTaskNotesPlugin; 84 | 85 | constructor(app: App, plugin: AgileTaskNotesPlugin) { 86 | super(app, plugin); 87 | this.plugin = plugin; 88 | } 89 | 90 | display(): void { 91 | const { containerEl, plugin } = this; 92 | 93 | containerEl.empty(); 94 | 95 | new Setting(containerEl) 96 | .setName('Backend TFS') 97 | .setDesc('The type of TFS you use.') 98 | .addDropdown((dropdown) => { 99 | for (const client in plugin.tfsClientImplementations) { 100 | dropdown.addOption(client, client); 101 | } 102 | dropdown.setValue(plugin.settings.selectedTfsClient).onChange(async (value) => { 103 | plugin.settings.selectedTfsClient = value; 104 | await plugin.saveSettings(); 105 | this.display(); 106 | }); 107 | }); 108 | 109 | new Setting(containerEl) 110 | .setName('Team Leader Mode') 111 | .setDesc('Pulls tasks of entire team and shows usernames in generated Kanban board. (ignores username list)') 112 | .addToggle((toggle) => 113 | toggle.setValue(plugin.settings.teamLeaderMode).onChange(async (value) => { 114 | plugin.settings.teamLeaderMode = value; 115 | await plugin.saveSettings(); 116 | }) 117 | ); 118 | 119 | plugin.tfsClientImplementations[plugin.settings.selectedTfsClient].setupSettings(containerEl, plugin, this); 120 | 121 | containerEl.createEl('h2', { text: 'Vault Settings' }); 122 | 123 | new Setting(containerEl) 124 | .setName('Target Folder (Optional)') 125 | .setDesc('The relative path to the parent folder in which to create/update Kanban boards') 126 | .addText((text) => 127 | text 128 | .setPlaceholder('Enter target folder') 129 | .setValue(plugin.settings.targetFolder) 130 | .onChange(async (value) => { 131 | plugin.settings.targetFolder = value; 132 | await plugin.saveSettings(); 133 | }) 134 | ); 135 | 136 | new Setting(containerEl) 137 | .setName('Inital Task Content') 138 | .setDesc( 139 | 'Set the inital content for each new task note. Available variables: {{TASK_ID}}, {{TASK_TITLE}}, {{TASK_TYPE}}, {{TASK_STATE}}, {{TASK_ASSIGNEDTO}}, {{TASK_LINK}}, {{TASK_DESCRIPTION}} Only For Azure: {{TASK_DUEDATE}} {{TASK_TAGS}} {{TASK_CRITERIA}} {{TASK_TESTS}}' 140 | ) 141 | .addTextArea((text) => { 142 | text 143 | .setPlaceholder('Initial content in raw markdown format') 144 | .setValue(this.plugin.settings.noteTemplate) 145 | .onChange(async (value) => { 146 | try { 147 | this.plugin.settings.noteTemplate = value; 148 | await this.plugin.saveSettings(); 149 | } catch (e) { 150 | return false; 151 | } 152 | }); 153 | text.inputEl.rows = 8; 154 | text.inputEl.cols = 50; 155 | }); 156 | new Setting(containerEl) 157 | .setName('Note Name') 158 | .setDesc( 159 | 'Set the format of the file name for each task note. Available variables: {{TASK_ID}}, {{TASK_TYPE}}, {{TASK_STATE}}, {{TASK_ASSIGNEDTO}}' 160 | ) 161 | .addText((text) => 162 | text 163 | .setPlaceholder('{{TASK_TYPE}} - {{TASK_ID}}') 164 | .setValue(plugin.settings.noteName) 165 | .onChange(async (value) => { 166 | plugin.settings.noteName = value; 167 | await plugin.saveSettings(); 168 | }) 169 | ); 170 | 171 | new Setting(containerEl) 172 | .setName('Update interval') 173 | .setDesc( 174 | "Interval (in minutes) to periodically update the kanban board and notes. Set to 0 for only manual updating. You'll need to restart Obsidian for this to take effect. Note: when an update occurs it will close the kanban board if it is open thus a number over 10 mins is recommended." 175 | ) 176 | .addText((text) => 177 | text 178 | .setPlaceholder('Enter number in minutes') 179 | .setValue(plugin.settings.intervalMinutes.toString()) 180 | .onChange(async (value) => { 181 | plugin.settings.intervalMinutes = parseInt(value); 182 | await plugin.saveSettings(); 183 | }) 184 | ); 185 | 186 | new Setting(containerEl) 187 | .setName('Create Kanban board?') 188 | .setDesc( 189 | 'Should a Kanban board be generated for the current sprint (requires the Kanban board plugin in addition to this one)' 190 | ) 191 | .addToggle((toggle) => 192 | toggle.setValue(plugin.settings.createKanban).onChange(async (value) => { 193 | plugin.settings.createKanban = value; 194 | await plugin.saveSettings(); 195 | }) 196 | ); 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/Clients/AzureDevopsClient.ts: -------------------------------------------------------------------------------- 1 | import AgileTaskNotesPlugin, { AgileTaskNotesPluginSettingTab, AgileTaskNotesSettings } from 'main'; 2 | import { App, normalizePath, requestUrl, Setting } from 'obsidian'; 3 | import { VaultHelper } from 'src/VaultHelper'; 4 | import { ITfsClient } from './ITfsClient'; 5 | import { Task } from 'src/Task'; 6 | 7 | export interface AzureDevopsSettings { 8 | instance: string; 9 | collection: string; 10 | project: string; 11 | team: string; 12 | usernames: string; 13 | accessToken: string; 14 | columns: string; 15 | } 16 | 17 | export const AZURE_DEVOPS_DEFAULT_SETTINGS: AzureDevopsSettings = { 18 | instance: '', 19 | collection: '', 20 | project: '', 21 | team: '', 22 | usernames: '', 23 | accessToken: '', 24 | columns: 'Pending,In Progress,In Merge,In Verification,Closed', 25 | }; 26 | 27 | const TASKS_QUERY: string = 28 | '{"query": "Select [System.Id], [System.Title], [System.State] From WorkItems Where [System.IterationPath] UNDER \\"{0}\\"{1}"}'; // iteration path, other(usernames) 29 | const USER_OPERAND: string = '[Assigned to] = \\"{0}\\"'; 30 | 31 | export class AzureDevopsClient implements ITfsClient { 32 | clientName: string = 'AzureDevops'; 33 | 34 | constructor(private app: App) {} 35 | 36 | public async update(settings: AgileTaskNotesSettings): Promise { 37 | const encoded64PAT = Buffer.from(`:${settings.azureDevopsSettings.accessToken}`).toString('base64'); 38 | 39 | const headers = { 40 | Authorization: `Basic ${encoded64PAT}`, 41 | 'Content-Type': 'application/json', 42 | }; 43 | 44 | let BaseURL = ''; 45 | 46 | if (settings.azureDevopsSettings.collection) { 47 | BaseURL = `https://${settings.azureDevopsSettings.instance}/${settings.azureDevopsSettings.collection}/${settings.azureDevopsSettings.project}`; 48 | } else { 49 | BaseURL = `https://${settings.azureDevopsSettings.instance}/${settings.azureDevopsSettings.project}`; 50 | } 51 | 52 | try { 53 | const iterationResponse = await requestUrl({ 54 | method: 'GET', 55 | headers: headers, 56 | url: `${BaseURL}/${settings.azureDevopsSettings.team}/_apis/work/teamsettings/iterations?$timeframe=current&api-version=6.0`, 57 | }); 58 | const currentSprint = iterationResponse.json.value[0]; 59 | const normalizeIterationPath = currentSprint.path.normalize().replace(/\\/g, '\\\\'); 60 | 61 | let taskIds: any; 62 | 63 | if (settings.teamLeaderMode) { 64 | const tasksReponse = await requestUrl({ 65 | method: 'POST', 66 | body: TASKS_QUERY.format(normalizeIterationPath, ''), 67 | headers: headers, 68 | url: `${BaseURL}/${settings.azureDevopsSettings.team}/_apis/wit/wiql?api-version=6.0`, 69 | }); 70 | 71 | taskIds = tasksReponse.json.workItems; 72 | } else { 73 | const usernames = settings.azureDevopsSettings.usernames 74 | .split(',') 75 | .map((username: string) => username.trim().replace("'", "\\'")); 76 | 77 | // Put query together dynamically based on number of usernames requested 78 | let multiUserOperands = ' AND '; 79 | for (let i = 0; i < usernames.length; i++) { 80 | multiUserOperands += USER_OPERAND.format(usernames[i]); 81 | 82 | if (i < usernames.length - 1) { 83 | multiUserOperands += ' OR '; 84 | } 85 | } 86 | 87 | const tasksReponse = await requestUrl({ 88 | method: 'POST', 89 | body: TASKS_QUERY.format(normalizeIterationPath, multiUserOperands), 90 | headers: headers, 91 | url: `${BaseURL}/${settings.azureDevopsSettings.team}/_apis/wit/wiql?api-version=6.0`, 92 | }); 93 | 94 | taskIds = tasksReponse.json.workItems; 95 | } 96 | 97 | const normalizedFolderPath = normalizePath(settings.targetFolder + '/' + currentSprint.path); 98 | 99 | // Ensure folder structure created 100 | VaultHelper.createFolders(normalizedFolderPath, this.app); 101 | 102 | // Get assigned tasks 103 | const assignedTasks = await Promise.all( 104 | taskIds.map((task: any) => 105 | requestUrl({ 106 | method: 'GET', 107 | headers: headers, 108 | url: task.url, 109 | }).then((r) => r.json) 110 | ) 111 | ); 112 | 113 | let tasks: Array = []; 114 | assignedTasks.forEach((task: any) => { 115 | let assigneeName = 'Unassigned'; 116 | const assignee = task.fields['System.AssignedTo'] ?? null; 117 | if (assignee !== null) { 118 | assigneeName = assignee['displayName']; 119 | } 120 | 121 | let tags = task.fields['System.Tags'] || ''; 122 | let replacedTags = tags 123 | .split(';') 124 | .map((part: string) => 125 | part 126 | .trim() 127 | .split(' ') 128 | .map((word) => word.replace(/\s+/g, '-')) 129 | .join('-') 130 | ) 131 | .filter(Boolean) 132 | .join(' '); 133 | 134 | const tempDate = new Date(task.fields['Microsoft.VSTS.Scheduling.DueDate']); 135 | const dueDate = tempDate && !isNaN(tempDate.getTime()) ? tempDate.toLocaleDateString('en-GB') : ''; 136 | 137 | const testScenarios = task.fields['Custom.Testscenarios'] 138 | ? task.fields['Custom.Testscenarios'] 139 | : 'No test scenarios provided'; 140 | const acceptanceCriteria = task.fields['Microsoft.VSTS.Common.AcceptanceCriteria'] 141 | ? task.fields['Microsoft.VSTS.Common.AcceptanceCriteria'] 142 | : 'No acceptance criteria provided'; 143 | const description = task.fields['System.Description'] 144 | ? task.fields['System.Description'] 145 | : 'No description provided'; 146 | 147 | tasks.push( 148 | new Task( 149 | task.id, 150 | task.fields['System.State'], 151 | task.fields['System.Title'], 152 | task.fields['System.WorkItemType'], 153 | assigneeName, 154 | `https://${settings.azureDevopsSettings.instance}/${settings.azureDevopsSettings.collection}/${settings.azureDevopsSettings.project}/_workitems/edit/${task.id}`, 155 | description, 156 | acceptanceCriteria, 157 | testScenarios, 158 | dueDate, 159 | replacedTags 160 | ) 161 | ); 162 | }); 163 | 164 | // Create markdown files based on remote task in current sprint 165 | await Promise.all( 166 | VaultHelper.createTaskNotes(normalizedFolderPath, tasks, settings.noteTemplate, settings.noteName, this.app) 167 | ).catch((e) => VaultHelper.logError(e)); 168 | 169 | if (settings.createKanban) { 170 | // Create or replace Kanban board of current sprint 171 | const columnIds = settings.azureDevopsSettings.columns 172 | .split(',') 173 | .map((columnName: string) => columnName.trim()); 174 | await VaultHelper.createKanbanBoard( 175 | normalizedFolderPath, 176 | tasks, 177 | columnIds, 178 | currentSprint.name, 179 | settings.teamLeaderMode, 180 | this.app 181 | ).catch((e) => VaultHelper.logError(e)); 182 | } 183 | } catch (e) { 184 | VaultHelper.logError(e); 185 | } 186 | } 187 | 188 | public setupSettings( 189 | container: HTMLElement, 190 | plugin: AgileTaskNotesPlugin, 191 | settingsTab: AgileTaskNotesPluginSettingTab 192 | ): any { 193 | container.createEl('h2', { text: 'AzureDevops Remote Repo Settings' }); 194 | 195 | new Setting(container) 196 | .setName('Instance') 197 | .setDesc('TFS server name (ex: dev.azure.com/OrgName)') 198 | .addText((text) => 199 | text 200 | .setPlaceholder('Enter instance base url') 201 | .setValue(plugin.settings.azureDevopsSettings.instance) 202 | .onChange(async (value) => { 203 | plugin.settings.azureDevopsSettings.instance = value; 204 | await plugin.saveSettings(); 205 | }) 206 | ); 207 | 208 | new Setting(container) 209 | .setName('Collection') 210 | .setDesc('The name of the Azure DevOps collection (leave empty if it does not apply)') 211 | .addText((text) => 212 | text 213 | .setPlaceholder('Enter Collection Name') 214 | .setValue(plugin.settings.azureDevopsSettings.collection) 215 | .onChange(async (value) => { 216 | plugin.settings.azureDevopsSettings.collection = value; 217 | await plugin.saveSettings(); 218 | }) 219 | ); 220 | 221 | new Setting(container) 222 | .setName('Project') 223 | .setDesc('AzureDevops Project ID or project name') 224 | .addText((text) => 225 | text 226 | .setPlaceholder('Enter project name') 227 | .setValue(plugin.settings.azureDevopsSettings.project) 228 | .onChange(async (value) => { 229 | plugin.settings.azureDevopsSettings.project = value; 230 | await plugin.saveSettings(); 231 | }) 232 | ); 233 | 234 | new Setting(container) 235 | .setName('Team') 236 | .setDesc('AzureDevops Team ID or team name') 237 | .addText((text) => 238 | text 239 | .setPlaceholder('Enter team name') 240 | .setValue(plugin.settings.azureDevopsSettings.team) 241 | .onChange(async (value) => { 242 | plugin.settings.azureDevopsSettings.team = value; 243 | await plugin.saveSettings(); 244 | }) 245 | ); 246 | 247 | new Setting(container) 248 | .setName('Usernames') 249 | .setDesc( 250 | 'A comma-separated list of usernames you want the tasks of. Simply put your username if you only need your own.' 251 | ) 252 | .addText((text) => 253 | text 254 | .setPlaceholder('Enter usernames') 255 | .setValue(plugin.settings.azureDevopsSettings.usernames) 256 | .onChange(async (value) => { 257 | plugin.settings.azureDevopsSettings.usernames = value; 258 | await plugin.saveSettings(); 259 | }) 260 | ); 261 | 262 | new Setting(container) 263 | .setName('Personal Access Token') 264 | .setDesc('Your AzureDevops PAT with full access') 265 | .addText((text) => 266 | text 267 | .setPlaceholder('Enter your PAT') 268 | .setValue(plugin.settings.azureDevopsSettings.accessToken) 269 | .onChange(async (value) => { 270 | plugin.settings.azureDevopsSettings.accessToken = value; 271 | await plugin.saveSettings(); 272 | }) 273 | ); 274 | 275 | new Setting(container) 276 | .setName('Column Names') 277 | .setDesc('Line-separated list of column key names from your team sprint board to be used in Kanban board') 278 | .addText((text) => 279 | text 280 | .setPlaceholder('Enter comma-separated list') 281 | .setValue(plugin.settings.azureDevopsSettings.columns) 282 | .onChange(async (value) => { 283 | plugin.settings.azureDevopsSettings.columns = value; 284 | await plugin.saveSettings(); 285 | }) 286 | ); 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/Clients/JiraClient.ts: -------------------------------------------------------------------------------- 1 | import AgileTaskNotesPlugin, { AgileTaskNotesPluginSettingTab, AgileTaskNotesSettings } from 'main'; 2 | import { App, normalizePath, requestUrl, RequestUrlResponse, Setting, TFile } from 'obsidian'; 3 | import { Task } from 'src/Task'; 4 | import { VaultHelper } from 'src/VaultHelper'; 5 | import { ITfsClient } from './ITfsClient'; 6 | 7 | export interface JiraSettings { 8 | baseUrl: string; 9 | usernames: string; 10 | email: string; 11 | authmode: string; 12 | apiToken: string; 13 | boardId: string; 14 | useSprintName: boolean; 15 | mode: string; 16 | excludeBacklog: boolean; 17 | } 18 | 19 | export const JIRA_DEFAULT_SETTINGS: JiraSettings = { 20 | baseUrl: '{yourserver}.atlassian.net', 21 | usernames: '', 22 | email: '', 23 | authmode: 'basic', 24 | apiToken: '', 25 | boardId: '', 26 | useSprintName: true, 27 | mode: 'sprints', 28 | excludeBacklog: false, 29 | }; 30 | 31 | export class JiraClient implements ITfsClient { 32 | clientName: string = 'Jira'; 33 | 34 | constructor(private app: App) {} 35 | 36 | public async update(settings: AgileTaskNotesSettings): Promise { 37 | let headers = { 38 | Authorization: '', 39 | 'Content-Type': 'application/json', 40 | }; 41 | if (settings.jiraSettings.authmode == 'basic') { 42 | const encoded64Key = Buffer.from(`${settings.jiraSettings.email}:${settings.jiraSettings.apiToken}`).toString( 43 | 'base64' 44 | ); 45 | headers.Authorization = `Basic ${encoded64Key}`; 46 | } else if ((settings.jiraSettings.authmode = 'bearer')) { 47 | headers.Authorization = `Bearer ${settings.jiraSettings.apiToken}`; 48 | } 49 | 50 | const BaseURL = `https://${settings.jiraSettings.baseUrl}/rest/agile/1.0`; 51 | 52 | try { 53 | if (settings.jiraSettings.mode == 'sprints') { 54 | const sprintsResponse = await requestUrl({ 55 | method: 'GET', 56 | headers: headers, 57 | url: `${BaseURL}/board/${settings.jiraSettings.boardId}/sprint?state=active`, 58 | }); 59 | const currentSprintId = sprintsResponse.json.values[0].id; 60 | const currentSprintName = sprintsResponse.json.values[0].name 61 | .replace(/Sprint/, '') 62 | .replace(/Board/, '') 63 | .replace(/^\s+|\s+$/g, '') 64 | .replace(/[^a-zA-Z0-9 -]/g, '') 65 | .replace(/\s+/g, '-') 66 | .replace(/-+/g, '-'); 67 | 68 | const sprintIdentifier = settings.jiraSettings.useSprintName ? currentSprintName : currentSprintId; 69 | 70 | const normalizedFolderPath = normalizePath(settings.targetFolder + '/sprint-' + sprintIdentifier); 71 | 72 | // Ensure folder structure created 73 | VaultHelper.createFolders(normalizedFolderPath, this.app); 74 | 75 | let tasks: Array = []; 76 | let issueResponseList: Array = []; 77 | 78 | if (settings.teamLeaderMode) { 79 | issueResponseList[0] = await requestUrl({ 80 | method: 'GET', 81 | headers: headers, 82 | url: `${BaseURL}/board/${settings.jiraSettings.boardId}/sprint/${currentSprintId}/issue?maxResults=1000`, 83 | }); 84 | } else { 85 | let usernames = settings.jiraSettings.usernames 86 | .split(',') 87 | .map((username: string) => username.trim().replace("'", "\\'")); 88 | 89 | issueResponseList = await Promise.all( 90 | usernames.map((username: string) => 91 | requestUrl({ 92 | method: 'GET', 93 | headers: headers, 94 | url: `${BaseURL}/board/${settings.jiraSettings.boardId}/sprint/${currentSprintId}/issue?jql=assignee="${username}"&maxResults=1000`, 95 | }) 96 | ) 97 | ); 98 | } 99 | 100 | issueResponseList.forEach((issueResponse: any) => { 101 | issueResponse.json.issues.forEach((issue: any) => { 102 | let assigneeName = 'Unassigned'; 103 | let assignee = issue.fields['assignee']; 104 | if (assignee !== null) { 105 | assigneeName = assignee['displayName']; 106 | } 107 | 108 | tasks.push( 109 | new Task( 110 | issue.key, 111 | issue.fields['status']['name'], 112 | issue.fields['summary'], 113 | issue.fields['issuetype']['name'], 114 | assigneeName, 115 | `https://${settings.jiraSettings.baseUrl}/browse/${issue.key}`, 116 | issue.fields['description'] 117 | ) 118 | ); 119 | }); 120 | }); 121 | 122 | // Create markdown files based on remote task in current sprint 123 | await Promise.all( 124 | VaultHelper.createTaskNotes(normalizedFolderPath, tasks, settings.noteTemplate, settings.noteName, this.app) 125 | ); 126 | 127 | if (settings.createKanban) { 128 | // Get the column names from the Jira board 129 | const boardConfigResponse = await requestUrl({ 130 | method: 'GET', 131 | headers: headers, 132 | url: `${BaseURL}/board/${settings.jiraSettings.boardId}/configuration`, 133 | }); 134 | const columnIds = boardConfigResponse.json.columnConfig.columns.map((column: any) => column.name); 135 | 136 | await VaultHelper.createKanbanBoard( 137 | normalizedFolderPath, 138 | tasks, 139 | columnIds, 140 | sprintIdentifier, 141 | settings.teamLeaderMode, 142 | this.app 143 | ); 144 | } 145 | } else if (settings.jiraSettings.mode == 'kanban') { 146 | const completedFolder = settings.targetFolder + '/Completed/'; 147 | const normalizedBaseFolderPath = normalizePath(settings.targetFolder); 148 | const normalizedCompletedfolderPath = normalizePath(completedFolder); 149 | 150 | // Ensure folder structures created 151 | VaultHelper.createFoldersFromList([normalizedBaseFolderPath, normalizedCompletedfolderPath], this.app); 152 | 153 | let activeTasks: Array = []; 154 | let completedTasks: Array = []; 155 | let issueResponseList: Array = []; 156 | 157 | if (settings.teamLeaderMode) { 158 | issueResponseList[0] = await requestUrl({ 159 | method: 'GET', 160 | headers: headers, 161 | url: `${BaseURL}/board/${settings.jiraSettings.boardId}/issue?maxResults=1000`, 162 | }); 163 | } else { 164 | let usernames = settings.jiraSettings.usernames 165 | .split(',') 166 | .map((username: string) => username.trim().replace("'", "\\'")); 167 | issueResponseList = await Promise.all( 168 | usernames.map((username: string) => 169 | requestUrl({ 170 | method: 'GET', 171 | headers: headers, 172 | url: `${BaseURL}/board/${settings.jiraSettings.boardId}/issue?jql=assignee="${username}"&maxResults=1000`, 173 | }) 174 | ) 175 | ); 176 | } 177 | 178 | issueResponseList.forEach((issueResponse: RequestUrlResponse) => { 179 | issueResponse.json.issues.forEach((issue: any) => { 180 | if ( 181 | !settings.jiraSettings.excludeBacklog || 182 | (settings.jiraSettings.excludeBacklog && issue.fields['status']['name'] !== 'Backlog') 183 | ) { 184 | let assigneeName = 'Unassigned'; 185 | let assignee = issue.fields['assignee']; 186 | if (assignee !== null) { 187 | assigneeName = assignee['displayName']; 188 | } 189 | 190 | let taskObj = new Task( 191 | issue.key, 192 | issue.fields['status']['name'], 193 | issue.fields['summary'], 194 | issue.fields['issuetype']['name'], 195 | assigneeName, 196 | `https://${settings.jiraSettings.baseUrl}/browse/${issue.key}`, 197 | issue.fields['description'] 198 | ); 199 | 200 | if (issue.fields['resolution'] != null) { 201 | completedTasks.push(taskObj); 202 | } else { 203 | activeTasks.push(taskObj); 204 | } 205 | } 206 | }); 207 | }); 208 | 209 | // Create markdown files 210 | await Promise.all( 211 | VaultHelper.createTaskNotes( 212 | normalizedBaseFolderPath, 213 | activeTasks, 214 | settings.noteTemplate, 215 | settings.noteName, 216 | this.app 217 | ) 218 | ); 219 | await Promise.all( 220 | VaultHelper.createTaskNotes( 221 | normalizedCompletedfolderPath, 222 | completedTasks, 223 | settings.noteTemplate, 224 | settings.noteName, 225 | this.app 226 | ) 227 | ); 228 | 229 | // Move pre-existing notes that became resolved state into the Completed folder and vise versa 230 | const completedTaskNoteFiles = completedTasks 231 | .map((task) => VaultHelper.getFileByTaskId(settings.targetFolder, task.id, this.app)) 232 | .filter((file): file is TFile => !!file); 233 | completedTaskNoteFiles.forEach((file) => 234 | this.app.vault.rename(file, normalizePath(completedFolder + file.name)) 235 | ); 236 | const activeTaskNoteFiles = activeTasks 237 | .map((task) => VaultHelper.getFileByTaskId(settings.targetFolder, task.id, this.app)) 238 | .filter((file): file is TFile => !!file); 239 | activeTaskNoteFiles.forEach((file) => 240 | this.app.vault.rename(file, normalizePath(settings.targetFolder + '/' + file.name)) 241 | ); 242 | 243 | if (settings.createKanban) { 244 | // Get the column names from the Jira board 245 | const boardConfigResponse = await requestUrl({ 246 | method: 'GET', 247 | headers: headers, 248 | url: `${BaseURL}/board/${settings.jiraSettings.boardId}/configuration`, 249 | }); 250 | let columnIds = boardConfigResponse.json.columnConfig.columns.map((column: any) => column.name); 251 | 252 | if (settings.jiraSettings.excludeBacklog) { 253 | columnIds = columnIds.filter((columnName: string) => columnName !== 'Backlog'); 254 | } 255 | 256 | await VaultHelper.createKanbanBoard( 257 | normalizedBaseFolderPath, 258 | activeTasks.concat(completedTasks), 259 | columnIds, 260 | settings.jiraSettings.boardId, 261 | settings.teamLeaderMode, 262 | this.app 263 | ); 264 | } 265 | } 266 | } catch (e) { 267 | VaultHelper.logError(e); 268 | } 269 | } 270 | 271 | public setupSettings( 272 | container: HTMLElement, 273 | plugin: AgileTaskNotesPlugin, 274 | settingsTab: AgileTaskNotesPluginSettingTab 275 | ): any { 276 | container.createEl('h2', { text: 'Jira Remote Repo Settings' }); 277 | 278 | new Setting(container) 279 | .setName('URL') 280 | .setDesc('The base URL of your Jira server or {ip:port}') 281 | .addText((text) => 282 | text 283 | .setPlaceholder('Enter Jira base URL') 284 | .setValue(plugin.settings.jiraSettings.baseUrl) 285 | .onChange(async (value) => { 286 | plugin.settings.jiraSettings.baseUrl = value; 287 | await plugin.saveSettings(); 288 | }) 289 | ); 290 | 291 | new Setting(container) 292 | .setName('Usernames') 293 | .setDesc( 294 | 'A comma-separated list of usernames you want the tasks of. Simply put your username if you only need your own.' 295 | ) 296 | .addText((text) => 297 | text 298 | .setPlaceholder('Enter usernames') 299 | .setValue(plugin.settings.jiraSettings.usernames) 300 | .onChange(async (value) => { 301 | plugin.settings.jiraSettings.usernames = value; 302 | await plugin.saveSettings(); 303 | }) 304 | ); 305 | 306 | new Setting(container) 307 | .setName('Email') 308 | .setDesc('The email of your Atlassian account for Jira') 309 | .addText((text) => 310 | text 311 | .setPlaceholder('Enter Atlassian email') 312 | .setValue(plugin.settings.jiraSettings.email) 313 | .onChange(async (value) => { 314 | plugin.settings.jiraSettings.email = value; 315 | await plugin.saveSettings(); 316 | }) 317 | ); 318 | 319 | new Setting(container) 320 | .setName('Authorization mode') 321 | .setDesc('Set the mode of authorization to be used') 322 | .addDropdown((dropdown) => { 323 | dropdown.addOption('basic', 'Basic Auth'); 324 | dropdown.addOption('bearer', 'Personal Access Token'); 325 | dropdown.setValue(plugin.settings.jiraSettings.authmode).onChange(async (value) => { 326 | plugin.settings.jiraSettings.authmode = value; 327 | await plugin.saveSettings(); 328 | }); 329 | }); 330 | 331 | new Setting(container) 332 | .setName('API Token') 333 | .setDesc('The API token generated with your account') 334 | .addText((text) => 335 | text 336 | .setPlaceholder('Enter API token') 337 | .setValue(plugin.settings.jiraSettings.apiToken) 338 | .onChange(async (value) => { 339 | plugin.settings.jiraSettings.apiToken = value; 340 | await plugin.saveSettings(); 341 | }) 342 | ); 343 | 344 | new Setting(container) 345 | .setName('Board ID') 346 | .setDesc('The ID of your Scrum board (the number in the URL when viewing scrum board in browser) ') 347 | .addText((text) => 348 | text 349 | .setPlaceholder('Enter Board ID') 350 | .setValue(plugin.settings.jiraSettings.boardId) 351 | .onChange(async (value) => { 352 | plugin.settings.jiraSettings.boardId = value; 353 | await plugin.saveSettings(); 354 | }) 355 | ); 356 | 357 | new Setting(container) 358 | .setName('Mode') 359 | .setDesc('Set the mode corresponding to how you use Jira') 360 | .addDropdown((dropdown) => { 361 | dropdown.addOption('sprints', 'Sprints'); 362 | dropdown.addOption('kanban', 'Kanban'); 363 | dropdown.setValue(plugin.settings.jiraSettings.mode).onChange(async (value) => { 364 | plugin.settings.jiraSettings.mode = value; 365 | await plugin.saveSettings(); 366 | settingsTab.display(); // Refresh settings to update view 367 | }); 368 | }); 369 | 370 | if (plugin.settings.jiraSettings.mode === 'sprints') { 371 | new Setting(container) 372 | .setName('Use Sprint Name (rather than id)') 373 | .setDesc("Uses the Sprint's human assigned name") 374 | .addToggle((text) => 375 | text.setValue(plugin.settings.jiraSettings.useSprintName).onChange(async (value) => { 376 | plugin.settings.jiraSettings.useSprintName = value; 377 | await plugin.saveSettings(); 378 | }) 379 | ); 380 | } else if (plugin.settings.jiraSettings.mode === 'kanban') { 381 | new Setting(container) 382 | .setName('Exclude Backlog') 383 | .setDesc('Enable to prevent creation of issues from the backlog') 384 | .addToggle((toggle) => 385 | toggle.setValue(plugin.settings.jiraSettings.excludeBacklog).onChange(async (value) => { 386 | plugin.settings.jiraSettings.excludeBacklog = value; 387 | await plugin.saveSettings(); 388 | }) 389 | ); 390 | } 391 | } 392 | } 393 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@nodelib/fs.scandir@2.1.5": 6 | version "2.1.5" 7 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" 8 | integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== 9 | dependencies: 10 | "@nodelib/fs.stat" "2.0.5" 11 | run-parallel "^1.1.9" 12 | 13 | "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": 14 | version "2.0.5" 15 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" 16 | integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== 17 | 18 | "@nodelib/fs.walk@^1.2.3": 19 | version "1.2.8" 20 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" 21 | integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== 22 | dependencies: 23 | "@nodelib/fs.scandir" "2.1.5" 24 | fastq "^1.6.0" 25 | 26 | "@types/codemirror@5.60.8": 27 | version "5.60.8" 28 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/@types/codemirror/-/codemirror-5.60.8.tgz#b647d04b470e8e1836dd84b2879988fc55c9de68" 29 | integrity sha512-VjFgDF/eB+Aklcy15TtOTLQeMjTo07k7KAjql8OK5Dirr7a6sJY4T1uVBDuTVG9VEmn1uUsohOpYnVfgC6/jyw== 30 | dependencies: 31 | "@types/tern" "*" 32 | 33 | "@types/estree@*": 34 | version "1.0.5" 35 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" 36 | integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== 37 | 38 | "@types/json-schema@^7.0.9": 39 | version "7.0.15" 40 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" 41 | integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== 42 | 43 | "@types/node@^16.11.6": 44 | version "16.18.106" 45 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/@types/node/-/node-16.18.106.tgz#62228200da6d98365d2de5601f7230cdf041f0e2" 46 | integrity sha512-YTgQUcpdXRc7iiEMutkkXl9WUx5lGUCVYvnfRg9CV+IA4l9epctEhCTbaw4KgzXaKYv8emvFJkEM65+MkNUhsQ== 47 | 48 | "@types/tern@*": 49 | version "0.23.9" 50 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/@types/tern/-/tern-0.23.9.tgz#6f6093a4a9af3e6bb8dde528e024924d196b367c" 51 | integrity sha512-ypzHFE/wBzh+BlH6rrBgS5I/Z7RD21pGhZ2rltb/+ZrVM1awdZwjx7hE5XfuYgHWk9uvV5HLZN3SloevCAp3Bw== 52 | dependencies: 53 | "@types/estree" "*" 54 | 55 | "@typescript-eslint/eslint-plugin@5.29.0": 56 | version "5.29.0" 57 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.29.0.tgz#c67794d2b0fd0b4a47f50266088acdc52a08aab6" 58 | integrity sha512-kgTsISt9pM53yRFQmLZ4npj99yGl3x3Pl7z4eA66OuTzAGC4bQB5H5fuLwPnqTKU3yyrrg4MIhjF17UYnL4c0w== 59 | dependencies: 60 | "@typescript-eslint/scope-manager" "5.29.0" 61 | "@typescript-eslint/type-utils" "5.29.0" 62 | "@typescript-eslint/utils" "5.29.0" 63 | debug "^4.3.4" 64 | functional-red-black-tree "^1.0.1" 65 | ignore "^5.2.0" 66 | regexpp "^3.2.0" 67 | semver "^7.3.7" 68 | tsutils "^3.21.0" 69 | 70 | "@typescript-eslint/parser@5.29.0": 71 | version "5.29.0" 72 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/@typescript-eslint/parser/-/parser-5.29.0.tgz#41314b195b34d44ff38220caa55f3f93cfca43cf" 73 | integrity sha512-ruKWTv+x0OOxbzIw9nW5oWlUopvP/IQDjB5ZqmTglLIoDTctLlAJpAQFpNPJP/ZI7hTT9sARBosEfaKbcFuECw== 74 | dependencies: 75 | "@typescript-eslint/scope-manager" "5.29.0" 76 | "@typescript-eslint/types" "5.29.0" 77 | "@typescript-eslint/typescript-estree" "5.29.0" 78 | debug "^4.3.4" 79 | 80 | "@typescript-eslint/scope-manager@5.29.0": 81 | version "5.29.0" 82 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/@typescript-eslint/scope-manager/-/scope-manager-5.29.0.tgz#2a6a32e3416cb133e9af8dcf54bf077a916aeed3" 83 | integrity sha512-etbXUT0FygFi2ihcxDZjz21LtC+Eps9V2xVx09zFoN44RRHPrkMflidGMI+2dUs821zR1tDS6Oc9IXxIjOUZwA== 84 | dependencies: 85 | "@typescript-eslint/types" "5.29.0" 86 | "@typescript-eslint/visitor-keys" "5.29.0" 87 | 88 | "@typescript-eslint/type-utils@5.29.0": 89 | version "5.29.0" 90 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/@typescript-eslint/type-utils/-/type-utils-5.29.0.tgz#241918001d164044020b37d26d5b9f4e37cc3d5d" 91 | integrity sha512-JK6bAaaiJozbox3K220VRfCzLa9n0ib/J+FHIwnaV3Enw/TO267qe0pM1b1QrrEuy6xun374XEAsRlA86JJnyg== 92 | dependencies: 93 | "@typescript-eslint/utils" "5.29.0" 94 | debug "^4.3.4" 95 | tsutils "^3.21.0" 96 | 97 | "@typescript-eslint/types@5.29.0": 98 | version "5.29.0" 99 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/@typescript-eslint/types/-/types-5.29.0.tgz#7861d3d288c031703b2d97bc113696b4d8c19aab" 100 | integrity sha512-X99VbqvAXOMdVyfFmksMy3u8p8yoRGITgU1joBJPzeYa0rhdf5ok9S56/itRoUSh99fiDoMtarSIJXo7H/SnOg== 101 | 102 | "@typescript-eslint/typescript-estree@5.29.0": 103 | version "5.29.0" 104 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/@typescript-eslint/typescript-estree/-/typescript-estree-5.29.0.tgz#e83d19aa7fd2e74616aab2f25dfbe4de4f0b5577" 105 | integrity sha512-mQvSUJ/JjGBdvo+1LwC+GY2XmSYjK1nAaVw2emp/E61wEVYEyibRHCqm1I1vEKbXCpUKuW4G7u9ZCaZhJbLoNQ== 106 | dependencies: 107 | "@typescript-eslint/types" "5.29.0" 108 | "@typescript-eslint/visitor-keys" "5.29.0" 109 | debug "^4.3.4" 110 | globby "^11.1.0" 111 | is-glob "^4.0.3" 112 | semver "^7.3.7" 113 | tsutils "^3.21.0" 114 | 115 | "@typescript-eslint/utils@5.29.0": 116 | version "5.29.0" 117 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/@typescript-eslint/utils/-/utils-5.29.0.tgz#775046effd5019667bd086bcf326acbe32cd0082" 118 | integrity sha512-3Eos6uP1nyLOBayc/VUdKZikV90HahXE5Dx9L5YlSd/7ylQPXhLk1BYb29SDgnBnTp+jmSZUU0QxUiyHgW4p7A== 119 | dependencies: 120 | "@types/json-schema" "^7.0.9" 121 | "@typescript-eslint/scope-manager" "5.29.0" 122 | "@typescript-eslint/types" "5.29.0" 123 | "@typescript-eslint/typescript-estree" "5.29.0" 124 | eslint-scope "^5.1.1" 125 | eslint-utils "^3.0.0" 126 | 127 | "@typescript-eslint/visitor-keys@5.29.0": 128 | version "5.29.0" 129 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/@typescript-eslint/visitor-keys/-/visitor-keys-5.29.0.tgz#7a4749fa7ef5160c44a451bf060ac1dc6dfb77ee" 130 | integrity sha512-Hpb/mCWsjILvikMQoZIE3voc9wtQcS0A9FUw3h8bhr9UxBdtI/tw1ZDZUOXHXLOVMedKCH5NxyzATwnU78bWCQ== 131 | dependencies: 132 | "@typescript-eslint/types" "5.29.0" 133 | eslint-visitor-keys "^3.3.0" 134 | 135 | array-union@^2.1.0: 136 | version "2.1.0" 137 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d" 138 | integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw== 139 | 140 | braces@^3.0.3: 141 | version "3.0.3" 142 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" 143 | integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== 144 | dependencies: 145 | fill-range "^7.1.1" 146 | 147 | builtin-modules@3.3.0: 148 | version "3.3.0" 149 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" 150 | integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== 151 | 152 | debug@^4.3.4: 153 | version "4.3.6" 154 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/debug/-/debug-4.3.6.tgz#2ab2c38fbaffebf8aa95fdfe6d88438c7a13c52b" 155 | integrity sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg== 156 | dependencies: 157 | ms "2.1.2" 158 | 159 | dir-glob@^3.0.1: 160 | version "3.0.1" 161 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" 162 | integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA== 163 | dependencies: 164 | path-type "^4.0.0" 165 | 166 | esbuild-android-64@0.14.47: 167 | version "0.14.47" 168 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-android-64/-/esbuild-android-64-0.14.47.tgz#ef95b42c67bcf4268c869153fa3ad1466c4cea6b" 169 | integrity sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g== 170 | 171 | esbuild-android-arm64@0.14.47: 172 | version "0.14.47" 173 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-android-arm64/-/esbuild-android-arm64-0.14.47.tgz#4ebd7ce9fb250b4695faa3ee46fd3b0754ecd9e6" 174 | integrity sha512-OkwOjj7ts4lBp/TL6hdd8HftIzOy/pdtbrNA4+0oVWgGG64HrdVzAF5gxtJufAPOsEjkyh1oIYvKAUinKKQRSQ== 175 | 176 | esbuild-darwin-64@0.14.47: 177 | version "0.14.47" 178 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-darwin-64/-/esbuild-darwin-64-0.14.47.tgz#e0da6c244f497192f951807f003f6a423ed23188" 179 | integrity sha512-R6oaW0y5/u6Eccti/TS6c/2c1xYTb1izwK3gajJwi4vIfNs1s8B1dQzI1UiC9T61YovOQVuePDcfqHLT3mUZJA== 180 | 181 | esbuild-darwin-arm64@0.14.47: 182 | version "0.14.47" 183 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.47.tgz#cd40fd49a672fca581ed202834239dfe540a9028" 184 | integrity sha512-seCmearlQyvdvM/noz1L9+qblC5vcBrhUaOoLEDDoLInF/VQ9IkobGiLlyTPYP5dW1YD4LXhtBgOyevoIHGGnw== 185 | 186 | esbuild-freebsd-64@0.14.47: 187 | version "0.14.47" 188 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.47.tgz#8da6a14c095b29c01fc8087a16cb7906debc2d67" 189 | integrity sha512-ZH8K2Q8/Ux5kXXvQMDsJcxvkIwut69KVrYQhza/ptkW50DC089bCVrJZZ3sKzIoOx+YPTrmsZvqeZERjyYrlvQ== 190 | 191 | esbuild-freebsd-arm64@0.14.47: 192 | version "0.14.47" 193 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.47.tgz#ad31f9c92817ff8f33fd253af7ab5122dc1b83f6" 194 | integrity sha512-ZJMQAJQsIOhn3XTm7MPQfCzEu5b9STNC+s90zMWe2afy9EwnHV7Ov7ohEMv2lyWlc2pjqLW8QJnz2r0KZmeAEQ== 195 | 196 | esbuild-linux-32@0.14.47: 197 | version "0.14.47" 198 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-linux-32/-/esbuild-linux-32-0.14.47.tgz#de085e4db2e692ea30c71208ccc23fdcf5196c58" 199 | integrity sha512-FxZOCKoEDPRYvq300lsWCTv1kcHgiiZfNrPtEhFAiqD7QZaXrad8LxyJ8fXGcWzIFzRiYZVtB3ttvITBvAFhKw== 200 | 201 | esbuild-linux-64@0.14.47: 202 | version "0.14.47" 203 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-linux-64/-/esbuild-linux-64-0.14.47.tgz#2a9321bbccb01f01b04cebfcfccbabeba3658ba1" 204 | integrity sha512-nFNOk9vWVfvWYF9YNYksZptgQAdstnDCMtR6m42l5Wfugbzu11VpMCY9XrD4yFxvPo9zmzcoUL/88y0lfJZJJw== 205 | 206 | esbuild-linux-arm64@0.14.47: 207 | version "0.14.47" 208 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.47.tgz#b9da7b6fc4b0ca7a13363a0c5b7bb927e4bc535a" 209 | integrity sha512-ywfme6HVrhWcevzmsufjd4iT3PxTfCX9HOdxA7Hd+/ZM23Y9nXeb+vG6AyA6jgq/JovkcqRHcL9XwRNpWG6XRw== 210 | 211 | esbuild-linux-arm@0.14.47: 212 | version "0.14.47" 213 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-linux-arm/-/esbuild-linux-arm-0.14.47.tgz#56fec2a09b9561c337059d4af53625142aded853" 214 | integrity sha512-ZGE1Bqg/gPRXrBpgpvH81tQHpiaGxa8c9Rx/XOylkIl2ypLuOcawXEAo8ls+5DFCcRGt/o3sV+PzpAFZobOsmA== 215 | 216 | esbuild-linux-mips64le@0.14.47: 217 | version "0.14.47" 218 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.47.tgz#9db21561f8f22ed79ef2aedb7bbef082b46cf823" 219 | integrity sha512-mg3D8YndZ1LvUiEdDYR3OsmeyAew4MA/dvaEJxvyygahWmpv1SlEEnhEZlhPokjsUMfRagzsEF/d/2XF+kTQGg== 220 | 221 | esbuild-linux-ppc64le@0.14.47: 222 | version "0.14.47" 223 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.47.tgz#dc3a3da321222b11e96e50efafec9d2de408198b" 224 | integrity sha512-WER+f3+szmnZiWoK6AsrTKGoJoErG2LlauSmk73LEZFQ/iWC+KhhDsOkn1xBUpzXWsxN9THmQFltLoaFEH8F8w== 225 | 226 | esbuild-linux-riscv64@0.14.47: 227 | version "0.14.47" 228 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.47.tgz#9bd6dcd3dca6c0357084ecd06e1d2d4bf105335f" 229 | integrity sha512-1fI6bP3A3rvI9BsaaXbMoaOjLE3lVkJtLxsgLHqlBhLlBVY7UqffWBvkrX/9zfPhhVMd9ZRFiaqXnB1T7BsL2g== 230 | 231 | esbuild-linux-s390x@0.14.47: 232 | version "0.14.47" 233 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.47.tgz#a458af939b52f2cd32fc561410d441a51f69d41f" 234 | integrity sha512-eZrWzy0xFAhki1CWRGnhsHVz7IlSKX6yT2tj2Eg8lhAwlRE5E96Hsb0M1mPSE1dHGpt1QVwwVivXIAacF/G6mw== 235 | 236 | esbuild-netbsd-64@0.14.47: 237 | version "0.14.47" 238 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.47.tgz#6388e785d7e7e4420cb01348d7483ab511b16aa8" 239 | integrity sha512-Qjdjr+KQQVH5Q2Q1r6HBYswFTToPpss3gqCiSw2Fpq/ua8+eXSQyAMG+UvULPqXceOwpnPo4smyZyHdlkcPppQ== 240 | 241 | esbuild-openbsd-64@0.14.47: 242 | version "0.14.47" 243 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.47.tgz#309af806db561aa886c445344d1aacab850dbdc5" 244 | integrity sha512-QpgN8ofL7B9z8g5zZqJE+eFvD1LehRlxr25PBkjyyasakm4599iroUpaj96rdqRlO2ShuyqwJdr+oNqWwTUmQw== 245 | 246 | esbuild-sunos-64@0.14.47: 247 | version "0.14.47" 248 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-sunos-64/-/esbuild-sunos-64-0.14.47.tgz#3f19612dcdb89ba6c65283a7ff6e16f8afbf8aaa" 249 | integrity sha512-uOeSgLUwukLioAJOiGYm3kNl+1wJjgJA8R671GYgcPgCx7QR73zfvYqXFFcIO93/nBdIbt5hd8RItqbbf3HtAQ== 250 | 251 | esbuild-windows-32@0.14.47: 252 | version "0.14.47" 253 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-windows-32/-/esbuild-windows-32-0.14.47.tgz#a92d279c8458d5dc319abcfeb30aa49e8f2e6f7f" 254 | integrity sha512-H0fWsLTp2WBfKLBgwYT4OTfFly4Im/8B5f3ojDv1Kx//kiubVY0IQunP2Koc/fr/0wI7hj3IiBDbSrmKlrNgLQ== 255 | 256 | esbuild-windows-64@0.14.47: 257 | version "0.14.47" 258 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-windows-64/-/esbuild-windows-64-0.14.47.tgz#2564c3fcf0c23d701edb71af8c52d3be4cec5f8a" 259 | integrity sha512-/Pk5jIEH34T68r8PweKRi77W49KwanZ8X6lr3vDAtOlH5EumPE4pBHqkCUdELanvsT14yMXLQ/C/8XPi1pAtkQ== 260 | 261 | esbuild-windows-arm64@0.14.47: 262 | version "0.14.47" 263 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.47.tgz#86d9db1a22d83360f726ac5fba41c2f625db6878" 264 | integrity sha512-HFSW2lnp62fl86/qPQlqw6asIwCnEsEoNIL1h2uVMgakddf+vUuMcCbtUY1i8sst7KkgHrVKCJQB33YhhOweCQ== 265 | 266 | esbuild@0.14.47: 267 | version "0.14.47" 268 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esbuild/-/esbuild-0.14.47.tgz#0d6415f6bd8eb9e73a58f7f9ae04c5276cda0e4d" 269 | integrity sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA== 270 | optionalDependencies: 271 | esbuild-android-64 "0.14.47" 272 | esbuild-android-arm64 "0.14.47" 273 | esbuild-darwin-64 "0.14.47" 274 | esbuild-darwin-arm64 "0.14.47" 275 | esbuild-freebsd-64 "0.14.47" 276 | esbuild-freebsd-arm64 "0.14.47" 277 | esbuild-linux-32 "0.14.47" 278 | esbuild-linux-64 "0.14.47" 279 | esbuild-linux-arm "0.14.47" 280 | esbuild-linux-arm64 "0.14.47" 281 | esbuild-linux-mips64le "0.14.47" 282 | esbuild-linux-ppc64le "0.14.47" 283 | esbuild-linux-riscv64 "0.14.47" 284 | esbuild-linux-s390x "0.14.47" 285 | esbuild-netbsd-64 "0.14.47" 286 | esbuild-openbsd-64 "0.14.47" 287 | esbuild-sunos-64 "0.14.47" 288 | esbuild-windows-32 "0.14.47" 289 | esbuild-windows-64 "0.14.47" 290 | esbuild-windows-arm64 "0.14.47" 291 | 292 | eslint-scope@^5.1.1: 293 | version "5.1.1" 294 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" 295 | integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== 296 | dependencies: 297 | esrecurse "^4.3.0" 298 | estraverse "^4.1.1" 299 | 300 | eslint-utils@^3.0.0: 301 | version "3.0.0" 302 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" 303 | integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== 304 | dependencies: 305 | eslint-visitor-keys "^2.0.0" 306 | 307 | eslint-visitor-keys@^2.0.0: 308 | version "2.1.0" 309 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" 310 | integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== 311 | 312 | eslint-visitor-keys@^3.3.0: 313 | version "3.4.3" 314 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" 315 | integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== 316 | 317 | esrecurse@^4.3.0: 318 | version "4.3.0" 319 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" 320 | integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== 321 | dependencies: 322 | estraverse "^5.2.0" 323 | 324 | estraverse@^4.1.1: 325 | version "4.3.0" 326 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" 327 | integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== 328 | 329 | estraverse@^5.2.0: 330 | version "5.3.0" 331 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" 332 | integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== 333 | 334 | fast-glob@^3.2.9: 335 | version "3.3.2" 336 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" 337 | integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== 338 | dependencies: 339 | "@nodelib/fs.stat" "^2.0.2" 340 | "@nodelib/fs.walk" "^1.2.3" 341 | glob-parent "^5.1.2" 342 | merge2 "^1.3.0" 343 | micromatch "^4.0.4" 344 | 345 | fastq@^1.6.0: 346 | version "1.17.1" 347 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/fastq/-/fastq-1.17.1.tgz#2a523f07a4e7b1e81a42b91b8bf2254107753b47" 348 | integrity sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w== 349 | dependencies: 350 | reusify "^1.0.4" 351 | 352 | fill-range@^7.1.1: 353 | version "7.1.1" 354 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" 355 | integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== 356 | dependencies: 357 | to-regex-range "^5.0.1" 358 | 359 | functional-red-black-tree@^1.0.1: 360 | version "1.0.1" 361 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" 362 | integrity sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g== 363 | 364 | glob-parent@^5.1.2: 365 | version "5.1.2" 366 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 367 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 368 | dependencies: 369 | is-glob "^4.0.1" 370 | 371 | globby@^11.1.0: 372 | version "11.1.0" 373 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" 374 | integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== 375 | dependencies: 376 | array-union "^2.1.0" 377 | dir-glob "^3.0.1" 378 | fast-glob "^3.2.9" 379 | ignore "^5.2.0" 380 | merge2 "^1.4.1" 381 | slash "^3.0.0" 382 | 383 | ignore@^5.2.0: 384 | version "5.3.2" 385 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" 386 | integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== 387 | 388 | is-extglob@^2.1.1: 389 | version "2.1.1" 390 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 391 | integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== 392 | 393 | is-glob@^4.0.1, is-glob@^4.0.3: 394 | version "4.0.3" 395 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 396 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 397 | dependencies: 398 | is-extglob "^2.1.1" 399 | 400 | is-number@^7.0.0: 401 | version "7.0.0" 402 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 403 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 404 | 405 | merge2@^1.3.0, merge2@^1.4.1: 406 | version "1.4.1" 407 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" 408 | integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== 409 | 410 | micromatch@^4.0.4: 411 | version "4.0.8" 412 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" 413 | integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== 414 | dependencies: 415 | braces "^3.0.3" 416 | picomatch "^2.3.1" 417 | 418 | moment@2.29.4: 419 | version "2.29.4" 420 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" 421 | integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== 422 | 423 | ms@2.1.2: 424 | version "2.1.2" 425 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 426 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 427 | 428 | obsidian@latest: 429 | version "1.6.6" 430 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/obsidian/-/obsidian-1.6.6.tgz#d45c4021c291765e1b77ed4a1c645e562ff6e77f" 431 | integrity sha512-GZHzeOiwmw/wBjB5JwrsxAZBLqxGQmqtEKSvJJvT0LtTcqeOFnV8jv0ZK5kO7hBb44WxJc+LdS7mZgLXbb+qXQ== 432 | dependencies: 433 | "@types/codemirror" "5.60.8" 434 | moment "2.29.4" 435 | 436 | path-type@^4.0.0: 437 | version "4.0.0" 438 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" 439 | integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== 440 | 441 | picomatch@^2.3.1: 442 | version "2.3.1" 443 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 444 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 445 | 446 | queue-microtask@^1.2.2: 447 | version "1.2.3" 448 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" 449 | integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== 450 | 451 | regexpp@^3.2.0: 452 | version "3.2.0" 453 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" 454 | integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== 455 | 456 | reusify@^1.0.4: 457 | version "1.0.4" 458 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" 459 | integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== 460 | 461 | run-parallel@^1.1.9: 462 | version "1.2.0" 463 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" 464 | integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== 465 | dependencies: 466 | queue-microtask "^1.2.2" 467 | 468 | sanitizer@^0.1.3: 469 | version "0.1.3" 470 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/sanitizer/-/sanitizer-0.1.3.tgz#d4f0af7475d9a7baf2a9e5a611718baa178a39e1" 471 | integrity sha512-j05vL56tR90rsYqm9ZD05v6K4HI7t4yMDEvvU0x4f+IADXM9Jx1x9mzatxOs5drJq6dGhugxDW99mcPvXVLl+Q== 472 | 473 | semver@^7.3.7: 474 | version "7.6.3" 475 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" 476 | integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== 477 | 478 | slash@^3.0.0: 479 | version "3.0.0" 480 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" 481 | integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== 482 | 483 | to-regex-range@^5.0.1: 484 | version "5.0.1" 485 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 486 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 487 | dependencies: 488 | is-number "^7.0.0" 489 | 490 | tslib@2.4.0: 491 | version "2.4.0" 492 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" 493 | integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== 494 | 495 | tslib@^1.8.1: 496 | version "1.14.1" 497 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" 498 | integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== 499 | 500 | tsutils@^3.21.0: 501 | version "3.21.0" 502 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" 503 | integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== 504 | dependencies: 505 | tslib "^1.8.1" 506 | 507 | typescript@4.7.4: 508 | version "4.7.4" 509 | resolved "https://artifactory.jsitelecom.com:443/artifactory/api/npm/npm-virtual/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" 510 | integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== 511 | --------------------------------------------------------------------------------