mirror of
https://github.com/alamion/obsidian-jira-sync.git
synced 2026-07-22 05:43:04 +00:00
settings update:
- css enchantments - template now can be selected instead of raw manual input - internationalization implementation - work statistics now integrated in settings
This commit is contained in:
parent
fbf8649c1a
commit
67308c0fec
80 changed files with 2610 additions and 899 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -11,6 +11,7 @@ node_modules
|
|||
# Don't include the compiled main.js file in the repo.
|
||||
# They should be uploaded to GitHub releases instead.
|
||||
*.js
|
||||
!/src/localization/**/*.js
|
||||
|
||||
# Exclude sourcemaps
|
||||
*.map
|
||||
|
|
@ -23,3 +24,6 @@ data.json
|
|||
|
||||
# Some other
|
||||
tmp/
|
||||
|
||||
# localization compiled
|
||||
src/localization/compiled
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ Not all fields are predefined, and some may need adjustments. For example, the t
|
|||
|
||||
Currently, the plugin provides the following commands:
|
||||
- `Get issue from Jira with custom key` - allows creating a file in the configured folder that imports information from Jira using a manually specified ID.
|
||||
- `Get issue from Jira` - allows updating the active file if its formatter contains a `key` (the Jira task ID).
|
||||
- `Update issue from Jira` - allows updating the information from the file in Jira using the key specified in the formatter. Some system fields (e.g., `status`) cannot be changed this way and have dedicated commands.
|
||||
- `Create issue from Jira` - allows creating a new task in Jira. The formatter must include `summary` (task title) and optionally `project` and `issuetype` (the latter two can be selected from existing options during creation).
|
||||
- `Update work log in Jira` - enables tracking time spent on a task. Currently, this is not reflected in the file, but it will be available in future updates. If the formatter contains `jira_selected_week_data` (as described in [[docs/jira_selected_week_data]]), instead of manual entry, a batch of data from `jira_selected_week_data` will be sent, updating each listed entity.
|
||||
- `Get current issue from Jira` - allows updating the active file if its formatter contains a `key` (the Jira task ID).
|
||||
- `Update issue in Jira` - allows updating the information from the file in Jira using the key specified in the formatter. Some system fields (e.g., `status`) cannot be changed this way and have dedicated commands.
|
||||
- `Create issue in Jira` - allows creating a new task in Jira. The formatter must include `summary` (task title) and optionally `project` and `issuetype` (the latter two can be selected from existing options during creation).
|
||||
- `Update work log in Jira` - enables tracking time spent on a task. Currently, this is not reflected in the file, but it will be available in future updates. If the formatter contains `jira_worklog_batch` (as described in [[docs/jira_worklog_batch]]), instead of manual entry, a batch of data from `jira_worklog_batch` will be sent, updating each listed entity.
|
||||
- `Update issue status in Jira` - allows updating a task's status by selecting one of the available options.
|
||||
|
||||
### Advanced Usage
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ formatter имеет приоритет и будет перезаписыват
|
|||
- `Get issue from Jira` - позволяет обновить активный файл, если в его formatter указан key - id задачи Jira.
|
||||
- `Update issue from Jira` - позволяет обновить информацию из файла в Jira по указанному в formatter ключу. Ряд системных полей (например, `status`, таким образом изменить нельзя. Для них созданы отдельные команды)
|
||||
- `Create issue from Jira` - позволяет создать в Jira новую задачу. В formatter обязательно нужно указать summary - название задачи и, опционально, `project` и `issuetype` (последние два можно выбрать из существующих при создании)
|
||||
- `Update work log in Jira` - позволяет вести учёт потраченного на задачу времени. В данный момент он никак не отображается в файле, это будет в ближайших обновлениях. Если в файле в formatter есть `jira_selected_week_data` (как это в [[docs/jira_selected_week_data]]), то вместо ручного заполнения будет послан батч данных из `jira_selected_week_data` с обновлением каждой из представленных сущностей.
|
||||
- `Update work log in Jira` - позволяет вести учёт потраченного на задачу времени. В данный момент он никак не отображается в файле, это будет в ближайших обновлениях. Если в файле в formatter есть `jira_worklog_batch` (как это в [[docs/jira_worklog_batch]]), то вместо ручного заполнения будет послан батч данных из `jira_worklog_batch` с обновлением каждой из представленных сущностей.
|
||||
- `Update issue status in Jira` - позволяет обновить статус задачи, выбрав один из возможных вариантов.
|
||||
|
||||
### Продвинутое использование
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "jira-sync",
|
||||
"name": "Jira Issue Manager",
|
||||
"version": "1.1.8",
|
||||
"version": "1.1.9",
|
||||
"minAppVersion": "1.7.7",
|
||||
"description": "Get Jira issues, create and update them. Issue status and worklog management.",
|
||||
"author": "Alamion",
|
||||
|
|
|
|||
15
package.json
15
package.json
|
|
@ -1,13 +1,16 @@
|
|||
{
|
||||
"name": "obsidian-sample-plugin",
|
||||
"version": "1.1.8",
|
||||
"version": "1.1.9",
|
||||
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json",
|
||||
"buildObsidian": "node buildStatistics.mjs"
|
||||
"validate_locale": "node src/localization/validator.js --watch",
|
||||
"compile_locale": "node src/localization/compiler.js --watch",
|
||||
"validate_locale_once": "node src/localization/validator.js",
|
||||
"compile_locale_once": "node src/localization/compiler.js",
|
||||
"dev": "yarn run compile_locale_once && node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && yarn run compile_locale_once && node esbuild.config.mjs production",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
|
@ -26,6 +29,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"acorn": "^8.14.1",
|
||||
"chokidar": "^4.0.3",
|
||||
"fs-extra": "^11.3.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"yaml": "^2.7.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,11 +3,14 @@ import JiraPlugin from "../main";
|
|||
import {addWorkLog} from "../api";
|
||||
import {debugLog} from "../tools/debugLogging";
|
||||
import {checkCommandCallback} from "../tools/check_command_callback";
|
||||
import {useTranslations} from "../localization/translator";
|
||||
|
||||
const t = useTranslations("commands.add_worklog.batch").t;
|
||||
|
||||
export function registerUpdateWorkLogBatchCommand(plugin: JiraPlugin): void {
|
||||
plugin.addCommand({
|
||||
id: "update-work-log-jira-batch",
|
||||
name: "Update work log in Jira",
|
||||
name: t("name"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
return checkCommandCallback(plugin, checking, processFrontmatterWorkLogs, ["jira_worklog_batch"], ["jira_worklog_batch"]);
|
||||
},
|
||||
|
|
@ -27,7 +30,7 @@ async function processFrontmatterWorkLogs(plugin: JiraPlugin, _: TFile, jira_wor
|
|||
workLogData = jira_worklog_batch;
|
||||
foundData = true;
|
||||
} else {
|
||||
throw new TypeError(`jira_selected_week_data has an invalid type: ${typeof jira_worklog_batch}`);
|
||||
throw new TypeError(`jira_worklog_batch has an invalid type: ${typeof jira_worklog_batch}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to parse jira_worklog_batch:", error);
|
||||
|
|
@ -43,7 +46,7 @@ async function processFrontmatterWorkLogs(plugin: JiraPlugin, _: TFile, jira_wor
|
|||
}
|
||||
}
|
||||
|
||||
async function processWorkLogBatch(plugin: JiraPlugin, workLogs: any[]): Promise<void> {
|
||||
export async function processWorkLogBatch(plugin: JiraPlugin, workLogs: any[]): Promise<void> {
|
||||
const results = {
|
||||
success: 0,
|
||||
total: workLogs.length,
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
|
||||
import {TFile} from "obsidian";
|
||||
import JiraPlugin from "../main";
|
||||
import {WorkLogModal} from "../modals/issueWorkLogModal";
|
||||
import {IssueWorkLogModal} from "../modals";
|
||||
import {addWorkLog} from "../api";
|
||||
import {checkCommandCallback} from "../tools/check_command_callback";
|
||||
import {useTranslations} from "../localization/translator";
|
||||
|
||||
const t = useTranslations("commands.add_worklog.manual").t;
|
||||
|
||||
export function registerUpdateWorkLogManuallyCommand(plugin: JiraPlugin): void {
|
||||
plugin.addCommand({
|
||||
id: "update-work-log-jira-manually",
|
||||
name: "Update work log in Jira manually",
|
||||
name: t("name"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
return checkCommandCallback(plugin, checking, processManualWorkLog, ["key"], ["key"]);
|
||||
},
|
||||
|
|
@ -17,7 +20,7 @@ export function registerUpdateWorkLogManuallyCommand(plugin: JiraPlugin): void {
|
|||
|
||||
async function processManualWorkLog(plugin: JiraPlugin, _: TFile, issueKey: string): Promise<void> {
|
||||
|
||||
new WorkLogModal(plugin.app, async (timeSpent: string, startDate: string, comment: string) => {
|
||||
new IssueWorkLogModal(plugin.app, async (timeSpent: string, startDate: string, comment: string) => {
|
||||
await addWorkLog(plugin, issueKey, timeSpent, startDate, comment);
|
||||
}).open();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ import { IssueTypeModal, ProjectModal } from "../modals";
|
|||
import {fetchIssueTypes, fetchProjects} from "../api";
|
||||
import {createIssueFromFile} from "../file_operations/createUpdateIssue";
|
||||
import {checkCommandCallback} from "../tools/check_command_callback";
|
||||
import {useTranslations} from "../localization/translator";
|
||||
|
||||
const t = useTranslations("commands.create_issue").t;
|
||||
|
||||
/**
|
||||
* Register the create issue command
|
||||
|
|
@ -11,7 +14,7 @@ import {checkCommandCallback} from "../tools/check_command_callback";
|
|||
export function registerCreateIssueCommand(plugin: JiraPlugin): void {
|
||||
plugin.addCommand({
|
||||
id: "create-issue-jira",
|
||||
name: "Create issue in Jira",
|
||||
name: t("name"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
return checkCommandCallback(plugin, checking, createIssue, ["summary"]);
|
||||
},
|
||||
|
|
@ -37,15 +40,15 @@ export async function createIssue(plugin: JiraPlugin, file: TFile): Promise<void
|
|||
try {
|
||||
// Create the issue with selected project and issue type
|
||||
const issueKey = await createIssueFromFile(plugin, file, projectKey, issueType);
|
||||
new Notice(`Issue ${issueKey} created successfully`);
|
||||
new Notice(t('success', { issueKey }));
|
||||
} catch (error) {
|
||||
new Notice("Error creating issue: " + (error.message || "Unknown error"), 3000);
|
||||
new Notice(t('error') + ": " + (error.message || "Unknown error"), 3000);
|
||||
console.error(error);
|
||||
}
|
||||
}).open();
|
||||
}).open();
|
||||
} catch (error) {
|
||||
new Notice("Error creating issue: " + (error.message || "Unknown error"));
|
||||
new Notice(t('error') + ": " + (error.message || "Unknown error"));
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
import {Notice, TFile} from "obsidian";
|
||||
import {TFile} from "obsidian";
|
||||
import JiraPlugin from "../main";
|
||||
import {IssueSearchModal} from "../modals";
|
||||
import {fetchIssue, validateSettings} from "../api";
|
||||
import {createOrUpdateIssueNote} from "../file_operations/getIssue";
|
||||
import {checkCommandCallback} from "../tools/check_command_callback";
|
||||
import {useTranslations} from "../localization/translator";
|
||||
|
||||
const t = useTranslations("commands.get_issue").t;
|
||||
|
||||
export function registerGetIssueCommandWithKey(plugin: JiraPlugin): void {
|
||||
plugin.addCommand({
|
||||
id: "get-issue-jira-key",
|
||||
name: "Get issue from Jira with custom key",
|
||||
name: t("with_key"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
const settings_are_valid = validateSettings(plugin);
|
||||
if (settings_are_valid) {
|
||||
|
|
@ -23,7 +26,7 @@ export function registerGetIssueCommandWithKey(plugin: JiraPlugin): void {
|
|||
export function registerGetIssueCommand(plugin: JiraPlugin): void {
|
||||
plugin.addCommand({
|
||||
id: "get-issue-jira",
|
||||
name: "Get current issue from Jira",
|
||||
name: t("without_key"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
return checkCommandCallback(plugin, checking, fetchAndOpenIssue, ["key"], ["key"]);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// Re-export all commands
|
||||
export * from './updateIssue';
|
||||
export * from './getIssue';
|
||||
export * from './addWorkLogBatch';
|
||||
export * from './addWorkLogManually';
|
||||
export * from './createIssue';
|
||||
export * from './getIssue';
|
||||
export * from './updateIssue';
|
||||
export * from './updateStatus';
|
||||
export {getCurrentFileMainInfo} from "../file_operations/common_prepareData";
|
||||
|
|
|
|||
|
|
@ -2,11 +2,14 @@ import {Notice, TFile} from "obsidian";
|
|||
import JiraPlugin from "../main";
|
||||
import {updateIssueFromFile} from "../file_operations/createUpdateIssue";
|
||||
import {checkCommandCallback} from "../tools/check_command_callback";
|
||||
import {useTranslations} from "../localization/translator";
|
||||
|
||||
const t = useTranslations("commands.update_issue").t;
|
||||
|
||||
export function registerUpdateIssueCommand(plugin: JiraPlugin): void {
|
||||
plugin.addCommand({
|
||||
id: "update-issue-jira",
|
||||
name: "Update issue in Jira",
|
||||
name: t("name"),
|
||||
checkCallback: (checking: boolean) => {
|
||||
return checkCommandCallback(plugin, checking, updateIssue, ["key"]);
|
||||
},
|
||||
|
|
@ -18,11 +21,11 @@ export async function updateIssue(plugin: JiraPlugin, file: TFile): Promise<void
|
|||
try {
|
||||
// Update the issue with all data from the file
|
||||
const issueKey = await updateIssueFromFile(plugin, file);
|
||||
new Notice(`Issue ${issueKey} updated successfully`);
|
||||
new Notice(t('success', {issueKey}));
|
||||
} catch (error) {
|
||||
new Notice("Error updating issue: " + (error.message || "Unknown error"), 3000);
|
||||
new Notice(t('error') + ": " + (error.message || "Unknown error"), 3000);
|
||||
}
|
||||
} catch (error) {
|
||||
new Notice("Error updating issue: " + (error.message || "Unknown error"));
|
||||
new Notice(t('error') + ": " + (error.message || "Unknown error"));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,17 @@ import {Notice, TFile} from "obsidian";
|
|||
import JiraPlugin from "../main";
|
||||
import {fetchIssueTransitions} from "../api";
|
||||
import {updateStatusFromFile} from "../file_operations/createUpdateIssue";
|
||||
import {IssueStatusModal} from "../modals/IssueStatusModal";
|
||||
import {IssueStatusModal} from "../modals";
|
||||
import {JiraTransitionType} from "../interfaces";
|
||||
import {checkCommandCallback} from "../tools/check_command_callback";
|
||||
import {useTranslations} from "../localization/translator";
|
||||
|
||||
const t = useTranslations("commands.update_status").t;
|
||||
|
||||
export function registerUpdateIssueStatusCommand(plugin: JiraPlugin): void {
|
||||
plugin.addCommand({
|
||||
id: "update-issue-status-jira",
|
||||
name: "Update issue status in Jira",
|
||||
name: t('name'),
|
||||
checkCallback: (checking: boolean) => {
|
||||
return checkCommandCallback(plugin, checking, updateIssueStatus, ["key"],["key"]);
|
||||
},
|
||||
|
|
@ -23,15 +26,15 @@ export async function updateIssueStatus(plugin: JiraPlugin, file: TFile, issueKe
|
|||
new IssueStatusModal(plugin.app, issueTransitions, async (transition: JiraTransitionType) => {
|
||||
try {
|
||||
await updateStatusFromFile(plugin, file, transition);
|
||||
new Notice(`Issue ${issueKey} updated successfully`);
|
||||
new Notice(t('success', {issueKey}));
|
||||
} catch (error) {
|
||||
new Notice("Error updating issue: " + (error.message || "Unknown error"), 3000);
|
||||
new Notice(t('error') + ": " + (error.message || "Unknown error"), 3000);
|
||||
console.error(error);
|
||||
}
|
||||
}).open();
|
||||
|
||||
} catch (error) {
|
||||
new Notice("Error updating issue: " + (error.message || "Unknown error"));
|
||||
new Notice(t('error') + ": " + (error.message || "Unknown error"));
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
11
src/default/default_template.ts
Normal file
11
src/default/default_template.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
export const defaultTemplate =
|
||||
`---
|
||||
key:
|
||||
summary:
|
||||
status:
|
||||
assignee:
|
||||
tags:
|
||||
description:
|
||||
---
|
||||
|
||||
`;
|
||||
|
|
@ -3,7 +3,7 @@ import {Notice, TFile} from "obsidian";
|
|||
import {extractAllJiraSyncValuesFromContent} from "../tools/sectionTools";
|
||||
import {localToJiraFields} from "../tools/mapObsidianJiraFields";
|
||||
import {debugLog} from "../tools/debugLogging";
|
||||
import {obsidianJiraFieldMappings} from "../constants/obsidianJiraFieldsMapping";
|
||||
import {obsidianJiraFieldMappings} from "../default/obsidianJiraFieldsMapping";
|
||||
|
||||
/**
|
||||
* Prepares Jira fields from the content and frontmatter of a file
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import {ensureIssuesFolder} from "../tools/filesUtils";
|
|||
import {sanitizeFileName} from "../tools/sanitizers";
|
||||
import {updateJiraToLocal} from "../tools/mapObsidianJiraFields";
|
||||
import {Notice, TFile} from "obsidian";
|
||||
import {defaultTemplate} from "../default/default_template";
|
||||
|
||||
export async function createOrUpdateIssueNote(plugin: JiraPlugin, issue: JiraIssue, filePath?: string): Promise<void> {
|
||||
try {
|
||||
|
|
@ -42,17 +43,7 @@ async function createNewIssueFile(
|
|||
): Promise<TFile> {
|
||||
let initialContent = "";
|
||||
|
||||
const hardcodedTemplate =
|
||||
`---
|
||||
key:
|
||||
summary:
|
||||
status:
|
||||
assignee:
|
||||
tags:
|
||||
description:
|
||||
---
|
||||
|
||||
`;
|
||||
let templatePath = plugin.settings.templatePath
|
||||
if (templatePath && !templatePath.endsWith(".md")) {
|
||||
templatePath += ".md";
|
||||
|
|
@ -66,7 +57,7 @@ description:
|
|||
new Notice(`Template file not found: ${templatePath}, using default template`);
|
||||
}
|
||||
}
|
||||
if (initialContent === "") initialContent = hardcodedTemplate
|
||||
if (initialContent === "") initialContent = defaultTemplate
|
||||
|
||||
// Create the file with initial content
|
||||
await plugin.app.vault.create(filePath, initialContent);
|
||||
|
|
|
|||
123
src/localization/compiler.js
Normal file
123
src/localization/compiler.js
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
const fs = require('fs-extra');
|
||||
const yaml = require('yaml');
|
||||
const path = require('path');
|
||||
const chokidar = require('chokidar');
|
||||
|
||||
const SOURCE_DIR = path.join(__dirname, 'source');
|
||||
const COMPILED_DIR = path.join(__dirname, 'compiled');
|
||||
|
||||
// Компиляция всех языков
|
||||
async function main() {
|
||||
try {
|
||||
const locales = fs.readdirSync(SOURCE_DIR);
|
||||
for (const locale of locales) {
|
||||
const localePath = path.join(SOURCE_DIR, locale);
|
||||
const stat = fs.statSync(localePath);
|
||||
if (stat.isDirectory()) {
|
||||
await compileLocale(locale);
|
||||
}
|
||||
}
|
||||
console.log('✅ All locales compiled');
|
||||
} catch (err) {
|
||||
console.error('❌ Error compiling all locales:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function compileLocale(locale) {
|
||||
const localeDir = path.join(SOURCE_DIR, locale);
|
||||
const result = {};
|
||||
|
||||
async function processDirectory(dirPath, parentKeys = []) {
|
||||
try {
|
||||
const items = fs.readdirSync(dirPath);
|
||||
|
||||
for (const item of items) {
|
||||
// Пропускаем временные и скрытые файлы
|
||||
if (item.startsWith('.')) continue;
|
||||
|
||||
const itemPath = path.join(dirPath, item);
|
||||
|
||||
let stat;
|
||||
try {
|
||||
stat = fs.statSync(itemPath);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') continue; // Файл был удален
|
||||
throw err;
|
||||
}
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
await processDirectory(itemPath, [...parentKeys, item]);
|
||||
} else if (item.endsWith('.yaml')) {
|
||||
try {
|
||||
const content = fs.readFileSync(itemPath, 'utf8');
|
||||
const data = yaml.parse(content);
|
||||
const fileNameWithoutExt = path.basename(item, '.yaml');
|
||||
|
||||
const targetKeys = fileNameWithoutExt === 'index'
|
||||
? parentKeys
|
||||
: [...parentKeys, fileNameWithoutExt];
|
||||
|
||||
let target = result;
|
||||
for (const key of targetKeys.slice(0, -1)) {
|
||||
target[key] = target[key] || {};
|
||||
target = target[key];
|
||||
}
|
||||
|
||||
const finalKey = targetKeys[targetKeys.length - 1];
|
||||
target[finalKey] = deepMerge(target[finalKey] || {}, data);
|
||||
} catch (err) {
|
||||
console.error(`⚠️ Failed to process file ${itemPath}:`, err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`⚠️ Failed to process directory ${dirPath}:`, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
await processDirectory(localeDir);
|
||||
const outputPath = path.join(COMPILED_DIR, `${locale}.json`);
|
||||
await fs.ensureDir(COMPILED_DIR);
|
||||
await fs.writeJson(outputPath, result, { spaces: 2 });
|
||||
}
|
||||
|
||||
function deepMerge(target, source) {
|
||||
for (const key in source) {
|
||||
if (source[key] instanceof Object && target[key]) {
|
||||
Object.assign(source[key], deepMerge(target[key], source[key]));
|
||||
}
|
||||
}
|
||||
return Object.assign(target || {}, source);
|
||||
}
|
||||
|
||||
|
||||
// Watch mode
|
||||
if (process.argv.includes('--watch')) {
|
||||
console.log('👀 Watching for changes...');
|
||||
|
||||
let debounceTimer;
|
||||
const debounceDelay = 100;
|
||||
|
||||
// Initial validation
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(() => {
|
||||
main();
|
||||
}, debounceDelay);
|
||||
|
||||
// Watch for changes in the compiled directory
|
||||
chokidar.watch(SOURCE_DIR, {
|
||||
ignoreInitial: true,
|
||||
ignored: /.*~$/, // Игнорировать скрытые файлы
|
||||
}).on('all', (event, path) => {
|
||||
if (event === 'change' || event === 'add' || event === 'unlink') {
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(() => {
|
||||
console.log(`🔁 Detected changes in ${path} (${event}), recompiling...`);
|
||||
main();
|
||||
}, debounceDelay);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Run once
|
||||
main();
|
||||
}
|
||||
4
src/localization/source/en/commands/add_worklog.yaml
Normal file
4
src/localization/source/en/commands/add_worklog.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
manual:
|
||||
name: Update work log in Jira manually
|
||||
batch:
|
||||
name: Update work log in Jira by batch
|
||||
3
src/localization/source/en/commands/create_issue.yaml
Normal file
3
src/localization/source/en/commands/create_issue.yaml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
name: Create issue in Jira
|
||||
success: Issue {issueKey} created successfully
|
||||
error: Error creating issue
|
||||
2
src/localization/source/en/commands/get_issue.yaml
Normal file
2
src/localization/source/en/commands/get_issue.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
with_key: Get issue from Jira with custom key
|
||||
without_key: Get current issue from Jira
|
||||
3
src/localization/source/en/commands/update_issue.yaml
Normal file
3
src/localization/source/en/commands/update_issue.yaml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
name: Update issue in Jira
|
||||
success: Issue {issueKey} updated successfully
|
||||
error: Error updating issue
|
||||
3
src/localization/source/en/commands/update_status.yaml
Normal file
3
src/localization/source/en/commands/update_status.yaml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
name: Update issue status in Jira
|
||||
success: Issue {issueKey} updated successfully
|
||||
error: Error updating issue
|
||||
1
src/localization/source/en/modals/project.yaml
Normal file
1
src/localization/source/en/modals/project.yaml
Normal file
|
|
@ -0,0 +1 @@
|
|||
placeholder: Search for a project...
|
||||
8
src/localization/source/en/modals/search.yaml
Normal file
8
src/localization/source/en/modals/search.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
desc: Search issue by key
|
||||
|
||||
key:
|
||||
name: Issue key
|
||||
placeholder: Issue key
|
||||
desc: Enter the Jira issue key (e.g., PROJECT-123)
|
||||
|
||||
submit: Search
|
||||
1
src/localization/source/en/modals/status.yaml
Normal file
1
src/localization/source/en/modals/status.yaml
Normal file
|
|
@ -0,0 +1 @@
|
|||
placeholder: Search for an issue status transition...
|
||||
1
src/localization/source/en/modals/type.yaml
Normal file
1
src/localization/source/en/modals/type.yaml
Normal file
|
|
@ -0,0 +1 @@
|
|||
placeholder: Search for an issue type...
|
||||
26
src/localization/source/en/modals/worklog.yaml
Normal file
26
src/localization/source/en/modals/worklog.yaml
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
name: Add work log
|
||||
|
||||
spent:
|
||||
name: Time spent
|
||||
desc: "Format: weeks days hours, minutes"
|
||||
placeholder: "3w 4d 12h 30m"
|
||||
|
||||
start:
|
||||
name: Start datetime
|
||||
desc: "Format: DD/MMM/YY HH:MM"
|
||||
placeholder: "03/feb/25 15:22"
|
||||
|
||||
comment:
|
||||
name: Work description
|
||||
desc: Optional
|
||||
placeholder: ""
|
||||
|
||||
months: ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"]
|
||||
|
||||
submit: Submit
|
||||
|
||||
warns:
|
||||
no_time: Time spent is required
|
||||
invalid_time: Invalid time format. Use combinations of w (weeks), d (days), h (hours), m (minutes)
|
||||
no_start: Start datetime is required
|
||||
invalid_start: Invalid date format. Use DD/MMM/YY HH:MM AM/PM
|
||||
34
src/localization/source/en/settings/connection.yaml
Normal file
34
src/localization/source/en/settings/connection.yaml
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
title: Connection settings
|
||||
|
||||
url:
|
||||
title: Jira URL
|
||||
desc: Your Jira instance URL
|
||||
def: https://yourcompany.atlassian.net
|
||||
|
||||
auth:
|
||||
title: Authentication method
|
||||
desc: Choose how to authenticate with Jira
|
||||
options:
|
||||
bearer: Bearer Token (PAT)
|
||||
basic: Basic Auth (Username + PAT)
|
||||
session: Session Cookie (Username + Password)
|
||||
|
||||
pat:
|
||||
title: Jira PAT
|
||||
desc: Personal Access Token
|
||||
def: NjM5MDI5MzQ0NT...
|
||||
|
||||
username:
|
||||
title: Jira username
|
||||
desc: Your Jira username
|
||||
def: admin
|
||||
|
||||
email:
|
||||
title: Jira email
|
||||
desc: Your Jira email
|
||||
def: admin@gmail.com
|
||||
|
||||
password:
|
||||
title: Jira password
|
||||
desc: Your Jira password
|
||||
def: qwerty
|
||||
23
src/localization/source/en/settings/fm.yaml
Normal file
23
src/localization/source/en/settings/fm.yaml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
title: Field mappings
|
||||
desc: Configure how fields are mapped between Obsidian and Jira. Each field requires both a 'To Jira' and 'From Jira' transformation function.
|
||||
fv:
|
||||
name: Enable field validation
|
||||
desc: Check fields before saving based on the standard API response (validation does not account for custom fields)
|
||||
secNote:
|
||||
name: "⚠️ Note: "
|
||||
desc: Field mapping uses JavaScript lambda functions.
|
||||
example:
|
||||
name: Example field mapping
|
||||
use: Use
|
||||
|
||||
from_jira: From Jira
|
||||
to_jira: To Jira
|
||||
field_name: Field name
|
||||
|
||||
add_mapping_tooltip: Add new field mapping
|
||||
reset_tooltip: Reset
|
||||
reset_confirm_title: Reset field mapping
|
||||
reset_confirm_text: Field mapping will be cleaned up. Are you sure?
|
||||
reload_defaults_tooltip: Reload
|
||||
reload_confirm_title: Reload default field mapping
|
||||
reload_confirm_text: Current field mappings will be overwritten with the default mappings. Are you sure?
|
||||
13
src/localization/source/en/settings/general.yaml
Normal file
13
src/localization/source/en/settings/general.yaml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
title: General settings
|
||||
|
||||
folder:
|
||||
name: Issues folder
|
||||
desc: Folder where Jira issues will be stored and searched for
|
||||
|
||||
template:
|
||||
name: Template path
|
||||
desc: Select a template file for creating Jira tasks
|
||||
selector:
|
||||
placeholder: Search templates...
|
||||
empty: Template is not chosen
|
||||
warning: No template plugins are currently enabled (Core Templates or Community Templater).
|
||||
4
src/localization/source/en/settings/issue_view.yaml
Normal file
4
src/localization/source/en/settings/issue_view.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
title: Raw issue viewer
|
||||
key:
|
||||
name: View raw issue data
|
||||
desc: Enter a Jira issue key to view its raw data from API
|
||||
1
src/localization/source/en/settings/kanban.yaml
Normal file
1
src/localization/source/en/settings/kanban.yaml
Normal file
|
|
@ -0,0 +1 @@
|
|||
title: Kanban board
|
||||
58
src/localization/source/en/settings/statistics.yaml
Normal file
58
src/localization/source/en/settings/statistics.yaml
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
title: Timekeep work log statistics
|
||||
description: Manage and send work logs to Jira
|
||||
|
||||
settings:
|
||||
max_weeks:
|
||||
name: Maximum Weeks to Show
|
||||
description: Number of recent weeks to display
|
||||
|
||||
time_range:
|
||||
name: Time Range
|
||||
description: Select the time range for grouping entries
|
||||
options:
|
||||
days: Days
|
||||
weeks: Weeks
|
||||
months: Months
|
||||
custom: Custom Range
|
||||
|
||||
max_items:
|
||||
name: Maximum Items to Show
|
||||
description: Number of recent time periods to display
|
||||
|
||||
date_from:
|
||||
name: From Date
|
||||
description: Start date for custom range
|
||||
placeholder: YYYY-MM-DD
|
||||
|
||||
date_to:
|
||||
name: To Date
|
||||
description: End date for custom range
|
||||
placeholder: YYYY-MM-DD
|
||||
|
||||
display:
|
||||
select_period: Select Period
|
||||
select_period_placeholder: Select a time period...
|
||||
selected_period: "Selected: {period}"
|
||||
all_periods: All Recent Periods
|
||||
no_entries: No timekeep entries found.
|
||||
period_of: Period of {date}
|
||||
|
||||
table:
|
||||
task: Task
|
||||
block_path: Subtask
|
||||
start_time: Start Time
|
||||
duration: Duration
|
||||
issue_key: Issue Key
|
||||
|
||||
actions:
|
||||
send_to_jira: Send Work Log to Jira
|
||||
refresh_data: Refresh Data
|
||||
|
||||
messages:
|
||||
select_period_first: Please select a time period first.
|
||||
refresh_success: Data refreshed successfully
|
||||
refresh_error: "Failed to refresh data: {error}"
|
||||
send_success: Work log sent to Jira successfully
|
||||
send_error: "Failed to send work log to Jira: {error}"
|
||||
invalid_date_range: Invalid date range. End date must be after start date.
|
||||
custom_range_required: Please specify both start and end dates for custom range.
|
||||
2
src/localization/source/en/settings/suggest_select.yaml
Normal file
2
src/localization/source/en/settings/suggest_select.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
placeholder: Search...
|
||||
empty_option_text: No selection
|
||||
11
src/localization/source/en/settings/tfm.yaml
Normal file
11
src/localization/source/en/settings/tfm.yaml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
title: Test field mapping
|
||||
name: Test field mapping
|
||||
desc: Test your field mapping against the current issue data from Raw issue viewer. Enter a fromJira expression to see the result.
|
||||
no_data: No test field mapping
|
||||
invalid_exp: Invalid expression
|
||||
|
||||
from_jira: From Jira
|
||||
value: Value
|
||||
|
||||
add_mapping_tooltip: Add test mapping entry
|
||||
reset_tooltip: Reset
|
||||
4
src/localization/source/ru/commands/add_worklog.yaml
Normal file
4
src/localization/source/ru/commands/add_worklog.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
manual:
|
||||
name: Вести журнал работы в Jira вручную
|
||||
batch:
|
||||
name: Вести журнал работы в Jira массово
|
||||
3
src/localization/source/ru/commands/create_issue.yaml
Normal file
3
src/localization/source/ru/commands/create_issue.yaml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
name: Создать задачу в Jira
|
||||
success: Задача {issueKey} успешно создана
|
||||
error: Ошибка при создании задачи
|
||||
2
src/localization/source/ru/commands/get_issue.yaml
Normal file
2
src/localization/source/ru/commands/get_issue.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
with_key: Получить данные задачи из Jira по указанному ключу
|
||||
without_key: Получить данные текущей задачи из Jira
|
||||
3
src/localization/source/ru/commands/update_issue.yaml
Normal file
3
src/localization/source/ru/commands/update_issue.yaml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
name: Обновить задачу в Jira
|
||||
success: Задача {issueKey} успешно обновлена
|
||||
error: Ошибка при обновлении задачи
|
||||
3
src/localization/source/ru/commands/update_status.yaml
Normal file
3
src/localization/source/ru/commands/update_status.yaml
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
name: Обновить статус задачи в Jira
|
||||
success: Статус задачи {issueKey} успешно обновлён
|
||||
error: Ошибка при обновлении статуса задачи
|
||||
1
src/localization/source/ru/modals/project.yaml
Normal file
1
src/localization/source/ru/modals/project.yaml
Normal file
|
|
@ -0,0 +1 @@
|
|||
placeholder: Поиск проекта...
|
||||
8
src/localization/source/ru/modals/search.yaml
Normal file
8
src/localization/source/ru/modals/search.yaml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
desc: Поиск задачи по номеру
|
||||
|
||||
key:
|
||||
name: Номер задачи
|
||||
placeholder: Номер задачи
|
||||
desc: Введите номер задачи Jira (например, PROJECT-123)
|
||||
|
||||
submit: Поиск
|
||||
1
src/localization/source/ru/modals/status.yaml
Normal file
1
src/localization/source/ru/modals/status.yaml
Normal file
|
|
@ -0,0 +1 @@
|
|||
placeholder: Поиск смены статуса задачи...
|
||||
1
src/localization/source/ru/modals/type.yaml
Normal file
1
src/localization/source/ru/modals/type.yaml
Normal file
|
|
@ -0,0 +1 @@
|
|||
placeholder: Поиск типа задачи...
|
||||
35
src/localization/source/ru/modals/worklog.yaml
Normal file
35
src/localization/source/ru/modals/worklog.yaml
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
name: Вести журнал работы
|
||||
|
||||
spent:
|
||||
name: Затраченное время
|
||||
desc: 'Формат: недели дни часы, минуты'
|
||||
placeholder: 3w 4d 12h 30m
|
||||
start:
|
||||
name: Дата и время начала
|
||||
desc: 'Формат: DD/MMM/YY HH:MM'
|
||||
placeholder: 03/фев/25 15:22
|
||||
comment:
|
||||
name: Описание работы
|
||||
desc: Необязательно
|
||||
placeholder: ''
|
||||
|
||||
months:
|
||||
- янв
|
||||
- фев
|
||||
- мар
|
||||
- апр
|
||||
- май
|
||||
- июн
|
||||
- июл
|
||||
- авг
|
||||
- сен
|
||||
- окт
|
||||
- ноя
|
||||
- дек
|
||||
submit: Отправить
|
||||
warns:
|
||||
no_time: Необходимо указать затраченное время
|
||||
invalid_time: Неверный формат времени. Используйте комбинации w (недели), d
|
||||
(дни), h (часы), m (минуты)
|
||||
no_start: Необходимо указать дату начала
|
||||
invalid_start: Неверный формат даты. Используйте DD/MMM/YY HH:MM AM/PM
|
||||
28
src/localization/source/ru/settings/connection.yaml
Normal file
28
src/localization/source/ru/settings/connection.yaml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
title: Настройки подключения
|
||||
url:
|
||||
title: URL Jira
|
||||
desc: URL вашей инстанции Jira
|
||||
def: https://yourcompany.atlassian.net
|
||||
auth:
|
||||
title: Метод аутентификации
|
||||
desc: Выберите способ подключения к Jira
|
||||
options:
|
||||
bearer: Bearer Token (PAT)
|
||||
basic: Basic Auth (Имя пользователя + PAT)
|
||||
session: Session Cookie (Имя пользователя + Пароль)
|
||||
pat:
|
||||
title: Jira PAT
|
||||
desc: Персональный токен доступа
|
||||
def: NjM5MDI5MzQ0NT...
|
||||
username:
|
||||
title: Имя пользователя Jira
|
||||
desc: Ваше имя пользователя Jira
|
||||
def: admin
|
||||
email:
|
||||
title: Email Jira
|
||||
desc: Ваш email в Jira
|
||||
def: admin@gmail.com
|
||||
password:
|
||||
title: Пароль Jira
|
||||
desc: Ваш пароль от Jira
|
||||
def: qwerty
|
||||
24
src/localization/source/ru/settings/fm.yaml
Normal file
24
src/localization/source/ru/settings/fm.yaml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
title: Сопоставление полей
|
||||
desc: Настройте, как поля будут сопоставляться между Obsidian и Jira. Для каждого
|
||||
поля требуется функция преобразования 'В Jira' и 'Из Jira'.
|
||||
fv:
|
||||
name: Включить проверку полей
|
||||
desc: Проверять поля перед сохранением базируясь на стандартном ответе API
|
||||
(при проверке не учитываются пользовательские поля)
|
||||
secNote:
|
||||
name: "⚠️ Примечание: "
|
||||
desc: Сопоставление полей использует JavaScript lambda функции.
|
||||
example:
|
||||
name: Примеры сопоставления полей
|
||||
use: Использовать
|
||||
from_jira: Из Jira
|
||||
to_jira: В Jira
|
||||
field_name: Название поля
|
||||
|
||||
add_mapping_tooltip: Добавить новое сопоставление полей
|
||||
reset_tooltip: Убрать все
|
||||
reset_confirm_title: Сбросить сопоставление полей
|
||||
reset_confirm_text: Сопоставление полей будет очищено. Вы уверены?
|
||||
reload_defaults_tooltip: Обновить
|
||||
reload_confirm_title: Обновить сопоставления полей по умолчанию
|
||||
reload_confirm_text: Текущие сопоставления полей будут перезаписаны сопоставлениями. Вы уверены?
|
||||
14
src/localization/source/ru/settings/general.yaml
Normal file
14
src/localization/source/ru/settings/general.yaml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
title: Общие настройки
|
||||
|
||||
folder:
|
||||
name: Папка для задач
|
||||
desc: Папка, в которой будут создаваться и искаться задачи Jira
|
||||
|
||||
template:
|
||||
name: Путь к шаблону
|
||||
desc: Выберите файл шаблона для создания задач Jira
|
||||
selector:
|
||||
placeholder: Поиск шаблонов...
|
||||
empty: Шаблон не выбран
|
||||
warning: В данный момент не активированы плагины шаблонов (Core Templates или
|
||||
Community Templater).
|
||||
4
src/localization/source/ru/settings/issue_view.yaml
Normal file
4
src/localization/source/ru/settings/issue_view.yaml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
title: Просмотр необработанных данных задачи
|
||||
key:
|
||||
name: Просмотр необработанных данных задачи
|
||||
desc: Введите номер задачи Jira, чтобы просмотреть необработанные данные, полученные из API
|
||||
1
src/localization/source/ru/settings/kanban.yaml
Normal file
1
src/localization/source/ru/settings/kanban.yaml
Normal file
|
|
@ -0,0 +1 @@
|
|||
title: Канбан доска
|
||||
50
src/localization/source/ru/settings/statistics.yaml
Normal file
50
src/localization/source/ru/settings/statistics.yaml
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
title: Статистика ведения журнала работы через Timekeep
|
||||
description: Управление и отправка журнала работы в Jira
|
||||
settings:
|
||||
max_weeks:
|
||||
name: Максимум недель для отображения
|
||||
description: Количество последних недель для показа
|
||||
time_range:
|
||||
name: Период времени
|
||||
description: Выберите период для группировки записей
|
||||
options:
|
||||
days: Дни
|
||||
weeks: Недели
|
||||
months: Месяцы
|
||||
custom: Произвольный период
|
||||
max_items:
|
||||
name: Максимум элементов для отображения
|
||||
description: Количество последних периодов для показа
|
||||
date_from:
|
||||
name: Дата начала
|
||||
description: Дата начала для произвольного периода
|
||||
placeholder: ГГГГ-ММ-ДД
|
||||
date_to:
|
||||
name: Дата окончания
|
||||
description: Дата окончания для произвольного периода
|
||||
placeholder: ГГГГ-ММ-ДД
|
||||
display:
|
||||
select_period: Выбрать период
|
||||
select_period_placeholder: Выберите период...
|
||||
selected_period: 'Выбрано: {period}'
|
||||
all_periods: Все последние периоды
|
||||
no_entries: Записи рабочего лога не найдены.
|
||||
period_of: Период от {date}
|
||||
table:
|
||||
task: Задача
|
||||
block_path: Подзадача
|
||||
start_time: Время начала
|
||||
duration: Длительность
|
||||
issue_key: Номер задачи
|
||||
actions:
|
||||
send_to_jira: Отправить рабочий лог в Jira
|
||||
refresh_data: Обновить данные
|
||||
messages:
|
||||
select_period_first: Сначала выберите период.
|
||||
refresh_success: Данные успешно обновлены
|
||||
refresh_error: 'Ошибка при обновлении данных: {error}'
|
||||
send_success: Рабочий лог успешно отправлен в Jira
|
||||
send_error: 'Не удалось отправить рабочий лог в Jira: {error}'
|
||||
invalid_date_range: Некорректный диапазон дат. Дата окончания должна быть позже
|
||||
даты начала.
|
||||
custom_range_required: Укажите дату начала и окончания для произвольного периода.
|
||||
2
src/localization/source/ru/settings/suggest_select.yaml
Normal file
2
src/localization/source/ru/settings/suggest_select.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
placeholder: Поиск...
|
||||
empty_option_text: Не выбрано
|
||||
11
src/localization/source/ru/settings/tfm.yaml
Normal file
11
src/localization/source/ru/settings/tfm.yaml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
title: Тестирование сопоставлений полей
|
||||
name: Тестирование сопоставлений полей
|
||||
desc: Проверьте сопоставления полей на текущих данных задачи из пункта 'Просмотр необработанных
|
||||
данных задачи'. Введите выражение 'Из Jira', чтобы увидеть результат.
|
||||
no_data: Не выбран номер задачи для тестирования
|
||||
invalid_exp: Некорректное выражение
|
||||
from_jira: Из Jira
|
||||
value: Значение
|
||||
|
||||
add_mapping_tooltip: Добавить тестовое сопоставление полей
|
||||
reset_tooltip: Сбросить
|
||||
68
src/localization/translator.ts
Normal file
68
src/localization/translator.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import * as en from './compiled/en.json';
|
||||
import * as ru from './compiled/ru.json';
|
||||
|
||||
export const defaultLocale = 'en';
|
||||
|
||||
const locales: Record<string, any> = {
|
||||
en,
|
||||
ru,
|
||||
};
|
||||
|
||||
const getCurrentLocale = (): string => {
|
||||
const stored = window.localStorage.getItem('language');
|
||||
return stored && locales[stored] ? stored : defaultLocale;
|
||||
};
|
||||
|
||||
const getNestedValue = (obj: any, path: string): any => {
|
||||
return path.split('.').reduce((current, key) => {
|
||||
return current && current[key] !== undefined ? current[key] : null;
|
||||
}, obj);
|
||||
};
|
||||
|
||||
function replacePlaceholders(template: string, dict: Record<string, unknown>): string {
|
||||
return template.replace(/{([^{}]+)}/g, (match, key) => {
|
||||
// Trim the key in case there's whitespace inside the braces
|
||||
const trimmedKey = key.trim();
|
||||
return dict.hasOwnProperty(trimmedKey) ? String(dict[trimmedKey]) : match;
|
||||
});
|
||||
}
|
||||
|
||||
export const t = (key: string, prefix?: string, dict?: Record<string, unknown>): string => {
|
||||
const currentLocale = getCurrentLocale();
|
||||
const fullPath = prefix ? `${prefix}.${key}` : key;
|
||||
|
||||
const currentDict = locales[currentLocale];
|
||||
const defaultDict = locales[defaultLocale];
|
||||
|
||||
let value = getNestedValue(currentDict, fullPath);
|
||||
|
||||
if (value === null && currentLocale !== defaultLocale) {
|
||||
value = getNestedValue(defaultDict, fullPath);
|
||||
}
|
||||
|
||||
if (value === null) {
|
||||
console.warn(`Translation key not found: "${fullPath}" in locales: ${currentLocale}, ${defaultLocale}`);
|
||||
return fullPath;
|
||||
}
|
||||
|
||||
// Only process dictionary replacements if we have a dict and the value isn't the fallback path
|
||||
if (dict && typeof value === 'string' && value !== fullPath) {
|
||||
return replacePlaceholders(value, dict);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export const useTranslations = (prefix?: string) => {
|
||||
const currentLocale = getCurrentLocale();
|
||||
|
||||
const translate = (key: string, dict?: Record<string, unknown>): string => {
|
||||
return t(key, prefix, dict);
|
||||
};
|
||||
|
||||
return {
|
||||
t: translate,
|
||||
locale: currentLocale,
|
||||
setPrefix: (newPrefix: string) => useTranslations(newPrefix)
|
||||
};
|
||||
};
|
||||
244
src/localization/validator.js
Normal file
244
src/localization/validator.js
Normal file
|
|
@ -0,0 +1,244 @@
|
|||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const chokidar = require('chokidar');
|
||||
|
||||
const COMPILED_DIR = path.join(__dirname, 'compiled');
|
||||
|
||||
// Function to get all keys from an object (including nested ones)
|
||||
function getAllKeys(obj, prefix = '', result = new Set()) {
|
||||
for (const key in obj) {
|
||||
const fullKey = prefix ? `${prefix}.${key}` : key;
|
||||
|
||||
result.add(fullKey);
|
||||
|
||||
if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
|
||||
getAllKeys(obj[key], fullKey, result);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check if a key exists in an object
|
||||
function keyExists(obj, keyPath) {
|
||||
const parts = keyPath.split('.');
|
||||
let current = obj;
|
||||
|
||||
for (const part of parts) {
|
||||
if (current[part] === undefined) {
|
||||
return false;
|
||||
}
|
||||
current = current[part];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get value from nested object using dot notation
|
||||
function getValue(obj, keyPath) {
|
||||
const parts = keyPath.split('.');
|
||||
let current = obj;
|
||||
|
||||
for (const part of parts) {
|
||||
if (current[part] === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
current = current[part];
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
// Function to validate translations
|
||||
function validateTranslations() {
|
||||
console.log('🔍 Validating translation files...');
|
||||
|
||||
// Read all compiled JSON files
|
||||
const files = fs.readdirSync(COMPILED_DIR).filter(file => file.endsWith('.json'));
|
||||
|
||||
if (files.length === 0) {
|
||||
console.log('⚠️ No compiled translation files found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Load all translation data
|
||||
const translations = {};
|
||||
for (const file of files) {
|
||||
const locale = path.basename(file, '.json');
|
||||
const filePath = path.join(COMPILED_DIR, file);
|
||||
translations[locale] = fs.readJsonSync(filePath);
|
||||
}
|
||||
|
||||
// Get all locales
|
||||
const locales = Object.keys(translations);
|
||||
console.log(`📁 Found ${locales.length} locales: ${locales.join(', ')}`);
|
||||
|
||||
if (locales.length < 2) {
|
||||
console.log('⚠️ Need at least 2 locales to compare');
|
||||
return;
|
||||
}
|
||||
|
||||
// Choose the first locale as reference
|
||||
const referenceLocale = locales[0];
|
||||
console.log(`🔑 Using ${referenceLocale} as reference locale`);
|
||||
|
||||
// Get all keys from reference locale
|
||||
const referenceKeys = getAllKeys(translations[referenceLocale]);
|
||||
console.log(`🔢 Reference locale has ${referenceKeys.size} unique keys`);
|
||||
|
||||
// Track statistics
|
||||
const stats = {
|
||||
missingKeys: {},
|
||||
extraKeys: {},
|
||||
typeErrors: {}
|
||||
};
|
||||
|
||||
// Initialize stats for each locale
|
||||
for (const locale of locales) {
|
||||
if (locale !== referenceLocale) {
|
||||
stats.missingKeys[locale] = [];
|
||||
stats.extraKeys[locale] = [];
|
||||
stats.typeErrors[locale] = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Check each locale against the reference
|
||||
for (const locale of locales) {
|
||||
if (locale === referenceLocale) continue;
|
||||
|
||||
const localeKeys = getAllKeys(translations[locale]);
|
||||
|
||||
// Check for missing keys
|
||||
for (const key of referenceKeys) {
|
||||
if (!keyExists(translations[locale], key)) {
|
||||
stats.missingKeys[locale].push(key);
|
||||
} else {
|
||||
// Check for type mismatches
|
||||
const refValue = getValue(translations[referenceLocale], key);
|
||||
const localeValue = getValue(translations[locale], key);
|
||||
|
||||
if (typeof refValue !== typeof localeValue) {
|
||||
stats.typeErrors[locale].push({
|
||||
key,
|
||||
refType: typeof refValue,
|
||||
localeType: typeof localeValue
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for extra keys
|
||||
for (const key of localeKeys) {
|
||||
if (!keyExists(translations[referenceLocale], key)) {
|
||||
stats.extraKeys[locale].push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Print results
|
||||
let hasIssues = false;
|
||||
|
||||
// Print missing keys
|
||||
for (const locale in stats.missingKeys) {
|
||||
const missing = stats.missingKeys[locale];
|
||||
if (missing.length > 0) {
|
||||
hasIssues = true;
|
||||
console.log(`❌ ${locale} is missing ${missing.length} keys:`);
|
||||
missing.forEach(key => {
|
||||
console.log(` - ${key}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Print extra keys
|
||||
for (const locale in stats.extraKeys) {
|
||||
const extra = stats.extraKeys[locale];
|
||||
if (extra.length > 0) {
|
||||
hasIssues = true;
|
||||
console.log(`⚠️ ${locale} has ${extra.length} extra keys:`);
|
||||
extra.forEach(key => {
|
||||
console.log(` - ${key}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Print type errors
|
||||
for (const locale in stats.typeErrors) {
|
||||
const typeErrors = stats.typeErrors[locale];
|
||||
if (typeErrors.length > 0) {
|
||||
hasIssues = true;
|
||||
console.log(`⚠️ ${locale} has ${typeErrors.length} type mismatches:`);
|
||||
typeErrors.forEach(err => {
|
||||
console.log(` - ${err.key}: expected ${err.refType}, got ${err.localeType}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Print empty values check if needed
|
||||
console.log('\n📊 Checking for empty values...');
|
||||
for (const locale of locales) {
|
||||
checkEmptyValues(translations[locale], locale);
|
||||
}
|
||||
|
||||
if (!hasIssues) {
|
||||
console.log('✅ All locales have consistent structure!');
|
||||
}
|
||||
|
||||
return hasIssues;
|
||||
}
|
||||
|
||||
// Function to check for empty values
|
||||
function checkEmptyValues(obj, locale, prefix = '') {
|
||||
for (const key in obj) {
|
||||
const fullKey = prefix ? `${prefix}.${key}` : key;
|
||||
const value = obj[key];
|
||||
|
||||
if (value === '') {
|
||||
console.log(`⚠️ ${locale} has empty string at ${fullKey}`);
|
||||
} else if (value === null) {
|
||||
console.log(`⚠️ ${locale} has null value at ${fullKey}`);
|
||||
} else if (typeof value === 'object' && !Array.isArray(value)) {
|
||||
checkEmptyValues(value, locale, fullKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Main function
|
||||
function main() {
|
||||
// Create compiled directory if it doesn't exist
|
||||
fs.ensureDirSync(COMPILED_DIR);
|
||||
|
||||
// Run validation
|
||||
validateTranslations();
|
||||
}
|
||||
|
||||
// Watch mode
|
||||
if (process.argv.includes('--watch')) {
|
||||
console.log('👀 Watching for changes...');
|
||||
|
||||
let debounceTimer;
|
||||
const debounceDelay = 100;
|
||||
|
||||
// Initial validation
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(() => {
|
||||
main();
|
||||
}, debounceDelay);
|
||||
|
||||
// Watch for changes in the compiled directory
|
||||
chokidar.watch(COMPILED_DIR, {
|
||||
ignoreInitial: true,
|
||||
ignored: /.*~$/, // Игнорировать скрытые файлы
|
||||
}).on('all', (event, path) => {
|
||||
if (event === 'change' || event === 'add' || event === 'unlink') {
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(() => {
|
||||
console.log(`🔁 Detected changes in ${path} (${event}), revalidating...`);
|
||||
main();
|
||||
}, debounceDelay);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Run once
|
||||
main();
|
||||
}
|
||||
|
|
@ -3,13 +3,11 @@ import { JiraSettingTab } from "./settings/JiraSettingTab";
|
|||
import { JiraIssueType, JiraProject } from "./interfaces";
|
||||
import {DEFAULT_SETTINGS, JiraSettings} from "./settings/default";
|
||||
import {
|
||||
registerUpdateIssueCommand,
|
||||
registerGetIssueCommand,
|
||||
registerUpdateIssueCommand, registerUpdateWorkLogManuallyCommand,
|
||||
registerGetIssueCommand, registerUpdateWorkLogBatchCommand,
|
||||
registerCreateIssueCommand, registerGetIssueCommandWithKey, registerUpdateIssueStatusCommand
|
||||
} from "./commands";
|
||||
import {registerUpdateWorkLogManuallyCommand} from "./commands/addWorkLogManually";
|
||||
import {transform_string_to_functions_mappings} from "./tools/convertFunctionString";
|
||||
import {registerUpdateWorkLogBatchCommand} from "./commands/addWorkLogBatch";
|
||||
|
||||
/**
|
||||
* Main plugin class
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import {App, Modal, Setting} from "obsidian";
|
||||
import {useTranslations} from "../localization/translator";
|
||||
|
||||
const t = useTranslations("modals.search").t;
|
||||
/**
|
||||
* Modal for searching issues by key
|
||||
*/
|
||||
|
|
@ -13,14 +15,14 @@ export class IssueSearchModal extends Modal {
|
|||
}
|
||||
|
||||
onOpen() {
|
||||
this.contentEl.createEl("h2", {text: "Search issue by key"});
|
||||
this.contentEl.createEl("h2", {text: t("desc")});
|
||||
|
||||
new Setting(this.contentEl)
|
||||
.setName("Issue key")
|
||||
.setDesc("Enter the Jira issue key (e.g., PROJECT-123)")
|
||||
.setName(t("key.name"))
|
||||
.setDesc(t("key.desc"))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("Issue key")
|
||||
.setPlaceholder(t("key.placeholder"))
|
||||
.onChange((value) => {
|
||||
this.issueKey = value;
|
||||
})
|
||||
|
|
@ -29,7 +31,7 @@ export class IssueSearchModal extends Modal {
|
|||
new Setting(this.contentEl)
|
||||
.addButton((btn) =>
|
||||
btn
|
||||
.setButtonText("Search")
|
||||
.setButtonText(t("submit"))
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
this.close();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import {App, SuggestModal} from "obsidian";
|
||||
import {JiraTransitionType} from "../interfaces";
|
||||
import {useTranslations} from "../localization/translator";
|
||||
|
||||
const t = useTranslations("modals.status").t;
|
||||
|
||||
/**
|
||||
* Modal for selecting an issue status
|
||||
|
|
@ -12,7 +15,7 @@ export class IssueStatusModal extends SuggestModal<JiraTransitionType> {
|
|||
super(app);
|
||||
this.onSubmit = onSubmit;
|
||||
this.issueTransitions = issueTransitions;
|
||||
this.setPlaceholder("Search for an issue transition...");
|
||||
this.setPlaceholder(t("placeholder"));
|
||||
}
|
||||
|
||||
getSuggestions(query: string): JiraTransitionType[] {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import {App, SuggestModal} from "obsidian";
|
||||
import {JiraIssueType} from "../interfaces";
|
||||
import {useTranslations} from "../localization/translator";
|
||||
|
||||
const t = useTranslations("modals.type").t;
|
||||
|
||||
/**
|
||||
* Modal for selecting an issue type
|
||||
|
|
@ -12,7 +15,7 @@ export class IssueTypeModal extends SuggestModal<JiraIssueType> {
|
|||
super(app);
|
||||
this.onSubmit = onSubmit;
|
||||
this.issueTypes = issueTypes;
|
||||
this.setPlaceholder("Search for an issue type...");
|
||||
this.setPlaceholder(t("placeholder"));
|
||||
}
|
||||
|
||||
getSuggestions(query: string): JiraIssueType[] {
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
// modals/WorkLogModal.ts
|
||||
import {App, Modal, Notice, Setting} from "obsidian";
|
||||
import {useTranslations} from "../localization/translator";
|
||||
|
||||
const t = useTranslations("modals.worklog").t;
|
||||
|
||||
/**
|
||||
* Modal for adding work log entries
|
||||
*/
|
||||
export class WorkLogModal extends Modal {
|
||||
export class IssueWorkLogModal extends Modal {
|
||||
private onSubmit: (timeSpent: string, startDate: string, workDescription: string) => void;
|
||||
private timeSpent: string = "";
|
||||
private startDate: string = "";
|
||||
|
|
@ -27,7 +29,7 @@ export class WorkLogModal extends Modal {
|
|||
this.startDate = date.toISOString().split('.')[0] + ".000+0000";
|
||||
|
||||
// Set the display format (01/Jun/21 09:00 AM)
|
||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
const months = t('months') as any;
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
const month = months[date.getMonth()];
|
||||
const year = String(date.getFullYear()).slice(2);
|
||||
|
|
@ -46,12 +48,16 @@ export class WorkLogModal extends Modal {
|
|||
const [day, month, year] = datePart.split('/');
|
||||
const [hours, minutes] = timePart.split(':');
|
||||
|
||||
const months = {'Jan': 0, 'Feb': 1, 'Mar': 2, 'Apr': 3, 'May': 4, 'Jun': 5,
|
||||
'Jul': 6, 'Aug': 7, 'Sep': 8, 'Oct': 9, 'Nov': 10, 'Dec': 11};
|
||||
const months = Object.fromEntries(
|
||||
(t('months') as any).map((month: string, index: number) => [month, index])
|
||||
) as Record<string, number>;
|
||||
|
||||
// {'Jan': 0, 'Feb': 1, 'Mar': 2, 'Apr': 3, 'May': 4, 'Jun': 5,
|
||||
// 'Jul': 6, 'Aug': 7, 'Sep': 8, 'Oct': 9, 'Nov': 10, 'Dec': 11};
|
||||
|
||||
let hour = parseInt(hours);
|
||||
if (ampm === 'PM' && hour < 12) hour += 12;
|
||||
if (ampm === 'AM' && hour === 12) hour = 0;
|
||||
if (ampm.toUpperCase() === 'PM' && hour < 12) hour += 12;
|
||||
if (ampm.toUpperCase() === 'AM' && hour === 12) hour = 0;
|
||||
|
||||
const fullYear = parseInt(year) + 2000;
|
||||
|
||||
|
|
@ -66,14 +72,14 @@ export class WorkLogModal extends Modal {
|
|||
}
|
||||
|
||||
onOpen() {
|
||||
new Setting(this.contentEl).setName('Add work log').setHeading();
|
||||
new Setting(this.contentEl).setName(t('name')).setHeading();
|
||||
// Time Spent field
|
||||
this.timeSpentSetting = new Setting(this.contentEl)
|
||||
.setName("Time spent")
|
||||
.setDesc("Format: 3w 4d 12h 30m (weeks, days, hours, minutes)")
|
||||
.setName(t('spent.name'))
|
||||
.setDesc(t('spent.desc'))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("e.g., 4h 30m")
|
||||
.setPlaceholder(t('spent.placeholder'))
|
||||
.setValue(this.timeSpent)
|
||||
.onChange((value) => {
|
||||
this.timeSpent = value;
|
||||
|
|
@ -81,11 +87,11 @@ export class WorkLogModal extends Modal {
|
|||
);
|
||||
|
||||
this.dateSetting = new Setting(this.contentEl)
|
||||
.setName("Start time")
|
||||
.setDesc("Format: DD/MMM/YY HH:MM AM/PM")
|
||||
.setName(t('start.name'))
|
||||
.setDesc(t('start.desc'))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("e.g., 01/Feb/25 10:00 AM")
|
||||
.setPlaceholder(t('start.placeholder'))
|
||||
.setValue(this.displayDate)
|
||||
.onChange((value) => {
|
||||
this.displayDate = value;
|
||||
|
|
@ -158,11 +164,11 @@ export class WorkLogModal extends Modal {
|
|||
|
||||
// Work Description field
|
||||
new Setting(this.contentEl)
|
||||
.setName("Work description")
|
||||
.setDesc("Optional description of the work done")
|
||||
.setName(t("comment.name"))
|
||||
.setDesc(t("comment.name"))
|
||||
.addTextArea((text) =>
|
||||
text
|
||||
.setPlaceholder("Description of work done...")
|
||||
.setPlaceholder(t("comment.placeholder"))
|
||||
.onChange((value) => {
|
||||
this.workDescription = value;
|
||||
})
|
||||
|
|
@ -179,7 +185,7 @@ export class WorkLogModal extends Modal {
|
|||
new Setting(this.contentEl)
|
||||
.addButton((btn) =>
|
||||
btn
|
||||
.setButtonText("Submit")
|
||||
.setButtonText(t("submit"))
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
if (!this.validateInputs()) {
|
||||
|
|
@ -196,7 +202,7 @@ export class WorkLogModal extends Modal {
|
|||
*/
|
||||
private validateInputs(): boolean {
|
||||
if (!this.timeSpent.trim()) {
|
||||
new Notice("Time spent is required");
|
||||
new Notice(t("warns.no_time"));
|
||||
this.timeSpentSetting.setClass('invalid');
|
||||
return false;
|
||||
}
|
||||
|
|
@ -204,20 +210,20 @@ export class WorkLogModal extends Modal {
|
|||
// Validate time spent format
|
||||
const timeSpentPattern = /^(\d+[wdhm]\s*)+$/;
|
||||
if (!timeSpentPattern.test(this.timeSpent)) {
|
||||
new Notice("Invalid time format. Use combinations of w (weeks), d (days), h (hours), m (minutes)");
|
||||
new Notice(t("warns.invalid_time"));
|
||||
this.timeSpentSetting.setClass('invalid');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.displayDate.trim()) {
|
||||
new Notice("Start date is required");
|
||||
new Notice(t("warns.no_start"));
|
||||
this.dateSetting.setClass('invalid');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate date format by trying to parse it
|
||||
if (!this.parseDisplayDate(this.displayDate)) {
|
||||
new Notice("Invalid date format. Use DD/MMM/YY HH:MM AM/PM");
|
||||
new Notice(t("warns.invalid_start"));
|
||||
this.dateSetting.setClass('invalid');
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
import {App, SuggestModal} from "obsidian";
|
||||
import {JiraProject} from "../interfaces";
|
||||
import {useTranslations} from "../localization/translator";
|
||||
|
||||
const t = useTranslations("modals.project").t;
|
||||
|
||||
/**
|
||||
* Modal for selecting a project
|
||||
|
|
@ -12,7 +15,7 @@ export class ProjectModal extends SuggestModal<JiraProject> {
|
|||
super(app);
|
||||
this.onSubmit = onSubmit;
|
||||
this.projects = projects;
|
||||
this.setPlaceholder("Search for a project...");
|
||||
this.setPlaceholder(t("placeholder"));
|
||||
}
|
||||
|
||||
getSuggestions(query: string): JiraProject[] {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
export * from "./IssueSearchModal";
|
||||
export * from "./IssueStatusModal";
|
||||
export * from "./IssueTypeModal";
|
||||
export * from "./IssueWorkLogModal";
|
||||
export * from "./ProjectModal";
|
||||
|
|
|
|||
|
|
@ -8,7 +8,10 @@ import { RawIssueViewerComponent } from "./components/RawIssueViewerComponent";
|
|||
import { TestFieldMappingsComponent } from "./components/TestFieldMappingsComponent";
|
||||
import { debugLog } from "../tools/debugLogging";
|
||||
import { CollapsibleSection } from "./CollapsibleSection";
|
||||
import {useTranslations} from "../localization/translator";
|
||||
import {TimekeepSettingsComponent} from "./components/TimekeepSettingsComponent";
|
||||
|
||||
const t = useTranslations("settings").t;
|
||||
/**
|
||||
* Settings tab for the Jira plugin
|
||||
* Orchestrates all components and manages the overall settings UI
|
||||
|
|
@ -20,6 +23,7 @@ export class JiraSettingTab extends PluginSettingTab {
|
|||
private fieldMappingsSettings: FieldMappingsComponent;
|
||||
private rawIssueViewer: RawIssueViewerComponent;
|
||||
private testFieldMappings: TestFieldMappingsComponent;
|
||||
private timekeepSettings: TimekeepSettingsComponent;
|
||||
|
||||
constructor(app: App, plugin: JiraPlugin) {
|
||||
super(app, plugin);
|
||||
|
|
@ -39,6 +43,7 @@ export class JiraSettingTab extends PluginSettingTab {
|
|||
this.rawIssueViewer = new RawIssueViewerComponent(componentProps);
|
||||
this.testFieldMappings = new TestFieldMappingsComponent({ ...componentProps, getCurrentIssue: () => this.rawIssueViewer.getCurrentIssue() });
|
||||
this.rawIssueViewer.onIssueDataChange = () => this.testFieldMappings.setCurrentIssue(this.rawIssueViewer.getCurrentIssue());
|
||||
this.timekeepSettings = new TimekeepSettingsComponent(componentProps);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -58,20 +63,25 @@ export class JiraSettingTab extends PluginSettingTab {
|
|||
|
||||
containerEl.empty();
|
||||
|
||||
new CollapsibleSection(this.plugin, containerEl, this.connectionSettings, "Connection settings",
|
||||
// remember to add collapsable section name ("connection", "general", etc.) in settings/default.ts in order to be able to save last state
|
||||
|
||||
new CollapsibleSection(this.plugin, containerEl, this.connectionSettings, t("connection.title"),
|
||||
this.plugin.settings.collapsedSections.connection, "connection").getContentContainer();
|
||||
|
||||
new CollapsibleSection(this.plugin, containerEl, this.generalSettings, "General settings",
|
||||
new CollapsibleSection(this.plugin, containerEl, this.generalSettings, t("general.title"),
|
||||
this.plugin.settings.collapsedSections.general, "general").getContentContainer();
|
||||
|
||||
new CollapsibleSection(this.plugin, containerEl, this.fieldMappingsSettings, "Field mappings",
|
||||
new CollapsibleSection(this.plugin, containerEl, this.fieldMappingsSettings, t("fm.title"),
|
||||
this.plugin.settings.collapsedSections.fieldMappings, "fieldMappings").getContentContainer();
|
||||
|
||||
new CollapsibleSection(this.plugin, containerEl, this.rawIssueViewer, "Raw issue viewer",
|
||||
new CollapsibleSection(this.plugin, containerEl, this.rawIssueViewer, t("issue_view.title"),
|
||||
this.plugin.settings.collapsedSections.rawIssueViewer, "rawIssueViewer").getContentContainer();
|
||||
|
||||
new CollapsibleSection(this.plugin, containerEl, this.testFieldMappings, "Test field mappings",
|
||||
new CollapsibleSection(this.plugin, containerEl, this.testFieldMappings, t("tfm.title"),
|
||||
this.plugin.settings.collapsedSections.testFieldMappings, "testFieldMappings").getContentContainer();
|
||||
|
||||
new CollapsibleSection(this.plugin, containerEl, this.timekeepSettings, t("statistics.title"),
|
||||
this.plugin.settings.collapsedSections.statistics, "statistics").getContentContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
import { Setting } from "obsidian";
|
||||
import { useTranslations } from "src/localization/translator"
|
||||
import { SettingsComponent, SettingsComponentProps } from "../../interfaces/settingsTypes";
|
||||
|
||||
const t = useTranslations("settings.connection").t
|
||||
|
||||
/**
|
||||
* Component for Jira connection settings
|
||||
*/
|
||||
|
|
@ -19,11 +22,11 @@ export class ConnectionSettingsComponent implements SettingsComponent {
|
|||
|
||||
// Always show: Jira URL
|
||||
new Setting(containerEl)
|
||||
.setName("Jira URL")
|
||||
.setDesc("Your Jira instance URL")
|
||||
.setName(t("url.title"))
|
||||
.setDesc(t("url.desc"))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("https://yourcompany.atlassian.net")
|
||||
.setPlaceholder(t("url.def"))
|
||||
.setValue(plugin.settings.jiraUrl)
|
||||
.onChange(async (value) => {
|
||||
plugin.settings.jiraUrl = value;
|
||||
|
|
@ -32,14 +35,14 @@ export class ConnectionSettingsComponent implements SettingsComponent {
|
|||
);
|
||||
|
||||
// Auth Method Select
|
||||
const authMethodSetting = new Setting(containerEl)
|
||||
.setName("Authentication method")
|
||||
.setDesc("Choose how to authenticate with Jira")
|
||||
new Setting(containerEl)
|
||||
.setName(t("auth.title"))
|
||||
.setDesc(t("auth.desc"))
|
||||
.addDropdown((cb) =>
|
||||
cb
|
||||
.addOption("bearer", "Bearer Token (PAT)")
|
||||
.addOption("basic", "Basic Auth (Username + PAT)")
|
||||
.addOption("session", "Session Cookie (Username + Password)")
|
||||
.addOption("bearer", t("auth.options.bearer"))
|
||||
.addOption("basic", t("auth.options.basic"))
|
||||
.addOption("session", t("auth.options.session"))
|
||||
.setValue(this.currentAuthMethod)
|
||||
.onChange(async (value: "bearer" | "basic" | "session") => {
|
||||
plugin.settings.authMethod = value;
|
||||
|
|
@ -54,12 +57,12 @@ export class ConnectionSettingsComponent implements SettingsComponent {
|
|||
|
||||
// Jira PAT
|
||||
const patSetting = new Setting(containerEl)
|
||||
.setName("Jira PAT")
|
||||
.setDesc("Personal Access Token")
|
||||
.setName(t("pat.title"))
|
||||
.setDesc(t("pat.desc"))
|
||||
.addText((text) => {
|
||||
text.inputEl.type = "password";
|
||||
text
|
||||
.setPlaceholder("NjM5MDI5MzQ0NT...")
|
||||
.setPlaceholder(t("pat.def"))
|
||||
.setValue(plugin.settings.apiToken)
|
||||
.onChange(async (value) => {
|
||||
plugin.settings.apiToken = value;
|
||||
|
|
@ -70,11 +73,11 @@ export class ConnectionSettingsComponent implements SettingsComponent {
|
|||
|
||||
// Jira username
|
||||
const usernameSetting = new Setting(containerEl)
|
||||
.setName("Jira username")
|
||||
.setDesc("Your Jira username")
|
||||
.setName(t("username.title"))
|
||||
.setDesc(t("username.desc"))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("admin")
|
||||
.setPlaceholder(t("username.def"))
|
||||
.setValue(plugin.settings.username)
|
||||
.onChange(async (value) => {
|
||||
plugin.settings.username = value;
|
||||
|
|
@ -85,11 +88,11 @@ export class ConnectionSettingsComponent implements SettingsComponent {
|
|||
|
||||
// Jira username
|
||||
const emailSetting = new Setting(containerEl)
|
||||
.setName("Jira email")
|
||||
.setDesc("Your Jira email")
|
||||
.setName(t("username.title"))
|
||||
.setDesc(t("username.desc"))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("admin@gmail.com")
|
||||
.setPlaceholder(t("username.def"))
|
||||
.setValue(plugin.settings.email)
|
||||
.onChange(async (value) => {
|
||||
plugin.settings.email = value;
|
||||
|
|
@ -100,12 +103,12 @@ export class ConnectionSettingsComponent implements SettingsComponent {
|
|||
|
||||
// Jira password
|
||||
const passwordSetting = new Setting(containerEl)
|
||||
.setName("Jira password")
|
||||
.setDesc("Your Jira password")
|
||||
.setName(t("password.title"))
|
||||
.setDesc(t("password.desc"))
|
||||
.addText((text) => {
|
||||
text.inputEl.type = "password";
|
||||
text
|
||||
.setPlaceholder("qwerty")
|
||||
.setPlaceholder(t("password.def"))
|
||||
.setValue(plugin.settings.password)
|
||||
.onChange(async (value) => {
|
||||
plugin.settings.password = value;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { validateField } from "../tools/fieldValidation";
|
||||
import {useTranslations} from "../../localization/translator";
|
||||
|
||||
const t = useTranslations("settings.fm").t;
|
||||
/**
|
||||
* Interface for field mapping item props
|
||||
*/
|
||||
|
|
@ -49,37 +51,40 @@ export class FieldMappingItem {
|
|||
|
||||
// Create field name input
|
||||
const fieldNameContainer = this.fieldContainer.createDiv({ cls: "field-name-container" });
|
||||
fieldNameContainer.createEl("span", { text: "Field Name:", cls: "field-mapping-label" });
|
||||
fieldNameContainer.createEl("span", { text: t("field_name"), cls: "field-mapping-label" });
|
||||
this.fieldNameInput = fieldNameContainer.createEl("input", {
|
||||
type: "text",
|
||||
value: fieldName,
|
||||
cls: "field-name-input"
|
||||
cls: "field-name-input",
|
||||
placeholder: t("field_name_placeholder") || "e.g., summary, assignee, priority"
|
||||
});
|
||||
|
||||
// Create functions container (grid layout)
|
||||
const functionsContainer = this.fieldContainer.createDiv({ cls: "functions-container" });
|
||||
|
||||
// Create toJira function input
|
||||
const toJiraContainer = this.fieldContainer.createDiv({ cls: "to-jira-container" });
|
||||
toJiraContainer.createEl("span", { text: "to Jira:", cls: "field-mapping-label" });
|
||||
const toJiraContainer = functionsContainer.createDiv({ cls: "to-jira-container" });
|
||||
toJiraContainer.createEl("span", { text: t("to_jira"), cls: "field-mapping-label" });
|
||||
this.toJiraInput = toJiraContainer.createEl("textarea", {
|
||||
cls: "to-jira-input"
|
||||
});
|
||||
this.toJiraInput.placeholder = "(value) => null";
|
||||
this.toJiraInput.placeholder = "(value) => {\n // Transform Obsidian value to Jira\n return value;\n}";
|
||||
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" });
|
||||
const fromJiraContainer = functionsContainer.createDiv({ cls: "from-jira-container" });
|
||||
fromJiraContainer.createEl("span", { text: t("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;
|
||||
this.fromJiraInput.placeholder = "(issue, data_source) => {\n // Extract value from Jira issue\n return issue.fields.fieldName;\n}";
|
||||
|
||||
// Add remove button
|
||||
const removeBtn = this.fieldContainer.createEl("button", {
|
||||
text: "✕",
|
||||
cls: "remove-field-btn"
|
||||
cls: "remove-field-btn",
|
||||
attr: { 'aria-label': t("remove_field") || 'Remove field mapping' }
|
||||
});
|
||||
|
||||
// Handle field removal
|
||||
|
|
@ -88,10 +93,27 @@ export class FieldMappingItem {
|
|||
this.props.onRemove();
|
||||
});
|
||||
|
||||
// Auto-resize textareas
|
||||
this.setupAutoResize(this.toJiraInput);
|
||||
this.setupAutoResize(this.fromJiraInput);
|
||||
|
||||
// Setup validation
|
||||
this.setupValidation(enableValidation);
|
||||
}
|
||||
|
||||
private setupAutoResize(textarea: HTMLTextAreaElement) {
|
||||
const resize = () => {
|
||||
textarea.style.height = 'auto';
|
||||
textarea.style.height = Math.max(40, textarea.scrollHeight) + 'px';
|
||||
};
|
||||
|
||||
textarea.addEventListener('input', resize);
|
||||
textarea.addEventListener('focus', resize);
|
||||
|
||||
// Initial resize
|
||||
setTimeout(resize, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up validation for inputs
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -3,8 +3,11 @@ import { SettingsComponent, SettingsComponentProps } from "../../interfaces/sett
|
|||
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 { obsidianJiraFieldMappings } from "../../default/obsidianJiraFieldsMapping";
|
||||
import { debugLog } from "../../tools/debugLogging";
|
||||
import {useTranslations} from "../../localization/translator";
|
||||
|
||||
const t = useTranslations("settings.fm").t;
|
||||
|
||||
/**
|
||||
* Component for field mappings section
|
||||
|
|
@ -29,13 +32,13 @@ export class FieldMappingsComponent implements SettingsComponent {
|
|||
|
||||
// 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."
|
||||
text: t("desc")
|
||||
});
|
||||
|
||||
// Add validation toggle
|
||||
new Setting(mappingSection)
|
||||
.setName('Enable field validation')
|
||||
.setDesc('Check fields before saving as functions')
|
||||
.setName(t("fv.name"))
|
||||
.setDesc(t("fv.desc"))
|
||||
.addToggle(toggle => toggle
|
||||
.setValue(plugin.settings.enableFieldValidation)
|
||||
.onChange(async (value) => {
|
||||
|
|
@ -56,8 +59,9 @@ export class FieldMappingsComponent implements SettingsComponent {
|
|||
|
||||
// Add button to add new field mapping
|
||||
const addFieldBtn = buttonView.createEl("button", {
|
||||
text: "",
|
||||
cls: "add-field-mapping-btn"
|
||||
text: t("add_mapping") || "Add Mapping",
|
||||
cls: "add-field-mapping-btn",
|
||||
attr: { 'data-tooltip': t("add_mapping_tooltip") || "Add new field mapping" }
|
||||
});
|
||||
setIcon(addFieldBtn, "circle-plus");
|
||||
|
||||
|
|
@ -67,26 +71,42 @@ export class FieldMappingsComponent implements SettingsComponent {
|
|||
|
||||
// Reset button
|
||||
const resetBtn = buttonView.createEl("button", {
|
||||
text: "",
|
||||
cls: "reset-field-mappings-btn"
|
||||
text: t("reset") || "Reset All",
|
||||
cls: "reset-field-mappings-btn",
|
||||
attr: { 'data-tooltip': t("reset_tooltip") || "Clear all field mappings" }
|
||||
});
|
||||
setIcon(resetBtn, "refresh-cw");
|
||||
|
||||
resetBtn.addEventListener("click", async () => {
|
||||
// Add confirmation dialog
|
||||
const confirmed = await this.showConfirmDialog(
|
||||
t("reset_confirm_title") || "Reset Field Mappings",
|
||||
t("reset_confirm_text") || "Are you sure you want to clear all field mappings? This action cannot be undone."
|
||||
);
|
||||
|
||||
if (confirmed) {
|
||||
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"
|
||||
text: t("reload_defaults") || "Load Defaults",
|
||||
cls: "reload-field-mappings-btn",
|
||||
attr: { 'data-tooltip': t("reload_defaults_tooltip") || "Restore default field mappings" }
|
||||
});
|
||||
setIcon(reloadBtn, "list-restart");
|
||||
|
||||
reloadBtn.addEventListener("click", async () => {
|
||||
const confirmed = await this.showConfirmDialog(
|
||||
t("reload_confirm_title") || "Load Default Mappings",
|
||||
t("reload_confirm_text") || "This will replace all current mappings with defaults. Continue?"
|
||||
);
|
||||
|
||||
if (confirmed) {
|
||||
plugin.settings.fieldMappings = obsidianJiraFieldMappings;
|
||||
|
||||
// Also reset the string representations with default values
|
||||
|
|
@ -101,6 +121,7 @@ export class FieldMappingsComponent implements SettingsComponent {
|
|||
|
||||
await this.loadExistingMappings();
|
||||
await plugin.saveSettings();
|
||||
}
|
||||
});
|
||||
|
||||
// Load existing mappings
|
||||
|
|
@ -111,9 +132,41 @@ export class FieldMappingsComponent implements SettingsComponent {
|
|||
|
||||
// Add security notice
|
||||
const securityNote = containerEl.createEl("div", { cls: "setting-item-description" });
|
||||
securityNote.createEl("strong", { text: "⚠️ Security note: " });
|
||||
securityNote.createEl("strong", { text: t("secNote.name") });
|
||||
securityNote.createSpan({
|
||||
text: "Field mapping uses JavaScript function strings. Validate any shared mappings to prevent security issues."
|
||||
text: t("secNote.desc")
|
||||
});
|
||||
}
|
||||
|
||||
private async showConfirmDialog(title: string, message: string): Promise<boolean> {
|
||||
return new Promise((resolve) => {
|
||||
const modal = document.createElement('div');
|
||||
modal.className = 'modal-container';
|
||||
modal.innerHTML = `
|
||||
<div class="modal">
|
||||
<div class="modal-title">${title}</div>
|
||||
<div class="modal-content">${message}</div>
|
||||
<div class="modal-button-container">
|
||||
<button class="mod-cta" id="confirm-btn">Confirm</button>
|
||||
<button id="cancel-btn">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(modal);
|
||||
|
||||
const confirmBtn = modal.querySelector('#confirm-btn') as HTMLButtonElement;
|
||||
const cancelBtn = modal.querySelector('#cancel-btn') as HTMLButtonElement;
|
||||
|
||||
confirmBtn?.addEventListener('click', () => {
|
||||
document.body.removeChild(modal);
|
||||
resolve(true);
|
||||
});
|
||||
|
||||
cancelBtn?.addEventListener('click', () => {
|
||||
document.body.removeChild(modal);
|
||||
resolve(false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -214,7 +267,8 @@ export class FieldMappingsComponent implements SettingsComponent {
|
|||
plugin.settings.fieldMappingsStrings = newStringMappings;
|
||||
plugin.settings.fieldMappings = functionMappings;
|
||||
|
||||
debugLog(`To strings ${JSON.stringify(plugin.settings.fieldMappingsStrings)}\nand funcs ${JSON.stringify(plugin.settings.fieldMappings)}`);
|
||||
debugLog(`To strings ${JSON.stringify(plugin.settings.fieldMappingsStrings).substring(0, 100)}\n
|
||||
and funcs ${JSON.stringify(plugin.settings.fieldMappings).substring(0, 100)}`);
|
||||
await plugin.saveSettings();
|
||||
}
|
||||
|
||||
|
|
@ -223,7 +277,7 @@ export class FieldMappingsComponent implements SettingsComponent {
|
|||
*/
|
||||
private renderExamples(containerEl: HTMLElement): void {
|
||||
const examplesSection = containerEl.createDiv({ cls: "mapping-examples" });
|
||||
examplesSection.createEl("h4", { text: "Example field mappings" });
|
||||
examplesSection.createEl("h4", { text: t("example.name") });
|
||||
|
||||
const examplesList = examplesSection.createEl("ul");
|
||||
|
||||
|
|
@ -251,7 +305,7 @@ export class FieldMappingsComponent implements SettingsComponent {
|
|||
|
||||
// Add button to use this example
|
||||
const useBtn = item.createEl("button", {
|
||||
text: "Use",
|
||||
text: t("example.use"),
|
||||
cls: "use-example-btn"
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,20 @@
|
|||
import { normalizePath, Setting } from "obsidian";
|
||||
import { App, TFile, TFolder, Setting, normalizePath } from "obsidian";
|
||||
import { SettingsComponent, SettingsComponentProps } from "../../interfaces/settingsTypes";
|
||||
import { SuggestSelectComponent, SuggestSelectOption } from "./SuggestSelectComponent";
|
||||
import {useTranslations} from "../../localization/translator";
|
||||
|
||||
const t = useTranslations("settings.general").t;
|
||||
|
||||
interface TemplatePluginInfo {
|
||||
coreTemplatesEnabled: boolean;
|
||||
templaterEnabled: boolean;
|
||||
templateDirectory: string | null;
|
||||
warningMessage: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component for general plugin settings
|
||||
*/
|
||||
export class GeneralSettingsComponent implements SettingsComponent {
|
||||
private props: SettingsComponentProps;
|
||||
private templateSelector: SuggestSelectComponent | null = null;
|
||||
|
||||
constructor(props: SettingsComponentProps) {
|
||||
this.props = props;
|
||||
|
|
@ -16,8 +25,8 @@ export class GeneralSettingsComponent implements SettingsComponent {
|
|||
|
||||
// Issues folder setting
|
||||
new Setting(containerEl)
|
||||
.setName("Issues folder")
|
||||
.setDesc("Folder where Jira issues will be stored")
|
||||
.setName(t("folder.name"))
|
||||
.setDesc(t("folder.desc"))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("jira-issues")
|
||||
|
|
@ -28,19 +37,115 @@ export class GeneralSettingsComponent implements SettingsComponent {
|
|||
})
|
||||
);
|
||||
|
||||
// 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;
|
||||
// Template path setting with selector
|
||||
this.createTemplateSelector(containerEl);
|
||||
}
|
||||
|
||||
private createTemplateSelector(containerEl: HTMLElement): void {
|
||||
const { plugin } = this.props;
|
||||
const templateInfo = this.detectTemplatePlugins(plugin.app);
|
||||
|
||||
const setting = new Setting(containerEl)
|
||||
.setName(t("template.name"))
|
||||
.setDesc(t("template.desc"));
|
||||
|
||||
// Add warning if no template plugins are enabled
|
||||
if (templateInfo.warningMessage) {
|
||||
setting.descEl.createDiv({}, (div) => {
|
||||
div.createEl("small", {
|
||||
text: templateInfo.warningMessage,
|
||||
cls: "mod-warning"
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Create template selector
|
||||
this.templateSelector = new SuggestSelectComponent({
|
||||
placeholder: t("template.selector.placeholder"),
|
||||
emptyOptionText: t("template.selector.empty"),
|
||||
searchEnabled: true,
|
||||
});
|
||||
|
||||
// Render selector in the setting control
|
||||
this.templateSelector.render(setting.controlEl);
|
||||
|
||||
// Load template options
|
||||
const templateOptions = this.loadTemplateOptions(templateInfo);
|
||||
this.templateSelector.setOptions(templateOptions);
|
||||
|
||||
// Set current value
|
||||
if (plugin.settings.templatePath) {
|
||||
this.templateSelector.setValue(plugin.settings.templatePath);
|
||||
}
|
||||
|
||||
// Handle changes
|
||||
this.templateSelector.onChange(async (selectedPath) => {
|
||||
plugin.settings.templatePath = selectedPath ? normalizePath(selectedPath) : '';
|
||||
await plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private detectTemplatePlugins(app: App): TemplatePluginInfo {
|
||||
const coreTemplates = (app as any).internalPlugins?.plugins?.templates;
|
||||
const templaterPlugin = (app as any).plugins?.plugins?.['templater-obsidian'];
|
||||
|
||||
const coreTemplatesEnabled = coreTemplates?.enabled || false;
|
||||
const templaterEnabled = (app as any).plugins?.enabledPlugins?.has('templater-obsidian') || false;
|
||||
|
||||
let templateDirectory: string | null = null;
|
||||
let warningMessage = "";
|
||||
|
||||
if (coreTemplatesEnabled && coreTemplates.instance?.options?.folder) {
|
||||
templateDirectory = coreTemplates.instance.options.folder;
|
||||
} else if (templaterEnabled && templaterPlugin?.settings?.template_folder) {
|
||||
templateDirectory = templaterPlugin.settings.template_folder;
|
||||
}
|
||||
|
||||
if (!coreTemplatesEnabled && !templaterEnabled) {
|
||||
warningMessage = t("template.warning");
|
||||
}
|
||||
|
||||
return {
|
||||
coreTemplatesEnabled,
|
||||
templaterEnabled,
|
||||
templateDirectory,
|
||||
warningMessage
|
||||
};
|
||||
}
|
||||
|
||||
private loadTemplateOptions(templateInfo: TemplatePluginInfo): SuggestSelectOption[] {
|
||||
const templateOptions: SuggestSelectOption[] = [];
|
||||
|
||||
// Get templates from specified folder or vault root
|
||||
const searchFolder = templateInfo.templateDirectory
|
||||
? this.props.plugin.app.vault.getAbstractFileByPath(templateInfo.templateDirectory)
|
||||
: this.props.plugin.app.vault.getRoot();
|
||||
|
||||
if (searchFolder instanceof TFolder) {
|
||||
this.scanFolderForTemplates(searchFolder, "", templateOptions);
|
||||
} else {
|
||||
// Fallback to root if specified folder doesn't exist
|
||||
this.scanFolderForTemplates(this.props.plugin.app.vault.getRoot(), "", templateOptions);
|
||||
}
|
||||
|
||||
// Sort templates alphabetically
|
||||
templateOptions.sort((a, b) => a.label.localeCompare(b.label));
|
||||
|
||||
return templateOptions;
|
||||
}
|
||||
|
||||
private scanFolderForTemplates(folder: TFolder, prefix: string, templateOptions: SuggestSelectOption[]): void {
|
||||
for (const child of folder.children) {
|
||||
if (child instanceof TFile && child.extension === 'md') {
|
||||
const displayName = prefix ? `${prefix}/${child.basename}` : child.basename;
|
||||
templateOptions.push({
|
||||
value: child.path,
|
||||
label: displayName
|
||||
});
|
||||
} else if (child instanceof TFolder) {
|
||||
const newPrefix = prefix ? `${prefix}/${child.name}` : child.name;
|
||||
this.scanFolderForTemplates(child, newPrefix, templateOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ import { SettingsComponent, SettingsComponentProps } from "../../interfaces/sett
|
|||
import { fetchIssue } from "../../api";
|
||||
import debounce from "lodash/debounce";
|
||||
import hljs from "highlight.js";
|
||||
import {useTranslations} from "../../localization/translator";
|
||||
|
||||
const t = useTranslations("settings.issue_view").t;
|
||||
/**
|
||||
* Component for viewing raw Jira issue data
|
||||
*/
|
||||
|
|
@ -22,8 +24,8 @@ export class RawIssueViewerComponent implements SettingsComponent {
|
|||
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")
|
||||
.setName(t("key.name"))
|
||||
.setDesc(t("key.desc"))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("PROJ-123")
|
||||
|
|
@ -66,10 +68,7 @@ export class RawIssueViewerComponent implements SettingsComponent {
|
|||
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;"
|
||||
}
|
||||
cls: "jira-copyable",
|
||||
});
|
||||
|
||||
const code = pre.createEl("code", {
|
||||
|
|
|
|||
127
src/settings/components/SuggestSelectComponent.ts
Normal file
127
src/settings/components/SuggestSelectComponent.ts
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import {useTranslations} from "../../localization/translator";
|
||||
|
||||
export interface SuggestSelectOption {
|
||||
value: string;
|
||||
label: string;
|
||||
isDefault?: boolean;
|
||||
}
|
||||
|
||||
export interface SuggestSelectConfig {
|
||||
placeholder?: string;
|
||||
emptyOptionText?: string;
|
||||
searchEnabled?: boolean;
|
||||
}
|
||||
|
||||
const t = useTranslations("settings.suggest_select").t;
|
||||
export class SuggestSelectComponent {
|
||||
private selectEl: HTMLSelectElement | null = null;
|
||||
private searchInputEl: HTMLInputElement | null = null;
|
||||
private allOptions: SuggestSelectOption[] = [];
|
||||
private filteredOptions: SuggestSelectOption[] = [];
|
||||
private config: SuggestSelectConfig;
|
||||
private onChangeCallback?: (value: string) => void
|
||||
|
||||
constructor(config: SuggestSelectConfig = {}) {
|
||||
this.config = {
|
||||
placeholder: t("placeholder"),
|
||||
emptyOptionText: t("empty_option_text"),
|
||||
searchEnabled: true,
|
||||
...config
|
||||
};
|
||||
}
|
||||
|
||||
render(containerEl: HTMLElement): HTMLElement {
|
||||
const selectorContainer = containerEl.createDiv({
|
||||
cls: "suggest-select-container suggest"
|
||||
});
|
||||
|
||||
if (this.config.searchEnabled) {
|
||||
this.searchInputEl = selectorContainer.createEl("input", {
|
||||
type: "text",
|
||||
placeholder: this.config.placeholder,
|
||||
cls: "suggest-select-item suggest-search-input"
|
||||
});
|
||||
|
||||
this.searchInputEl.addEventListener('input', (e) => {
|
||||
const searchTerm = (e.target as HTMLInputElement).value.toLowerCase();
|
||||
this.filterOptions(searchTerm);
|
||||
});
|
||||
}
|
||||
|
||||
this.selectEl = selectorContainer.createEl("select", {
|
||||
cls: "suggest-select-item suggest-select-dropdown dropdown"
|
||||
});
|
||||
|
||||
this.selectEl.addEventListener('change', (e) => {
|
||||
const selectedValue = (e.target as HTMLSelectElement).value;
|
||||
this.onChangeCallback?.(selectedValue);
|
||||
});
|
||||
|
||||
return selectorContainer;
|
||||
}
|
||||
|
||||
setOptions(options: SuggestSelectOption[]): void {
|
||||
this.allOptions = [...options];
|
||||
this.filteredOptions = [...options];
|
||||
this.populateSelect();
|
||||
}
|
||||
|
||||
setValue(value: string): void {
|
||||
if (!this.selectEl) return;
|
||||
|
||||
const options = this.selectEl.options;
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
if (options[i].value === value) {
|
||||
this.selectEl.selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getValue(): string {
|
||||
return this.selectEl?.value || "";
|
||||
}
|
||||
|
||||
onChange(callback: (value: string) => void): void {
|
||||
this.onChangeCallback = callback;
|
||||
}
|
||||
|
||||
private populateSelect(): void {
|
||||
if (!this.selectEl) return;
|
||||
|
||||
this.selectEl.innerHTML = "";
|
||||
|
||||
// Add empty option
|
||||
this.selectEl.createEl("option", {
|
||||
value: "",
|
||||
text: this.config.emptyOptionText
|
||||
});
|
||||
|
||||
// Add filtered options
|
||||
this.filteredOptions.forEach(option => {
|
||||
this.selectEl!.createEl("option", {
|
||||
value: option.value,
|
||||
text: option.label
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private filterOptions(searchTerm: string): void {
|
||||
if (!searchTerm) {
|
||||
this.filteredOptions = [...this.allOptions];
|
||||
} else {
|
||||
this.filteredOptions = this.allOptions.filter(option =>
|
||||
option.label.toLowerCase().includes(searchTerm) ||
|
||||
option.value.toLowerCase().includes(searchTerm)
|
||||
);
|
||||
}
|
||||
|
||||
this.populateSelect();
|
||||
|
||||
// Maintain selection if the current value is still available
|
||||
const currentValue = this.getValue();
|
||||
if (currentValue) {
|
||||
this.setValue(currentValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +1,17 @@
|
|||
import { SettingsComponent, SettingsComponentProps } from "../../interfaces/settingsTypes";
|
||||
import debounce from "lodash/debounce";
|
||||
import { safeStringToFunction } from "../../tools/convertFunctionString";
|
||||
import {useTranslations} from "../../localization/translator";
|
||||
import {setIcon} from "obsidian";
|
||||
|
||||
const t = useTranslations("settings.tfm").t;
|
||||
|
||||
export class TestFieldMappingsComponent implements SettingsComponent {
|
||||
private props: SettingsComponentProps;
|
||||
private testMappingsContainer: HTMLElement | null = null;
|
||||
private currentIssueData: any = null;
|
||||
private getCurrentIssue: (() => any) | null = null;
|
||||
private _testMappingItemsContainer: HTMLElement | null = null;
|
||||
|
||||
constructor(props: SettingsComponentProps & { getCurrentIssue?: () => any }) {
|
||||
this.props = props;
|
||||
|
|
@ -21,36 +26,44 @@ export class TestFieldMappingsComponent implements SettingsComponent {
|
|||
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.",
|
||||
text: t("desc"),
|
||||
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
|
||||
this._testMappingItemsContainer = this.testMappingsContainer.createDiv({ cls: "test-mapping-items-list" });
|
||||
|
||||
// Add first test mapping item
|
||||
this.addTestMappingItem();
|
||||
|
||||
const buttonView = this.testMappingsContainer.createDiv({ cls: "button-view" });
|
||||
|
||||
// Add new mapping button (only once, at the bottom)
|
||||
const addBtn = this.testMappingsContainer.createEl("button", {
|
||||
text: "+ Add test mapping",
|
||||
cls: "add-test-mapping-btn"
|
||||
const addBtn = buttonView.createEl("button", {
|
||||
text: t("add_mapping") || "Add Mapping",
|
||||
cls: "add-field-mapping-btn",
|
||||
attr: { 'data-tooltip': t("add_mapping_tooltip") || "Add new field mapping" }
|
||||
});
|
||||
setIcon(addBtn, "circle-plus");
|
||||
addBtn.addEventListener("click", () => {
|
||||
this.addTestMappingItem();
|
||||
});
|
||||
|
||||
const resetBtn = buttonView.createEl("button", {
|
||||
text: t("reset") || "Reset All",
|
||||
cls: "reset-field-mappings-btn",
|
||||
attr: { 'data-tooltip': t("reset_tooltip") || "Clear all field mappings" }
|
||||
});
|
||||
setIcon(resetBtn, "refresh-cw");
|
||||
resetBtn.addEventListener("click", () => {
|
||||
this._testMappingItemsContainer?.empty();
|
||||
});
|
||||
}
|
||||
|
||||
private addTestMappingItem(): void {
|
||||
// Use the dedicated items container
|
||||
const itemsContainer = (this as any)._testMappingItemsContainer as HTMLElement;
|
||||
const itemsContainer = this._testMappingItemsContainer as HTMLElement;
|
||||
if (!itemsContainer) return;
|
||||
|
||||
const itemContainer = itemsContainer.createDiv({
|
||||
|
|
@ -59,7 +72,7 @@ export class TestFieldMappingsComponent implements SettingsComponent {
|
|||
|
||||
// From Jira expression input
|
||||
const fromJiraContainer = itemContainer.createDiv({ cls: "from-jira-container" });
|
||||
fromJiraContainer.createEl("span", { text: "from Jira:", cls: "field-mapping-label" });
|
||||
fromJiraContainer.createEl("span", { text: t("from_jira")+":", cls: "field-mapping-label" });
|
||||
|
||||
const fromJiraInput = fromJiraContainer.createEl("textarea", {
|
||||
cls: "from-jira-input"
|
||||
|
|
@ -69,7 +82,7 @@ export class TestFieldMappingsComponent implements SettingsComponent {
|
|||
|
||||
// Value display
|
||||
const valueContainer = itemContainer.createDiv({ cls: "value-container" });
|
||||
valueContainer.createEl("span", { text: "Value:", cls: "field-mapping-label" });
|
||||
valueContainer.createEl("span", { text: t("value")+":", cls: "field-mapping-label" });
|
||||
const valueDisplay = valueContainer.createEl("div", {
|
||||
cls: "value-display",
|
||||
attr: {
|
||||
|
|
@ -81,7 +94,7 @@ export class TestFieldMappingsComponent implements SettingsComponent {
|
|||
const debouncedEvaluate = debounce(async () => {
|
||||
const issueData = this.getCurrentIssue ? this.getCurrentIssue() : this.currentIssueData;
|
||||
if (!issueData) {
|
||||
valueDisplay.setText("No issue data available");
|
||||
valueDisplay.setText(t("no_data"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -91,7 +104,7 @@ export class TestFieldMappingsComponent implements SettingsComponent {
|
|||
const result = fromJiraFn(issueData, null);
|
||||
valueDisplay.setText(JSON.stringify(result, null, 2));
|
||||
} else {
|
||||
valueDisplay.setText("Invalid expression");
|
||||
valueDisplay.setText(t("invalid_exp"));
|
||||
}
|
||||
} catch (error) {
|
||||
valueDisplay.setText(`Error: ${error.message}`);
|
||||
|
|
|
|||
522
src/settings/components/TimekeepSettingsComponent.ts
Normal file
522
src/settings/components/TimekeepSettingsComponent.ts
Normal file
|
|
@ -0,0 +1,522 @@
|
|||
import { Setting, Notice } from 'obsidian';
|
||||
import { SettingsComponent, SettingsComponentProps } from '../../interfaces/settingsTypes';
|
||||
import { processWorkLogBatch } from "../../commands";
|
||||
import { SuggestSelectComponent, SuggestSelectConfig } from './SuggestSelectComponent';
|
||||
import {useTranslations} from "../../localization/translator";
|
||||
import {debugLog} from "../../tools/debugLogging";
|
||||
|
||||
type TimeRangeType = 'days' | 'weeks' | 'months' | 'custom';
|
||||
|
||||
interface Entry {
|
||||
name: string;
|
||||
startTime?: string;
|
||||
endTime?: string;
|
||||
subEntries?: Entry[];
|
||||
}
|
||||
|
||||
interface TimekeepData {
|
||||
entries: Entry[];
|
||||
}
|
||||
|
||||
interface TimekeepBlock {
|
||||
file: string;
|
||||
path: string;
|
||||
issueKey?: string;
|
||||
data: TimekeepData;
|
||||
}
|
||||
|
||||
interface ProcessedEntry {
|
||||
file: string;
|
||||
issueKey?: string;
|
||||
blockPath: string;
|
||||
startTime: string;
|
||||
endTime: string;
|
||||
duration: string;
|
||||
timestamp: number; // For easier sorting and filtering
|
||||
}
|
||||
|
||||
interface GroupedEntries {
|
||||
[periodKey: string]: ProcessedEntry[];
|
||||
}
|
||||
|
||||
const t = useTranslations("settings.statistics").t;
|
||||
|
||||
export class TimekeepSettingsComponent implements SettingsComponent {
|
||||
private props: SettingsComponentProps;
|
||||
|
||||
// Data
|
||||
private groupedByPeriod: GroupedEntries = {};
|
||||
private selectedPeriodData: ProcessedEntry[] = [];
|
||||
private containerEl: HTMLElement | null = null;
|
||||
private periodSelector: SuggestSelectComponent | null = null;
|
||||
|
||||
// Elements
|
||||
private maxItemsSetting: Setting | null = null;
|
||||
private customDatesContainer: HTMLElement | null = null;
|
||||
private sendButton: HTMLButtonElement | null = null;
|
||||
|
||||
constructor(props: SettingsComponentProps) {
|
||||
this.props = props;
|
||||
}
|
||||
|
||||
render(containerEl: HTMLElement): void {
|
||||
this.containerEl = containerEl;
|
||||
containerEl.empty();
|
||||
|
||||
this.renderSettings(containerEl);
|
||||
this.renderPeriodSelector(containerEl);
|
||||
this.toggleCustomDateInputs();
|
||||
|
||||
// Initialize data
|
||||
this.refreshData(true);
|
||||
}
|
||||
|
||||
private renderSettings(containerEl: HTMLElement): void {
|
||||
const settingsContainer = containerEl.createDiv('timekeep-settings');
|
||||
|
||||
// Time range selector
|
||||
new Setting(settingsContainer)
|
||||
.setName(t("settings.time_range.name"))
|
||||
.setDesc(t("settings.time_range.description"))
|
||||
.addDropdown(dropdown => {
|
||||
dropdown.addOption('days', t("settings.time_range.options.days"));
|
||||
dropdown.addOption('weeks', t("settings.time_range.options.weeks"));
|
||||
dropdown.addOption('months', t("settings.time_range.options.months"));
|
||||
dropdown.addOption('custom', t("settings.time_range.options.custom"));
|
||||
dropdown.setValue(this.props.plugin.settings.statisticsTimeType);
|
||||
dropdown.onChange(async (value: TimeRangeType) => {
|
||||
this.props.plugin.settings.statisticsTimeType = value;
|
||||
this.toggleCustomDateInputs();
|
||||
await this.refreshData();
|
||||
});
|
||||
});
|
||||
|
||||
// Max items slider (hidden for custom range)
|
||||
const maxItemsSetting = new Setting(settingsContainer)
|
||||
.setName(t("settings.max_items.name"))
|
||||
.setDesc(t("settings.max_items.description"))
|
||||
.addSlider(slider => {
|
||||
slider.setLimits(1, 20, 1)
|
||||
.setValue(this.props.plugin.settings.maxItemsToShow)
|
||||
.setDynamicTooltip()
|
||||
.onChange(async (value) => {
|
||||
this.props.plugin.settings.maxItemsToShow = value;
|
||||
await this.refreshData();
|
||||
});
|
||||
});
|
||||
|
||||
// Custom date inputs (initially hidden)
|
||||
const customDatesContainer = settingsContainer.createDiv('custom-dates-container');
|
||||
|
||||
new Setting(customDatesContainer)
|
||||
.setName(t("settings.date_from.name"))
|
||||
.setDesc(t("settings.date_from.description"))
|
||||
.addText(text => {
|
||||
text.setPlaceholder(t("settings.date_from.placeholder"));
|
||||
text.setValue(this.props.plugin.settings.customDateRange.start);
|
||||
text.onChange(async (value) => {
|
||||
this.props.plugin.settings.customDateRange.start = value;
|
||||
if (this.props.plugin.settings.statisticsTimeType === 'custom' && this.props.plugin.settings.customDateRange.end) {
|
||||
await this.refreshData();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(customDatesContainer)
|
||||
.setName(t("settings.date_to.name"))
|
||||
.setDesc(t("settings.date_to.description"))
|
||||
.addText(text => {
|
||||
text.setPlaceholder(t("settings.date_to.placeholder"));
|
||||
text.setValue(this.props.plugin.settings.customDateRange.end);
|
||||
text.onChange(async (value) => {
|
||||
this.props.plugin.settings.customDateRange.end = value;
|
||||
if (this.props.plugin.settings.statisticsTimeType === 'custom' && this.props.plugin.settings.customDateRange.start) {
|
||||
await this.refreshData();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Store references for toggling
|
||||
this.maxItemsSetting = maxItemsSetting;
|
||||
this.customDatesContainer = customDatesContainer;
|
||||
}
|
||||
|
||||
private toggleCustomDateInputs(): void {
|
||||
if (!this.maxItemsSetting || !this.customDatesContainer) return;
|
||||
|
||||
const isCustom = this.props.plugin.settings.statisticsTimeType === 'custom';
|
||||
|
||||
if (isCustom) {
|
||||
this.maxItemsSetting.settingEl.hide()
|
||||
this.customDatesContainer.show();
|
||||
} else {
|
||||
this.maxItemsSetting.settingEl.show();
|
||||
this.customDatesContainer.hide();
|
||||
}
|
||||
}
|
||||
|
||||
private async refreshData(disableNotification = false): Promise<void> {
|
||||
if (!this.containerEl) return;
|
||||
|
||||
// Показываем индикатор загрузки
|
||||
this.containerEl.addClass('timekeep-loading');
|
||||
|
||||
try {
|
||||
await this.processFiles();
|
||||
this.updateDisplay();
|
||||
if (!disableNotification) new Notice(t("messages.refresh_success"));
|
||||
} catch (error) {
|
||||
console.error('Error refreshing timekeep data:', error);
|
||||
new Notice(t("messages.refresh_error", { error: error.message }));
|
||||
} finally {
|
||||
// Убираем индикатор загрузки
|
||||
this.containerEl.removeClass('timekeep-loading');
|
||||
}
|
||||
}
|
||||
|
||||
private updateDisplay(): void {
|
||||
if (!this.containerEl) return;
|
||||
|
||||
// Remove existing display elements
|
||||
this.containerEl.querySelector('.all-periods-display')?.remove();
|
||||
this.containerEl.querySelector('.timekeep-no-data')?.remove();
|
||||
|
||||
if (Object.keys(this.groupedByPeriod).length === 0) {
|
||||
this.containerEl.createEl('div', {
|
||||
text: t("display.no_entries"),
|
||||
cls: 'timekeep-no-data'
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.renderAllPeriodsDisplay(this.containerEl as HTMLElement);
|
||||
}
|
||||
|
||||
private renderPeriodSelector(container: HTMLElement): void {
|
||||
const setting = new Setting(container).setClass('period-selector')
|
||||
.setName(t("display.select_period") + ': ');
|
||||
|
||||
const config: SuggestSelectConfig = {
|
||||
placeholder: t("display.select_period_placeholder"),
|
||||
searchEnabled: false
|
||||
};
|
||||
|
||||
this.periodSelector = new SuggestSelectComponent(config);
|
||||
this.periodSelector.render(setting.controlEl);
|
||||
|
||||
this.sendButton = setting.controlEl.createEl('button', {
|
||||
text: t("actions.send_to_jira"),
|
||||
cls: 'timekeep-btn'
|
||||
});
|
||||
this.sendButton.addEventListener('click', () => this.sendWorkLogToJira());
|
||||
|
||||
this.periodSelector.onChange((selectedPeriod) => {
|
||||
if (selectedPeriod) {
|
||||
this.selectedPeriodData = this.groupedByPeriod[selectedPeriod] || [];
|
||||
this.sendButton? this.sendButton.disabled = false : null;
|
||||
}
|
||||
else {
|
||||
this.selectedPeriodData = [];
|
||||
this.sendButton? this.sendButton.disabled = true : null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private renderAllPeriodsDisplay(container: HTMLElement): void {
|
||||
const display = container.createDiv('all-periods-display jira-copyable timekeep-fade-in');
|
||||
display.createEl('h3', {
|
||||
text: t("display.all_periods"),
|
||||
cls: 'timekeep-section-title'
|
||||
});
|
||||
|
||||
const sortedPeriods = Object.keys(this.groupedByPeriod).sort().reverse();
|
||||
|
||||
sortedPeriods.forEach((period, index) => {
|
||||
const periodDiv = display.createDiv('period-section');
|
||||
// Добавляем небольшую задержку для каскадной анимации
|
||||
periodDiv.style.animationDelay = `${index * 50}ms`;
|
||||
|
||||
periodDiv.createEl('h4', {
|
||||
text: this.formatPeriodLabel(period),
|
||||
cls: 'timekeep-period-title'
|
||||
});
|
||||
|
||||
const entries = this.groupedByPeriod[period];
|
||||
this.createEntriesTable(periodDiv, entries);
|
||||
});
|
||||
}
|
||||
|
||||
private createEntriesTable(container: HTMLElement, entries: ProcessedEntry[]): void {
|
||||
const tableContainer = container.createDiv('timekeep-table-container');
|
||||
const table = tableContainer.createEl('table', { cls: 'timekeep-entries-table' });
|
||||
const thead = table.createEl('thead');
|
||||
const headerRow = thead.createEl('tr');
|
||||
|
||||
headerRow.createEl('th', { text: t("display.table.task") });
|
||||
headerRow.createEl('th', { text: t("display.table.issue_key") });
|
||||
headerRow.createEl('th', { text: t("display.table.block_path") });
|
||||
headerRow.createEl('th', { text: t("display.table.start_time") });
|
||||
headerRow.createEl('th', { text: t("display.table.duration") });
|
||||
|
||||
const tbody = table.createEl('tbody');
|
||||
entries.forEach((entry, index) => {
|
||||
const row = tbody.createEl('tr');
|
||||
if (index % 2 === 1) row.addClass('timekeep-table-row-alt');
|
||||
|
||||
// Добавляем title для длинных текстов
|
||||
const taskCell = row.createEl('td', { text: entry.file });
|
||||
taskCell.title = entry.file;
|
||||
|
||||
const issueCell = row.createEl('td', { text: entry.issueKey || '-' });
|
||||
if (entry.issueKey) {
|
||||
issueCell.title = `Issue: ${entry.issueKey}`;
|
||||
}
|
||||
|
||||
const pathCell = row.createEl('td', { text: entry.blockPath });
|
||||
pathCell.title = entry.blockPath; // Полный путь в тултипе
|
||||
|
||||
const timeCell = row.createEl('td', { text: entry.startTime });
|
||||
timeCell.title = `Started: ${entry.startTime}`;
|
||||
|
||||
const durationCell = row.createEl('td', { text: entry.duration });
|
||||
durationCell.title = `Duration: ${entry.duration}`;
|
||||
});
|
||||
}
|
||||
|
||||
private async sendWorkLogToJira(): Promise<void> {
|
||||
if (this.selectedPeriodData.length === 0) {
|
||||
new Notice(t("messages.select_period_first"));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await processWorkLogBatch(this.props.plugin, this.selectedPeriodData);
|
||||
new Notice(t("messages.send_success"));
|
||||
} catch (error) {
|
||||
console.error('Error sending work log to Jira:', error);
|
||||
new Notice(t("messages.send_error", { error: error.message }));
|
||||
}
|
||||
}
|
||||
|
||||
private formatPeriodLabel(periodKey: string): string {
|
||||
return t("display.period_of", { date: this.formatPeriodDate(periodKey) });
|
||||
}
|
||||
|
||||
private formatPeriodDate(periodKey: string): string {
|
||||
const date = new Date(periodKey);
|
||||
|
||||
switch (this.props.plugin.settings.statisticsTimeType) {
|
||||
case 'days':
|
||||
return date.toLocaleDateString();
|
||||
case 'weeks':
|
||||
const weekEnd = new Date(date);
|
||||
weekEnd.setDate(weekEnd.getDate() + 6);
|
||||
return `${date.toLocaleDateString()} - ${weekEnd.toLocaleDateString()}`;
|
||||
case 'months':
|
||||
return date.toLocaleDateString('en-US', { year: 'numeric', month: 'long' });
|
||||
case 'custom':
|
||||
return `${this.props.plugin.settings.customDateRange.start} - ${this.props.plugin.settings.customDateRange.end}`;
|
||||
default:
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
}
|
||||
|
||||
private getPeriodKey(date: Date): string {
|
||||
switch (this.props.plugin.settings.statisticsTimeType) {
|
||||
case 'days':
|
||||
return date.toISOString().split('T')[0];
|
||||
case 'weeks':
|
||||
return this.getWeekStartDate(date).toISOString().split('T')[0];
|
||||
case 'months':
|
||||
return new Date(date.getFullYear(), date.getMonth(), 1).toISOString().split('T')[0];
|
||||
case 'custom':
|
||||
return `${this.props.plugin.settings.customDateRange.start}_${this.props.plugin.settings.customDateRange.end}`;
|
||||
default:
|
||||
return date.toISOString().split('T')[0];
|
||||
}
|
||||
}
|
||||
|
||||
private isDateInRange(date: Date): boolean {
|
||||
if (this.props.plugin.settings.statisticsTimeType !== 'custom') return true;
|
||||
|
||||
if (!this.props.plugin.settings.customDateRange.start || !this.props.plugin.settings.customDateRange.end) return false;
|
||||
|
||||
const from = new Date(this.props.plugin.settings.customDateRange.start);
|
||||
const to = new Date(this.props.plugin.settings.customDateRange.end);
|
||||
|
||||
return date >= from && date <= to;
|
||||
}
|
||||
|
||||
private validateCustomRange(): boolean {
|
||||
if (this.props.plugin.settings.statisticsTimeType !== 'custom') return true;
|
||||
|
||||
if (!this.props.plugin.settings.customDateRange.start || !this.props.plugin.settings.customDateRange.end) {
|
||||
new Notice(t("messages.custom_range_required"));
|
||||
return false;
|
||||
}
|
||||
|
||||
const from = new Date(this.props.plugin.settings.customDateRange.start);
|
||||
const to = new Date(this.props.plugin.settings.customDateRange.end);
|
||||
|
||||
if (from >= to) {
|
||||
new Notice(t("messages.invalid_date_range"));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Utility methods
|
||||
private getWeekStartDate(date: Date): Date {
|
||||
const result = new Date(date);
|
||||
const day = result.getDay();
|
||||
const diff = result.getDate() - day + (day === 0 ? -6 : 1);
|
||||
result.setDate(diff);
|
||||
return new Date(result.setHours(0, 0, 0, 0));
|
||||
}
|
||||
|
||||
private formatDuration(ms: number): string {
|
||||
const units = [
|
||||
{ label: "w", ms: 1000 * 60 * 60 * 24 * 7 },
|
||||
{ label: "d", ms: 1000 * 60 * 60 * 24 },
|
||||
{ label: "h", ms: 1000 * 60 * 60 },
|
||||
{ label: "m", ms: 1000 * 60 },
|
||||
{ label: "s", ms: 1000 }
|
||||
];
|
||||
|
||||
let remainingMs = ms;
|
||||
const result: string[] = [];
|
||||
|
||||
for (const unit of units) {
|
||||
const value = Math.floor(remainingMs / unit.ms);
|
||||
if (value > 0) {
|
||||
result.push(`${value}${unit.label}`);
|
||||
remainingMs %= unit.ms;
|
||||
}
|
||||
}
|
||||
|
||||
return result.length > 0 ? result.join(" ") : "0s";
|
||||
}
|
||||
|
||||
private trimOldEntries(groupedEntries: GroupedEntries): void {
|
||||
if (this.props.plugin.settings.statisticsTimeType === 'custom') return;
|
||||
|
||||
const periods = Object.keys(groupedEntries).sort();
|
||||
while (periods.length > this.props.plugin.settings.maxItemsToShow) {
|
||||
const oldestPeriod = periods.shift();
|
||||
if (oldestPeriod) {
|
||||
delete groupedEntries[oldestPeriod];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private processEntries(
|
||||
entries: Entry[] | undefined,
|
||||
fileName: string,
|
||||
issueKey: string | undefined,
|
||||
parentPath: string,
|
||||
groupedEntries: GroupedEntries
|
||||
): void {
|
||||
if (!entries || !Array.isArray(entries)) return;
|
||||
|
||||
for (const entry of entries) {
|
||||
const currentPath = parentPath ? `${parentPath} > ${entry.name}` : entry.name;
|
||||
|
||||
if (entry.startTime) {
|
||||
const startTime = new Date(entry.startTime);
|
||||
|
||||
// Check if date is in range for custom range
|
||||
if (!this.isDateInRange(startTime)) continue;
|
||||
|
||||
const periodKey = this.getPeriodKey(startTime);
|
||||
if (!groupedEntries[periodKey]) groupedEntries[periodKey] = [];
|
||||
|
||||
let duration: string;
|
||||
let endTime: Date;
|
||||
|
||||
if (entry.endTime) {
|
||||
endTime = new Date(entry.endTime);
|
||||
duration = this.formatDuration(endTime.getTime() - startTime.getTime());
|
||||
} else {
|
||||
endTime = new Date();
|
||||
duration = "ongoing";
|
||||
}
|
||||
|
||||
groupedEntries[periodKey].push({
|
||||
file: fileName.replace(".md", ""),
|
||||
issueKey: issueKey,
|
||||
blockPath: currentPath,
|
||||
startTime: startTime.toLocaleString(),
|
||||
endTime: entry.endTime ? endTime.toLocaleString() : "ongoing",
|
||||
duration: duration,
|
||||
timestamp: startTime.getTime()
|
||||
});
|
||||
}
|
||||
|
||||
// Process sub-entries recursively
|
||||
if (entry.subEntries && Array.isArray(entry.subEntries)) {
|
||||
this.processEntries(entry.subEntries, fileName, issueKey, currentPath, groupedEntries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async processFiles(): Promise<void> {
|
||||
if (!this.validateCustomRange()) return;
|
||||
|
||||
const timekeepBlocks: TimekeepBlock[] = [];
|
||||
const files = this.props.app.vault.getMarkdownFiles().filter(
|
||||
file => file.path.startsWith(`${this.props.plugin.settings.issuesFolder}/`)
|
||||
);
|
||||
|
||||
await Promise.all(files.map(async file => {
|
||||
const content = await this.props.app.vault.read(file);
|
||||
if (!content.includes("```timekeep")) return;
|
||||
|
||||
const metadata = this.props.app.metadataCache.getFileCache(file);
|
||||
const issueKey = metadata?.frontmatter?.key;
|
||||
|
||||
const timekeepMatches = content.match(/```timekeep([\s\S]*?)```/g);
|
||||
timekeepMatches?.forEach(block => {
|
||||
try {
|
||||
const jsonContent = block.slice(11, -3).trim();
|
||||
const data = JSON.parse(jsonContent);
|
||||
|
||||
timekeepBlocks.push({
|
||||
file: file.name,
|
||||
path: file.path,
|
||||
issueKey,
|
||||
data
|
||||
});
|
||||
} catch (error) {
|
||||
console.error(`Error parsing timekeep block in ${file.name}:`, error);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
this.groupedByPeriod = {};
|
||||
|
||||
timekeepBlocks.forEach(block => {
|
||||
this.processEntries(block.data.entries, block.file, block.issueKey, "", this.groupedByPeriod);
|
||||
});
|
||||
|
||||
debugLog("grouped entries", this.groupedByPeriod);
|
||||
|
||||
this.trimOldEntries(this.groupedByPeriod);
|
||||
|
||||
const sortedPeriods = Object.keys(this.groupedByPeriod).sort().reverse(); // Most recent first
|
||||
const options = sortedPeriods.map(period => ({
|
||||
value: period,
|
||||
label: this.formatPeriodLabel(period)
|
||||
}));
|
||||
|
||||
this.periodSelector?.setOptions(options);
|
||||
this.periodSelector?.setValue("");
|
||||
this.sendButton ? this.sendButton.disabled = true : null;
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.containerEl = null;
|
||||
this.periodSelector = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import {FieldMapping} from "../constants/obsidianJiraFieldsMapping";
|
||||
import {FieldMapping} from "../default/obsidianJiraFieldsMapping";
|
||||
|
||||
export interface JiraSettings {
|
||||
collapsedSections: Record<string, boolean>;
|
||||
|
|
@ -16,13 +16,20 @@ export interface JiraSettings {
|
|||
fieldMappings: Record<string, FieldMapping>;
|
||||
fieldMappingsStrings: Record<string, { toJira: string; fromJira: string }>;
|
||||
enableFieldValidation: boolean;
|
||||
|
||||
statisticsTimeType: string;
|
||||
maxItemsToShow: number;
|
||||
customDateRange: { start: string; end: string };
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: JiraSettings = {
|
||||
collapsedSections: {
|
||||
connection: true,
|
||||
general: true,
|
||||
fieldMappings: false
|
||||
fieldMappings: false,
|
||||
rawIssueViewer: false,
|
||||
testFieldMappings: false,
|
||||
statistics: false,
|
||||
},
|
||||
authMethod: "bearer",
|
||||
apiToken: "",
|
||||
|
|
@ -36,6 +43,10 @@ export const DEFAULT_SETTINGS: JiraSettings = {
|
|||
templatePath: "",
|
||||
fieldMappings: {},
|
||||
fieldMappingsStrings: {},
|
||||
enableFieldValidation: true
|
||||
enableFieldValidation: true,
|
||||
|
||||
statisticsTimeType: "weeks",
|
||||
maxItemsToShow: 10,
|
||||
customDateRange: { start: "", end: "" }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import { Notice } from "obsidian";
|
|||
import { JiraIssue } from "../interfaces";
|
||||
import { parse } from "acorn";
|
||||
import {debugLog} from "./debugLogging";
|
||||
import {FieldMapping} from "../constants/obsidianJiraFieldsMapping";
|
||||
import {defaultIssue} from "../constants/defaultIssue";
|
||||
import {FieldMapping} from "../default/obsidianJiraFieldsMapping";
|
||||
import {defaultIssue} from "../default/defaultIssue";
|
||||
|
||||
// Constants for validation and error messages
|
||||
const FORBIDDEN_PATTERNS = ["document", "window", "eval", "Function", "fetch", "setTimeout"]; // Prevent unsafe code execution
|
||||
|
|
@ -26,7 +26,7 @@ export function validateFunctionStringBrowser(fnString: string, approved_vars: s
|
|||
if (isSimpleExpression(fnString) || (fnString.startsWith("{") && fnString.endsWith("}") && !fnString.includes("return"))) {
|
||||
fnString = `(${approved_vars.map(varName => varName==='issue'?`${varName} = ${JSON.stringify(defaultIssue)}`:`${varName} = {}`).join(', ')}) => ${fnString}`;
|
||||
}
|
||||
debugLog(`checking function: ${fnString}`);
|
||||
debugLog(`checking function: ${fnString.substring(0, 100)}`);
|
||||
let func = (iframeWindow as any).eval(fnString);
|
||||
if (typeof func === 'function') {
|
||||
func(); // Execute the function to catch runtime errors
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import {jiraToMarkdown} from "./markdown_html";
|
|||
import {Notice, TFile} from "obsidian";
|
||||
import JiraPlugin from "../main";
|
||||
import {extractAllJiraSyncValuesFromContent, updateJiraSyncContent} from "./sectionTools";
|
||||
import {FieldMapping, obsidianJiraFieldMappings} from "../constants/obsidianJiraFieldsMapping";
|
||||
import {FieldMapping, obsidianJiraFieldMappings} from "../default/obsidianJiraFieldsMapping";
|
||||
|
||||
|
||||
export function localToJiraFields(
|
||||
|
|
|
|||
|
|
@ -1,2 +0,0 @@
|
|||
// I want to create something like template choice from Kanban plugin here,
|
||||
// but it's GNU licensed... Probably will implement something similar later
|
||||
579
styles.css
579
styles.css
|
|
@ -18,11 +18,49 @@
|
|||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.jira-test-mappings-container {
|
||||
margin-top: 2em;
|
||||
padding: 1em 0;
|
||||
|
||||
/* Template Selector Styles */
|
||||
.suggest-select-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.suggest-select-container .icon-open {
|
||||
position: absolute;
|
||||
bottom: 1em;
|
||||
right: 1em;
|
||||
}
|
||||
|
||||
.suggest-search-input {
|
||||
padding: 6px 12px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 4px;
|
||||
background: var(--background-primary);
|
||||
color: var(--text-normal);
|
||||
font-size: var(--font-ui-small);
|
||||
}
|
||||
|
||||
.suggest-search-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--interactive-accent);
|
||||
}
|
||||
|
||||
.suggest-select-item {
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
|
||||
.mod-warning {
|
||||
color: var(--text-warning);
|
||||
font-style: italic;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
|
||||
.test-mappings-header {
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
|
|
@ -51,11 +89,12 @@
|
|||
}
|
||||
|
||||
.value-display {
|
||||
background: var(--background-primary-alt);
|
||||
background: var(--background-secondary);
|
||||
border-radius: 4px;
|
||||
padding: 0.5em 0.8em;
|
||||
font-family: var(--font-monospace);
|
||||
/*font-family: var(--font-monospace);*/
|
||||
font-size: 0.98em;
|
||||
min-height: 2em;
|
||||
margin-top: 0.2em;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
|
|
@ -69,34 +108,17 @@
|
|||
.test-mapping-item .from-jira-input {
|
||||
width: 100%;
|
||||
min-height: 2.2em;
|
||||
font-family: var(--font-monospace);
|
||||
/*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);
|
||||
background: var(--background-secondary);
|
||||
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;
|
||||
|
|
@ -109,26 +131,31 @@
|
|||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
.add-test-mapping-btn:hover {
|
||||
background: var(--interactive-accent-hover);
|
||||
}
|
||||
|
||||
.jira-field-mappings {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.field-mappings-list {
|
||||
margin: 10px 0;
|
||||
margin: 20px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
background: var(--background-primary-alt);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.field-mapping-item {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 2fr 2fr auto;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
padding: 8px;
|
||||
background: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
align-items: center;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
position: relative;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.field-mapping-item:hover {
|
||||
border-color: var(--interactive-accent);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.work-description-container textarea {
|
||||
|
|
@ -136,63 +163,111 @@
|
|||
}
|
||||
|
||||
.field-name-container {
|
||||
background-color: var(--background-primary);
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 2px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
|
||||
.field-mapping-label {
|
||||
font-weight: 500;
|
||||
font-weight: 600;
|
||||
color: var(--text-normal);
|
||||
font-size: 0.9em;
|
||||
/*text-transform: uppercase;*/
|
||||
letter-spacing: 0.5px;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.field-name-input, .to-jira-input, .from-jira-input {
|
||||
.field-name-input {
|
||||
width: 100%;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
padding: 10px 12px;
|
||||
border: 2px solid var(--background-modifier-border);
|
||||
border-radius: 6px;
|
||||
background: var(--background-secondary);
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.field-name-input:focus {
|
||||
border-color: var(--interactive-accent);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px rgba(var(--interactive-accent-rgb), 0.1);
|
||||
}
|
||||
|
||||
.functions-container {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.to-jira-container,
|
||||
.from-jira-container {
|
||||
background: var(--background-secondary-alt);
|
||||
padding: 16px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--background-modifier-border-hover);
|
||||
}
|
||||
|
||||
.test-mapping-item .from-jira-container {
|
||||
background: var(--transparent);
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
|
||||
.field-mapping-item .invalid,
|
||||
.setting-item.invalid input {
|
||||
background-color: rgba(var(--color-red-rgb), 0.2);
|
||||
.to-jira-input,
|
||||
.from-jira-input {
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 4px;
|
||||
background: var(--background-primary);
|
||||
font-family: var(--font-monospace);
|
||||
font-size: 0.9em;
|
||||
line-height: 1.4;
|
||||
resize: vertical;
|
||||
transition: border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.to-jira-input:focus,
|
||||
.from-jira-input:focus {
|
||||
border-color: var(--interactive-accent);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px rgba(var(--interactive-accent-rgb), 0.1);
|
||||
}
|
||||
|
||||
.field-mapping-item .invalid {
|
||||
border-color: var(--color-red) !important;
|
||||
background-color: rgba(var(--color-red-rgb), 0.1) !important;
|
||||
box-shadow: 0 0 0 2px rgba(var(--color-red-rgb), 0.2) !important;
|
||||
}
|
||||
|
||||
.remove-field-btn {
|
||||
background-color: var(--background-modifier-error);
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 16px;
|
||||
background: var(--background-modifier-error);
|
||||
color: var(--text-on-accent);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.add-field-mapping-btn {
|
||||
padding: 6px 12px;
|
||||
background-color: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.mapping-examples {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.mapping-examples ul {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.mapping-examples li {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.use-example-btn {
|
||||
margin-left: 8px;
|
||||
padding: 2px 6px;
|
||||
background-color: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-size: 0.8em;
|
||||
.remove-field-btn:hover {
|
||||
background: var(--background-modifier-error-hover);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.button-view {
|
||||
|
|
@ -207,6 +282,121 @@
|
|||
width: fit-content;
|
||||
}
|
||||
|
||||
.button-view {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
gap: 12px;
|
||||
margin-top: 20px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.button-view button {
|
||||
padding: 10px 16px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.add-field-mapping-btn {
|
||||
background: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
}
|
||||
|
||||
.add-field-mapping-btn:hover {
|
||||
background: var(--interactive-accent-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.reset-field-mappings-btn,
|
||||
.reload-field-mappings-btn {
|
||||
background: var(--background-secondary);
|
||||
color: var(--text-normal);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.reset-field-mappings-btn:hover,
|
||||
.reload-field-mappings-btn:hover {
|
||||
background: var(--background-secondary-alt);
|
||||
border-color: var(--interactive-accent);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.button-view button::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: var(--background-tooltip);
|
||||
color: var(--text-on-accent);
|
||||
padding: 6px 10px;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8em;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s ease;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.button-view button:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.mapping-examples {
|
||||
margin-top: 24px;
|
||||
padding: 20px;
|
||||
background: var(--background-secondary-alt);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.mapping-examples h4 {
|
||||
color: var(--text-normal);
|
||||
margin-bottom: 16px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.mapping-examples ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.mapping-examples li {
|
||||
background: var(--background-primary);
|
||||
padding: 16px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--background-modifier-border-hover);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.use-example-btn {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
padding: 6px 12px;
|
||||
background: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 0.8em;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.use-example-btn:hover {
|
||||
background: var(--interactive-accent-hover);
|
||||
}
|
||||
|
||||
/*hljs.css*/
|
||||
pre code.hljs {
|
||||
|
|
@ -331,3 +521,236 @@ code.hljs {
|
|||
/* purposely ignored */
|
||||
|
||||
}
|
||||
|
||||
/* Period selector improvements */
|
||||
.period-selector .setting-item-control {
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.period-selector .timekeep-btn {
|
||||
background: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
padding: 0.5rem 1rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.period-selector .timekeep-btn:hover:not(:disabled) {
|
||||
background: var(--interactive-accent-hover);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.period-selector .timekeep-btn:disabled {
|
||||
background: var(--background-modifier-border);
|
||||
color: var(--text-muted);
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* Custom dates container */
|
||||
.custom-dates-container {
|
||||
margin-top: 1rem;
|
||||
padding: 1.25rem;
|
||||
background: var(--background-primary-alt);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--background-modifier-border-hover);
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
/* Main display container */
|
||||
.all-periods-display {
|
||||
margin-top: 1.5rem;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
}
|
||||
|
||||
.timekeep-section-title {
|
||||
color: var(--text-normal);
|
||||
margin-bottom: 1.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 2px solid var(--interactive-accent);
|
||||
font-size: 1.1em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Period sections */
|
||||
.period-section {
|
||||
margin-bottom: 2rem;
|
||||
background: var(--background-primary);
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
overflow: hidden;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.timekeep-period-title {
|
||||
background: var(--background-secondary);
|
||||
padding: 0.875rem 1.25rem;
|
||||
margin: 0;
|
||||
color: var(--text-normal);
|
||||
font-weight: 600;
|
||||
font-size: 0.95em;
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
|
||||
/* Table container and styling */
|
||||
.timekeep-table-container {
|
||||
overflow-x: auto;
|
||||
background: var(--background-primary);
|
||||
}
|
||||
|
||||
.timekeep-entries-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.875em;
|
||||
}
|
||||
|
||||
.timekeep-entries-table th {
|
||||
background: var(--background-secondary-alt);
|
||||
color: var(--text-normal);
|
||||
font-weight: 600;
|
||||
text-align: left;
|
||||
padding: 0.75rem 1rem;
|
||||
border-bottom: 2px solid var(--background-modifier-border);
|
||||
font-size: 0.8em;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.timekeep-entries-table td {
|
||||
padding: 0.875rem 1rem;
|
||||
border-bottom: 1px solid var(--background-modifier-border-hover);
|
||||
vertical-align: top;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.timekeep-entries-table tbody tr {
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.timekeep-entries-table tbody tr:hover {
|
||||
background: var(--background-secondary-alt);
|
||||
}
|
||||
|
||||
.timekeep-table-row-alt {
|
||||
background: var(--background-primary-alt);
|
||||
}
|
||||
|
||||
.timekeep-table-row-alt:hover {
|
||||
background: var(--background-secondary-alt);
|
||||
}
|
||||
|
||||
/* Column-specific styling */
|
||||
.timekeep-entries-table td:nth-child(1) {
|
||||
font-weight: 500;
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.timekeep-entries-table td:nth-child(2) {
|
||||
font-family: var(--font-monospace);
|
||||
font-size: 0.85em;
|
||||
color: var(--interactive-accent);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.timekeep-entries-table td:nth-child(3) {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.85em;
|
||||
max-width: 200px;
|
||||
word-wrap: break-word;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
.timekeep-entries-table td:nth-child(4) {
|
||||
font-family: var(--font-monospace);
|
||||
font-size: 0.8em;
|
||||
color: var(--text-muted);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.timekeep-entries-table td:nth-child(5) {
|
||||
font-family: var(--font-monospace);
|
||||
font-weight: 600;
|
||||
color: var(--text-accent);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* No data state */
|
||||
.timekeep-no-data {
|
||||
text-align: center;
|
||||
padding: 3rem 1rem;
|
||||
color: var(--text-muted);
|
||||
font-style: italic;
|
||||
background: var(--background-secondary-alt);
|
||||
border-radius: 8px;
|
||||
border: 1px dashed var(--background-modifier-border);
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
/* Responsive design */
|
||||
@media (max-width: 768px) {
|
||||
.functions-container {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.button-view {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.button-view button {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.timekeep-entries-table {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.timekeep-entries-table td {
|
||||
padding: 0.625rem 0.75rem;
|
||||
}
|
||||
|
||||
.timekeep-entries-table th {
|
||||
padding: 0.625rem 0.75rem;
|
||||
font-size: 0.75em;
|
||||
}
|
||||
|
||||
.timekeep-entries-table td:nth-child(3) {
|
||||
max-width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Loading and transition states */
|
||||
.timekeep-loading {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.timekeep-fade-in {
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@
|
|||
"module": "node16",
|
||||
"target": "ES2018",
|
||||
"allowJs": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "node16",
|
||||
"importHelpers": true,
|
||||
|
|
|
|||
776
yarn.lock
776
yarn.lock
|
|
@ -2,83 +2,131 @@
|
|||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@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/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/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/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==
|
||||
|
||||
"@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"
|
||||
|
|
@ -87,12 +135,12 @@
|
|||
"@nodelib/fs.stat" "2.0.5"
|
||||
run-parallel "^1.1.9"
|
||||
|
||||
"@nodelib/fs.stat@^2.0.2", "@nodelib/fs.stat@2.0.5":
|
||||
"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
|
||||
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.8":
|
||||
"@nodelib/fs.walk@^1.2.3":
|
||||
version "1.2.8"
|
||||
resolved "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz"
|
||||
integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
|
||||
|
|
@ -149,7 +197,7 @@
|
|||
semver "^7.3.7"
|
||||
tsutils "^3.21.0"
|
||||
|
||||
"@typescript-eslint/parser@^5.0.0", "@typescript-eslint/parser@5.29.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==
|
||||
|
|
@ -214,66 +262,16 @@
|
|||
"@typescript-eslint/types" "5.29.0"
|
||||
eslint-visitor-keys "^3.3.0"
|
||||
|
||||
"@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:
|
||||
acorn@^8.14.1:
|
||||
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"
|
||||
|
|
@ -286,57 +284,20 @@ builtin-modules@3.3.0:
|
|||
resolved "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz"
|
||||
integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==
|
||||
|
||||
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==
|
||||
chokidar@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-4.0.3.tgz#7be37a4c03c9aee1ecfe862a4a23b2c70c205d30"
|
||||
integrity sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==
|
||||
dependencies:
|
||||
ansi-styles "^4.1.0"
|
||||
supports-color "^7.1.0"
|
||||
readdirp "^4.0.1"
|
||||
|
||||
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:
|
||||
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"
|
||||
|
|
@ -344,13 +305,6 @@ 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"
|
||||
|
|
@ -382,11 +336,6 @@ 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"
|
||||
|
|
@ -395,14 +344,6 @@ 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"
|
||||
|
|
@ -420,76 +361,6 @@ 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"
|
||||
|
|
@ -502,26 +373,11 @@ 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"
|
||||
|
|
@ -533,16 +389,6 @@ 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"
|
||||
|
|
@ -550,13 +396,6 @@ 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"
|
||||
|
|
@ -564,32 +403,14 @@ 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==
|
||||
fs-extra@^11.3.0:
|
||||
version "11.3.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.3.0.tgz#0daced136bbaf65a555a326719af931adc7a314d"
|
||||
integrity sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==
|
||||
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==
|
||||
graceful-fs "^4.2.0"
|
||||
jsonfile "^6.0.1"
|
||||
universalify "^2.0.0"
|
||||
|
||||
functional-red-black-tree@^1.0.1:
|
||||
version "1.0.1"
|
||||
|
|
@ -603,32 +424,6 @@ 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"
|
||||
|
|
@ -641,15 +436,10 @@ 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==
|
||||
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
||||
version "4.2.11"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
|
||||
integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
|
||||
|
||||
highlight.js@^11.11.1:
|
||||
version "11.11.1"
|
||||
|
|
@ -661,38 +451,12 @@ ignore@^5.2.0:
|
|||
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.0, is-glob@^4.0.1, is-glob@^4.0.3:
|
||||
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==
|
||||
|
|
@ -704,64 +468,14 @@ 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==
|
||||
jsonfile@^6.0.1:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
|
||||
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
|
||||
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==
|
||||
universalify "^2.0.0"
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
lodash@^4.17.21:
|
||||
version "4.17.21"
|
||||
|
|
@ -781,13 +495,6 @@ 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"
|
||||
|
|
@ -798,11 +505,6 @@ 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"
|
||||
|
|
@ -811,61 +513,6 @@ 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"
|
||||
|
|
@ -876,43 +523,26 @@ 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"
|
||||
integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
|
||||
|
||||
readdirp@^4.0.1:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-4.1.2.tgz#eb85801435fbf2a7ee58f19e0921b068fc69948d"
|
||||
integrity sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==
|
||||
|
||||
regexpp@^3.2.0:
|
||||
version "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"
|
||||
|
|
@ -925,52 +555,11 @@ 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"
|
||||
|
|
@ -978,16 +567,16 @@ to-regex-range@^5.0.1:
|
|||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
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==
|
||||
|
||||
tslib@^1.8.1:
|
||||
version "1.14.1"
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz"
|
||||
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
|
||||
|
||||
tsutils@^3.21.0:
|
||||
version "3.21.0"
|
||||
resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz"
|
||||
|
|
@ -995,58 +584,17 @@ tsutils@^3.21.0:
|
|||
dependencies:
|
||||
tslib "^1.8.1"
|
||||
|
||||
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:
|
||||
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==
|
||||
universalify@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.1.tgz#168efc2180964e6386d061e094df61afe239b18d"
|
||||
integrity sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==
|
||||
|
||||
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==
|
||||
|
|
|
|||
Loading…
Reference in a new issue