mirror of
https://github.com/groldsf/obsidian_check_plugin.git
synced 2026-07-22 05:37:48 +00:00
рабочая версия
This commit is contained in:
parent
a7f33e676d
commit
96727c61f5
25 changed files with 1496 additions and 307 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -22,3 +22,4 @@ data.json
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
coverage/
|
coverage/
|
||||||
|
test-report.html
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { CheckboxUtils } from "src/checkboxUtils";
|
import { CheckboxUtils } from "src/core/checkboxUtils";
|
||||||
import { FileFilter } from "src/FileFilter";
|
import { FileFilter } from "src/FileFilter";
|
||||||
import TextSyncPipeline from "src/TextSyncPipeline";
|
import TextSyncPipeline from "src/TextSyncPipeline";
|
||||||
import { CheckboxSyncPluginSettings, DEFAULT_SETTINGS } from "src/types";
|
import { CheckboxSyncPluginSettings, DEFAULT_SETTINGS } from "src/types";
|
||||||
|
|
|
||||||
|
|
@ -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'; // Путь к вашему файлу типов
|
import { CheckboxState, CheckboxSyncPluginSettings, DEFAULT_SETTINGS } from '../src/types'; // Путь к вашему файлу типов
|
||||||
|
|
||||||
// Вспомогательная функция для создания настроек с переопределениями
|
// Вспомогательная функция для создания настроек с переопределениями
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
// checkboxUtils.plainLists.test.ts
|
// checkboxUtils.plainLists.test.ts
|
||||||
|
|
||||||
import { CheckboxUtils } from '../src/checkboxUtils';
|
import { CheckboxUtils } from '../src/core/checkboxUtils';
|
||||||
import { CheckboxState, CheckboxSyncPluginSettings, DEFAULT_SETTINGS } from '../src/types';
|
import { CheckboxState, CheckboxSyncPluginSettings, DEFAULT_SETTINGS } from '../src/types';
|
||||||
|
|
||||||
// Вспомогательная функция для создания настроек с переопределениями
|
// Вспомогательная функция для создания настроек с переопределениями
|
||||||
|
|
|
||||||
312
__tests__/core/CheckboxUtils2.test.ts
Normal file
312
__tests__/core/CheckboxUtils2.test.ts
Normal file
|
|
@ -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<CheckboxSyncPluginSettings> = {}): Readonly<CheckboxSyncPluginSettings> => {
|
||||||
|
return { ...DEFAULT_SETTINGS, ...overrides } as Readonly<CheckboxSyncPluginSettings>;
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -8,13 +8,13 @@ export class InMemoryFilePathStateHolder implements IFilePathStateHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
public setByPath(filePath: string, text: string): void {
|
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);
|
this.states.set(filePath, text);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getByPath(filePath: string): string | undefined {
|
public getByPath(filePath: string): string | undefined {
|
||||||
const state = this.states.get(filePath);
|
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;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,18 @@
|
||||||
* https://jestjs.io/docs/configuration
|
* https://jestjs.io/docs/configuration
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type {Config} from 'jest';
|
import type { Config } from 'jest';
|
||||||
|
|
||||||
const config: Config = {
|
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
|
// Automatically clear mock calls, instances, contexts and results before every test
|
||||||
clearMocks: true,
|
clearMocks: true,
|
||||||
|
|
||||||
|
|
@ -18,14 +26,14 @@ const config: Config = {
|
||||||
|
|
||||||
// Indicates which provider should be used to instrument code for coverage
|
// Indicates which provider should be used to instrument code for coverage
|
||||||
// coverageProvider: "v8",
|
// coverageProvider: "v8",
|
||||||
coverageProvider: "babel",
|
coverageProvider: "babel",
|
||||||
|
|
||||||
// An array of file extensions your modules use
|
// An array of file extensions your modules use
|
||||||
moduleFileExtensions: [
|
moduleFileExtensions: [
|
||||||
"ts",
|
"ts",
|
||||||
"tsx",
|
"tsx",
|
||||||
"js",
|
"js",
|
||||||
"jsx",
|
"jsx",
|
||||||
"json",
|
"json",
|
||||||
"node"
|
"node"
|
||||||
],
|
],
|
||||||
|
|
@ -43,11 +51,11 @@ const config: Config = {
|
||||||
'^src/(.*)$': '<rootDir>/src/$1',
|
'^src/(.*)$': '<rootDir>/src/$1',
|
||||||
},
|
},
|
||||||
|
|
||||||
testPathIgnorePatterns: [
|
testPathIgnorePatterns: [
|
||||||
"/node_modules/", // Хорошо иметь это явно, хотя Jest часто делает это по умолчанию
|
"/node_modules/", // Хорошо иметь это явно, хотя Jest часто делает это по умолчанию
|
||||||
"/__tests__/fakes/" // Исключаем папку fakes внутри __tests__
|
"/__tests__/fakes/" // Исключаем папку fakes внутри __tests__
|
||||||
],
|
],
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|
|
||||||
1024
package-lock.json
generated
1024
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -22,6 +22,7 @@
|
||||||
"builtin-modules": "3.3.0",
|
"builtin-modules": "3.3.0",
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
|
"jest-html-reporter": "^4.3.0",
|
||||||
"obsidian": "^1.7.2",
|
"obsidian": "^1.7.2",
|
||||||
"ts-jest": "^29.2.5",
|
"ts-jest": "^29.2.5",
|
||||||
"ts-node": "^10.9.2",
|
"ts-node": "^10.9.2",
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,15 @@
|
||||||
import { Mutex } from "async-mutex";
|
import { Mutex } from "async-mutex";
|
||||||
import { Editor, EditorChange, MarkdownFileInfo, MarkdownView, TFile, Vault } from "obsidian";
|
import { Editor, EditorChange, MarkdownFileInfo, MarkdownView, TFile, Vault } from "obsidian";
|
||||||
import { CheckboxUtils } from "./checkboxUtils";
|
|
||||||
import TextSyncPipeline from "./TextSyncPipeline";
|
import TextSyncPipeline from "./TextSyncPipeline";
|
||||||
|
|
||||||
export default class SyncController {
|
export default class SyncController {
|
||||||
private vault: Vault;
|
private vault: Vault;
|
||||||
private checkboxUtils: CheckboxUtils;
|
|
||||||
textSyncPipeline: TextSyncPipeline
|
textSyncPipeline: TextSyncPipeline
|
||||||
|
|
||||||
private mutex: Mutex;
|
private mutex: Mutex;
|
||||||
|
|
||||||
constructor(vault: Vault, checkboxUtils: CheckboxUtils, textSyncPipeline: TextSyncPipeline) {
|
constructor(vault: Vault, textSyncPipeline: TextSyncPipeline) {
|
||||||
this.vault = vault;
|
this.vault = vault;
|
||||||
this.checkboxUtils = checkboxUtils;
|
|
||||||
this.textSyncPipeline = textSyncPipeline;
|
this.textSyncPipeline = textSyncPipeline;
|
||||||
this.mutex = new Mutex();
|
this.mutex = new Mutex();
|
||||||
}
|
}
|
||||||
|
|
@ -47,7 +44,7 @@ export default class SyncController {
|
||||||
const newLines = newText.split("\n");
|
const newLines = newText.split("\n");
|
||||||
const oldLines = oldText.split("\n");
|
const oldLines = oldText.split("\n");
|
||||||
|
|
||||||
const diffIndexes = this.checkboxUtils.findDifferentLineIndexes(oldLines, newLines);
|
const diffIndexes = this.findDifferentLineIndexes(oldLines, newLines);
|
||||||
|
|
||||||
const changes: EditorChange[] = [];
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import { CheckboxUtils } from "./checkboxUtils";
|
import { ICheckboxUtils } from "./core/interface/ICheckboxUtils";
|
||||||
import { FileFilter } from "./FileFilter";
|
import { FileFilter } from "./FileFilter";
|
||||||
import { IFilePathStateHolder } from "./IFilePathStateHolder";
|
import { IFilePathStateHolder } from "./IFilePathStateHolder";
|
||||||
|
|
||||||
export default class TextSyncPipeline {
|
export default class TextSyncPipeline {
|
||||||
private checkboxUtils: CheckboxUtils;
|
private checkboxUtils: ICheckboxUtils;
|
||||||
private fileStateHolder: IFilePathStateHolder;
|
private fileStateHolder: IFilePathStateHolder;
|
||||||
private fileFilter: FileFilter;
|
private fileFilter: FileFilter;
|
||||||
|
|
||||||
|
|
||||||
constructor(checkboxUtils: CheckboxUtils, fileStateHolder: IFilePathStateHolder, fileFilter: FileFilter) {
|
constructor(checkboxUtils: ICheckboxUtils, fileStateHolder: IFilePathStateHolder, fileFilter: FileFilter) {
|
||||||
this.checkboxUtils = checkboxUtils;
|
this.checkboxUtils = checkboxUtils;
|
||||||
this.fileStateHolder = fileStateHolder;
|
this.fileStateHolder = fileStateHolder;
|
||||||
this.fileFilter = fileFilter;
|
this.fileFilter = fileFilter;
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,18 @@
|
||||||
import { CheckboxState, CheckboxSyncPluginSettings } from "src/types";
|
import { CheckboxSyncPluginSettings } from "src/types";
|
||||||
import { ICheckboxUtils } from "./interface/ICheckboxUtils";
|
import { ICheckboxUtils } from "./interface/ICheckboxUtils";
|
||||||
import { ContextFactory } from "./model/ContextFactory";
|
import { ContextFactory } from "./model/ContextFactory";
|
||||||
import { PropagateStateToChildrenProcess } from "./process/PropagateStateToChildrenProcess";
|
import { PropagateStateToChildrenProcess } from "./process/PropagateStateToChildrenProcess";
|
||||||
import { PropagateStateToParentProcess } from "./process/PropagateStateToParentProcess";
|
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 {
|
export class CheckboxUtils2 implements ICheckboxUtils {
|
||||||
private settings: Readonly<CheckboxSyncPluginSettings>;
|
private settings: Readonly<CheckboxSyncPluginSettings>;
|
||||||
private propagateStateToChildrenProcces: PropagateStateToChildrenProcess;
|
private propagateStateToChildrenProcess: PropagateStateToChildrenProcess;
|
||||||
private propagateStateToParentProcces: PropagateStateToParentProcess;
|
private propagateStateToParentProcess: PropagateStateToParentProcess;
|
||||||
|
|
||||||
constructor(settings: Readonly<CheckboxSyncPluginSettings>) {
|
constructor(settings: Readonly<CheckboxSyncPluginSettings>) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
|
this.propagateStateToChildrenProcess = new PropagateStateToChildrenProcess();
|
||||||
|
this.propagateStateToParentProcess = new PropagateStateToParentProcess();
|
||||||
}
|
}
|
||||||
|
|
||||||
syncText(text: string, textBefore: string | undefined): string {
|
syncText(text: string, textBefore: string | undefined): string {
|
||||||
|
|
@ -29,8 +21,8 @@ export class CheckboxUtils2 implements ICheckboxUtils {
|
||||||
}
|
}
|
||||||
const context = ContextFactory.createContext(text, textBefore, this.settings);
|
const context = ContextFactory.createContext(text, textBefore, this.settings);
|
||||||
|
|
||||||
this.propagateStateToChildrenProcces.process(context);
|
this.propagateStateToChildrenProcess.process(context);
|
||||||
this.propagateStateToParentProcces.process(context);
|
this.propagateStateToParentProcess.process(context);
|
||||||
|
|
||||||
return context.getResultText();
|
return context.getResultText();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
import { CheckboxState, CheckboxSyncPluginSettings } from "./types";
|
import { CheckboxState, CheckboxSyncPluginSettings } from "src/types";
|
||||||
|
import { ICheckboxUtils } from "./interface/ICheckboxUtils";
|
||||||
|
|
||||||
|
|
||||||
export interface CheckboxLineInfo {
|
export interface CheckboxLineInfo {
|
||||||
indent: number;
|
indent: number;
|
||||||
|
|
@ -10,7 +12,7 @@ export interface CheckboxLineInfo {
|
||||||
listItemText?: string;
|
listItemText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CheckboxUtils {
|
export class CheckboxUtils implements ICheckboxUtils {
|
||||||
private settings: Readonly<CheckboxSyncPluginSettings>;
|
private settings: Readonly<CheckboxSyncPluginSettings>;
|
||||||
constructor(settings: Readonly<CheckboxSyncPluginSettings>) {
|
constructor(settings: Readonly<CheckboxSyncPluginSettings>) {
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
|
|
@ -3,66 +3,42 @@ import { Context } from "./Context";
|
||||||
import { TreeNode } from "./TreeNode";
|
import { TreeNode } from "./TreeNode";
|
||||||
import { Line } from "./Line";
|
import { Line } from "./Line";
|
||||||
import { View } from "./View";
|
import { View } from "./View";
|
||||||
|
import { CheckboxLine } from "./line/CheckboxLine";
|
||||||
|
import { ListLine } from "./line/ListLine";
|
||||||
|
import { TextLine } from "./line/TextLine";
|
||||||
|
|
||||||
export class ContextFactory {
|
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() {
|
private constructor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static createContext(text: string, textBefore: string | undefined, settings: Readonly<CheckboxSyncPluginSettings>): Context {
|
static createContext(text: string, textBefore: string | undefined, settings: Readonly<CheckboxSyncPluginSettings>): Context {
|
||||||
return new Context(text, textBefore, settings);
|
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) {
|
static createView(context: Context) {
|
||||||
const settings = context.getSettings();
|
|
||||||
|
|
||||||
const lines = context.getLines();
|
const lines = context.getLines();
|
||||||
|
const treeNodes = this.createRootTreeNodesFromLines(lines);
|
||||||
// построить дерево
|
return new View(treeNodes);
|
||||||
const treeNodes = this.createNodesFromLines(lines);
|
|
||||||
|
|
||||||
// проверить ноды на modify
|
|
||||||
let isModify = false;
|
|
||||||
for (const treeNode of treeNodes) {
|
|
||||||
isModify = isModify || treeNode.isModify();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new View(treeNodes, isModify);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 nodes: TreeNode[] = [];
|
||||||
const stack: TreeNode[] = [];
|
const stack: TreeNode[] = [];
|
||||||
for (let index = 0; index < textLines.length; index++) {
|
for (const line of lines) {
|
||||||
const line = textLines[index];
|
|
||||||
const node = new TreeNode(line);
|
const node = new TreeNode(line);
|
||||||
|
|
||||||
// найти родителя
|
// найти родителя
|
||||||
|
|
@ -86,15 +62,83 @@ export class ContextFactory {
|
||||||
return nodes;
|
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[] из текста
|
// создаёт Line[] из текста
|
||||||
private static createLinesFromTextLines(textLines: string[], settings: Readonly<CheckboxSyncPluginSettings>): Line[] {
|
private static createLinesFromTextLines(textLines: string[], settings: Readonly<CheckboxSyncPluginSettings>): Line[] {
|
||||||
return textLines.map(line => {
|
return textLines.map(line => {
|
||||||
return new Line(line, settings);
|
return this.createLineFromTextLine(line, settings);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// создаёт объект из одного из классов, кто реализует Line в зависимости от строки.
|
||||||
|
static createLineFromTextLine(textLine: string, settings: Readonly<CheckboxSyncPluginSettings>): 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 {
|
private static findSingleDiffLineIndex(lines1: string[], lines2: string[]): number | undefined {
|
||||||
const diffLines = this.findDifferentLineIndexes(lines1, lines2);
|
const diffLines = this.findDifferentLineIndexes(lines1, lines2);
|
||||||
if (diffLines.length == 1) {
|
if (diffLines.length == 1) {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { CheckboxState } from "src/types";
|
||||||
|
|
||||||
export interface Line {
|
export interface Line {
|
||||||
getIndent(): number;
|
getIndent(): number;
|
||||||
|
|
||||||
|
|
@ -5,4 +7,6 @@ export interface Line {
|
||||||
|
|
||||||
toResultText(): string;
|
toResultText(): string;
|
||||||
|
|
||||||
|
getState(): CheckboxState;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
import { Line } from "./Line";
|
import { Line } from "./Line";
|
||||||
|
import { CheckboxLine } from "./line/CheckboxLine";
|
||||||
|
|
||||||
export class TreeNode {
|
export class TreeNode {
|
||||||
private parent?: TreeNode;
|
private parent?: TreeNode;
|
||||||
private childrens: TreeNode[];
|
private childrens: TreeNode[] = [];
|
||||||
|
|
||||||
private line: Line;
|
private line: Line;
|
||||||
|
// метка того, что Единичное изменение произошло в этой ноде или дочерних нодах
|
||||||
private modify = false;
|
private modify = false;
|
||||||
|
|
||||||
constructor(line: Line) {
|
constructor(line: Line) {
|
||||||
this.line = line;
|
this.line = line;
|
||||||
if (line.isChange()) {
|
if (line instanceof CheckboxLine && line.isChange()) {
|
||||||
this.modify = true;
|
this.modify = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,16 @@ import { TreeNode } from "./TreeNode";
|
||||||
|
|
||||||
export class View {
|
export class View {
|
||||||
private treeNodes: TreeNode[];
|
private treeNodes: TreeNode[];
|
||||||
|
// метка того, что Единичное изменение произошло в этой View
|
||||||
private modify: boolean;
|
private modify: boolean;
|
||||||
|
|
||||||
constructor(treeNodes: TreeNode[], isModify: boolean) {
|
constructor(treeNodes: TreeNode[]) {
|
||||||
this.treeNodes = treeNodes;
|
this.treeNodes = treeNodes;
|
||||||
|
// проверить ноды на modify
|
||||||
|
let isModify = false;
|
||||||
|
for (const treeNode of treeNodes) {
|
||||||
|
isModify = isModify || treeNode.isModify();
|
||||||
|
}
|
||||||
this.modify = isModify;
|
this.modify = isModify;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,33 @@
|
||||||
|
import { CheckboxState } from "src/types";
|
||||||
import { Line } from "../Line";
|
import { Line } from "../Line";
|
||||||
|
|
||||||
export abstract class AbstractLine implements Line {
|
export abstract class AbstractLine implements Line {
|
||||||
|
|
||||||
|
protected indentString: string;
|
||||||
|
protected tabSize: number;
|
||||||
// длина отступа
|
// длина отступа
|
||||||
protected indent: number;
|
protected indent: number;
|
||||||
// текст после чекбокса
|
// текст после чекбокса
|
||||||
protected listText: string;
|
protected listText: string;
|
||||||
|
|
||||||
constructor(indent:number, lineText: string){
|
constructor(indentString: string, lineText: string, tabSize: number) {
|
||||||
this.indent = indent;
|
this.indentString = indentString;
|
||||||
this.listText = lineText;
|
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 {
|
getIndent(): number {
|
||||||
|
|
@ -21,9 +38,7 @@ export abstract class AbstractLine implements Line {
|
||||||
return this.listText;
|
return this.listText;
|
||||||
}
|
}
|
||||||
|
|
||||||
setText(lineText: string){
|
|
||||||
this.listText = lineText;
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract toResultText(): string;
|
abstract toResultText(): string;
|
||||||
|
|
||||||
|
abstract getState(): CheckboxState;
|
||||||
}
|
}
|
||||||
|
|
@ -2,32 +2,31 @@ import { CheckboxState, CheckboxSyncPluginSettings } from "src/types";
|
||||||
import { AbstractLine } from "./AbstractLine";
|
import { AbstractLine } from "./AbstractLine";
|
||||||
|
|
||||||
export class CheckboxLine extends AbstractLine {
|
export class CheckboxLine extends AbstractLine {
|
||||||
|
|
||||||
// символы перед чекбоксом
|
// символы перед чекбоксом
|
||||||
private marker: string;
|
private marker: string;
|
||||||
// символ в чекбоксе
|
// символ в чекбоксе
|
||||||
private checkChar: string;
|
private checkChar: string;
|
||||||
// позиция символа чекбокса в исходной строке
|
// позиция символа чекбокса в исходной строке
|
||||||
private checkboxCharPosition: number;
|
// private checkboxCharPosition: number;
|
||||||
// интерпретация символа чекбокса(или его отсутствия)
|
// интерпретация символа чекбокса(или его отсутствия)
|
||||||
private checkboxState: CheckboxState;
|
private checkboxState: CheckboxState;
|
||||||
// интерпретация состояния чекбокса(или его отсутствия)
|
// интерпретация состояния чекбокса(или его отсутствия)
|
||||||
// private isChecked?: boolean | undefined;
|
// private isChecked?: boolean | undefined;
|
||||||
|
|
||||||
|
|
||||||
private hasChange = false;
|
// метка того, что Единичное изменение произошло в этой строке
|
||||||
|
private hasChange = false;
|
||||||
|
|
||||||
protected settings: Readonly<CheckboxSyncPluginSettings>;
|
protected settings: Readonly<CheckboxSyncPluginSettings>;
|
||||||
|
|
||||||
constructor(indent: number, marker: string, checkChar: string, lineText: string, settings: Readonly<CheckboxSyncPluginSettings>) {
|
constructor(indentString:string, marker: string, checkChar: string, listItemText: string, settings: Readonly<CheckboxSyncPluginSettings>) {
|
||||||
super(indent, lineText);
|
super(indentString, listItemText, settings.tabSize);
|
||||||
this.marker = marker;
|
this.marker = marker;
|
||||||
this.checkChar = checkChar;
|
this.checkChar = checkChar;
|
||||||
this.settings = settings;
|
this.settings = settings;
|
||||||
|
|
||||||
this.checkboxCharPosition = ;
|
// this.checkboxCharPosition = indent + marker.length + 2;
|
||||||
this.checkboxState = ;
|
this.checkboxState = this.getCheckboxState(checkChar);
|
||||||
throw new Error("Constructor not implemented.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isChange(): boolean {
|
isChange(): boolean {
|
||||||
|
|
@ -38,36 +37,12 @@ export class CheckboxLine extends AbstractLine {
|
||||||
this.hasChange = hasChange;
|
this.hasChange = hasChange;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
isCheckbox(): boolean {
|
|
||||||
return this.checkboxState !== CheckboxState.NoCheckbox;
|
|
||||||
}
|
|
||||||
|
|
||||||
setState(state: CheckboxState): void {
|
setState(state: CheckboxState): void {
|
||||||
// обновить checkboxState
|
// обновить checkboxState
|
||||||
this.checkboxState = state;
|
this.checkboxState = state;
|
||||||
// обновить checkChar
|
// обновить checkChar
|
||||||
switch (state) {
|
this.checkChar = this.getCharFromState(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}.`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setStateIfNotEquals(newState: CheckboxState) {
|
setStateIfNotEquals(newState: CheckboxState) {
|
||||||
|
|
@ -80,5 +55,42 @@ export class CheckboxLine extends AbstractLine {
|
||||||
return this.checkboxState;
|
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}.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { CheckboxState } from "src/types";
|
||||||
import { AbstractLine } from "./AbstractLine";
|
import { AbstractLine } from "./AbstractLine";
|
||||||
|
|
||||||
export class ListLine extends AbstractLine {
|
export class ListLine extends AbstractLine {
|
||||||
|
|
@ -5,8 +6,8 @@ export class ListLine extends AbstractLine {
|
||||||
// символы перед текстом
|
// символы перед текстом
|
||||||
private marker: string;
|
private marker: string;
|
||||||
|
|
||||||
constructor(indent: number, marker: string, listText: string) {
|
constructor(indentString: string, marker: string, listText: string, tabSize: number) {
|
||||||
super(indent, listText);
|
super(indentString, listText, tabSize);
|
||||||
this.marker = marker;
|
this.marker = marker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -19,9 +20,12 @@ export class ListLine extends AbstractLine {
|
||||||
}
|
}
|
||||||
|
|
||||||
toResultText(): string {
|
toResultText(): string {
|
||||||
const spaces = ' '.repeat(this.indent);
|
// const spaces = ' '.repeat(this.indent);
|
||||||
|
const resultText = this.indentString + this.marker + " " + this.listText;
|
||||||
const resultText = spaces + this.marker + " " + this.listText;
|
|
||||||
return resultText;
|
return resultText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getState(): CheckboxState {
|
||||||
|
return CheckboxState.NoCheckbox;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,9 +1,14 @@
|
||||||
|
import { CheckboxState } from "src/types";
|
||||||
import { AbstractLine } from "./AbstractLine";
|
import { AbstractLine } from "./AbstractLine";
|
||||||
|
|
||||||
export class TextLine extends AbstractLine {
|
export class TextLine extends AbstractLine {
|
||||||
toResultText(): string {
|
toResultText(): string {
|
||||||
const spaces = ' '.repeat(this.indent);
|
// const spaces = ' '.repeat(this.indent);
|
||||||
const resultText = spaces + this.listText;
|
const resultText = this.indentString + this.listText;
|
||||||
return resultText;
|
return resultText;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getState(): CheckboxState {
|
||||||
|
return CheckboxState.NoCheckbox;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,6 +2,7 @@ import { CheckboxState } from "src/types";
|
||||||
import { CheckboxProcess } from "../interface/CheckboxProcess";
|
import { CheckboxProcess } from "../interface/CheckboxProcess";
|
||||||
import { Context } from "../model/Context";
|
import { Context } from "../model/Context";
|
||||||
import { TreeNode } from "../model/TreeNode";
|
import { TreeNode } from "../model/TreeNode";
|
||||||
|
import { CheckboxLine } from "../model/line/CheckboxLine";
|
||||||
|
|
||||||
export class PropagateStateToChildrenProcess implements CheckboxProcess {
|
export class PropagateStateToChildrenProcess implements CheckboxProcess {
|
||||||
|
|
||||||
|
|
@ -21,46 +22,51 @@ export class PropagateStateToChildrenProcess implements CheckboxProcess {
|
||||||
|
|
||||||
const nodes = view.getTreeNodes();
|
const nodes = view.getTreeNodes();
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
this.propageteStateToChildrenFromChangeLineNode(node);
|
this.propagateStateToChildrenFromChangeLineNode(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// находит изменённую Line, если она существует и вызывает от неё propagateStateToChildren
|
// находит изменённую Line, если она существует и вызывает от неё propagateStateToChildren
|
||||||
propageteStateToChildrenFromChangeLineNode(node: TreeNode) {
|
propagateStateToChildrenFromChangeLineNode(node: TreeNode) {
|
||||||
if (!node.isModify()) {
|
if (!node.isModify()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const line = node.getLine();
|
const line = node.getLine();
|
||||||
// если line изменён
|
// если line изменён
|
||||||
if (line.isChange()) {
|
if (line instanceof CheckboxLine && line.isChange()) {
|
||||||
// значит мы нашли нужную ноду
|
// значит мы нашли нужную ноду
|
||||||
this.propagateStateToChildren(node);
|
this.propagateStateToChildrenFromNode(node);
|
||||||
} else {
|
} else {
|
||||||
// иначе ищем вглубь среди детей
|
// иначе ищем вглубь среди детей
|
||||||
const childrens = node.getChildrenNodes();
|
const childrens = node.getChildrenNodes();
|
||||||
for (const children of childrens) {
|
for (const children of childrens) {
|
||||||
this.propageteStateToChildrenFromChangeLineNode(children);
|
this.propagateStateToChildrenFromChangeLineNode(children);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
propagateStateToChildren(modifiedNode: TreeNode) {
|
propagateStateToChildrenFromNode(modifiedNode: TreeNode) {
|
||||||
const state = modifiedNode.getLine().getState();
|
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) {
|
if (state === CheckboxState.Ignore || state === CheckboxState.NoCheckbox) {
|
||||||
console.warn(`propagateStateToChildren ${state.toString()}`);
|
console.warn(`propagateStateToChildren ${state.toString()}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.propagateToChildren(modifiedNode, state);
|
this.propagateStateToChildren(modifiedNode, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
propagateToChildren(node: TreeNode, state: CheckboxState) {
|
propagateStateToChildren(node: TreeNode, state: CheckboxState) {
|
||||||
const line = node.getLine();
|
const line = node.getLine();
|
||||||
if (line.isCheckbox()) {
|
if (line instanceof CheckboxLine) {
|
||||||
line.setStateIfNotEquals(state);
|
line.setStateIfNotEquals(state);
|
||||||
}
|
}
|
||||||
for (const childrenNode of node.getChildrenNodes()) {
|
for (const childrenNode of node.getChildrenNodes()) {
|
||||||
this.propagateToChildren(childrenNode, state);
|
this.propagateStateToChildren(childrenNode, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { CheckboxState } from "src/types";
|
||||||
import { CheckboxProcess } from "../interface/CheckboxProcess";
|
import { CheckboxProcess } from "../interface/CheckboxProcess";
|
||||||
import { Context } from "../model/Context";
|
import { Context } from "../model/Context";
|
||||||
import { TreeNode } from "../model/TreeNode";
|
import { TreeNode } from "../model/TreeNode";
|
||||||
|
import { CheckboxLine } from "../model/line/CheckboxLine";
|
||||||
|
|
||||||
export class PropagateStateToParentProcess implements CheckboxProcess {
|
export class PropagateStateToParentProcess implements CheckboxProcess {
|
||||||
|
|
||||||
|
|
@ -16,24 +17,42 @@ export class PropagateStateToParentProcess implements CheckboxProcess {
|
||||||
const nodes = view.getTreeNodes();
|
const nodes = view.getTreeNodes();
|
||||||
|
|
||||||
for (const node of nodes) {
|
for (const node of nodes) {
|
||||||
this.propagateStateFromChildrens(node);
|
this.propagateStateToParent(node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// возврат значения нужен для того, чтобы правильно обрабатывать случаи с "не чекбоксам", чтобы они передавали значения детей
|
// возврат значения нужен для того, чтобы правильно обрабатывать случаи с "не чекбоксам", чтобы они передавали значения детей
|
||||||
propagateStateFromChildrens(node: TreeNode): CheckboxState {
|
propagateStateToParent(node: TreeNode): CheckboxState {
|
||||||
const line = node.getLine();
|
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 resultIfHasRelevantChildren = CheckboxState.Checked;
|
||||||
let hasRelevantChildren = false;
|
let hasRelevantChildren = false;
|
||||||
for (const childrenNode of node.getChildrenNodes()) {
|
for (const childrenState of childrenStates) {
|
||||||
const childrenState = this.propagateStateFromChildrens(childrenNode);
|
if (childrenState !== CheckboxState.Ignore && childrenState !== CheckboxState.NoCheckbox) {
|
||||||
|
|
||||||
if (childrenState === CheckboxState.Checked || childrenState === CheckboxState.Unchecked) {
|
|
||||||
hasRelevantChildren = true;
|
hasRelevantChildren = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,17 +60,12 @@ export class PropagateStateToParentProcess implements CheckboxProcess {
|
||||||
resultIfHasRelevantChildren = CheckboxState.Unchecked;
|
resultIfHasRelevantChildren = CheckboxState.Unchecked;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// делаем эту проверку только после обработки детей
|
|
||||||
if (line.getState() == CheckboxState.Ignore) {
|
|
||||||
return line.getState();
|
|
||||||
}
|
|
||||||
// проверка для нод, все дети которых не релевантны
|
|
||||||
if (!hasRelevantChildren) {
|
|
||||||
return line.getState();
|
|
||||||
}
|
|
||||||
|
|
||||||
line.setStateIfNotEquals(resultIfHasRelevantChildren);
|
if (hasRelevantChildren) {
|
||||||
|
return resultIfHasRelevantChildren;
|
||||||
return resultIfHasRelevantChildren;
|
} else {
|
||||||
|
return actualState;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { Plugin, TFile } from "obsidian";
|
import { Plugin, TFile } from "obsidian";
|
||||||
import { CheckboxUtils } from "./checkboxUtils";
|
|
||||||
import { FileChangeEventHandler } from "./events/FileChangeEventHandler";
|
import { FileChangeEventHandler } from "./events/FileChangeEventHandler";
|
||||||
import { FileLoadEventHandler } from "./events/FileLoadEventHandler";
|
import { FileLoadEventHandler } from "./events/FileLoadEventHandler";
|
||||||
import FileStateHolder from "./FileStateHolder";
|
import FileStateHolder from "./FileStateHolder";
|
||||||
|
|
@ -8,6 +7,8 @@ import SyncController from "./SyncController";
|
||||||
import { CheckboxSyncPluginSettings, DEFAULT_SETTINGS } from "./types";
|
import { CheckboxSyncPluginSettings, DEFAULT_SETTINGS } from "./types";
|
||||||
import { FileFilter } from "./FileFilter";
|
import { FileFilter } from "./FileFilter";
|
||||||
import TextSyncPipeline from "./TextSyncPipeline";
|
import TextSyncPipeline from "./TextSyncPipeline";
|
||||||
|
import { ICheckboxUtils } from "./core/interface/ICheckboxUtils";
|
||||||
|
import { CheckboxUtils2 } from "./core/CheckboxUtils2";
|
||||||
|
|
||||||
const DEBUG_FLAG_NAME = 'CHECKBOX_SYNC_DEBUG';
|
const DEBUG_FLAG_NAME = 'CHECKBOX_SYNC_DEBUG';
|
||||||
|
|
||||||
|
|
@ -23,7 +24,7 @@ export default class CheckboxSyncPlugin extends Plugin {
|
||||||
private _settings: CheckboxSyncPluginSettings;
|
private _settings: CheckboxSyncPluginSettings;
|
||||||
|
|
||||||
private syncController: SyncController;
|
private syncController: SyncController;
|
||||||
private checkboxUtils: CheckboxUtils;
|
private checkboxUtils: ICheckboxUtils;
|
||||||
private fileStateHolder: FileStateHolder;
|
private fileStateHolder: FileStateHolder;
|
||||||
private fileLoadEventHandler: FileLoadEventHandler;
|
private fileLoadEventHandler: FileLoadEventHandler;
|
||||||
private fileChangeEventHandler: FileChangeEventHandler;
|
private fileChangeEventHandler: FileChangeEventHandler;
|
||||||
|
|
@ -35,9 +36,9 @@ export default class CheckboxSyncPlugin extends Plugin {
|
||||||
|
|
||||||
this.fileFilter = new FileFilter(this.settings);
|
this.fileFilter = new FileFilter(this.settings);
|
||||||
this.fileStateHolder = new FileStateHolder(this.app.vault);
|
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.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.fileLoadEventHandler = new FileLoadEventHandler(this, this.app, this.syncController, this.fileStateHolder);
|
||||||
this.fileChangeEventHandler = new FileChangeEventHandler(this, this.app, this.syncController, this.fileStateHolder);
|
this.fileChangeEventHandler = new FileChangeEventHandler(this, this.app, this.syncController, this.fileStateHolder);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
export interface CheckboxSyncPluginSettings {
|
export interface CheckboxSyncPluginSettings {
|
||||||
|
tabSize: number,
|
||||||
enableAutomaticParentState: boolean;
|
enableAutomaticParentState: boolean;
|
||||||
enableAutomaticChildState: boolean;
|
enableAutomaticChildState: boolean;
|
||||||
checkedSymbols: string[];
|
checkedSymbols: string[];
|
||||||
|
|
@ -18,6 +19,7 @@ export enum CheckboxState {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_SETTINGS: CheckboxSyncPluginSettings = {
|
export const DEFAULT_SETTINGS: CheckboxSyncPluginSettings = {
|
||||||
|
tabSize: 4,
|
||||||
enableAutomaticParentState: true,
|
enableAutomaticParentState: true,
|
||||||
enableAutomaticChildState: true,
|
enableAutomaticChildState: true,
|
||||||
checkedSymbols: ['x'],
|
checkedSymbols: ['x'],
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue