From 96727c61f563d1face168cf2fcbf2567d61ad475 Mon Sep 17 00:00:00 2001 From: Grol Grol Date: Sat, 5 Jul 2025 14:27:48 +0300 Subject: [PATCH] =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=87=D0=B0=D1=8F=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + __tests__/TextSyncPipeline.test.ts | 2 +- __tests__/checkboxUtils.base.test.ts | 2 +- __tests__/checkboxUtils.plainLists.test.ts | 2 +- __tests__/core/CheckboxUtils2.test.ts | 312 +++++ .../fakes/InMemoryFilePathStateHolder.ts | 4 +- jest.config.ts | 18 +- package-lock.json | 1024 ++++++++++++++--- package.json | 1 + src/SyncController.ts | 22 +- src/TextSyncPipeline.ts | 6 +- src/core/CheckboxUtils2.ts | 22 +- src/{ => core}/checkboxUtils.ts | 6 +- src/core/model/ContextFactory.ts | 136 ++- src/core/model/Line.ts | 4 + src/core/model/TreeNode.ts | 6 +- src/core/model/View.ts | 8 +- src/core/model/line/AbstractLine.ts | 31 +- src/core/model/line/CheckboxLine.ts | 82 +- src/core/model/line/ListLine.ts | 14 +- src/core/model/line/TextLine.ts | 9 +- .../PropagateStateToChildrenProcess.ts | 28 +- .../process/PropagateStateToParentProcess.ts | 52 +- src/main.ts | 9 +- src/types.ts | 2 + 25 files changed, 1496 insertions(+), 307 deletions(-) create mode 100644 __tests__/core/CheckboxUtils2.test.ts rename src/{ => core}/checkboxUtils.ts (97%) diff --git a/.gitignore b/.gitignore index 295c0d0..29a06d7 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ data.json .DS_Store coverage/ +test-report.html \ No newline at end of file diff --git a/__tests__/TextSyncPipeline.test.ts b/__tests__/TextSyncPipeline.test.ts index 180f243..1789b2a 100644 --- a/__tests__/TextSyncPipeline.test.ts +++ b/__tests__/TextSyncPipeline.test.ts @@ -1,4 +1,4 @@ -import { CheckboxUtils } from "src/checkboxUtils"; +import { CheckboxUtils } from "src/core/checkboxUtils"; import { FileFilter } from "src/FileFilter"; import TextSyncPipeline from "src/TextSyncPipeline"; import { CheckboxSyncPluginSettings, DEFAULT_SETTINGS } from "src/types"; diff --git a/__tests__/checkboxUtils.base.test.ts b/__tests__/checkboxUtils.base.test.ts index c277cec..2a73eaf 100644 --- a/__tests__/checkboxUtils.base.test.ts +++ b/__tests__/checkboxUtils.base.test.ts @@ -1,4 +1,4 @@ -import { CheckboxLineInfo, CheckboxUtils } from '../src/checkboxUtils'; // Путь к вашему файлу +import { CheckboxLineInfo, CheckboxUtils } from '../src/core/checkboxUtils'; // Путь к вашему файлу import { CheckboxState, CheckboxSyncPluginSettings, DEFAULT_SETTINGS } from '../src/types'; // Путь к вашему файлу типов // Вспомогательная функция для создания настроек с переопределениями diff --git a/__tests__/checkboxUtils.plainLists.test.ts b/__tests__/checkboxUtils.plainLists.test.ts index 45bd743..29ae833 100644 --- a/__tests__/checkboxUtils.plainLists.test.ts +++ b/__tests__/checkboxUtils.plainLists.test.ts @@ -1,6 +1,6 @@ // checkboxUtils.plainLists.test.ts -import { CheckboxUtils } from '../src/checkboxUtils'; +import { CheckboxUtils } from '../src/core/checkboxUtils'; import { CheckboxState, CheckboxSyncPluginSettings, DEFAULT_SETTINGS } from '../src/types'; // Вспомогательная функция для создания настроек с переопределениями diff --git a/__tests__/core/CheckboxUtils2.test.ts b/__tests__/core/CheckboxUtils2.test.ts new file mode 100644 index 0000000..e85bffd --- /dev/null +++ b/__tests__/core/CheckboxUtils2.test.ts @@ -0,0 +1,312 @@ +// import { CheckboxLineInfo, CheckboxUtils } from '../src/checkboxUtils'; // Путь к вашему файлу +import { CheckboxUtils2 } from 'src/core/CheckboxUtils2'; +import { CheckboxSyncPluginSettings, DEFAULT_SETTINGS } from 'src/types'; // Путь к вашему файлу типов + +// Вспомогательная функция для создания настроек с переопределениями +const createSettings = (overrides: Partial = {}): Readonly => { + return { ...DEFAULT_SETTINGS, ...overrides } as Readonly; +}; + +describe('CheckboxUtils2', () => { + // --- Тесты для syncText --- + describe('syncText', () => { + it('should propagate down only if child sync enabled and diff matches', () => { + const utils = new CheckboxUtils2(createSettings({ + enableAutomaticChildState: true, + enableAutomaticParentState: false, // Parent sync off + })); + const textBefore = [ + '- [ ] Parent', + ' - [ ] Child', + ].join('\n'); + const textAfterParentChecked = [ // Simulate user checking parent + '- [x] Parent', + ' - [ ] Child', + ].join('\n'); + const expected = [ + '- [x] Parent', + ' - [x] Child', // Propagated down + ].join('\n'); + expect(utils.syncText(textAfterParentChecked, textBefore)).toBe(expected); + }); + + it('should propagate up only if parent sync enabled', () => { + const utils = new CheckboxUtils2(createSettings({ + enableAutomaticChildState: false, // Child sync off + enableAutomaticParentState: true, + })); + const textBefore = [ + '- [ ] Parent', + ' - [ ] Child', + ].join('\n'); + const textAfterChildChecked = [ // Simulate user checking child + '- [ ] Parent', + ' - [x] Child', + ].join('\n'); + const expected = [ + '- [x] Parent', // Propagated up + ' - [x] Child', + ].join('\n'); + // syncText calls propagateFromChildren unconditionally if enabled + expect(utils.syncText(textAfterChildChecked, textBefore)).toBe(expected); + }); + + it('should propagate down then up if both enabled', () => { + const utils = new CheckboxUtils2(createSettings({ + enableAutomaticChildState: true, + enableAutomaticParentState: true, + })); + const textBefore = [ + '- [ ] Parent', + ' - [ ] Child', + ].join('\n'); + const textAfterParentChecked = [ // Simulate user checking parent + '- [x] Parent', + ' - [ ] Child', + ].join('\n'); + // 1. User checks parent: '- [x] Parent', ' - [ ] Child' + // 2. Propagate down: '- [x] Parent', ' - [x] Child' + // 3. Propagate up: (no change needed as parent is already checked) + const expectedDown = [ + '- [x] Parent', + ' - [x] Child', // Propagated down + ].join('\n'); + expect(utils.syncText(textAfterParentChecked, textBefore)).toBe(expectedDown); + + const textAfterChildChecked = [ // Simulate user checking child + '- [ ] Parent', + ' - [x] Child', + ].join('\n'); + // 1. User checks child: '- [ ] Parent', ' - [x] Child' + // 2. Propagate down: (no change, as only one line changed, and it wasn't the parent) + // 3. Propagate up: '- [x] Parent', ' - [x] Child' + const expectedUp = [ + '- [x] Parent', // Propagated up + ' - [x] Child', + ].join('\n'); + expect(utils.syncText(textAfterChildChecked, textBefore)).toBe(expectedUp); + }); + + it('should do nothing if both syncs disabled', () => { + const utils = new CheckboxUtils2(createSettings({ + enableAutomaticChildState: false, + enableAutomaticParentState: false, + })); + const textBefore = [ + '- [ ] Parent', + ' - [ ] Child', + ].join('\n'); + const textAfterParentChecked = [ // Simulate user checking parent + '- [x] Parent', + ' - [ ] Child', + ].join('\n'); + const textAfterChildChecked = [ // Simulate user checking child + '- [ ] Parent', + ' - [x] Child', + ].join('\n'); + expect(utils.syncText(textAfterParentChecked, textBefore)).toBe(textAfterParentChecked); + expect(utils.syncText(textAfterChildChecked, textBefore)).toBe(textAfterChildChecked); + }); + + it('should not propagate down if diff is not a single checkbox state change', () => { + const utils = new CheckboxUtils2(createSettings({ + enableAutomaticChildState: true, // Down propagation enabled + enableAutomaticParentState: false, + })); + const textBefore = [ + '- [ ] Parent', + ' - [ ] Child', + ].join('\n'); + const textMultipleChanges = [ + '- [x] Parent Changed', // Text changed too + ' - [x] Child also changed', + ].join('\n'); + const textIndentChange = [ + ' - [x] Parent', // Indent changed + ' - [ ] Child', + ].join('\n'); + + // Multiple lines changed + expect(utils.syncText(textMultipleChanges, textBefore)).toBe(textMultipleChanges); + // Indent changed (diff > 1 line index) + expect(utils.syncText(textIndentChange, textBefore)).toBe(textIndentChange); + }); + + it('equals texts', () => { + const utils = new CheckboxUtils2(createSettings({ + enableAutomaticChildState: true, // Down propagation enabled + enableAutomaticParentState: true, + })); + const textBefore = [ + '- [ ] Parent', + ' - [ ] Child', + ].join('\n'); + + // Multiple lines changed + expect(utils.syncText(textBefore, textBefore)).toBe(textBefore); + }); + + it('checkbox, list', ()=> { + const utils = new CheckboxUtils2(createSettings({ + enableAutomaticChildState: true, + enableAutomaticParentState: true, + })); + const textBefore = [ + '- [ ] Parent', + ' - Child1', + ' - [ ] Child2', + ].join('\n'); + const actualText = [ + '- [ ] Parent', + ' - Child1', + ' - [x] Child2', + ].join('\n'); + const expectedText = [ + '- [x] Parent', + ' - Child1', + ' - [x] Child2', + ].join('\n'); + + // Multiple lines changed + expect(utils.syncText(actualText, textBefore)).toBe(expectedText); + }); + + it('checkbox, plain text', ()=> { + const utils = new CheckboxUtils2(createSettings({ + enableAutomaticChildState: true, + enableAutomaticParentState: true, + })); + const textBefore = [ + '- [ ] Parent', + ' Child1', + ' - [ ] Child2', + ].join('\n'); + const actualText = [ + '- [ ] Parent', + ' Child1', + ' - [x] Child2', + ].join('\n'); + const expectedText = [ + '- [x] Parent', + ' Child1', + ' - [x] Child2', + ].join('\n'); + + // Multiple lines changed + expect(utils.syncText(actualText, textBefore)).toBe(expectedText); + }); + + describe('without textBefore', () => { + it('done from child to parent', () => { + const utils = new CheckboxUtils2(createSettings({ + enableAutomaticChildState: true, + enableAutomaticParentState: true, + })); + const actualText = [ + '- [ ] Parent', + ' - [x] Child', + ].join('\n'); + const expected = [ + '- [x] Parent', + ' - [x] Child', + ].join('\n'); + expect(utils.syncText(actualText, undefined)).toBe(expected); + }); + it('todo from child to parent', () => { + const utils = new CheckboxUtils2(createSettings({ + enableAutomaticChildState: true, + enableAutomaticParentState: true, + })); + const actualText = [ + '- [x] Parent', + ' - [ ] Child', + ].join('\n'); + const expected = [ + '- [ ] Parent', + ' - [ ] Child', + ].join('\n'); + expect(utils.syncText(actualText, undefined)).toBe(expected); + }); + }); + describe('many roots', () => { + it('two roots witout text before', () => { + const utils = new CheckboxUtils2(createSettings({ + enableAutomaticChildState: true, + enableAutomaticParentState: true, + })); + const actualText = [ + '- [x] Parent1', + ' - [ ] Child1', + '- [ ] Parent2', + ' - [x] Child2', + ].join('\n'); + const expected = [ + '- [ ] Parent1', + ' - [ ] Child1', + '- [x] Parent2', + ' - [x] Child2', + ].join('\n'); + expect(utils.syncText(actualText, undefined)).toBe(expected); + }); + + it('two roots', () => { + const utils = new CheckboxUtils2(createSettings({ + enableAutomaticChildState: true, + enableAutomaticParentState: true, + })); + const textBefore = [ + '- [x] Parent1', + ' - [x] Child1', + '- [ ] Parent2', + ' - [ ] Child2', + ].join('\n'); + const actualText = [ + '- [x] Parent1', + ' - [ ] Child1', + '- [ ] Parent2', + ' - [x] Child2', + ].join('\n'); + const expected = [ + '- [ ] Parent1', + ' - [ ] Child1', + '- [x] Parent2', + ' - [x] Child2', + ].join('\n'); + expect(utils.syncText(actualText, textBefore)).toBe(expected); + }); + + it('all type roots', () => { + const utils = new CheckboxUtils2(createSettings({ + enableAutomaticChildState: true, + enableAutomaticParentState: true, + })); + const textBefore = [ + '- [x] Parent1', + ' - [x] Child1', + '- Parent2', + ' - Child2', + 'Parent3', + ' Child3', + ].join('\n'); + const actualText = [ + '- [x] Parent1', + ' - [ ] Child1', + '- Parent2', + ' - Child2', + 'Parent3', + ' Child3', + ].join('\n'); + const expected = [ + '- [ ] Parent1', + ' - [ ] Child1', + '- Parent2', + ' - Child2', + 'Parent3', + ' Child3', + ].join('\n'); + expect(utils.syncText(actualText, textBefore)).toBe(expected); + }); + }); + + }); +}); \ No newline at end of file diff --git a/__tests__/fakes/InMemoryFilePathStateHolder.ts b/__tests__/fakes/InMemoryFilePathStateHolder.ts index 9861498..a89d3e3 100644 --- a/__tests__/fakes/InMemoryFilePathStateHolder.ts +++ b/__tests__/fakes/InMemoryFilePathStateHolder.ts @@ -8,13 +8,13 @@ export class InMemoryFilePathStateHolder implements IFilePathStateHolder { } public setByPath(filePath: string, text: string): void { - console.log(`InMemoryFileStateHolder: Setting state for "${filePath}"`); + // console.log(`InMemoryFileStateHolder: Setting state for "${filePath}"`); this.states.set(filePath, text); } public getByPath(filePath: string): string | undefined { const state = this.states.get(filePath); - console.log(`InMemoryFileStateHolder: Getting state for "${filePath}". Found: ${!!state}`); + // console.log(`InMemoryFileStateHolder: Getting state for "${filePath}". Found: ${!!state}`); return state; } diff --git a/jest.config.ts b/jest.config.ts index 27a022a..07e2ce5 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -3,10 +3,18 @@ * https://jestjs.io/docs/configuration */ -import type {Config} from 'jest'; +import type { Config } from 'jest'; const config: Config = { + reporters: [ + 'default', + ['jest-html-reporter', { + pageTitle: 'Test Report', + outputPath: './test-report.html', + }] + ], + // Automatically clear mock calls, instances, contexts and results before every test clearMocks: true, @@ -18,14 +26,14 @@ const config: Config = { // Indicates which provider should be used to instrument code for coverage // coverageProvider: "v8", - coverageProvider: "babel", + coverageProvider: "babel", // An array of file extensions your modules use moduleFileExtensions: [ "ts", "tsx", "js", - "jsx", + "jsx", "json", "node" ], @@ -43,11 +51,11 @@ const config: Config = { '^src/(.*)$': '/src/$1', }, - testPathIgnorePatterns: [ + testPathIgnorePatterns: [ "/node_modules/", // Хорошо иметь это явно, хотя Jest часто делает это по умолчанию "/__tests__/fakes/" // Исключаем папку fakes внутри __tests__ ], - + }; export default config; diff --git a/package-lock.json b/package-lock.json index 6d81217..092c3b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "builtin-modules": "3.3.0", "esbuild": "^0.25.0", "jest": "^29.7.0", + "jest-html-reporter": "^4.3.0", "obsidian": "^1.7.2", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", @@ -42,24 +43,24 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", + "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" + "picocolors": "^1.1.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.26.8", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz", - "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.0.tgz", + "integrity": "sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==", "dev": true, "license": "MIT", "engines": { @@ -67,22 +68,22 @@ } }, "node_modules/@babel/core": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz", - "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.0.tgz", + "integrity": "sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==", "dev": true, "license": "MIT", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.9", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.9", - "@babel/parser": "^7.26.9", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.9", - "@babel/types": "^7.26.9", + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-module-transforms": "^7.27.3", + "@babel/helpers": "^7.27.6", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -108,16 +109,16 @@ } }, "node_modules/@babel/generator": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz", - "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.0.tgz", + "integrity": "sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.28.0", + "@babel/types": "^7.28.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -125,14 +126,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.26.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz", - "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", + "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.26.5", - "@babel/helper-validator-option": "^7.25.9", + "@babel/compat-data": "^7.27.2", + "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -151,30 +152,40 @@ "semver": "bin/semver.js" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-module-imports": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", - "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", + "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.25.9", - "@babel/types": "^7.25.9" + "@babel/traverse": "^7.27.1", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", - "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz", + "integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9", - "@babel/traverse": "^7.25.9" + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1", + "@babel/traverse": "^7.27.3" }, "engines": { "node": ">=6.9.0" @@ -194,9 +205,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", - "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, "license": "MIT", "engines": { @@ -204,9 +215,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", - "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", "dev": true, "license": "MIT", "engines": { @@ -214,9 +225,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.25.9", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", - "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, "license": "MIT", "engines": { @@ -224,27 +235,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz", - "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==", + "version": "7.27.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.6.tgz", + "integrity": "sha512-muE8Tt8M22638HU31A3CgfSUciwz1fhATfoVai05aPXGor//CdWDCbnlY1yvBPo07njuVOCNGCSp/GTt12lIug==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.10" + "@babel/template": "^7.27.2", + "@babel/types": "^7.27.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", - "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.0.tgz", + "integrity": "sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.26.9" + "@babel/types": "^7.28.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -493,58 +504,48 @@ } }, "node_modules/@babel/template": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", - "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", + "version": "7.27.2", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", + "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.26.9", - "@babel/types": "^7.26.9" + "@babel/code-frame": "^7.27.1", + "@babel/parser": "^7.27.2", + "@babel/types": "^7.27.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.26.9", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz", - "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.0.tgz", + "integrity": "sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.9", - "@babel/parser": "^7.26.9", - "@babel/template": "^7.26.9", - "@babel/types": "^7.26.9", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.27.1", + "@babel/generator": "^7.28.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/template": "^7.27.2", + "@babel/types": "^7.28.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/@babel/types": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz", - "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.0.tgz", + "integrity": "sha512-jYnje+JyZG5YThjHiF28oT4SIZLnYOcSBb6+SDaFIyzDVSkXQmQQYclJ2R+YxcdmK0AX6x1E5OQNtuh3jHDrUg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.27.1" }, "engines": { "node": ">=6.9.0" @@ -1149,6 +1150,109 @@ "license": "BSD-3-Clause", "peer": true }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1409,6 +1513,30 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/reporters": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", @@ -1559,18 +1687,14 @@ } }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "version": "0.3.12", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.12.tgz", + "integrity": "sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -1583,16 +1707,6 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", @@ -1601,9 +1715,9 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.29", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.29.tgz", + "integrity": "sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1657,6 +1771,17 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -2077,8 +2202,7 @@ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "dev": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/acorn": { "version": "8.14.0", @@ -2379,9 +2503,9 @@ "license": "MIT" }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", "dependencies": { @@ -2403,9 +2527,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.25.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.25.1.tgz", + "integrity": "sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==", "dev": true, "funding": [ { @@ -2423,10 +2547,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", + "caniuse-lite": "^1.0.30001726", + "electron-to-chromium": "^1.5.173", "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -2499,9 +2623,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001700", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz", - "integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==", + "version": "1.0.30001726", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001726.tgz", + "integrity": "sha512-VQAUIUzBiZ/UnlM28fSp2CRF3ivUn1BWEvxMcVTNwpw91Py1pGbPIyIKtd+tzct9C3ouceCVdGAXxZOpZAsgdw==", "dev": true, "funding": [ { @@ -2680,6 +2804,16 @@ "node": ">= 8" } }, + "node_modules/dateformat": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.2.tgz", + "integrity": "sha512-EelsCzH0gMC2YmXuMeaZ3c6md1sUJQxyb1XXc4xaisi/K6qKukqZhKPrEQyRkdNIncgYyLoDTReq0nNyuKerTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -2778,6 +2912,13 @@ "node": ">=6.0.0" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ejs": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", @@ -2795,9 +2936,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.102", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.102.tgz", - "integrity": "sha512-eHhqaja8tE/FNpIiBrvBjFV/SSKpyWHLvxuR9dPTdo+3V9ppdLmFB7ZZQ98qNovcngPLYIz0oOBF9P0FfZef5Q==", + "version": "1.5.179", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.179.tgz", + "integrity": "sha512-UWKi/EbBopgfFsc5k61wFpV7WrnnSlSzW/e2XcBmS6qKYTivZlLtoll5/rdqRTxGglGHkmkW0j0pFNJG10EUIQ==", "dev": true, "license": "ISC" }, @@ -3185,6 +3326,16 @@ "node": ">= 0.8.0" } }, + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/expect": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", @@ -3300,9 +3451,9 @@ } }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3377,6 +3528,36 @@ "license": "ISC", "peer": true }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/foreground-child/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3852,6 +4033,22 @@ "node": ">=8" } }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jake": { "version": "10.9.2", "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", @@ -4125,6 +4322,442 @@ "fsevents": "^2.3.2" } }, + "node_modules/jest-html-reporter": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jest-html-reporter/-/jest-html-reporter-4.3.0.tgz", + "integrity": "sha512-lq4Zx35yc6Ehw513CXJ1ok3wUmkSiOImWcyLAmylfzrz7DAqtrhDF9V73F4qfstmGxlr8X0QrEjWsl/oqhf4sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/reporters": "^30.0.2", + "@jest/test-result": "^30.0.2", + "@jest/types": "^30.0.1", + "dateformat": "3.0.2", + "mkdirp": "^1.0.3", + "strip-ansi": "6.0.1", + "xmlbuilder": "15.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "jest": "19.x - 30.x", + "typescript": "^3.7.x || ^4.3.x || ^5.x" + } + }, + "node_modules/jest-html-reporter/node_modules/@jest/console": { + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.0.4.tgz", + "integrity": "sha512-tMLCDvBJBwPqMm4OAiuKm2uF5y5Qe26KgcMn+nrDSWpEW+eeFmqA0iO4zJfL16GP7gE3bUUQ3hIuUJ22AqVRnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.1", + "@types/node": "*", + "chalk": "^4.1.2", + "jest-message-util": "30.0.2", + "jest-util": "30.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-html-reporter/node_modules/@jest/reporters": { + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.0.4.tgz", + "integrity": "sha512-6ycNmP0JSJEEys1FbIzHtjl9BP0tOZ/KN6iMeAKrdvGmUsa1qfRdlQRUDKJ4P84hJ3xHw1yTqJt4fvPNHhyE+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^0.2.3", + "@jest/console": "30.0.4", + "@jest/test-result": "30.0.4", + "@jest/transform": "30.0.4", + "@jest/types": "30.0.1", + "@jridgewell/trace-mapping": "^0.3.25", + "@types/node": "*", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.3.10", + "graceful-fs": "^4.2.11", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-instrument": "^6.0.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^5.0.0", + "istanbul-reports": "^3.1.3", + "jest-message-util": "30.0.2", + "jest-util": "30.0.2", + "jest-worker": "30.0.2", + "slash": "^3.0.0", + "string-length": "^4.0.2", + "v8-to-istanbul": "^9.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "peerDependencies": { + "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/jest-html-reporter/node_modules/@jest/schemas": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.1.tgz", + "integrity": "sha512-+g/1TKjFuGrf1Hh0QPCv0gISwBxJ+MQSNXmG9zjHy7BmFhtoJ9fdNhWJp3qUKRi93AOZHXtdxZgJ1vAtz6z65w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-html-reporter/node_modules/@jest/test-result": { + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.0.4.tgz", + "integrity": "sha512-Mfpv8kjyKTHqsuu9YugB6z1gcdB3TSSOaKlehtVaiNlClMkEHY+5ZqCY2CrEE3ntpBMlstX/ShDAf84HKWsyIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/console": "30.0.4", + "@jest/types": "30.0.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-html-reporter/node_modules/@jest/transform": { + "version": "30.0.4", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.0.4.tgz", + "integrity": "sha512-atvy4hRph/UxdCIBp+UB2jhEA/jJiUeGZ7QPgBi9jUUKNgi3WEoMXGNG7zbbELG2+88PMabUNCDchmqgJy3ELg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.27.4", + "@jest/types": "30.0.1", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.0", + "chalk": "^4.1.2", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.0.2", + "jest-regex-util": "30.0.1", + "jest-util": "30.0.2", + "micromatch": "^4.0.8", + "pirates": "^4.0.7", + "slash": "^3.0.0", + "write-file-atomic": "^5.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-html-reporter/node_modules/@jest/types": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.0.1.tgz", + "integrity": "sha512-HGwoYRVF0QSKJu1ZQX0o5ZrUrrhj0aOOFA8hXrumD7SIzjouevhawbTjmXdwOmURdGluU9DM/XvGm3NyFoiQjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.1", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "*", + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-html-reporter/node_modules/@sinclair/typebox": { + "version": "0.34.37", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.37.tgz", + "integrity": "sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jest-html-reporter/node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-html-reporter/node_modules/babel-plugin-istanbul": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.0.tgz", + "integrity": "sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", + "test-exclude": "^6.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jest-html-reporter/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/jest-html-reporter/node_modules/ci-info": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.2.0.tgz", + "integrity": "sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-html-reporter/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-html-reporter/node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/jest-html-reporter/node_modules/jest-haste-map": { + "version": "30.0.2", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.0.2.tgz", + "integrity": "sha512-telJBKpNLeCb4MaX+I5k496556Y2FiKR/QLZc0+MGBYl4k3OO0472drlV2LUe7c1Glng5HuAu+5GLYp//GpdOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.1", + "@types/node": "*", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.0.2", + "jest-worker": "30.0.2", + "micromatch": "^4.0.8", + "walker": "^1.0.8" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "optionalDependencies": { + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-html-reporter/node_modules/jest-message-util": { + "version": "30.0.2", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.0.2.tgz", + "integrity": "sha512-vXywcxmr0SsKXF/bAD7t7nMamRvPuJkras00gqYeB1V0WllxZrbZ0paRr3XqpFU2sYYjD0qAaG2fRyn/CGZ0aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.0.1", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "micromatch": "^4.0.8", + "pretty-format": "30.0.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.6" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-html-reporter/node_modules/jest-regex-util": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-html-reporter/node_modules/jest-util": { + "version": "30.0.2", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.0.2.tgz", + "integrity": "sha512-8IyqfKS4MqprBuUpZNlFB5l+WFehc8bfCe1HSZFHzft2mOuND8Cvi9r1musli+u6F3TqanCZ/Ik4H4pXUolZIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.0.1", + "@types/node": "*", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.2" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-html-reporter/node_modules/jest-worker": { + "version": "30.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.0.2.tgz", + "integrity": "sha512-RN1eQmx7qSLFA+o9pfJKlqViwL5wt+OL3Vff/A+/cPsmuw7NPwfgl33AP+/agRmHzPOFgXviRycR9kYwlcRQXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.0.2", + "merge-stream": "^2.0.0", + "supports-color": "^8.1.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-html-reporter/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-html-reporter/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-html-reporter/node_modules/pretty-format": { + "version": "30.0.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.0.2.tgz", + "integrity": "sha512-yC5/EBSOrTtqhCKfLHqoUIAXVRZnukHPwWBJWR7h84Q3Be1DRQZLncwcfLoPA5RPQ65qfiCMqgYwdUuQ//eVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "30.0.1", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-html-reporter/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-html-reporter/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jest-html-reporter/node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/jest-leak-detector": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", @@ -4714,6 +5347,29 @@ "node": "*" } }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", @@ -4878,6 +5534,13 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -4948,6 +5611,30 @@ "dev": true, "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -4979,9 +5666,9 @@ } }, "node_modules/pirates": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", - "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "dev": true, "license": "MIT", "engines": { @@ -5447,6 +6134,22 @@ "node": ">=8" } }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -5460,6 +6163,20 @@ "node": ">=8" } }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -5755,9 +6472,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.2.tgz", - "integrity": "sha512-PPypAm5qvlD7XMZC3BujecnaOxwhrtoFR+Dqkk5Aa/6DssiH0ibKoketaj9w8LP7Bont1rYeoV5plxD7RTEPRg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", "dev": true, "funding": [ { @@ -5881,6 +6598,25 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -5902,6 +6638,16 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, + "node_modules/xmlbuilder": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.0.0.tgz", + "integrity": "sha512-KLu/G0DoWhkncQ9eHSI6s0/w+T4TM7rQaLhtCaL6tORv8jFlJPlnGumsgTcGfYeS1qZ/IHqrvDG7zJZ4d7e+nw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.0" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 310d583..f5f59f5 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "builtin-modules": "3.3.0", "esbuild": "^0.25.0", "jest": "^29.7.0", + "jest-html-reporter": "^4.3.0", "obsidian": "^1.7.2", "ts-jest": "^29.2.5", "ts-node": "^10.9.2", diff --git a/src/SyncController.ts b/src/SyncController.ts index 7ab4eff..0e3708a 100644 --- a/src/SyncController.ts +++ b/src/SyncController.ts @@ -1,18 +1,15 @@ import { Mutex } from "async-mutex"; import { Editor, EditorChange, MarkdownFileInfo, MarkdownView, TFile, Vault } from "obsidian"; -import { CheckboxUtils } from "./checkboxUtils"; import TextSyncPipeline from "./TextSyncPipeline"; export default class SyncController { private vault: Vault; - private checkboxUtils: CheckboxUtils; textSyncPipeline: TextSyncPipeline private mutex: Mutex; - constructor(vault: Vault, checkboxUtils: CheckboxUtils, textSyncPipeline: TextSyncPipeline) { + constructor(vault: Vault, textSyncPipeline: TextSyncPipeline) { this.vault = vault; - this.checkboxUtils = checkboxUtils; this.textSyncPipeline = textSyncPipeline; this.mutex = new Mutex(); } @@ -47,7 +44,7 @@ export default class SyncController { const newLines = newText.split("\n"); const oldLines = oldText.split("\n"); - const diffIndexes = this.checkboxUtils.findDifferentLineIndexes(oldLines, newLines); + const diffIndexes = this.findDifferentLineIndexes(oldLines, newLines); const changes: EditorChange[] = []; @@ -73,4 +70,19 @@ export default class SyncController { } } + private findDifferentLineIndexes(lines1: string[], lines2: string[]): number[] { + if (lines1.length !== lines2.length) { + throw new Error("the length of the lines must be equal"); + } + + const length = lines1.length; + const result: number[] = []; + for (let i = 0; i < length; i++) { + if (lines1[i] !== lines2[i]) { + result.push(i); + } + } + return result; + } + } diff --git a/src/TextSyncPipeline.ts b/src/TextSyncPipeline.ts index d02fc31..cac317f 100644 --- a/src/TextSyncPipeline.ts +++ b/src/TextSyncPipeline.ts @@ -1,14 +1,14 @@ -import { CheckboxUtils } from "./checkboxUtils"; +import { ICheckboxUtils } from "./core/interface/ICheckboxUtils"; import { FileFilter } from "./FileFilter"; import { IFilePathStateHolder } from "./IFilePathStateHolder"; export default class TextSyncPipeline { - private checkboxUtils: CheckboxUtils; + private checkboxUtils: ICheckboxUtils; private fileStateHolder: IFilePathStateHolder; private fileFilter: FileFilter; - constructor(checkboxUtils: CheckboxUtils, fileStateHolder: IFilePathStateHolder, fileFilter: FileFilter) { + constructor(checkboxUtils: ICheckboxUtils, fileStateHolder: IFilePathStateHolder, fileFilter: FileFilter) { this.checkboxUtils = checkboxUtils; this.fileStateHolder = fileStateHolder; this.fileFilter = fileFilter; diff --git a/src/core/CheckboxUtils2.ts b/src/core/CheckboxUtils2.ts index 1131a03..d70f9b8 100644 --- a/src/core/CheckboxUtils2.ts +++ b/src/core/CheckboxUtils2.ts @@ -1,26 +1,18 @@ -import { CheckboxState, CheckboxSyncPluginSettings } from "src/types"; +import { CheckboxSyncPluginSettings } from "src/types"; import { ICheckboxUtils } from "./interface/ICheckboxUtils"; import { ContextFactory } from "./model/ContextFactory"; import { PropagateStateToChildrenProcess } from "./process/PropagateStateToChildrenProcess"; import { PropagateStateToParentProcess } from "./process/PropagateStateToParentProcess"; -export interface CheckboxLineInfo { - indent: number; - marker: string; - checkChar?: string; - checkboxCharPosition?: number; - checkboxState: CheckboxState; - isChecked?: boolean | undefined; - listItemText?: string; -} - export class CheckboxUtils2 implements ICheckboxUtils { private settings: Readonly; - private propagateStateToChildrenProcces: PropagateStateToChildrenProcess; - private propagateStateToParentProcces: PropagateStateToParentProcess; + private propagateStateToChildrenProcess: PropagateStateToChildrenProcess; + private propagateStateToParentProcess: PropagateStateToParentProcess; constructor(settings: Readonly) { this.settings = settings; + this.propagateStateToChildrenProcess = new PropagateStateToChildrenProcess(); + this.propagateStateToParentProcess = new PropagateStateToParentProcess(); } syncText(text: string, textBefore: string | undefined): string { @@ -29,8 +21,8 @@ export class CheckboxUtils2 implements ICheckboxUtils { } const context = ContextFactory.createContext(text, textBefore, this.settings); - this.propagateStateToChildrenProcces.process(context); - this.propagateStateToParentProcces.process(context); + this.propagateStateToChildrenProcess.process(context); + this.propagateStateToParentProcess.process(context); return context.getResultText(); } diff --git a/src/checkboxUtils.ts b/src/core/checkboxUtils.ts similarity index 97% rename from src/checkboxUtils.ts rename to src/core/checkboxUtils.ts index 7bdbc85..72e39d2 100644 --- a/src/checkboxUtils.ts +++ b/src/core/checkboxUtils.ts @@ -1,4 +1,6 @@ -import { CheckboxState, CheckboxSyncPluginSettings } from "./types"; +import { CheckboxState, CheckboxSyncPluginSettings } from "src/types"; +import { ICheckboxUtils } from "./interface/ICheckboxUtils"; + export interface CheckboxLineInfo { indent: number; @@ -10,7 +12,7 @@ export interface CheckboxLineInfo { listItemText?: string; } -export class CheckboxUtils { +export class CheckboxUtils implements ICheckboxUtils { private settings: Readonly; constructor(settings: Readonly) { this.settings = settings; diff --git a/src/core/model/ContextFactory.ts b/src/core/model/ContextFactory.ts index 4aace2d..888091b 100644 --- a/src/core/model/ContextFactory.ts +++ b/src/core/model/ContextFactory.ts @@ -3,66 +3,42 @@ import { Context } from "./Context"; import { TreeNode } from "./TreeNode"; import { Line } from "./Line"; import { View } from "./View"; +import { CheckboxLine } from "./line/CheckboxLine"; +import { ListLine } from "./line/ListLine"; +import { TextLine } from "./line/TextLine"; export class ContextFactory { + static readonly CHECKBOX_REGEXP = /^(\s*)([*+-]|\d+\.) \[(.)\]\s(.*)$/; + static readonly LIST_ITEM_REGEXP = /^(\s*)([*+-]|\d+\.)\s+(?!\[.?\])(.*)$/; + static readonly TEXT_REGEXP = /^(\s*)(.*)$/; + private constructor() { } - public static createContext(text: string, textBefore: string | undefined, settings: Readonly): Context { + static createContext(text: string, textBefore: string | undefined, settings: Readonly): Context { return new Context(text, textBefore, settings); } - static createLines(context: Context): Line[] { - const settings = context.getSettings(); - const textLines = context.getTextLines(); - const lines = this.createLinesFromTextLines(context.getTextLines(), settings); - if (context.textBeforeChangeIsPresent()) { - const textBeforeLines = context.getTextBeforeChangeLines()!; - if (textLines.length === textBeforeLines.length) { - const diffIndex = this.findSingleDiffLineIndex(textLines, textBeforeLines); - if (diffIndex) { - const oldLine = new Line(textBeforeLines[diffIndex], settings); - const actualLine = lines[diffIndex]; - - // если новая и старая строка чекбокс - // если текст остался старым - // если изменилось состояние чекбокса - if (oldLine.isCheckbox() && - actualLine.isCheckbox() && - oldLine.getText() === actualLine.getText() && - oldLine.getState() !== actualLine.getState() - ) { - actualLine.setChange(true); - } - } - } - } - return lines; - } - static createView(context: Context) { - const settings = context.getSettings(); - const lines = context.getLines(); - - // построить дерево - const treeNodes = this.createNodesFromLines(lines); - - // проверить ноды на modify - let isModify = false; - for (const treeNode of treeNodes) { - isModify = isModify || treeNode.isModify(); - } - - return new View(treeNodes, isModify); + const treeNodes = this.createRootTreeNodesFromLines(lines); + return new View(treeNodes); } - private static createNodesFromLines(textLines: Line[]): TreeNode[] { + // создаёт TreeNode из Line + // возвращает только корневые узлы + private static createRootTreeNodesFromLines(lines: Line[]): TreeNode[] { + const flatTreeNodes = this.createFlatNodesFromLines(lines); + return flatTreeNodes.filter(node => !node.getParent()); + } + + // создаёт TreeNode из Line + // осторожно, возвращает ВСЕ ноды в порядке линий + private static createFlatNodesFromLines(lines: Line[]): TreeNode[] { const nodes: TreeNode[] = []; const stack: TreeNode[] = []; - for (let index = 0; index < textLines.length; index++) { - const line = textLines[index]; + for (const line of lines) { const node = new TreeNode(line); // найти родителя @@ -86,15 +62,83 @@ export class ContextFactory { return nodes; } + static createLines(context: Context): Line[] { + const settings = context.getSettings(); + const lines = this.createLinesFromTextLines(context.getTextLines(), settings); + this.markChangeIfNeeded(lines, context); + return lines; + } + // помечает line как изменённую при необходимости + static markChangeIfNeeded(lines: Line[], context: Context) { + if (!context.textBeforeChangeIsPresent()) { + return; + } + + const textLines = context.getTextLines(); + const textBeforeLines = context.getTextBeforeChangeLines()!; + if (textLines.length !== textBeforeLines.length) { + return; + } + + const diffIndex = this.findSingleDiffLineIndex(textLines, textBeforeLines); + if (diffIndex === undefined) { + return; + } + + const oldLine = this.createLineFromTextLine(textBeforeLines[diffIndex], context.getSettings()); + const actualLine = lines[diffIndex]; + + // если новая и старая строка чекбокс + if (oldLine instanceof CheckboxLine && actualLine instanceof CheckboxLine) { + // если текст остался старым + // если изменилось состояние чекбокса + if ( + oldLine.getText() === actualLine.getText() && + oldLine.getState() !== actualLine.getState() + ) { + actualLine.setChange(true); + } + } + + + } // создаёт Line[] из текста private static createLinesFromTextLines(textLines: string[], settings: Readonly): Line[] { return textLines.map(line => { - return new Line(line, settings); + return this.createLineFromTextLine(line, settings); }); } + // создаёт объект из одного из классов, кто реализует Line в зависимости от строки. + static createLineFromTextLine(textLine: string, settings: Readonly): Line { + const checkboxMatch = textLine.match(this.CHECKBOX_REGEXP); + if (checkboxMatch) { + const indentString = checkboxMatch[1]; + const marker = checkboxMatch[2]; + const checkChar = checkboxMatch[3]; + + const listItemText = checkboxMatch[4].trimStart(); + + return new CheckboxLine(indentString, marker, checkChar, listItemText, settings); + } + const listItemMatch = textLine.match(this.LIST_ITEM_REGEXP); + if (listItemMatch) { + const indentString = listItemMatch[1]; + const marker = listItemMatch[2]; + const listItemText = listItemMatch[3].trimStart(); + + return new ListLine(indentString, marker, listItemText, settings.tabSize); + } + + const textLineMatch = textLine.match(this.TEXT_REGEXP)!; + const indentString = textLineMatch[1]; + const itemText = textLineMatch[2]; + return new TextLine(indentString, itemText, settings.tabSize); + + } + private static findSingleDiffLineIndex(lines1: string[], lines2: string[]): number | undefined { const diffLines = this.findDifferentLineIndexes(lines1, lines2); if (diffLines.length == 1) { diff --git a/src/core/model/Line.ts b/src/core/model/Line.ts index dce5e2b..4dc1593 100644 --- a/src/core/model/Line.ts +++ b/src/core/model/Line.ts @@ -1,3 +1,5 @@ +import { CheckboxState } from "src/types"; + export interface Line { getIndent(): number; @@ -5,4 +7,6 @@ export interface Line { toResultText(): string; + getState(): CheckboxState; + } \ No newline at end of file diff --git a/src/core/model/TreeNode.ts b/src/core/model/TreeNode.ts index 83fb4de..4e7b56d 100644 --- a/src/core/model/TreeNode.ts +++ b/src/core/model/TreeNode.ts @@ -1,15 +1,17 @@ import { Line } from "./Line"; +import { CheckboxLine } from "./line/CheckboxLine"; export class TreeNode { private parent?: TreeNode; - private childrens: TreeNode[]; + private childrens: TreeNode[] = []; private line: Line; + // метка того, что Единичное изменение произошло в этой ноде или дочерних нодах private modify = false; constructor(line: Line) { this.line = line; - if (line.isChange()) { + if (line instanceof CheckboxLine && line.isChange()) { this.modify = true; } } diff --git a/src/core/model/View.ts b/src/core/model/View.ts index 3d9b203..501e75f 100644 --- a/src/core/model/View.ts +++ b/src/core/model/View.ts @@ -2,10 +2,16 @@ import { TreeNode } from "./TreeNode"; export class View { private treeNodes: TreeNode[]; + // метка того, что Единичное изменение произошло в этой View private modify: boolean; - constructor(treeNodes: TreeNode[], isModify: boolean) { + constructor(treeNodes: TreeNode[]) { this.treeNodes = treeNodes; + // проверить ноды на modify + let isModify = false; + for (const treeNode of treeNodes) { + isModify = isModify || treeNode.isModify(); + } this.modify = isModify; } diff --git a/src/core/model/line/AbstractLine.ts b/src/core/model/line/AbstractLine.ts index 6b2a90f..19a9bf3 100644 --- a/src/core/model/line/AbstractLine.ts +++ b/src/core/model/line/AbstractLine.ts @@ -1,16 +1,33 @@ +import { CheckboxState } from "src/types"; import { Line } from "../Line"; export abstract class AbstractLine implements Line { + protected indentString: string; + protected tabSize: number; // длина отступа protected indent: number; // текст после чекбокса - protected listText: string; + protected listText: string; - constructor(indent:number, lineText: string){ - this.indent = indent; + constructor(indentString: string, lineText: string, tabSize: number) { + this.indentString = indentString; this.listText = lineText; - + this.tabSize = tabSize; + + this.indent = this.getIndentFromString(indentString, tabSize); + } + + private getIndentFromString(indentString: string, tabSize: number): number { + let indent = 0; + for (const char of indentString) { + if (char === '\t') { + indent += tabSize; + } else if (char === ' ') { + indent += 1; + } + } + return indent; } getIndent(): number { @@ -21,9 +38,7 @@ export abstract class AbstractLine implements Line { return this.listText; } - setText(lineText: string){ - this.listText = lineText; - } - abstract toResultText(): string; + + abstract getState(): CheckboxState; } \ No newline at end of file diff --git a/src/core/model/line/CheckboxLine.ts b/src/core/model/line/CheckboxLine.ts index 3d71851..00b9e9e 100644 --- a/src/core/model/line/CheckboxLine.ts +++ b/src/core/model/line/CheckboxLine.ts @@ -2,32 +2,31 @@ import { CheckboxState, CheckboxSyncPluginSettings } from "src/types"; import { AbstractLine } from "./AbstractLine"; export class CheckboxLine extends AbstractLine { - + // символы перед чекбоксом private marker: string; // символ в чекбоксе private checkChar: string; // позиция символа чекбокса в исходной строке - private checkboxCharPosition: number; + // private checkboxCharPosition: number; // интерпретация символа чекбокса(или его отсутствия) private checkboxState: CheckboxState; // интерпретация состояния чекбокса(или его отсутствия) // private isChecked?: boolean | undefined; - - private hasChange = false; + // метка того, что Единичное изменение произошло в этой строке + private hasChange = false; protected settings: Readonly; - constructor(indent: number, marker: string, checkChar: string, lineText: string, settings: Readonly) { - super(indent, lineText); + constructor(indentString:string, marker: string, checkChar: string, listItemText: string, settings: Readonly) { + super(indentString, listItemText, settings.tabSize); this.marker = marker; this.checkChar = checkChar; this.settings = settings; - this.checkboxCharPosition = ; - this.checkboxState = ; - throw new Error("Constructor not implemented."); + // this.checkboxCharPosition = indent + marker.length + 2; + this.checkboxState = this.getCheckboxState(checkChar); } isChange(): boolean { @@ -38,36 +37,12 @@ export class CheckboxLine extends AbstractLine { this.hasChange = hasChange; } - - - isCheckbox(): boolean { - return this.checkboxState !== CheckboxState.NoCheckbox; - } setState(state: CheckboxState): void { // обновить checkboxState this.checkboxState = state; // обновить checkChar - switch (state) { - case CheckboxState.Checked: - this.checkChar = this.settings.checkedSymbols.length > 0 ? this.settings.checkedSymbols[0] : 'x'; // 'x' как дефолт - break; - case CheckboxState.Unchecked: - this.checkChar = this.settings.uncheckedSymbols.length > 0 ? this.settings.uncheckedSymbols[0] : ' '; // ' ' как дефолт - break; - case CheckboxState.Ignore: - if (this.settings.ignoreSymbols.length > 0) { - this.checkChar = this.settings.ignoreSymbols[0]; - } else { - throw new Error("Not found ignore char."); - } - break; - case CheckboxState.NoCheckbox: - this.checkChar = undefined; - break; - default: - throw new Error(`Unexpected value for parameter [state] = ${state}.`); - } + this.checkChar = this.getCharFromState(state); } setStateIfNotEquals(newState: CheckboxState) { @@ -80,5 +55,42 @@ export class CheckboxLine extends AbstractLine { return this.checkboxState; } - + toResultText(): string { + // const spaces = ' '.repeat(this.indent); + const resultText = this.indentString + this.marker + " [" + this.checkChar + "] " + this.listText; + return resultText; + } + + private getCheckboxState(text: string): CheckboxState { + if (this.settings.checkedSymbols.includes(text)) { + return CheckboxState.Checked; + } + if (this.settings.uncheckedSymbols.includes(text)) { + return CheckboxState.Unchecked; + } + if (this.settings.ignoreSymbols.includes(text)) { + return CheckboxState.Ignore; + } + return this.settings.unknownSymbolPolicy; + } + + private getCharFromState(state: CheckboxState): string { + switch (state) { + case CheckboxState.Checked: + return this.settings.checkedSymbols.length > 0 ? this.settings.checkedSymbols[0] : 'x'; // 'x' как дефолт + case CheckboxState.Unchecked: + return this.settings.uncheckedSymbols.length > 0 ? this.settings.uncheckedSymbols[0] : ' '; // ' ' как дефолт + case CheckboxState.Ignore: + if (this.settings.ignoreSymbols.length > 0) { + return this.settings.ignoreSymbols[0]; + } else { + throw new Error("Not found ignore char."); + } + case CheckboxState.NoCheckbox: + throw new Error(`Unexpected value for parameter [state] = ${state}. "NoCheckbox".`); + default: + throw new Error(`Unexpected value for parameter [state] = ${state}.`); + } + } + } \ No newline at end of file diff --git a/src/core/model/line/ListLine.ts b/src/core/model/line/ListLine.ts index 66a05c8..87b5c61 100644 --- a/src/core/model/line/ListLine.ts +++ b/src/core/model/line/ListLine.ts @@ -1,3 +1,4 @@ +import { CheckboxState } from "src/types"; import { AbstractLine } from "./AbstractLine"; export class ListLine extends AbstractLine { @@ -5,8 +6,8 @@ export class ListLine extends AbstractLine { // символы перед текстом private marker: string; - constructor(indent: number, marker: string, listText: string) { - super(indent, listText); + constructor(indentString: string, marker: string, listText: string, tabSize: number) { + super(indentString, listText, tabSize); this.marker = marker; } @@ -19,9 +20,12 @@ export class ListLine extends AbstractLine { } toResultText(): string { - const spaces = ' '.repeat(this.indent); - - const resultText = spaces + this.marker + " " + this.listText; + // const spaces = ' '.repeat(this.indent); + const resultText = this.indentString + this.marker + " " + this.listText; return resultText; } + + getState(): CheckboxState { + return CheckboxState.NoCheckbox; + } } \ No newline at end of file diff --git a/src/core/model/line/TextLine.ts b/src/core/model/line/TextLine.ts index 8d51a04..e56ea53 100644 --- a/src/core/model/line/TextLine.ts +++ b/src/core/model/line/TextLine.ts @@ -1,9 +1,14 @@ +import { CheckboxState } from "src/types"; import { AbstractLine } from "./AbstractLine"; export class TextLine extends AbstractLine { toResultText(): string { - const spaces = ' '.repeat(this.indent); - const resultText = spaces + this.listText; + // const spaces = ' '.repeat(this.indent); + const resultText = this.indentString + this.listText; return resultText; } + + getState(): CheckboxState { + return CheckboxState.NoCheckbox; + } } \ No newline at end of file diff --git a/src/core/process/PropagateStateToChildrenProcess.ts b/src/core/process/PropagateStateToChildrenProcess.ts index fb3f93c..d8416c3 100644 --- a/src/core/process/PropagateStateToChildrenProcess.ts +++ b/src/core/process/PropagateStateToChildrenProcess.ts @@ -2,6 +2,7 @@ import { CheckboxState } from "src/types"; import { CheckboxProcess } from "../interface/CheckboxProcess"; import { Context } from "../model/Context"; import { TreeNode } from "../model/TreeNode"; +import { CheckboxLine } from "../model/line/CheckboxLine"; export class PropagateStateToChildrenProcess implements CheckboxProcess { @@ -21,46 +22,51 @@ export class PropagateStateToChildrenProcess implements CheckboxProcess { const nodes = view.getTreeNodes(); for (const node of nodes) { - this.propageteStateToChildrenFromChangeLineNode(node); + this.propagateStateToChildrenFromChangeLineNode(node); } } // находит изменённую Line, если она существует и вызывает от неё propagateStateToChildren - propageteStateToChildrenFromChangeLineNode(node: TreeNode) { + propagateStateToChildrenFromChangeLineNode(node: TreeNode) { if (!node.isModify()) { return; } const line = node.getLine(); // если line изменён - if (line.isChange()) { + if (line instanceof CheckboxLine && line.isChange()) { // значит мы нашли нужную ноду - this.propagateStateToChildren(node); + this.propagateStateToChildrenFromNode(node); } else { // иначе ищем вглубь среди детей const childrens = node.getChildrenNodes(); for (const children of childrens) { - this.propageteStateToChildrenFromChangeLineNode(children); + this.propagateStateToChildrenFromChangeLineNode(children); } } } - propagateStateToChildren(modifiedNode: TreeNode) { - const state = modifiedNode.getLine().getState(); + propagateStateToChildrenFromNode(modifiedNode: TreeNode) { + const line = modifiedNode.getLine(); + if (!(line instanceof CheckboxLine)) { + console.warn(`propagateStateToChildren from !CheckboxLine.`); + return; + } + const state = line.getState(); if (state === CheckboxState.Ignore || state === CheckboxState.NoCheckbox) { console.warn(`propagateStateToChildren ${state.toString()}`); return; } - this.propagateToChildren(modifiedNode, state); + this.propagateStateToChildren(modifiedNode, state); } - propagateToChildren(node: TreeNode, state: CheckboxState) { + propagateStateToChildren(node: TreeNode, state: CheckboxState) { const line = node.getLine(); - if (line.isCheckbox()) { + if (line instanceof CheckboxLine) { line.setStateIfNotEquals(state); } for (const childrenNode of node.getChildrenNodes()) { - this.propagateToChildren(childrenNode, state); + this.propagateStateToChildren(childrenNode, state); } } } diff --git a/src/core/process/PropagateStateToParentProcess.ts b/src/core/process/PropagateStateToParentProcess.ts index 214a649..b26becc 100644 --- a/src/core/process/PropagateStateToParentProcess.ts +++ b/src/core/process/PropagateStateToParentProcess.ts @@ -2,6 +2,7 @@ import { CheckboxState } from "src/types"; import { CheckboxProcess } from "../interface/CheckboxProcess"; import { Context } from "../model/Context"; import { TreeNode } from "../model/TreeNode"; +import { CheckboxLine } from "../model/line/CheckboxLine"; export class PropagateStateToParentProcess implements CheckboxProcess { @@ -16,24 +17,42 @@ export class PropagateStateToParentProcess implements CheckboxProcess { const nodes = view.getTreeNodes(); for (const node of nodes) { - this.propagateStateFromChildrens(node); + this.propagateStateToParent(node); } } // возврат значения нужен для того, чтобы правильно обрабатывать случаи с "не чекбоксам", чтобы они передавали значения детей - propagateStateFromChildrens(node: TreeNode): CheckboxState { + propagateStateToParent(node: TreeNode): CheckboxState { const line = node.getLine(); - if (!node.hasChildren()) { - return line.getState(); + // обходим всех детей + const childrenStates: CheckboxState[] = []; + for (const childrenNode of node.getChildrenNodes()) { + const childrenState = this.propagateStateToParent(childrenNode); + childrenStates.push(childrenState); } + const newState = this.getNewState(line.getState(), childrenStates); + + if (line instanceof CheckboxLine){ + line.setStateIfNotEquals(newState); + } + + return newState; + } + + getNewState(actualState: CheckboxState, childrenStates: CheckboxState[]): CheckboxState { + if (actualState === CheckboxState.Ignore) { + return actualState; + } + + // обходим всех детей + // если есть релевантные дети, то помечаем это и обновляем "состояние" Line + // "состояние" - потому что даже не чекбоксы могут иметь состояние через своих детей let resultIfHasRelevantChildren = CheckboxState.Checked; let hasRelevantChildren = false; - for (const childrenNode of node.getChildrenNodes()) { - const childrenState = this.propagateStateFromChildrens(childrenNode); - - if (childrenState === CheckboxState.Checked || childrenState === CheckboxState.Unchecked) { + for (const childrenState of childrenStates) { + if (childrenState !== CheckboxState.Ignore && childrenState !== CheckboxState.NoCheckbox) { hasRelevantChildren = true; } @@ -41,17 +60,12 @@ export class PropagateStateToParentProcess implements CheckboxProcess { resultIfHasRelevantChildren = CheckboxState.Unchecked; } } - // делаем эту проверку только после обработки детей - if (line.getState() == CheckboxState.Ignore) { - return line.getState(); - } - // проверка для нод, все дети которых не релевантны - if (!hasRelevantChildren) { - return line.getState(); - } - line.setStateIfNotEquals(resultIfHasRelevantChildren); - - return resultIfHasRelevantChildren; + if (hasRelevantChildren) { + return resultIfHasRelevantChildren; + } else { + return actualState; + } } + } diff --git a/src/main.ts b/src/main.ts index 8d9c097..c240324 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,5 +1,4 @@ import { Plugin, TFile } from "obsidian"; -import { CheckboxUtils } from "./checkboxUtils"; import { FileChangeEventHandler } from "./events/FileChangeEventHandler"; import { FileLoadEventHandler } from "./events/FileLoadEventHandler"; import FileStateHolder from "./FileStateHolder"; @@ -8,6 +7,8 @@ import SyncController from "./SyncController"; import { CheckboxSyncPluginSettings, DEFAULT_SETTINGS } from "./types"; import { FileFilter } from "./FileFilter"; import TextSyncPipeline from "./TextSyncPipeline"; +import { ICheckboxUtils } from "./core/interface/ICheckboxUtils"; +import { CheckboxUtils2 } from "./core/CheckboxUtils2"; const DEBUG_FLAG_NAME = 'CHECKBOX_SYNC_DEBUG'; @@ -23,7 +24,7 @@ export default class CheckboxSyncPlugin extends Plugin { private _settings: CheckboxSyncPluginSettings; private syncController: SyncController; - private checkboxUtils: CheckboxUtils; + private checkboxUtils: ICheckboxUtils; private fileStateHolder: FileStateHolder; private fileLoadEventHandler: FileLoadEventHandler; private fileChangeEventHandler: FileChangeEventHandler; @@ -35,9 +36,9 @@ export default class CheckboxSyncPlugin extends Plugin { this.fileFilter = new FileFilter(this.settings); this.fileStateHolder = new FileStateHolder(this.app.vault); - this.checkboxUtils = new CheckboxUtils(this.settings); + this.checkboxUtils = new CheckboxUtils2(this.settings); this.textSyncPipeline = new TextSyncPipeline(this.checkboxUtils,this.fileStateHolder, this.fileFilter); - this.syncController = new SyncController(this.app.vault, this.checkboxUtils, this.textSyncPipeline); + this.syncController = new SyncController(this.app.vault, this.textSyncPipeline); this.fileLoadEventHandler = new FileLoadEventHandler(this, this.app, this.syncController, this.fileStateHolder); this.fileChangeEventHandler = new FileChangeEventHandler(this, this.app, this.syncController, this.fileStateHolder); diff --git a/src/types.ts b/src/types.ts index 6c16730..674d810 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,5 @@ export interface CheckboxSyncPluginSettings { + tabSize: number, enableAutomaticParentState: boolean; enableAutomaticChildState: boolean; checkedSymbols: string[]; @@ -18,6 +19,7 @@ export enum CheckboxState { } export const DEFAULT_SETTINGS: CheckboxSyncPluginSettings = { + tabSize: 4, enableAutomaticParentState: true, enableAutomaticChildState: true, checkedSymbols: ['x'],