settings rework:

- collapsable sections in settings
  - separation of auth variants
  - field mapping validation small fix
  - added raw issue viewer
  - added test field mapping (for testing js expressions with rawissue viewer data)
This commit is contained in:
Alamion 2025-05-06 20:47:30 +03:00
parent f98a4bc1ff
commit fbf8649c1a
No known key found for this signature in database
GPG key ID: 05B153011396F0BC
23 changed files with 2626 additions and 602 deletions

View file

@ -1,7 +1,7 @@
{
"id": "jira-sync",
"name": "Jira Issue Manager",
"version": "1.1.7",
"version": "1.1.8",
"minAppVersion": "1.7.7",
"description": "Get Jira issues, create and update them. Issue status and worklog management.",
"author": "Alamion",

47
package-lock.json generated
View file

@ -1,25 +1,26 @@
{
"name": "obsidian-sample-plugin",
"version": "1.0.0",
"version": "1.1.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "obsidian-sample-plugin",
"version": "1.0.0",
"version": "1.1.8",
"license": "MIT",
"dependencies": {
"@types/turndown": "^5.0.5",
"acorn": "^8.14.1",
"turndown": "^7.2.0",
"highlight.js": "^11.11.1",
"yaml": "^2.7.0"
},
"devDependencies": {
"@types/lodash": "^4.17.16",
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"esbuild": "0.25.0",
"lodash": "^4.17.21",
"obsidian": "latest",
"tslib": "2.4.0",
"typescript": "4.7.4"
@ -185,11 +186,6 @@
"dev": true,
"peer": true
},
"node_modules/@mixmark-io/domino": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/@mixmark-io/domino/-/domino-2.2.0.tgz",
"integrity": "sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw=="
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@ -252,6 +248,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/lodash": {
"version": "4.17.16",
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz",
"integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==",
"dev": true
},
"node_modules/@types/node": {
"version": "16.18.121",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.121.tgz",
@ -269,11 +271,6 @@
"@types/estree": "*"
}
},
"node_modules/@types/turndown": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz",
"integrity": "sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w=="
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "5.29.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.29.0.tgz",
@ -1251,6 +1248,14 @@
"node": ">=8"
}
},
"node_modules/highlight.js": {
"version": "11.11.1",
"resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz",
"integrity": "sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@ -1431,6 +1436,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -1897,14 +1908,6 @@
"dev": true,
"license": "0BSD"
},
"node_modules/turndown": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/turndown/-/turndown-7.2.0.tgz",
"integrity": "sha512-eCZGBN4nNNqM9Owkv9HAtWRYfLA4h909E/WGAWWBpmB275ehNhZyk87/Tpvjbp0jjNl9XwCsbe6bm6CqFsgD+A==",
"dependencies": {
"@mixmark-io/domino": "^2.2.0"
}
},
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",

View file

@ -1,6 +1,6 @@
{
"name": "obsidian-sample-plugin",
"version": "1.1.7",
"version": "1.1.8",
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
"main": "main.js",
"scripts": {
@ -13,17 +13,20 @@
"author": "",
"license": "MIT",
"devDependencies": {
"@types/lodash": "^4.17.16",
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"esbuild": "0.25.0",
"lodash": "^4.17.21",
"obsidian": "latest",
"tslib": "2.4.0",
"typescript": "4.7.4"
},
"dependencies": {
"acorn": "^8.14.1",
"highlight.js": "^11.11.1",
"yaml": "^2.7.0"
}
}

View file

@ -49,24 +49,56 @@ export function validateSettings(plugin: JiraPlugin): boolean {
return true;
}
// Helper function to create Basic Auth header
function createBasicAuthHeader(email: string, token: string): string {
const credentials = `${email}:${token}`;
// Convert to base64
const base64Credentials = btoa(credentials);
return `Basic ${base64Credentials}`;
}
export async function getAuthHeaders(plugin: JiraPlugin): Promise<Record<string, string>> {
const pat = plugin.settings.apiToken;
if (pat) {
return {
Authorization: `Bearer ${pat}`,
"Content-type": "application/json",
Origin: plugin.settings.jiraUrl,
};
}
let cookie = localStorage.getItem(getSessionCookieKey(plugin));
if (!cookie) {
await authenticate(plugin);
cookie = localStorage.getItem(getSessionCookieKey(plugin));
}
return {
Cookie: `${plugin.settings.sessionCookieName}=${cookie}`,
// Common headers
const commonHeaders = {
"Content-type": "application/json",
Origin: plugin.settings.jiraUrl,
};
// Personal Access Token
const pat = plugin.settings.apiToken?.trim() || "";
// Option 1: Bearer token (PAT)
if (plugin.settings.authMethod === "bearer" || !plugin.settings.authMethod) {
return {
...commonHeaders,
Authorization: `Bearer ${pat}`,
};
}
// Option 2: Basic Auth with API token
else if (plugin.settings.authMethod === "basic") {
return {
...commonHeaders,
Authorization: createBasicAuthHeader(plugin.settings.email || "", pat),
};
}
else if (plugin.settings.authMethod === "session") {
// Option 3: Session cookie
let cookie = localStorage.getItem(getSessionCookieKey(plugin));
if (!cookie) {
await authenticate(plugin);
cookie = localStorage.getItem(getSessionCookieKey(plugin));
}
if (cookie) {
return {
...commonHeaders,
Cookie: `${plugin.settings.sessionCookieName}=${cookie}`,
};
}
}
// No valid auth method found
throw new Error("No valid authentication method available");
}

View file

@ -1,32 +1,38 @@
import JiraPlugin from "../main";
import {requestUrl} from "obsidian";
import {getAuthHeaders} from "./auth";
import {authenticate, getAuthHeaders, getSessionCookieKey} from "./auth";
import {debugLog} from "../tools/debugLogging";
export async function baseRequest(
plugin: JiraPlugin,
method: string,
additional_url_path: string,
body?: string
body?: string,
retries: number = 1
): Promise<any> {
const response = await requestUrl({
const requestParams = {
url: `${plugin.settings.jiraUrl}/rest/api/2${additional_url_path}`,
method: method,
headers: await getAuthHeaders(plugin),
contentType: "application/json",
throw: false,
body
});
}
debugLog(requestParams)
const response = await requestUrl(requestParams);
debugLog(response);
if (response.status < 200 || response.status >= 300) {
const error = new Error(`Error updating issue:
if (response.status === 401 && retries > 0) {
await authenticate(plugin);
return await baseRequest(plugin, method, additional_url_path, body, retries-1);
}
// console.error(error);
throw new Error(`
${response.text || "Unknown error"}
${body && '\nbody:' + body || ""}`);
console.error(error);
throw error;
}
debugLog({"url": `${plugin.settings.jiraUrl}/rest/api/2${additional_url_path}`,
"method": method, "body": body});
debugLog(response);
return response.status === 204 ? null : response.json;
}

View file

@ -37,11 +37,6 @@ function openIssueModal(plugin: JiraPlugin): void {
}
export async function fetchAndOpenIssue(plugin: JiraPlugin, file: TFile | null, issueKey: string): Promise<void> {
try {
const issue = await fetchIssue(plugin, issueKey);
await createOrUpdateIssueNote(plugin, issue, file?.path);
} catch (error) {
new Notice("Error retrieving issue: " + (error.message || "Unknown error"));
console.error(error);
}
const issue = await fetchIssue(plugin, issueKey);
await createOrUpdateIssueNote(plugin, issue, file?.path);
}

View file

@ -0,0 +1,405 @@
export const defaultIssue = {
"expand": "",
"id": "",
"self": "",
"key": "",
"fields": {
"issuetype": {
"self": "",
"id": "",
"description": "",
"iconUrl": "",
"name": "",
"subtask": 0,
"avatarId": 0
},
"timespent": 0,
"project": {
"self": "",
"id": "",
"key": "",
"name": "",
"projectTypeKey": "",
"avatarUrls": {
"48x48": "",
"24x24": "",
"16x16": "",
"32x32": ""
}
},
"fixVersions": [],
"aggregatetimespent": 0,
"resolution": null,
"resolutiondate": null,
"workratio": 0,
"lastViewed": "",
"watches": {
"self": "",
"watchCount": 0,
"isWatching": 0
},
"created": "",
"priority": {
"self": "",
"iconUrl": "",
"name": "",
"id": ""
},
"labels": [],
"timeestimate": 0,
"aggregatetimeoriginalestimate": null,
"versions": [],
"issuelinks": [],
"assignee": {
"self": "",
"name": "",
"key": "",
"emailAddress": "",
"avatarUrls": {
"48x48": "",
"24x24": "",
"16x16": "",
"32x32": ""
},
"displayName": "",
"active": 0,
"timeZone": ""
},
"updated": "",
"status": {
"self": "",
"description": "",
"iconUrl": "",
"name": "",
"id": "",
"statusCategory": {
"self": "",
"id": 0,
"key": "",
"colorName": "",
"name": ""
}
},
"components": [],
"timeoriginalestimate": null,
"description": "",
"archiveddate": null,
"timetracking": {
"remainingEstimate": "",
"timeSpent": "",
"remainingEstimateSeconds": 0,
"timeSpentSeconds": 0
},
"attachment": [],
"aggregatetimeestimate": 0,
"summary": "",
"creator": {
"self": "",
"name": "",
"key": "",
"emailAddress": "",
"avatarUrls": {
"48x48": "",
"24x24": "",
"16x16": "",
"32x32": ""
},
"displayName": "",
"active": 0,
"timeZone": ""
},
"subtasks": [],
"reporter": {
"self": "",
"name": "",
"key": "",
"emailAddress": "",
"avatarUrls": {
"48x48": "",
"24x24": "",
"16x16": "",
"32x32": ""
},
"displayName": "",
"active": 0,
"timeZone": ""
},
"aggregateprogress": {
"progress": 0,
"total": 0,
"percent": 0
},
"environment": null,
"duedate": null,
"progress": {
"progress": 0,
"total": 0,
"percent": 0
},
"comment": {
"comments": [],
"maxResults": 0,
"total": 0,
"startAt": 0
},
"votes": {
"self": "",
"votes": 0,
"hasVoted": 0
},
"worklog": {
"startAt": 0,
"maxResults": 0,
"total": 0,
"worklogs": [
{
"self": "",
"author": {
"self": "",
"name": "",
"key": "",
"emailAddress": "",
"avatarUrls": {
"48x48": "",
"24x24": "",
"16x16": "",
"32x32": ""
},
"displayName": "",
"active": 0,
"timeZone": ""
},
"updateAuthor": {
"self": "",
"name": "",
"key": "",
"emailAddress": "",
"avatarUrls": {
"48x48": "",
"24x24": "",
"16x16": "",
"32x32": ""
},
"displayName": "",
"active": 0,
"timeZone": ""
},
"comment": "",
"created": "",
"updated": "",
"started": "",
"timeSpent": "",
"timeSpentSeconds": 0,
"id": "",
"issueId": ""
},
{
"self": "",
"author": {
"self": "",
"name": "",
"key": "",
"emailAddress": "",
"avatarUrls": {
"48x48": "",
"24x24": "",
"16x16": "",
"32x32": ""
},
"displayName": "",
"active": 0,
"timeZone": ""
},
"updateAuthor": {
"self": "",
"name": "",
"key": "",
"emailAddress": "",
"avatarUrls": {
"48x48": "",
"24x24": "",
"16x16": "",
"32x32": ""
},
"displayName": "",
"active": 0,
"timeZone": ""
},
"comment": "",
"created": "",
"updated": "",
"started": "",
"timeSpent": "",
"timeSpentSeconds": 0,
"id": "",
"issueId": ""
},
{
"self": "",
"author": {
"self": "",
"name": "",
"key": "",
"emailAddress": "",
"avatarUrls": {
"48x48": "",
"24x24": "",
"16x16": "",
"32x32": ""
},
"displayName": "",
"active": 0,
"timeZone": ""
},
"updateAuthor": {
"self": "",
"name": "",
"key": "",
"emailAddress": "",
"avatarUrls": {
"48x48": "",
"24x24": "",
"16x16": "",
"32x32": ""
},
"displayName": "",
"active": 0,
"timeZone": ""
},
"comment": "",
"created": "",
"updated": "",
"started": "",
"timeSpent": "",
"timeSpentSeconds": 0,
"id": "",
"issueId": ""
},
{
"self": "",
"author": {
"self": "",
"name": "",
"key": "",
"emailAddress": "",
"avatarUrls": {
"48x48": "",
"24x24": "",
"16x16": "",
"32x32": ""
},
"displayName": "",
"active": 0,
"timeZone": ""
},
"updateAuthor": {
"self": "",
"name": "",
"key": "",
"emailAddress": "",
"avatarUrls": {
"48x48": "",
"24x24": "",
"16x16": "",
"32x32": ""
},
"displayName": "",
"active": 0,
"timeZone": ""
},
"comment": "",
"created": "",
"updated": "",
"started": "",
"timeSpent": "",
"timeSpentSeconds": 0,
"id": "",
"issueId": ""
},
{
"self": "",
"author": {
"self": "",
"name": "",
"key": "",
"emailAddress": "",
"avatarUrls": {
"48x48": "",
"24x24": "",
"16x16": "",
"32x32": ""
},
"displayName": "",
"active": 0,
"timeZone": ""
},
"updateAuthor": {
"self": "",
"name": "",
"key": "",
"emailAddress": "",
"avatarUrls": {
"48x48": "",
"24x24": "",
"16x16": "",
"32x32": ""
},
"displayName": "",
"active": 0,
"timeZone": ""
},
"comment": "",
"created": "",
"updated": "",
"started": "",
"timeSpent": "",
"timeSpentSeconds": 0,
"id": "",
"issueId": ""
},
{
"self": "",
"author": {
"self": "",
"name": "",
"key": "",
"emailAddress": "",
"avatarUrls": {
"48x48": "",
"24x24": "",
"16x16": "",
"32x32": ""
},
"displayName": "",
"active": 0,
"timeZone": ""
},
"updateAuthor": {
"self": "",
"name": "",
"key": "",
"emailAddress": "",
"avatarUrls": {
"48x48": "",
"24x24": "",
"16x16": "",
"32x32": ""
},
"displayName": "",
"active": 0,
"timeZone": ""
},
"comment": "",
"created": "",
"updated": "",
"started": "",
"timeSpent": "",
"timeSpentSeconds": 0,
"id": "",
"issueId": ""
}
]
},
"archivedby": null
}
}

View file

@ -32,6 +32,7 @@ export async function createOrUpdateIssueNote(plugin: JiraPlugin, issue: JiraIss
new Notice(`Issue ${issue.key} imported successfully`);
} catch (error) {
new Notice("Error creating issue note: " + (error.message || "Unknown error"));
console.error(error);
}
}

View file

@ -0,0 +1,59 @@
import { App } from "obsidian";
import JiraPlugin from "../main";
/**
* Function type for field mappings
*/
export type JiraFieldMappingFunction = (...args: any[]) => any;
/**
* Interface for field mappings between Obsidian and Jira
*/
export interface JiraFieldMapping {
toJira: JiraFieldMappingFunction;
fromJira: JiraFieldMappingFunction;
}
/**
* Interface for string representation of field mappings
*/
export interface JiraFieldMappingString {
toJira: string;
fromJira: string;
}
/**
* Base interface for settings components
*/
export interface SettingsComponent {
render(containerEl: HTMLElement): void;
hide?(): void;
}
/**
* Props for settings components
*/
export interface SettingsComponentProps {
app: App;
plugin: JiraPlugin;
onSettingsChange?: () => Promise<void>;
}
/**
* Validation result interface
*/
export interface ValidationResult {
isValid: boolean;
errorMessage?: string;
}
/**
* Interface for collapsed sections
*/
export interface CollapsedSections {
connection: boolean;
general: boolean;
fieldMappings: boolean;
rawIssueViewer: boolean;
testFieldMappings: boolean;
}

View file

@ -0,0 +1,98 @@
import { ButtonComponent } from "obsidian";
import JiraPlugin from "../main";
import { SettingsComponent } from "../interfaces/settingsTypes";
import { debugLog } from "../tools/debugLogging";
export class CollapsibleSection {
private containerEl: HTMLDivElement;
private contentEl: HTMLElement;
private child: SettingsComponent;
private isOpen = false;
private headerText: string;
private saveStateKey?: string;
private plugin: JiraPlugin;
private toggleBtn: ButtonComponent;
constructor(
plugin: JiraPlugin,
parentEl: HTMLElement,
child: SettingsComponent,
headerText: string,
isOpenByDefault = false,
saveStateKey?: string
) {
this.plugin = plugin;
this.child = child;
this.headerText = headerText;
this.isOpen = isOpenByDefault;
this.saveStateKey = saveStateKey;
// Create the container div for the entire collapsible section
this.containerEl = parentEl.createDiv({
cls: "jira-collapsible-section",
});
// Create the header element
const headerEl = this.containerEl.createDiv({
cls: "jira-collapsible-header",
text: headerText,
});
headerEl.style.cursor = "pointer";
headerEl.addEventListener("click", async () => {
await this.toggle();
});
// Add toggle button to the header
this.toggleBtn = new ButtonComponent(headerEl as any);
this.toggleBtn.setIcon(this.isOpen ? "chevron-down" : "chevron-right")
.setTooltip(this.isOpen ? "Collapse" : "Expand")
// Content wrapper
this.contentEl = this.containerEl.createDiv({
cls: "jira-collapsible-content",
});
this.child.render(this.contentEl);
if (!this.isOpen) {
this.contentEl.hide();
}
// Optional: restore saved state
// if (this.saveStateKey && this.plugin.settings.collapsedSections[this.saveStateKey] !== undefined) {
// this.isOpen = !this.plugin.settings.collapsedSections[this.saveStateKey];
// if (!this.isOpen) {
// this.contentEl.hide();
// }
// }
debugLog("Rendered", this.saveStateKey || this.headerText, "as", this.isOpen);
}
public getContentContainer(): HTMLElement {
return this.contentEl;
}
private async toggle() {
this.isOpen = !this.isOpen;
debugLog("Toggled", this.saveStateKey || this.headerText, "to", this.isOpen);
const icon = this.isOpen ? "chevron-down" : "chevron-right";
const tooltip = this.isOpen ? "Collapse" : "Expand";
// Rebuild or update the toggle button if needed
// Or store a reference to the button and just update its properties
if (this.isOpen) {
this.contentEl.show();
} else {
this.contentEl.hide();
}
this.toggleBtn.setIcon(icon).setTooltip(tooltip);
if (this.saveStateKey && this.plugin) {
this.plugin.settings.collapsedSections[this.saveStateKey] = this.isOpen;
await this.plugin.saveSettings();
}
}
}

View file

@ -1,446 +1,94 @@
import {App, normalizePath, Notice, PluginSettingTab, setIcon, Setting} from "obsidian";
import { App, PluginSettingTab } from "obsidian";
import JiraPlugin from "../main";
import {obsidianJiraFieldMappings} from "../constants/obsidianJiraFieldsMapping";
import {
jiraFunctionToString, transform_string_to_functions_mappings, validateFunctionString
} from "../tools/convertFunctionString";
import {debugLog} from "../tools/debugLogging";
import { SettingsComponentProps } from "../interfaces/settingsTypes";
import { ConnectionSettingsComponent } from "./components/ConnectionSettingsComponent";
import { GeneralSettingsComponent } from "./components/GeneralSettingsComponent";
import { FieldMappingsComponent } from "./components/FieldMappingsComponent";
import { RawIssueViewerComponent } from "./components/RawIssueViewerComponent";
import { TestFieldMappingsComponent } from "./components/TestFieldMappingsComponent";
import { debugLog } from "../tools/debugLogging";
import { CollapsibleSection } from "./CollapsibleSection";
/**
* Settings tab for the plugin
* Settings tab for the Jira plugin
* Orchestrates all components and manages the overall settings UI
*/
export class JiraSettingTab extends PluginSettingTab {
private plugin: JiraPlugin;
private connectionSettings: ConnectionSettingsComponent;
private generalSettings: GeneralSettingsComponent;
private fieldMappingsSettings: FieldMappingsComponent;
private rawIssueViewer: RawIssueViewerComponent;
private testFieldMappings: TestFieldMappingsComponent;
constructor(app: App, plugin: JiraPlugin) {
super(app, plugin);
this.plugin = plugin;
// Create shared props for all components
const componentProps: SettingsComponentProps = {
app,
plugin,
onSettingsChange: this.handleSettingsChange.bind(this)
};
// Initialize all settings components
this.connectionSettings = new ConnectionSettingsComponent(componentProps);
this.generalSettings = new GeneralSettingsComponent(componentProps);
this.fieldMappingsSettings = new FieldMappingsComponent(componentProps);
this.rawIssueViewer = new RawIssueViewerComponent(componentProps);
this.testFieldMappings = new TestFieldMappingsComponent({ ...componentProps, getCurrentIssue: () => this.rawIssueViewer.getCurrentIssue() });
this.rawIssueViewer.onIssueDataChange = () => this.testFieldMappings.setCurrentIssue(this.rawIssueViewer.getCurrentIssue());
}
// Function to save all field mappings
async saveStringFieldMappings (element: HTMLDivElement) {
debugLog(`From strings ${JSON.stringify(this.plugin.settings.fieldMappingsStrings)}\nand funcs ${JSON.stringify(this.plugin.settings.fieldMappings)}`)
const mappings: Record<string, { toJira: string; fromJira: string }> = {};
const fieldItems = element.querySelectorAll(".field-mapping-item");
fieldItems.forEach(item => {
const fieldNameInput = item.querySelector(".field-name-input");
const toJiraInput = item.querySelector(".to-jira-input");
const fromJiraInput = item.querySelector(".from-jira-input");
if (!fieldNameInput || !toJiraInput || !fromJiraInput) {
return;
}
const fieldName = (fieldNameInput as any).value.trim();
const toJira = (toJiraInput as any).value.trim();
const fromJira = (fromJiraInput as any).value.trim();
// Only save valid mappings with all fields filled
if (fieldName) {
mappings[fieldName] = {
toJira: toJira,
fromJira: fromJira
};
}
});
debugLog(`Mappings: ${JSON.stringify(mappings)}`)
this.plugin.settings.fieldMappingsStrings = mappings;
this.plugin.settings.fieldMappings = await transform_string_to_functions_mappings(mappings,
this.plugin.settings.enableFieldValidation);
// Save the functional mappings
debugLog(`To strings ${JSON.stringify(this.plugin.settings.fieldMappingsStrings)}\nand funcs ${JSON.stringify(this.plugin.settings.fieldMappings)}`)
/**
* Handle settings changes from any component
* This can be used for cross-component updates or validation
*/
private async handleSettingsChange(): Promise<void> {
debugLog("Settings changed, handling updates");
await this.plugin.saveSettings();
};
}
/**
* Display the settings UI
*/
display(): void {
const { containerEl } = this;
containerEl.empty();
new Setting(containerEl)
.setName("Jira URL")
.setDesc("Your Jira instance URL (e.g., https://yourcompany.atlassian.net)")
.addText((text) =>
text
.setPlaceholder("URL")
.setValue(this.plugin.settings.jiraUrl)
.onChange(async (value) => {
this.plugin.settings.jiraUrl = value;
await this.plugin.saveSettings();
})
);
new CollapsibleSection(this.plugin, containerEl, this.connectionSettings, "Connection settings",
this.plugin.settings.collapsedSections.connection, "connection").getContentContainer();
new Setting(containerEl)
.setName("Jira PAT")
.setDesc("Personal Access Token (preferred over username+password authentication)")
.addText((text) => {
text.inputEl.type = "password";
text
.setPlaceholder("NjM5MDI5MzQ0NT...")
.setValue(this.plugin.settings.apiToken)
.onChange(async (value) => {
this.plugin.settings.apiToken = value;
await this.plugin.saveSettings();
})
});
new CollapsibleSection(this.plugin, containerEl, this.generalSettings, "General settings",
this.plugin.settings.collapsedSections.general, "general").getContentContainer();
new Setting(containerEl)
.setName("Jira username")
.setDesc("Your Jira username or email")
.addText((text) =>
text
.setPlaceholder("admin")
.setValue(this.plugin.settings.username)
.onChange(async (value) => {
this.plugin.settings.username = value;
await this.plugin.saveSettings();
})
);
new CollapsibleSection(this.plugin, containerEl, this.fieldMappingsSettings, "Field mappings",
this.plugin.settings.collapsedSections.fieldMappings, "fieldMappings").getContentContainer();
new Setting(containerEl)
.setName("Jira password")
.setDesc("Your Jira password or API token")
.addText((text) => {
text.inputEl.type = "password";
text
.setPlaceholder("qwerty")
.setValue(this.plugin.settings.password)
.onChange(async (value) => {
this.plugin.settings.password = value;
await this.plugin.saveSettings();
});
});
new CollapsibleSection(this.plugin, containerEl, this.rawIssueViewer, "Raw issue viewer",
this.plugin.settings.collapsedSections.rawIssueViewer, "rawIssueViewer").getContentContainer();
new Setting(containerEl)
.setName("Session cookie name")
.setDesc("Only needed for username+password authentication")
.addText((text) =>
text
.setPlaceholder("JSESSIONID")
.setValue(this.plugin.settings.sessionCookieName)
.onChange(async (value) => {
this.plugin.settings.sessionCookieName = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName("Issues folder")
.setDesc("Folder where Jira issues will be stored")
.addText((text) =>
text
.setPlaceholder("jira-issues")
.setValue(this.plugin.settings.issuesFolder)
.onChange(async (value) => {
this.plugin.settings.issuesFolder = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl)
.setName("Template path")
.setDesc("The path of used template for creating Jira tasks (with or without .md extension)")
.addText((text) =>
text
.setPlaceholder("templates/template1")
.setValue(this.plugin.settings.templatePath)
.onChange(async (value) => {
value = normalizePath(value);
this.plugin.settings.templatePath = value;
await this.plugin.saveSettings();
})
);
// Add Jira-Obsidian Mapping setting
new Setting(containerEl).setName("Jira-Obsidian field mapping").setHeading();
// Create Field Mappings UI
const mappingSection = containerEl.createDiv({ cls: "jira-field-mappings" });
// Add field mapping explanation
mappingSection.createEl("p", {
text: "Configure how fields are mapped between Obsidian and Jira. Each field requires both a toJira and fromJira transformation function."
});
new Setting(mappingSection)
.setName('Enable Field validation')
.setDesc('Check fields before saving as functions')
.addToggle(toggle => toggle
.setValue(this.plugin.settings.enableFieldValidation)
.onChange(async (value) => {
this.plugin.settings.enableFieldValidation = value;
await this.plugin.saveSettings();
const fieldItems = fieldsList.querySelectorAll(".field-mapping-item");
fieldItems.forEach(async item => {
const fieldNameInput = item.querySelector(".field-name-input");
const toJiraInput = item.querySelector(".to-jira-input");
const fromJiraInput = item.querySelector(".from-jira-input");
if (fieldNameInput) await fieldValidation(fieldNameInput as HTMLInputElement, value, 'string');
if (fieldNameInput) await fieldValidation(toJiraInput as HTMLTextAreaElement, value, 'function', ['value']);
if (fieldNameInput) await fieldValidation(fromJiraInput as HTMLTextAreaElement, value, 'function', ['issue', 'data_source']);
});
}));
// Add change listeners to update settings
const pointInvalidField = (validation: { isValid: boolean; errorMessage?: string }, element: HTMLInputElement | HTMLTextAreaElement, console_output: boolean) => {
if (!validation.isValid) {
element.classList.add("invalid");
if (console_output) {
console.warn(`${validation.errorMessage}`);
new Notice(`${validation.errorMessage}`);
}
} else {
element.classList.remove("invalid");
}
}
async function fieldValidation(input: HTMLInputElement | HTMLTextAreaElement, requireValidating: boolean = true, type: string = 'string', validateParams: Array<string> = []) {
const validatorFunction = async (event?: Event)=> {
const console_output = !!event;
let validation;
switch(type) {
case 'string':
validation = input.value.length === 0? { isValid: false, errorMessage: "Field name cannot be empty" } : { isValid: true };
break;
case 'function':
validation = await validateFunctionString(input.value, validateParams);
break;
default:
validation = { isValid: true };
break;
}
pointInvalidField(validation, input, console_output);
}
if (!input.hasOwnProperty('_validatorFunction')) {
Object.defineProperty(input, '_validatorFunction', {
value: validatorFunction,
writable: true,
configurable: true
});
} else {
input.removeEventListener("change", (input as any)._validatorFunction);
(input as any)._validatorFunction = validatorFunction;
}
if (requireValidating) {
input.addEventListener("change", (input as any)._validatorFunction);
await validatorFunction(); // Run initial validation
} else {
input.removeEventListener("change", (input as any)._validatorFunction);
pointInvalidField({ isValid: true}, input, false); // Clear any validation errors
}
}
// Create a list container for field mappings
const fieldsList = mappingSection.createDiv({ cls: "field-mappings-list" });
// Function to add a new field mapping UI
const addFieldMapping = async (fieldName = "", toJira = "", fromJira = "") => {
const fieldContainer = fieldsList.createDiv({ cls: "field-mapping-item" });
// Field name input
const fieldNameContainer = fieldContainer.createDiv({ cls: "field-name-container" });
fieldNameContainer.createEl("span", { text: "Field Name:", cls: "field-mapping-label" });
const fieldNameInput = fieldNameContainer.createEl("input", {
type: "text",
value: fieldName,
cls: "field-name-input"
});
// toJira function input
const toJiraContainer = fieldContainer.createDiv({ cls: "to-jira-container" });
toJiraContainer.createEl("span", { text: "to Jira:", cls: "field-mapping-label" });
const toJiraInput = toJiraContainer.createEl("textarea", {
cls: "to-jira-input"
});
toJiraInput.placeholder = "(value) => null";
toJiraInput.value = toJira;
toJiraInput.rows = 1;
// fromJira function input
const fromJiraContainer = fieldContainer.createDiv({ cls: "from-jira-container" });
fromJiraContainer.createEl("span", { text: "from Jira:", cls: "field-mapping-label" });
const fromJiraInput = fromJiraContainer.createEl("textarea", {
cls: "from-jira-input"
});
fromJiraInput.value = fromJira;
fromJiraInput.placeholder = "(issue, data_source) => null";
fromJiraInput.rows = 1;
// Add remove button
const removeBtn = fieldContainer.createEl("button", {
text: "✕",
cls: "remove-field-btn"
});
// Handle field removal
removeBtn.addEventListener("click", () => {
fieldContainer.remove();
// saveFieldMappings();
});
await fieldValidation(fieldNameInput, this.plugin.settings.enableFieldValidation, 'string');
await fieldValidation(toJiraInput, this.plugin.settings.enableFieldValidation, 'function', ['value']);
await fieldValidation(fromJiraInput, this.plugin.settings.enableFieldValidation, 'function', ['issue', 'data_source']);
return { fieldNameInput, toJiraInput, fromJiraInput, container: fieldContainer };
};
const buttonView = mappingSection.createDiv({ cls: "button-view" });
// Add button to add new field mapping
const addFieldBtn = buttonView.createEl("button", {
text: "",
cls: "add-field-mapping-btn"
});
setIcon(addFieldBtn, "circle-plus")
addFieldBtn.addEventListener("click", async () => {
await addFieldMapping();
});
const resetBtn = buttonView.createEl("button", {
text: "",
cls: "reset-field-mappings-btn"
})
setIcon(resetBtn, "refresh-cw")
resetBtn.addEventListener("click", async () => {
this.plugin.settings.fieldMappings = {};
this.plugin.settings.fieldMappingsStrings = {};
await loadExistingMappings();
await this.plugin.saveSettings();
});
const reloadBtn = buttonView.createEl("button", {
text: "",
cls: "reload-field-mappings-btn"
})
setIcon(reloadBtn, "list-restart")
reloadBtn.addEventListener("click", async () => {
this.plugin.settings.fieldMappings = obsidianJiraFieldMappings;
// Also reset the string representations with default values
const defaultMappingsStrings: Record<string, { toJira: string; fromJira: string }> = {};
for (const [fieldName, mapping] of Object.entries(obsidianJiraFieldMappings)) {
defaultMappingsStrings[fieldName] = {
toJira: jiraFunctionToString(mapping.toJira, false),
fromJira: jiraFunctionToString(mapping.fromJira, true)
};
}
this.plugin.settings.fieldMappingsStrings = defaultMappingsStrings;
await loadExistingMappings();
await this.plugin.saveSettings();
});
// Load existing mappings if available
const loadExistingMappings = async () => {
debugLog(`Loading mapping settings`)
// Clear existing field list
fieldsList.empty();
// If we have string representations stored, use those
if (this.plugin.settings.fieldMappingsStrings &&
Object.keys(this.plugin.settings.fieldMappingsStrings).length > 0) {
debugLog('Current mapping (string) is: ',this.plugin.settings.fieldMappingsStrings)
const savedMappings = this.plugin.settings.fieldMappingsStrings;
for (const [fieldName, mapping] of Object.entries(savedMappings)) {
if (mapping && typeof mapping === 'object' && 'toJira' in mapping && 'fromJira' in mapping) {
await addFieldMapping(
fieldName,
mapping.toJira,
mapping.fromJira
);
}
}
this.plugin.settings.fieldMappings = await transform_string_to_functions_mappings(
this.plugin.settings.fieldMappingsStrings, this.plugin.settings.enableFieldValidation);
}
// Otherwise, try to use the function mappings and convert them to strings
else if (this.plugin.settings.fieldMappings &&
Object.keys(this.plugin.settings.fieldMappings).length > 0) {
debugLog('Current mapping is: ',this.plugin.settings.fieldMappings)
const existingMappings = this.plugin.settings.fieldMappings;
for (const [fieldName, mapping] of Object.entries(existingMappings)) {
if (mapping && typeof mapping === 'object' && 'toJira' in mapping && 'fromJira' in mapping) {
await addFieldMapping(
fieldName,
jiraFunctionToString(mapping.toJira, false),
jiraFunctionToString(mapping.fromJira, true)
);
}
}
// Create the string representations for future use
await this.saveStringFieldMappings(fieldsList);
}
};
// Make sure to call this function after it's defined
loadExistingMappings();
// Add example mappings section
const examplesSection = mappingSection.createDiv({ cls: "mapping-examples" });
examplesSection.createEl("h4", { text: "Example field mappings" });
const examplesList = examplesSection.createEl("ul");
[
{
name: "summary",
toJira: "value",
fromJira: "issue.fields.summary"
},
{
name: "reporter",
toJira: "null",
fromJira: "issue.fields.reporter.name"
},
{
name: "project",
toJira: "({ key: value })",
fromJira: "issue.fields.project ? issue.fields.project.key : \"\""
}
].forEach(example => {
const item = examplesList.createEl("li");
item.createEl("strong", { text: example.name });
// Add button to use this example
const useBtn = item.createEl("button", {
text: "Use",
cls: "use-example-btn"
});
item.createEl("br");
item.createSpan({ text: `toJira: ${example.toJira}` });
item.createEl("br");
item.createSpan({ text: `fromJira: ${example.fromJira}` });
useBtn.addEventListener("click", async () => {
await addFieldMapping(example.name, example.toJira, example.fromJira);
});
});
// Add security notice
const securityNote = containerEl.createEl("div", { cls: "setting-item-description" });
securityNote.createEl("strong", { text: "⚠️ Security Note: " });
securityNote.createSpan({ text: "Field mapping uses JavaScript function strings. Validate any shared mappings to prevent security issues." });
new CollapsibleSection(this.plugin, containerEl, this.testFieldMappings, "Test field mappings",
this.plugin.settings.collapsedSections.testFieldMappings, "testFieldMappings").getContentContainer();
}
hide() {
const fieldsList = this.containerEl.querySelector(".field-mappings-list");
if (fieldsList) {
this.saveStringFieldMappings(fieldsList as HTMLDivElement);
/**
* Called when the settings tab is hidden
*/
hide(): void {
// Call hide on components that need cleanup
if (this.fieldMappingsSettings.hide) {
this.fieldMappingsSettings.hide();
}
if (this.rawIssueViewer.hide) {
this.rawIssueViewer.hide();
}
if (this.testFieldMappings.hide) {
this.testFieldMappings.hide();
}
super.hide();
}
}

View file

@ -0,0 +1,187 @@
import { Setting } from "obsidian";
import { SettingsComponent, SettingsComponentProps } from "../../interfaces/settingsTypes";
/**
* Component for Jira connection settings
*/
export class ConnectionSettingsComponent implements SettingsComponent {
private props: SettingsComponentProps;
private currentAuthMethod: "bearer" | "basic" | "session";
private settingElements: Map<string, HTMLElement> = new Map();
constructor(props: SettingsComponentProps) {
this.props = props;
this.currentAuthMethod = props.plugin.settings.authMethod || "bearer";
}
render(containerEl: HTMLElement): void {
const { plugin } = this.props;
// Always show: Jira URL
new Setting(containerEl)
.setName("Jira URL")
.setDesc("Your Jira instance URL")
.addText((text) =>
text
.setPlaceholder("https://yourcompany.atlassian.net")
.setValue(plugin.settings.jiraUrl)
.onChange(async (value) => {
plugin.settings.jiraUrl = value;
await plugin.saveSettings();
})
);
// Auth Method Select
const authMethodSetting = new Setting(containerEl)
.setName("Authentication method")
.setDesc("Choose how to authenticate with Jira")
.addDropdown((cb) =>
cb
.addOption("bearer", "Bearer Token (PAT)")
.addOption("basic", "Basic Auth (Username + PAT)")
.addOption("session", "Session Cookie (Username + Password)")
.setValue(this.currentAuthMethod)
.onChange(async (value: "bearer" | "basic" | "session") => {
plugin.settings.authMethod = value;
await plugin.saveSettings();
this.currentAuthMethod = value;
this.updateVisibility();
})
);
// // Store reference for dynamic visibility control
// this.settingElements.set("authMethod", authMethodSetting.settingEl);
// Jira PAT
const patSetting = new Setting(containerEl)
.setName("Jira PAT")
.setDesc("Personal Access Token")
.addText((text) => {
text.inputEl.type = "password";
text
.setPlaceholder("NjM5MDI5MzQ0NT...")
.setValue(plugin.settings.apiToken)
.onChange(async (value) => {
plugin.settings.apiToken = value;
await plugin.saveSettings();
});
});
this.settingElements.set("pat", patSetting.settingEl);
// Jira username
const usernameSetting = new Setting(containerEl)
.setName("Jira username")
.setDesc("Your Jira username")
.addText((text) =>
text
.setPlaceholder("admin")
.setValue(plugin.settings.username)
.onChange(async (value) => {
plugin.settings.username = value;
await plugin.saveSettings();
})
);
this.settingElements.set("username", usernameSetting.settingEl);
// Jira username
const emailSetting = new Setting(containerEl)
.setName("Jira email")
.setDesc("Your Jira email")
.addText((text) =>
text
.setPlaceholder("admin@gmail.com")
.setValue(plugin.settings.email)
.onChange(async (value) => {
plugin.settings.email = value;
await plugin.saveSettings();
})
);
this.settingElements.set("email", emailSetting.settingEl);
// Jira password
const passwordSetting = new Setting(containerEl)
.setName("Jira password")
.setDesc("Your Jira password")
.addText((text) => {
text.inputEl.type = "password";
text
.setPlaceholder("qwerty")
.setValue(plugin.settings.password)
.onChange(async (value) => {
plugin.settings.password = value;
await plugin.saveSettings();
});
});
this.settingElements.set("password", passwordSetting.settingEl);
// // Session cookie name
// const sessionCookieSetting = new Setting(containerEl)
// .setName("Session cookie name")
// .setDesc("Only needed for username+password authentication")
// .addText((text) =>
// text
// .setPlaceholder("JSESSIONID")
// .setValue(plugin.settings.sessionCookieName)
// .onChange(async (value) => {
// plugin.settings.sessionCookieName = value;
// await plugin.saveSettings();
// })
// );
// this.settingElements.set("sessionCookie", sessionCookieSetting.settingEl);
// Initial visibility
this.updateVisibility();
}
private updateVisibility(): void {
const method = this.currentAuthMethod;
// // Show always
// this.showElement("authMethod");
// Show PAT if bearer or basic
if (method === "bearer" || method === "basic") {
this.showElement("pat");
} else {
this.hideElement("pat");
}
// Show username if basic or session
if (method === "session") {
this.showElement("username");
} else {
this.hideElement("username");
}
// Show email if basic or session
if (method === "basic") {
this.showElement("email");
} else {
this.hideElement("email");
}
// Show password only if session
if (method === "session") {
this.showElement("password");
} else {
this.hideElement("password");
}
// // Show session cookie only if session
// if (method === "session") {
// this.showElement("sessionCookie");
// } else {
// this.hideElement("sessionCookie");
// }
}
private showElement(key: string): void {
const el = this.settingElements.get(key);
if (el) el.style.display = "";
}
private hideElement(key: string): void {
const el = this.settingElements.get(key);
if (el) el.style.display = "none";
}
}

View file

@ -0,0 +1,110 @@
import { validateField } from "../tools/fieldValidation";
/**
* Interface for field mapping item props
*/
export interface FieldMappingItemProps {
container: HTMLElement;
fieldName?: string;
toJira?: string;
fromJira?: string;
enableValidation: boolean;
onRemove: () => void;
}
/**
* Class representing a single field mapping row
*/
export class FieldMappingItem {
private props: FieldMappingItemProps;
private fieldContainer: HTMLDivElement;
private fieldNameInput: HTMLInputElement;
private toJiraInput: HTMLTextAreaElement;
private fromJiraInput: HTMLTextAreaElement;
constructor(props: FieldMappingItemProps) {
this.props = props;
this.render();
}
/**
* Get the current field values
*/
getValues() {
return {
fieldName: this.fieldNameInput.value.trim(),
toJira: this.toJiraInput.value.trim(),
fromJira: this.fromJiraInput.value.trim()
};
}
/**
* Render the field mapping item
*/
private render() {
const { container, fieldName = "", toJira = "", fromJira = "", enableValidation } = this.props;
// Create the field container
this.fieldContainer = container.createDiv({ cls: "field-mapping-item" });
// Create field name input
const fieldNameContainer = this.fieldContainer.createDiv({ cls: "field-name-container" });
fieldNameContainer.createEl("span", { text: "Field Name:", cls: "field-mapping-label" });
this.fieldNameInput = fieldNameContainer.createEl("input", {
type: "text",
value: fieldName,
cls: "field-name-input"
});
// Create toJira function input
const toJiraContainer = this.fieldContainer.createDiv({ cls: "to-jira-container" });
toJiraContainer.createEl("span", { text: "to Jira:", cls: "field-mapping-label" });
this.toJiraInput = toJiraContainer.createEl("textarea", {
cls: "to-jira-input"
});
this.toJiraInput.placeholder = "(value) => null";
this.toJiraInput.value = toJira;
this.toJiraInput.rows = 1;
// Create fromJira function input
const fromJiraContainer = this.fieldContainer.createDiv({ cls: "from-jira-container" });
fromJiraContainer.createEl("span", { text: "from Jira:", cls: "field-mapping-label" });
this.fromJiraInput = fromJiraContainer.createEl("textarea", {
cls: "from-jira-input"
});
this.fromJiraInput.value = fromJira;
this.fromJiraInput.placeholder = "(issue, data_source) => null";
this.fromJiraInput.rows = 1;
// Add remove button
const removeBtn = this.fieldContainer.createEl("button", {
text: "✕",
cls: "remove-field-btn"
});
// Handle field removal
removeBtn.addEventListener("click", () => {
this.fieldContainer.remove();
this.props.onRemove();
});
// Setup validation
this.setupValidation(enableValidation);
}
/**
* Set up validation for inputs
*/
async setupValidation(enableValidation: boolean) {
await validateField(this.fieldNameInput, enableValidation, 'string');
await validateField(this.toJiraInput, enableValidation, 'function', ['value']);
await validateField(this.fromJiraInput, enableValidation, 'function', ['issue', 'data_source']);
}
/**
* Update validation state
*/
async updateValidation(enableValidation: boolean) {
await this.setupValidation(enableValidation);
}
}

View file

@ -0,0 +1,275 @@
import { Setting, setIcon } from "obsidian";
import { SettingsComponent, SettingsComponentProps } from "../../interfaces/settingsTypes";
import { FieldMappingItem } from "./FieldMappingItem";
import { collectFieldMappingsFromUI, processMappings } from "../tools/mappingTransformers";
import {jiraFunctionToString, transform_string_to_functions_mappings} from "../../tools/convertFunctionString";
import { obsidianJiraFieldMappings } from "../../constants/obsidianJiraFieldsMapping";
import { debugLog } from "../../tools/debugLogging";
/**
* Component for field mappings section
*/
export class FieldMappingsComponent implements SettingsComponent {
private props: SettingsComponentProps;
private fieldsList: HTMLDivElement;
private mappingItems: FieldMappingItem[] = [];
constructor(props: SettingsComponentProps) {
this.props = props;
}
/**
* Render the field mappings component
*/
render(containerEl: HTMLElement): void {
const { plugin } = this.props;
// Create the mapping section
const mappingSection = containerEl.createDiv({ cls: "jira-field-mappings" });
// Add explanation
mappingSection.createEl("p", {
text: "Configure how fields are mapped between Obsidian and Jira. Each field requires both a toJira and fromJira transformation function."
});
// Add validation toggle
new Setting(mappingSection)
.setName('Enable field validation')
.setDesc('Check fields before saving as functions')
.addToggle(toggle => toggle
.setValue(plugin.settings.enableFieldValidation)
.onChange(async (value) => {
plugin.settings.enableFieldValidation = value;
await plugin.saveSettings();
// Update validation for all mapping items
this.mappingItems.forEach(item => {
item.updateValidation(value);
});
}));
// Create list container for field mappings
this.fieldsList = mappingSection.createDiv({ cls: "field-mappings-list" });
// Create button container
const buttonView = mappingSection.createDiv({ cls: "button-view" });
// Add button to add new field mapping
const addFieldBtn = buttonView.createEl("button", {
text: "",
cls: "add-field-mapping-btn"
});
setIcon(addFieldBtn, "circle-plus");
addFieldBtn.addEventListener("click", () => {
this.addFieldMapping();
});
// Reset button
const resetBtn = buttonView.createEl("button", {
text: "",
cls: "reset-field-mappings-btn"
});
setIcon(resetBtn, "refresh-cw");
resetBtn.addEventListener("click", async () => {
plugin.settings.fieldMappings = {};
plugin.settings.fieldMappingsStrings = {};
await this.loadExistingMappings();
await plugin.saveSettings();
});
// Reload default mappings button
const reloadBtn = buttonView.createEl("button", {
text: "",
cls: "reload-field-mappings-btn"
});
setIcon(reloadBtn, "list-restart");
reloadBtn.addEventListener("click", async () => {
plugin.settings.fieldMappings = obsidianJiraFieldMappings;
// Also reset the string representations with default values
const defaultMappingsStrings: Record<string, { toJira: string; fromJira: string }> = {};
for (const [fieldName, mapping] of Object.entries(obsidianJiraFieldMappings)) {
defaultMappingsStrings[fieldName] = {
toJira: jiraFunctionToString(mapping.toJira, false),
fromJira: jiraFunctionToString(mapping.fromJira, true)
};
}
plugin.settings.fieldMappingsStrings = defaultMappingsStrings;
await this.loadExistingMappings();
await plugin.saveSettings();
});
// Load existing mappings
this.loadExistingMappings();
// Add examples section
this.renderExamples(mappingSection);
// Add security notice
const securityNote = containerEl.createEl("div", { cls: "setting-item-description" });
securityNote.createEl("strong", { text: "⚠️ Security note: " });
securityNote.createSpan({
text: "Field mapping uses JavaScript function strings. Validate any shared mappings to prevent security issues."
});
}
/**
* Add a new field mapping item
*/
addFieldMapping(fieldName = "", toJira = "", fromJira = ""): FieldMappingItem {
const item = new FieldMappingItem({
container: this.fieldsList,
fieldName,
toJira,
fromJira,
enableValidation: this.props.plugin.settings.enableFieldValidation,
onRemove: () => {
// Remove from array when item is removed
const index = this.mappingItems.indexOf(item);
if (index !== -1) {
this.mappingItems.splice(index, 1);
}
}
});
this.mappingItems.push(item);
return item;
}
/**
* Load existing mappings
*/
async loadExistingMappings(): Promise<void> {
const { plugin } = this.props;
// Clear existing field list and items array
this.fieldsList.empty();
this.mappingItems = [];
debugLog(`Loading mapping settings`);
// If we have string representations stored, use those
if (plugin.settings.fieldMappingsStrings &&
Object.keys(plugin.settings.fieldMappingsStrings).length > 0) {
debugLog('Current mapping (string) is: ', plugin.settings.fieldMappingsStrings);
const savedMappings = plugin.settings.fieldMappingsStrings;
for (const [fieldName, mapping] of Object.entries(savedMappings)) {
if (mapping && typeof mapping === 'object' && 'toJira' in mapping && 'fromJira' in mapping) {
this.addFieldMapping(
fieldName,
mapping.toJira,
mapping.fromJira
);
}
}
plugin.settings.fieldMappings = await transform_string_to_functions_mappings(
plugin.settings.fieldMappingsStrings,
plugin.settings.enableFieldValidation
);
}
// Otherwise, try to use the function mappings and convert them to strings
else if (plugin.settings.fieldMappings &&
Object.keys(plugin.settings.fieldMappings).length > 0) {
debugLog('Current mapping is: ', plugin.settings.fieldMappings);
const existingMappings = plugin.settings.fieldMappings;
for (const [fieldName, mapping] of Object.entries(existingMappings)) {
if (mapping && typeof mapping === 'object' && 'toJira' in mapping && 'fromJira' in mapping) {
this.addFieldMapping(
fieldName,
jiraFunctionToString(mapping.toJira, false),
jiraFunctionToString(mapping.fromJira, true)
);
}
}
// Create the string representations for future use
await this.saveFieldMappings();
}
}
/**
* Save current field mappings
*/
async saveFieldMappings(): Promise<void> {
const { plugin } = this.props;
debugLog(`From strings ${JSON.stringify(plugin.settings.fieldMappingsStrings)}\nand funcs ${JSON.stringify(plugin.settings.fieldMappings)}`);
// Collect mappings from UI
const stringMappings = await collectFieldMappingsFromUI(this.fieldsList);
// Process mappings
const { stringMappings: newStringMappings, functionMappings } = await processMappings(
stringMappings,
plugin.settings.enableFieldValidation
);
// Update settings
plugin.settings.fieldMappingsStrings = newStringMappings;
plugin.settings.fieldMappings = functionMappings;
debugLog(`To strings ${JSON.stringify(plugin.settings.fieldMappingsStrings)}\nand funcs ${JSON.stringify(plugin.settings.fieldMappings)}`);
await plugin.saveSettings();
}
/**
* Render example mappings
*/
private renderExamples(containerEl: HTMLElement): void {
const examplesSection = containerEl.createDiv({ cls: "mapping-examples" });
examplesSection.createEl("h4", { text: "Example field mappings" });
const examplesList = examplesSection.createEl("ul");
const examples = [
{
name: "summary",
toJira: "value",
fromJira: "issue.fields.summary"
},
{
name: "reporter",
toJira: "null",
fromJira: "issue.fields.reporter.name"
},
{
name: "project",
toJira: "({ key: value })",
fromJira: "issue.fields.project ? issue.fields.project.key : \"\""
}
];
examples.forEach(example => {
const item = examplesList.createEl("li");
item.createEl("strong", { text: example.name });
// Add button to use this example
const useBtn = item.createEl("button", {
text: "Use",
cls: "use-example-btn"
});
item.createEl("br");
item.createSpan({ text: `toJira: ${example.toJira}` });
item.createEl("br");
item.createSpan({ text: `fromJira: ${example.fromJira}` });
useBtn.addEventListener("click", () => {
this.addFieldMapping(example.name, example.toJira, example.fromJira);
});
});
}
/**
* Called when the component is hidden
*/
hide(): void {
this.saveFieldMappings();
}
}

View file

@ -0,0 +1,46 @@
import { normalizePath, Setting } from "obsidian";
import { SettingsComponent, SettingsComponentProps } from "../../interfaces/settingsTypes";
/**
* Component for general plugin settings
*/
export class GeneralSettingsComponent implements SettingsComponent {
private props: SettingsComponentProps;
constructor(props: SettingsComponentProps) {
this.props = props;
}
render(containerEl: HTMLElement): void {
const { plugin } = this.props;
// Issues folder setting
new Setting(containerEl)
.setName("Issues folder")
.setDesc("Folder where Jira issues will be stored")
.addText((text) =>
text
.setPlaceholder("jira-issues")
.setValue(plugin.settings.issuesFolder)
.onChange(async (value) => {
plugin.settings.issuesFolder = value;
await plugin.saveSettings();
})
);
// Template path setting
new Setting(containerEl)
.setName("Template path")
.setDesc("The path of used template for creating Jira tasks (with or without .md extension)")
.addText((text) =>
text
.setPlaceholder("templates/template1")
.setValue(plugin.settings.templatePath)
.onChange(async (value) => {
value = normalizePath(value);
plugin.settings.templatePath = value;
await plugin.saveSettings();
})
);
}
}

View file

@ -0,0 +1,95 @@
import { Setting, Notice } from "obsidian";
import { SettingsComponent, SettingsComponentProps } from "../../interfaces/settingsTypes";
import { fetchIssue } from "../../api";
import debounce from "lodash/debounce";
import hljs from "highlight.js";
/**
* Component for viewing raw Jira issue data
*/
export class RawIssueViewerComponent implements SettingsComponent {
private props: SettingsComponentProps;
private issueDataContainer: HTMLElement | null = null;
private debouncedFetch: ReturnType<typeof debounce>;
private currentIssueData: any = null;
public onIssueDataChange?: () => void;
constructor(props: SettingsComponentProps) {
this.props = props;
this.debouncedFetch = debounce(this.fetchIssueData.bind(this), 500);
}
render(containerEl: HTMLElement): void {
// Add input field for issue key
new Setting(containerEl)
.setName("View raw issue data")
.setDesc("Enter a Jira issue key to view its raw data from the API")
.addText((text) =>
text
.setPlaceholder("PROJ-123")
.onChange((value) => {
if (value) {
this.debouncedFetch(value);
} else {
this.clearIssueData();
}
})
);
// Create container for issue data
this.issueDataContainer = containerEl.createDiv({
cls: "jira-raw-issue-container"
});
}
public getCurrentIssue(): any {
return this.currentIssueData;
}
private async fetchIssueData(value: string): Promise<void> {
try {
const issueData = await fetchIssue(this.props.plugin, value);
this.currentIssueData = issueData;
this.displayIssueData(issueData);
} catch (error) {
new Notice(`Failed to fetch issue: ${error.message}`);
this.clearIssueData();
}
if (this.onIssueDataChange) {
this.onIssueDataChange();
}
}
private displayIssueData(issueData: any): void {
if (!this.issueDataContainer) return;
this.issueDataContainer.empty();
const pre = this.issueDataContainer.createEl("pre", {
cls: "jira-raw-issue-data",
attr: {
style: "user-select: text; -webkit-user-select: text; -moz-user-select: text; -ms-user-select: text;"
}
});
const code = pre.createEl("code", {
cls: "language-json"
});
const jsonString = JSON.stringify(issueData, null, 2);
code.setText(jsonString);
hljs.highlightElement(code);
}
private clearIssueData(): void {
if (this.issueDataContainer) {
this.issueDataContainer.empty();
}
this.currentIssueData = null;
}
hide(): void {
this.clearIssueData();
this.debouncedFetch.cancel();
}
}

View file

@ -0,0 +1,126 @@
import { SettingsComponent, SettingsComponentProps } from "../../interfaces/settingsTypes";
import debounce from "lodash/debounce";
import { safeStringToFunction } from "../../tools/convertFunctionString";
export class TestFieldMappingsComponent implements SettingsComponent {
private props: SettingsComponentProps;
private testMappingsContainer: HTMLElement | null = null;
private currentIssueData: any = null;
private getCurrentIssue: (() => any) | null = null;
constructor(props: SettingsComponentProps & { getCurrentIssue?: () => any }) {
this.props = props;
if (props.getCurrentIssue) {
this.getCurrentIssue = props.getCurrentIssue;
}
}
render(containerEl: HTMLElement): void {
// Add section header
this.testMappingsContainer = containerEl.createDiv({
cls: "jira-test-mappings-container"
});
this.testMappingsContainer.createEl("h3", {
text: "Test field mappings",
cls: "test-mappings-header"
});
this.testMappingsContainer.createEl("p", {
text: "Test your field mappings against the current issue data from Raw issue viewer. Enter a fromJira expression to see the result.",
cls: "test-mappings-desc"
});
// Container for all test mapping items
const itemsContainer = this.testMappingsContainer.createDiv({ cls: "test-mapping-items-list" });
(this as any)._testMappingItemsContainer = itemsContainer; // store for later use
// Add first test mapping item
this.addTestMappingItem();
// Add new mapping button (only once, at the bottom)
const addBtn = this.testMappingsContainer.createEl("button", {
text: "+ Add test mapping",
cls: "add-test-mapping-btn"
});
addBtn.addEventListener("click", () => {
this.addTestMappingItem();
});
}
private addTestMappingItem(): void {
// Use the dedicated items container
const itemsContainer = (this as any)._testMappingItemsContainer as HTMLElement;
if (!itemsContainer) return;
const itemContainer = itemsContainer.createDiv({
cls: "test-mapping-item"
});
// From Jira expression input
const fromJiraContainer = itemContainer.createDiv({ cls: "from-jira-container" });
fromJiraContainer.createEl("span", { text: "from Jira:", cls: "field-mapping-label" });
const fromJiraInput = fromJiraContainer.createEl("textarea", {
cls: "from-jira-input"
});
fromJiraInput.rows = 1;
fromJiraInput.placeholder = "(issue) => null";
// Value display
const valueContainer = itemContainer.createDiv({ cls: "value-container" });
valueContainer.createEl("span", { text: "Value:", cls: "field-mapping-label" });
const valueDisplay = valueContainer.createEl("div", {
cls: "value-display",
attr: {
style: "user-select: text; -webkit-user-select: text; -moz-user-select: text; -ms-user-select: text;"
}
});
// Debounced evaluation function
const debouncedEvaluate = debounce(async () => {
const issueData = this.getCurrentIssue ? this.getCurrentIssue() : this.currentIssueData;
if (!issueData) {
valueDisplay.setText("No issue data available");
return;
}
try {
const fromJiraFn = await safeStringToFunction(fromJiraInput.value, 'fromJira', false);
if (fromJiraFn) {
const result = fromJiraFn(issueData, null);
valueDisplay.setText(JSON.stringify(result, null, 2));
} else {
valueDisplay.setText("Invalid expression");
}
} catch (error) {
valueDisplay.setText(`Error: ${error.message}`);
}
}, 500);
// Add event listeners
fromJiraInput.addEventListener("input", debouncedEvaluate);
// Add remove button
const removeBtn = itemContainer.createEl("button", {
text: "✕",
cls: "remove-field-btn"
});
removeBtn.addEventListener("click", () => {
itemContainer.remove();
});
}
// Optionally, allow setting the current issue externally
setCurrentIssue(issue: any) {
this.currentIssueData = issue;
// Trigger re-evaluation of all test mappings
this.testMappingsContainer?.querySelectorAll(".from-jira-input").forEach((input: HTMLTextAreaElement) => {
input.dispatchEvent(new Event("input"));
});
}
hide(): void {
// No persistent state to clear
}
}

View file

@ -1,10 +1,15 @@
import {FieldMapping} from "../constants/obsidianJiraFieldsMapping";
export interface JiraSettings {
collapsedSections: Record<string, boolean>;
authMethod: "bearer" | "basic" | "session";
apiToken: string;
username: string;
email: string;
password: string;
jiraUrl: string;
issuesFolder: string;
sessionCookieName: string;
templatePath: string;
@ -14,10 +19,18 @@ export interface JiraSettings {
}
export const DEFAULT_SETTINGS: JiraSettings = {
collapsedSections: {
connection: true,
general: true,
fieldMappings: false
},
authMethod: "bearer",
apiToken: "",
username: "",
email: "",
password: "",
jiraUrl: "",
issuesFolder: "jira-issues",
sessionCookieName: "JSESSIONID",
templatePath: "",

View file

@ -0,0 +1,89 @@
import { Notice } from "obsidian";
import { ValidationResult } from "../../interfaces/settingsTypes";
import { validateFunctionString } from "../../tools/convertFunctionString";
import { debugLog } from "../../tools/debugLogging";
/**
* Validate a field and update its visual state
*/
export async function validateField(
input: HTMLInputElement | HTMLTextAreaElement,
requireValidating: boolean = true,
type: string = 'string',
validateParams: Array<string> = []
): Promise<void> {
const validatorFunction = async (event?: Event) => {
const consoleOutput = !!event;
const validation = await getValidationResult(input.value, type, validateParams);
updateFieldAppearance(validation, input, consoleOutput);
};
// Store the validator function on the element to allow for removal
setupValidatorProperty(input, validatorFunction);
if (requireValidating) {
input.addEventListener("change", (input as any)._validatorFunction);
await validatorFunction(); // Run initial validation
} else {
input.removeEventListener("change", (input as any)._validatorFunction);
updateFieldAppearance({ isValid: true }, input, false); // Clear any validation errors
}
}
/**
* Get validation result based on type and value
*/
async function getValidationResult(
value: string,
type: string = 'string',
validateParams: Array<string> = []
): Promise<ValidationResult> {
switch(type) {
case 'string':
return value.length === 0
? { isValid: false, errorMessage: "Field name cannot be empty" }
: { isValid: true };
case 'function':
return await validateFunctionString(value, validateParams);
default:
return { isValid: true };
}
}
/**
* Update the field appearance based on validation result
*/
export function updateFieldAppearance(
validation: ValidationResult,
element: HTMLInputElement | HTMLTextAreaElement,
consoleOutput: boolean
): void {
if (!validation.isValid) {
element.classList.add("invalid");
if (consoleOutput && validation.errorMessage) {
debugLog(validation.errorMessage);
new Notice(validation.errorMessage);
}
} else {
element.classList.remove("invalid");
}
}
/**
* Setup validator property on input element
*/
function setupValidatorProperty(
input: HTMLInputElement | HTMLTextAreaElement,
validatorFunction: (event?: Event) => Promise<void>
): void {
if (!input.hasOwnProperty('_validatorFunction')) {
Object.defineProperty(input, '_validatorFunction', {
value: validatorFunction,
writable: true,
configurable: true
});
} else {
input.removeEventListener("change", (input as any)._validatorFunction);
(input as any)._validatorFunction = validatorFunction;
}
}

View file

@ -0,0 +1,85 @@
import { JiraFieldMapping, JiraFieldMappingString } from "../../interfaces/settingsTypes";
import {
jiraFunctionToString,
transform_string_to_functions_mappings,
// validateFunctionString
} from "../../tools/convertFunctionString";
import { debugLog } from "../../tools/debugLogging";
/**
* Convert function mappings to their string representation
*/
export function convertFunctionMappingsToStrings(
mappings: Record<string, JiraFieldMapping>
): Record<string, JiraFieldMappingString> {
const result: Record<string, JiraFieldMappingString> = {};
for (const [fieldName, mapping] of Object.entries(mappings)) {
if (mapping && typeof mapping === 'object' && 'toJira' in mapping && 'fromJira' in mapping) {
result[fieldName] = {
toJira: jiraFunctionToString(mapping.toJira, false),
fromJira: jiraFunctionToString(mapping.fromJira, true)
};
}
}
return result;
}
/**
* Collect field mappings from the UI
*/
export async function collectFieldMappingsFromUI(
element: HTMLElement,
// enableValidation: boolean
): Promise<Record<string, JiraFieldMappingString>> {
const mappings: Record<string, JiraFieldMappingString> = {};
const fieldItems = element.querySelectorAll(".field-mapping-item");
fieldItems.forEach(item => {
const fieldNameInput = item.querySelector(".field-name-input");
const toJiraInput = item.querySelector(".to-jira-input");
const fromJiraInput = item.querySelector(".from-jira-input");
if (!fieldNameInput || !toJiraInput || !fromJiraInput) {
return;
}
const fieldName = (fieldNameInput as HTMLInputElement).value.trim();
const toJira = (toJiraInput as HTMLTextAreaElement).value.trim();
const fromJira = (fromJiraInput as HTMLTextAreaElement).value.trim();
// Only save valid mappings with field name filled
if (fieldName) {
mappings[fieldName] = {
toJira: toJira,
fromJira: fromJira
};
}
});
debugLog(`Collected mappings: ${JSON.stringify(mappings)}`);
return mappings;
}
/**
* Process string mappings to function mappings
*/
export async function processMappings(
stringMappings: Record<string, JiraFieldMappingString>,
enableValidation: boolean
): Promise<{
stringMappings: Record<string, JiraFieldMappingString>,
functionMappings: Record<string, JiraFieldMapping>
}> {
// Convert string mappings to function mappings
const functionMappings = await transform_string_to_functions_mappings(
stringMappings,
enableValidation
);
return {
stringMappings,
functionMappings
};
}

View file

@ -3,6 +3,7 @@ import { JiraIssue } from "../interfaces";
import { parse } from "acorn";
import {debugLog} from "./debugLogging";
import {FieldMapping} from "../constants/obsidianJiraFieldsMapping";
import {defaultIssue} from "../constants/defaultIssue";
// Constants for validation and error messages
const FORBIDDEN_PATTERNS = ["document", "window", "eval", "Function", "fetch", "setTimeout"]; // Prevent unsafe code execution
@ -23,7 +24,7 @@ export function validateFunctionStringBrowser(fnString: string, approved_vars: s
try {
if (isSimpleExpression(fnString) || (fnString.startsWith("{") && fnString.endsWith("}") && !fnString.includes("return"))) {
fnString = `(${approved_vars.map(varName => varName==='issue'?`${varName} = {fields: {}}`:`${varName} = {}`).join(', ')}) => ${fnString}`;
fnString = `(${approved_vars.map(varName => varName==='issue'?`${varName} = ${JSON.stringify(defaultIssue)}`:`${varName} = {}`).join(', ')}) => ${fnString}`;
}
debugLog(`checking function: ${fnString}`);
let func = (iframeWindow as any).eval(fnString);

View file

@ -1,3 +1,118 @@
.jira-collapsible-section {
padding: 8px;
border-left: 2px solid var(--background-modifier-border);
margin-bottom: 4px;
}
.jira-collapsible-header {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
font-weight: bold;
padding: 4px 0;
}
.jira-collapsible-content {
margin-top: 20px;
padding-left: 20px;
}
.jira-test-mappings-container {
margin-top: 2em;
padding: 1em 0;
}
.test-mappings-header {
margin-bottom: 0.2em;
}
.test-mappings-desc {
margin-bottom: 1em;
color: var(--text-muted);
}
.test-mapping-items-list {
display: flex;
flex-direction: column;
gap: 1.2em;
}
.test-mapping-item {
background: var(--background-secondary-alt);
border-radius: 8px;
padding: 1em 1em 1em 1.2em;
position: relative;
box-shadow: 0 1px 4px rgba(0,0,0,0.04);
}
.value-container {
margin-bottom: 0.5em;
}
.value-display {
background: var(--background-primary-alt);
border-radius: 4px;
padding: 0.5em 0.8em;
font-family: var(--font-monospace);
font-size: 0.98em;
margin-top: 0.2em;
white-space: pre-wrap;
word-break: break-all;
border: 1px solid var(--background-modifier-border);
}
.test-mapping-item .from-jira-container {
margin-bottom: 0.5em;
}
.test-mapping-item .from-jira-input {
width: 100%;
min-height: 2.2em;
font-family: var(--font-monospace);
font-size: 1em;
margin-top: 0.2em;
border-radius: 4px;
border: 1px solid var(--background-modifier-border);
background: var(--background-primary-alt);
color: var(--text-normal);
padding: 0.3em 0.5em;
resize: vertical;
}
.remove-field-btn {
position: absolute;
top: 0.5em;
right: 0.7em;
background: var(--background-modifier-error);
color: var(--text-on-accent);
border: none;
border-radius: 4px;
font-size: 1.1em;
padding: 0.1em 0.5em;
cursor: pointer;
transition: background 0.2s;
}
.remove-field-btn:hover {
background: var(--background-modifier-error-hover);
}
.add-test-mapping-btn {
display: block;
margin: 1.5em auto 0 auto;
padding: 0.5em 1.5em;
background: var(--interactive-accent);
color: var(--text-on-accent);
border: none;
border-radius: 6px;
font-size: 1em;
cursor: pointer;
transition: background 0.2s;
}
.add-test-mapping-btn:hover {
background: var(--interactive-accent-hover);
}
.jira-field-mappings {
margin-top: 10px;
}
@ -91,3 +206,128 @@
.button-view button {
width: fit-content;
}
/*hljs.css*/
pre code.hljs {
display: block;
overflow-x: auto;
padding: 1em
}
code.hljs {
padding: 3px 5px
}
/*!
Theme: GitHub
Description: Light theme as seen on github.com
Author: github.com
Maintainer: @Hirse
Updated: 2021-05-15
Outdated base version: https://github.com/primer/github-syntax-light
Current colors taken from GitHub's CSS
*/
.hljs {
color: #24292e;
background: #ffffff
}
.hljs-doctag,
.hljs-keyword,
.hljs-meta .hljs-keyword,
.hljs-template-tag,
.hljs-template-variable,
.hljs-type,
.hljs-variable.language_ {
/* prettylights-syntax-keyword */
color: #f6cc42
}
.hljs-title,
.hljs-title.class_,
.hljs-title.class_.inherited__,
.hljs-title.function_ {
/* prettylights-syntax-entity */
color: #6f42c1
}
.hljs-attr,
.hljs-attribute,
.hljs-literal,
.hljs-meta,
.hljs-operator,
.hljs-variable,
.hljs-selector-attr,
.hljs-selector-class,
.hljs-selector-id {
/* prettylights-syntax-constant */
color: #6ecbfa
}
.hljs-number {
/* prettylights-syntax-numeric-literal */
color: #93cda7
}
.hljs-regexp,
.hljs-string,
.hljs-meta .hljs-string {
/* prettylights-syntax-string */
color: #cd905c
}
.hljs-built_in,
.hljs-symbol {
/* prettylights-syntax-variable */
color: #e36209
}
.hljs-comment,
.hljs-code,
.hljs-formula {
/* prettylights-syntax-comment */
color: #6a737d
}
.hljs-name,
.hljs-quote,
.hljs-selector-tag,
.hljs-selector-pseudo {
/* prettylights-syntax-entity-tag */
color: #22863a
}
.hljs-subst {
/* prettylights-syntax-storage-modifier-import */
color: #24292e
}
.hljs-section {
/* prettylights-syntax-markup-heading */
color: #005cc5;
font-weight: bold
}
.hljs-bullet {
/* prettylights-syntax-markup-list */
color: #735c0f
}
.hljs-emphasis {
/* prettylights-syntax-markup-italic */
color: #24292e;
font-style: italic
}
.hljs-strong {
/* prettylights-syntax-markup-bold */
color: #24292e;
font-weight: bold
}
.hljs-addition {
/* prettylights-syntax-markup-inserted */
color: #22863a;
background-color: #f0fff4
}
.hljs-deletion {
/* prettylights-syntax-markup-deleted */
color: #b31d28;
background-color: #ffeef0
}
.hljs-char.escape_,
.hljs-link,
.hljs-params,
.hljs-property,
.hljs-punctuation,
.hljs-tag {
/* purposely ignored */
}

767
yarn.lock
View file

@ -2,131 +2,83 @@
# yarn lockfile v1
"@esbuild/aix-ppc64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz#499600c5e1757a524990d5d92601f0ac3ce87f64"
integrity sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==
"@codemirror/state@^6.0.0", "@codemirror/state@^6.5.0":
version "6.5.2"
resolved "https://registry.npmjs.org/@codemirror/state/-/state-6.5.2.tgz"
integrity sha512-FVqsPqtPWKVVL3dPSxy8wEF/ymIEuVzF1PK3VbUgrxXpJUSHQWWZz4JMToquRxnkw+36LTamCZG2iua2Ptq0fA==
dependencies:
"@marijn/find-cluster-break" "^1.0.0"
"@esbuild/android-arm64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz#b9b8231561a1dfb94eb31f4ee056b92a985c324f"
integrity sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==
"@esbuild/android-arm@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.25.0.tgz#ca6e7888942505f13e88ac9f5f7d2a72f9facd2b"
integrity sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==
"@esbuild/android-x64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.25.0.tgz#e765ea753bac442dfc9cb53652ce8bd39d33e163"
integrity sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==
"@esbuild/darwin-arm64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz#fa394164b0d89d4fdc3a8a21989af70ef579fa2c"
integrity sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==
"@esbuild/darwin-x64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz#91979d98d30ba6e7d69b22c617cc82bdad60e47a"
integrity sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==
"@esbuild/freebsd-arm64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz#b97e97073310736b430a07b099d837084b85e9ce"
integrity sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==
"@esbuild/freebsd-x64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz#f3b694d0da61d9910ec7deff794d444cfbf3b6e7"
integrity sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==
"@esbuild/linux-arm64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz#f921f699f162f332036d5657cad9036f7a993f73"
integrity sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==
"@esbuild/linux-arm@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz#cc49305b3c6da317c900688995a4050e6cc91ca3"
integrity sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==
"@esbuild/linux-ia32@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz#3e0736fcfab16cff042dec806247e2c76e109e19"
integrity sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==
"@esbuild/linux-loong64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz#ea2bf730883cddb9dfb85124232b5a875b8020c7"
integrity sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==
"@esbuild/linux-mips64el@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz#4cababb14eede09248980a2d2d8b966464294ff1"
integrity sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==
"@esbuild/linux-ppc64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz#8860a4609914c065373a77242e985179658e1951"
integrity sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==
"@esbuild/linux-riscv64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz#baf26e20bb2d38cfb86ee282dff840c04f4ed987"
integrity sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==
"@esbuild/linux-s390x@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz#8323afc0d6cb1b6dc6e9fd21efd9e1542c3640a4"
integrity sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==
"@esbuild/linux-x64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz#08fcf60cb400ed2382e9f8e0f5590bac8810469a"
integrity sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==
"@esbuild/netbsd-arm64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz#935c6c74e20f7224918fbe2e6c6fe865b6c6ea5b"
integrity sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==
"@esbuild/netbsd-x64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz#414677cef66d16c5a4d210751eb2881bb9c1b62b"
integrity sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==
"@esbuild/openbsd-arm64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz#8fd55a4d08d25cdc572844f13c88d678c84d13f7"
integrity sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==
"@esbuild/openbsd-x64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz#0c48ddb1494bbc2d6bcbaa1429a7f465fa1dedde"
integrity sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==
"@esbuild/sunos-x64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz#86ff9075d77962b60dd26203d7352f92684c8c92"
integrity sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==
"@esbuild/win32-arm64@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz#849c62327c3229467f5b5cd681bf50588442e96c"
integrity sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==
"@esbuild/win32-ia32@0.25.0":
version "0.25.0"
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz#f62eb480cd7cca088cb65bb46a6db25b725dc079"
integrity sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==
"@codemirror/view@^6.0.0":
version "6.36.4"
resolved "https://registry.npmjs.org/@codemirror/view/-/view-6.36.4.tgz"
integrity sha512-ZQ0V5ovw/miKEXTvjgzRyjnrk9TwriUB1k4R5p7uNnHR9Hus+D1SXHGdJshijEzPFjU25xea/7nhIeSqYFKdbA==
dependencies:
"@codemirror/state" "^6.5.0"
style-mod "^4.1.0"
w3c-keyname "^2.2.4"
"@esbuild/win32-x64@0.25.0":
version "0.25.0"
resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz"
integrity sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==
"@eslint-community/eslint-utils@^4.2.0":
version "4.5.1"
resolved "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.5.1.tgz"
integrity sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==
dependencies:
eslint-visitor-keys "^3.4.3"
"@eslint-community/regexpp@^4.6.1":
version "4.12.1"
resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz"
integrity sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==
"@eslint/eslintrc@^2.1.4":
version "2.1.4"
resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz"
integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==
dependencies:
ajv "^6.12.4"
debug "^4.3.2"
espree "^9.6.0"
globals "^13.19.0"
ignore "^5.2.0"
import-fresh "^3.2.1"
js-yaml "^4.1.0"
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/js@8.57.1":
version "8.57.1"
resolved "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz"
integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==
"@humanwhocodes/config-array@^0.13.0":
version "0.13.0"
resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz"
integrity sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==
dependencies:
"@humanwhocodes/object-schema" "^2.0.3"
debug "^4.3.1"
minimatch "^3.0.5"
"@humanwhocodes/module-importer@^1.0.1":
version "1.0.1"
resolved "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz"
integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
"@humanwhocodes/object-schema@^2.0.3":
version "2.0.3"
resolved "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz"
integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==
"@marijn/find-cluster-break@^1.0.0":
version "1.0.2"
resolved "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz"
integrity sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
@ -135,12 +87,12 @@
"@nodelib/fs.stat" "2.0.5"
run-parallel "^1.1.9"
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
version "2.0.5"
resolved "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz"
integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
"@nodelib/fs.walk@^1.2.3":
"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8":
version "1.2.8"
resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz"
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
@ -165,6 +117,11 @@
resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==
"@types/lodash@^4.17.16":
version "4.17.16"
resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz"
integrity sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==
"@types/node@^16.11.6":
version "16.18.121"
resolved "https://registry.npmjs.org/@types/node/-/node-16.18.121.tgz"
@ -192,7 +149,7 @@
semver "^7.3.7"
tsutils "^3.21.0"
"@typescript-eslint/parser@5.29.0":
"@typescript-eslint/parser@^5.0.0", "@typescript-eslint/parser@5.29.0":
version "5.29.0"
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.29.0.tgz"
integrity sha512-ruKWTv+x0OOxbzIw9nW5oWlUopvP/IQDjB5ZqmTglLIoDTctLlAJpAQFpNPJP/ZI7hTT9sARBosEfaKbcFuECw==
@ -257,16 +214,66 @@
"@typescript-eslint/types" "5.29.0"
eslint-visitor-keys "^3.3.0"
acorn@^8.14.1:
"@ungap/structured-clone@^1.2.0":
version "1.3.0"
resolved "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz"
integrity sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==
acorn-jsx@^5.3.2:
version "5.3.2"
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
"acorn@^6.0.0 || ^7.0.0 || ^8.0.0", acorn@^8.14.1, acorn@^8.9.0:
version "8.14.1"
resolved "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz"
integrity sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==
ajv@^6.12.4:
version "6.12.6"
resolved "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
dependencies:
fast-deep-equal "^3.1.1"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
ansi-styles@^4.1.0:
version "4.3.0"
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz"
integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
dependencies:
color-convert "^2.0.1"
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz"
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
array-union@^2.1.0:
version "2.1.0"
resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz"
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
balanced-match@^1.0.0:
version "1.0.2"
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz"
integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
braces@^3.0.3:
version "3.0.3"
resolved "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz"
@ -279,13 +286,57 @@ builtin-modules@3.3.0:
resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz"
integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==
debug@^4.3.4:
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
chalk@^4.0.0:
version "4.1.2"
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
dependencies:
ansi-styles "^4.1.0"
supports-color "^7.1.0"
color-convert@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz"
integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
dependencies:
color-name "~1.1.4"
color-name@~1.1.4:
version "1.1.4"
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
cross-spawn@^7.0.2:
version "7.0.6"
resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz"
integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==
dependencies:
path-key "^3.1.0"
shebang-command "^2.0.0"
which "^2.0.1"
debug@^4.3.1, debug@^4.3.2, debug@^4.3.4:
version "4.3.7"
resolved "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz"
integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
dependencies:
ms "^2.1.3"
deep-is@^0.1.3:
version "0.1.4"
resolved "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
dir-glob@^3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz"
@ -293,6 +344,13 @@ dir-glob@^3.0.1:
dependencies:
path-type "^4.0.0"
doctrine@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz"
integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
dependencies:
esutils "^2.0.2"
esbuild@0.25.0:
version "0.25.0"
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz"
@ -324,6 +382,11 @@ esbuild@0.25.0:
"@esbuild/win32-ia32" "0.25.0"
"@esbuild/win32-x64" "0.25.0"
escape-string-regexp@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
eslint-scope@^5.1.1:
version "5.1.1"
resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz"
@ -332,6 +395,14 @@ eslint-scope@^5.1.1:
esrecurse "^4.3.0"
estraverse "^4.1.1"
eslint-scope@^7.2.2:
version "7.2.2"
resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz"
integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==
dependencies:
esrecurse "^4.3.0"
estraverse "^5.2.0"
eslint-utils@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz"
@ -349,6 +420,76 @@ eslint-visitor-keys@^3.3.0:
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
eslint-visitor-keys@^3.4.1:
version "3.4.3"
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
eslint-visitor-keys@^3.4.3:
version "3.4.3"
resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz"
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
eslint@*, "eslint@^6.0.0 || ^7.0.0 || ^8.0.0", "eslint@^6.0.0 || ^7.0.0 || >=8.0.0", eslint@>=5:
version "8.57.1"
resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz"
integrity sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.6.1"
"@eslint/eslintrc" "^2.1.4"
"@eslint/js" "8.57.1"
"@humanwhocodes/config-array" "^0.13.0"
"@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8"
"@ungap/structured-clone" "^1.2.0"
ajv "^6.12.4"
chalk "^4.0.0"
cross-spawn "^7.0.2"
debug "^4.3.2"
doctrine "^3.0.0"
escape-string-regexp "^4.0.0"
eslint-scope "^7.2.2"
eslint-visitor-keys "^3.4.3"
espree "^9.6.1"
esquery "^1.4.2"
esutils "^2.0.2"
fast-deep-equal "^3.1.3"
file-entry-cache "^6.0.1"
find-up "^5.0.0"
glob-parent "^6.0.2"
globals "^13.19.0"
graphemer "^1.4.0"
ignore "^5.2.0"
imurmurhash "^0.1.4"
is-glob "^4.0.0"
is-path-inside "^3.0.3"
js-yaml "^4.1.0"
json-stable-stringify-without-jsonify "^1.0.1"
levn "^0.4.1"
lodash.merge "^4.6.2"
minimatch "^3.1.2"
natural-compare "^1.4.0"
optionator "^0.9.3"
strip-ansi "^6.0.1"
text-table "^0.2.0"
espree@^9.6.0, espree@^9.6.1:
version "9.6.1"
resolved "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz"
integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
dependencies:
acorn "^8.9.0"
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.4.1"
esquery@^1.4.2:
version "1.6.0"
resolved "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz"
integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==
dependencies:
estraverse "^5.1.0"
esrecurse@^4.3.0:
version "4.3.0"
resolved "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz"
@ -361,11 +502,26 @@ estraverse@^4.1.1:
resolved "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz"
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
estraverse@^5.1.0:
version "5.3.0"
resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz"
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
estraverse@^5.2.0:
version "5.3.0"
resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz"
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
esutils@^2.0.2:
version "2.0.3"
resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
fast-glob@^3.2.9:
version "3.3.2"
resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz"
@ -377,6 +533,16 @@ fast-glob@^3.2.9:
merge2 "^1.3.0"
micromatch "^4.0.4"
fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
fast-levenshtein@^2.0.6:
version "2.0.6"
resolved "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz"
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
fastq@^1.6.0:
version "1.17.1"
resolved "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz"
@ -384,6 +550,13 @@ fastq@^1.6.0:
dependencies:
reusify "^1.0.4"
file-entry-cache@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz"
integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
dependencies:
flat-cache "^3.0.4"
fill-range@^7.1.1:
version "7.1.1"
resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz"
@ -391,6 +564,33 @@ fill-range@^7.1.1:
dependencies:
to-regex-range "^5.0.1"
find-up@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz"
integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
dependencies:
locate-path "^6.0.0"
path-exists "^4.0.0"
flat-cache@^3.0.4:
version "3.2.0"
resolved "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz"
integrity sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==
dependencies:
flatted "^3.2.9"
keyv "^4.5.3"
rimraf "^3.0.2"
flatted@^3.2.9:
version "3.3.3"
resolved "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz"
integrity sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
functional-red-black-tree@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz"
@ -403,6 +603,32 @@ glob-parent@^5.1.2:
dependencies:
is-glob "^4.0.1"
glob-parent@^6.0.2:
version "6.0.2"
resolved "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz"
integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
dependencies:
is-glob "^4.0.3"
glob@^7.1.3:
version "7.2.3"
resolved "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz"
integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.1.1"
once "^1.3.0"
path-is-absolute "^1.0.0"
globals@^13.19.0:
version "13.24.0"
resolved "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz"
integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==
dependencies:
type-fest "^0.20.2"
globby@^11.1.0:
version "11.1.0"
resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz"
@ -415,17 +641,58 @@ globby@^11.1.0:
merge2 "^1.4.1"
slash "^3.0.0"
graphemer@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz"
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
highlight.js@^11.11.1:
version "11.11.1"
resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-11.11.1.tgz"
integrity sha512-Xwwo44whKBVCYoliBQwaPvtd/2tYFkRQtXDWj1nackaV2JPXx3L0+Jvd8/qCJ2p+ML0/XVkJ2q+Mr+UVdpJK5w==
ignore@^5.2.0:
version "5.3.2"
resolved "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz"
integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
import-fresh@^3.2.1:
version "3.3.1"
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz"
integrity sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==
dependencies:
parent-module "^1.0.0"
resolve-from "^4.0.0"
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz"
integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
inflight@^1.0.4:
version "1.0.6"
resolved "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz"
integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
dependencies:
once "^1.3.0"
wrappy "1"
inherits@2:
version "2.0.4"
resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
is-glob@^4.0.1, is-glob@^4.0.3:
is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
version "4.0.3"
resolved "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz"
integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
@ -437,6 +704,70 @@ is-number@^7.0.0:
resolved "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz"
integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
is-path-inside@^3.0.3:
version "3.0.3"
resolved "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz"
integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
js-yaml@^4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
dependencies:
argparse "^2.0.1"
json-buffer@3.0.1:
version "3.0.1"
resolved "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz"
integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json-stable-stringify-without-jsonify@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz"
integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
keyv@^4.5.3:
version "4.5.4"
resolved "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz"
integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
dependencies:
json-buffer "3.0.1"
levn@^0.4.1:
version "0.4.1"
resolved "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz"
integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
dependencies:
prelude-ls "^1.2.1"
type-check "~0.4.0"
locate-path@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz"
integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
dependencies:
p-locate "^5.0.0"
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
merge2@^1.3.0, merge2@^1.4.1:
version "1.4.1"
resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz"
@ -450,6 +781,13 @@ micromatch@^4.0.4:
braces "^3.0.3"
picomatch "^2.3.1"
minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.2"
resolved "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz"
integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
dependencies:
brace-expansion "^1.1.7"
moment@2.29.4:
version "2.29.4"
resolved "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz"
@ -460,6 +798,11 @@ ms@^2.1.3:
resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
natural-compare@^1.4.0:
version "1.4.0"
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
obsidian@latest:
version "1.8.7"
resolved "https://registry.npmjs.org/obsidian/-/obsidian-1.8.7.tgz"
@ -468,6 +811,61 @@ obsidian@latest:
"@types/codemirror" "5.60.8"
moment "2.29.4"
once@^1.3.0:
version "1.4.0"
resolved "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
dependencies:
wrappy "1"
optionator@^0.9.3:
version "0.9.4"
resolved "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz"
integrity sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==
dependencies:
deep-is "^0.1.3"
fast-levenshtein "^2.0.6"
levn "^0.4.1"
prelude-ls "^1.2.1"
type-check "^0.4.0"
word-wrap "^1.2.5"
p-limit@^3.0.2:
version "3.1.0"
resolved "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz"
integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
dependencies:
yocto-queue "^0.1.0"
p-locate@^5.0.0:
version "5.0.0"
resolved "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz"
integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
dependencies:
p-limit "^3.0.2"
parent-module@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
dependencies:
callsites "^3.0.0"
path-exists@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz"
integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz"
integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
path-key@^3.1.0:
version "3.1.1"
resolved "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
path-type@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz"
@ -478,6 +876,16 @@ picomatch@^2.3.1:
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
prelude-ls@^1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
punycode@^2.1.0:
version "2.3.1"
resolved "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz"
integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==
queue-microtask@^1.2.2:
version "1.2.3"
resolved "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz"
@ -488,11 +896,23 @@ regexpp@^3.2.0:
resolved "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz"
integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz"
integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
rimraf@^3.0.2:
version "3.0.2"
resolved "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz"
integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
dependencies:
glob "^7.1.3"
run-parallel@^1.1.9:
version "1.2.0"
resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz"
@ -505,11 +925,52 @@ semver@^7.3.7:
resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
shebang-command@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz"
integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
dependencies:
shebang-regex "^3.0.0"
shebang-regex@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz"
integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
strip-ansi@^6.0.1:
version "6.0.1"
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
dependencies:
ansi-regex "^5.0.1"
strip-json-comments@^3.1.1:
version "3.1.1"
resolved "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
style-mod@^4.1.0:
version "4.1.2"
resolved "https://registry.npmjs.org/style-mod/-/style-mod-4.1.2.tgz"
integrity sha512-wnD1HyVqpJUI2+eKZ+eo1UwghftP6yuFheBqqe+bWCotBjC2K1YnteJILRMs3SM4V/0dLEW1SC27MWP5y+mwmw==
supports-color@^7.1.0:
version "7.2.0"
resolved "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz"
integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
dependencies:
has-flag "^4.0.0"
text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz"
@ -517,16 +978,16 @@ to-regex-range@^5.0.1:
dependencies:
is-number "^7.0.0"
tslib@2.4.0:
version "2.4.0"
resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz"
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
tslib@^1.8.1:
version "1.14.1"
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
tslib@2.4.0:
version "2.4.0"
resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz"
integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==
tsutils@^3.21.0:
version "3.21.0"
resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz"
@ -534,12 +995,58 @@ tsutils@^3.21.0:
dependencies:
tslib "^1.8.1"
typescript@4.7.4:
type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0"
resolved "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz"
integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
dependencies:
prelude-ls "^1.2.1"
type-fest@^0.20.2:
version "0.20.2"
resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz"
integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
"typescript@>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta", typescript@4.7.4:
version "4.7.4"
resolved "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz"
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz"
integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
dependencies:
punycode "^2.1.0"
w3c-keyname@^2.2.4:
version "2.2.8"
resolved "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz"
integrity sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==
which@^2.0.1:
version "2.0.2"
resolved "https://registry.npmjs.org/which/-/which-2.0.2.tgz"
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
dependencies:
isexe "^2.0.0"
word-wrap@^1.2.5:
version "1.2.5"
resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz"
integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==
wrappy@1:
version "1.0.2"
resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
yaml@^2.7.0:
version "2.7.0"
resolved "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz"
integrity sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==
yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==