mirror of
https://github.com/groldsf/obsidian_check_plugin.git
synced 2026-07-22 05:37:48 +00:00
stable?
This commit is contained in:
parent
32a5fe4229
commit
198adf253f
11 changed files with 892 additions and 241 deletions
570
__tests__/checkboxUtils.test.ts
Normal file
570
__tests__/checkboxUtils.test.ts
Normal file
|
|
@ -0,0 +1,570 @@
|
|||
// checkboxUtils.test.ts
|
||||
import { CheckboxUtils, CheckboxLineInfo } from '../src/checkboxUtils';
|
||||
import { CheckboxSyncPluginSettings } from '../src/types'; // Предполагаем, что типы в types.ts
|
||||
|
||||
// --- Mock Settings ---
|
||||
const defaultSettings: Readonly<CheckboxSyncPluginSettings> = {
|
||||
xOnlyMode: false, // По умолчанию: любой не-пробел считается 'checked'
|
||||
};
|
||||
|
||||
const xOnlySettings: Readonly<CheckboxSyncPluginSettings> = {
|
||||
xOnlyMode: true, // Только 'x' считается 'checked'
|
||||
};
|
||||
|
||||
// --- Test Suite ---
|
||||
describe('CheckboxUtils', () => {
|
||||
let utilsDefault: CheckboxUtils;
|
||||
let utilsXOnly: CheckboxUtils;
|
||||
|
||||
beforeEach(() => {
|
||||
// Создаем новые экземпляры перед каждым тестом для изоляции
|
||||
utilsDefault = new CheckboxUtils(defaultSettings);
|
||||
utilsXOnly = new CheckboxUtils(xOnlySettings);
|
||||
});
|
||||
|
||||
// --- matchCheckboxLine ---
|
||||
describe('matchCheckboxLine', () => {
|
||||
it.each([
|
||||
// Простые случаи
|
||||
['- [ ] Text', { indent: 0, marker: '-', checkChar: ' ', checkboxCharPosition: 3 }],
|
||||
['* [x] Task', { indent: 0, marker: '*', checkChar: 'x', checkboxCharPosition: 3 }],
|
||||
['+ [?] Query', { indent: 0, marker: '+', checkChar: '?', checkboxCharPosition: 3 }],
|
||||
['1. [!] Num', { indent: 0, marker: '1.', checkChar: '!', checkboxCharPosition: 4 }],
|
||||
// С отступами
|
||||
[' - [ ] Indented', { indent: 2, marker: '-', checkChar: ' ', checkboxCharPosition: 5 }],
|
||||
['\t* [x] Tab Indent', { indent: 1, marker: '*', checkChar: 'x', checkboxCharPosition: 4 }], // Зависит от размера таба, Jest может считать его 1
|
||||
[' 10. [A] Deep', { indent: 4, marker: '10.', checkChar: 'A', checkboxCharPosition: 9 }],
|
||||
])('should match valid checkbox line: %s', (line, expected) => {
|
||||
const result = utilsDefault.matchCheckboxLine(line);
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it.each([
|
||||
'Not a list item',
|
||||
'- Just text',
|
||||
'-[] No space',
|
||||
'- [No Char]',
|
||||
'- [ ]', // Нет пробела после скобки, как требует регекс \s
|
||||
' Non-checkbox line',
|
||||
'-[ ] No space before marker',
|
||||
'1.[ ] No space after numbered marker',
|
||||
'- ] Missing opening bracket',
|
||||
'* [x Missing closing bracket',
|
||||
])('should return null for invalid lines: %s', (line) => {
|
||||
expect(utilsDefault.matchCheckboxLine(line)).toBeNull();
|
||||
});
|
||||
|
||||
it('should require a space after the closing bracket', () => {
|
||||
expect(utilsDefault.matchCheckboxLine('- [ ]')).toBeNull(); // Missing trailing space
|
||||
expect(utilsDefault.matchCheckboxLine('- [ ] ')).not.toBeNull(); // Has trailing space
|
||||
});
|
||||
|
||||
it.each([' ', 'x', '-', '?', '!'])('should correctly parse the checkChar', (state) => {
|
||||
const lineInfo = utilsDefault.matchCheckboxLine(`- [${state}] Task`);
|
||||
expect(lineInfo).not.toBeNull();
|
||||
expect(lineInfo?.checkChar).toBe(state);
|
||||
});
|
||||
});
|
||||
|
||||
// --- isCheckedSymbol ---
|
||||
describe('isCheckedSymbol', () => {
|
||||
// Default mode (xOnlyMode: false)
|
||||
it('should check symbols correctly in default mode', () => {
|
||||
expect(utilsDefault.isCheckedSymbol('x')).toBe(true);
|
||||
expect(utilsDefault.isCheckedSymbol('X')).toBe(true);
|
||||
expect(utilsDefault.isCheckedSymbol('/')).toBe(true);
|
||||
expect(utilsDefault.isCheckedSymbol('-')).toBe(true);
|
||||
expect(utilsDefault.isCheckedSymbol('?')).toBe(true);
|
||||
expect(utilsDefault.isCheckedSymbol(' ')).toBe(false); // Только пробел - не отмечено
|
||||
});
|
||||
|
||||
// xOnly mode (xOnlyMode: true)
|
||||
it('should check symbols correctly in xOnly mode', () => {
|
||||
expect(utilsXOnly.isCheckedSymbol('x')).toBe(true); // Только 'x' - отмечено
|
||||
expect(utilsXOnly.isCheckedSymbol('X')).toBe(false);
|
||||
expect(utilsXOnly.isCheckedSymbol('/')).toBe(false);
|
||||
expect(utilsXOnly.isCheckedSymbol('-')).toBe(false);
|
||||
expect(utilsXOnly.isCheckedSymbol('?')).toBe(false);
|
||||
expect(utilsXOnly.isCheckedSymbol(' ')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
// --- updateLineCheckboxStateWithInfo ---
|
||||
describe('updateLineCheckboxStateWithInfo', () => {
|
||||
|
||||
it('should check an unchecked box', () => {
|
||||
const uncheckedLine = ' - [ ] Task';
|
||||
const uncheckedLineInfo = utilsDefault.matchCheckboxLine(uncheckedLine)!;
|
||||
const updatedLine = utilsDefault.updateLineCheckboxStateWithInfo(uncheckedLine, true, uncheckedLineInfo);
|
||||
expect(updatedLine).toBe(' - [x] Task');
|
||||
});
|
||||
|
||||
it('should uncheck a checked box', () => {
|
||||
const checkedLine = ' - [x] Task';
|
||||
const checkedLineInfo = utilsDefault.matchCheckboxLine(checkedLine)!;
|
||||
const updatedLine = utilsDefault.updateLineCheckboxStateWithInfo(checkedLine, false, checkedLineInfo);
|
||||
expect(updatedLine).toBe(' - [ ] Task');
|
||||
});
|
||||
|
||||
it('should handle other initial check chars', () => {
|
||||
const otherLine = ' - [?] Task';
|
||||
const otherLineInfo = utilsDefault.matchCheckboxLine(otherLine)!;
|
||||
expect(utilsDefault.updateLineCheckboxStateWithInfo(otherLine, true, otherLineInfo)).toBe(' - [x] Task');
|
||||
expect(utilsDefault.updateLineCheckboxStateWithInfo(otherLine, false, otherLineInfo)).toBe(' - [ ] Task');
|
||||
});
|
||||
|
||||
it('should return original line if position is invalid (and warn)', () => {
|
||||
const warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => { }); // Подавить вывод в консоль
|
||||
const uncheckedLine = ' - [ ] Task';
|
||||
const uncheckedLineInfo = utilsDefault.matchCheckboxLine(uncheckedLine)!;
|
||||
const invalidInfo = { ...uncheckedLineInfo, checkboxCharPosition: 100 };
|
||||
const result = utilsDefault.updateLineCheckboxStateWithInfo(uncheckedLine, true, invalidInfo);
|
||||
expect(result).toBe(uncheckedLine);
|
||||
expect(warnSpy).toHaveBeenCalled();
|
||||
warnSpy.mockRestore();
|
||||
});
|
||||
});
|
||||
|
||||
// --- propagateStateToChildren ---
|
||||
describe('propagateStateToChildren', () => {
|
||||
const text = [
|
||||
'- [ ] Parent 1', // 0
|
||||
' - [ ] Child 1.1', // 1
|
||||
' - [x] Child 1.2', // 2
|
||||
' - [ ] Grandchild 1.2.1', // 3
|
||||
'- [x] Parent 2', // 4
|
||||
' - [x] Child 2.1', // 5
|
||||
].join('\n');
|
||||
|
||||
it('should check children when parent is checked', () => {
|
||||
// Имитируем проверку Parent 1 (индекс 0)
|
||||
const lines = text.split('\n');
|
||||
lines[0] = '- [x] Parent 1'; // Manually check parent for the test input
|
||||
const initialTextModified = lines.join('\n');
|
||||
|
||||
const result = utilsDefault.propagateStateToChildren(initialTextModified, 0);
|
||||
const expected = [
|
||||
'- [x] Parent 1', // Checked
|
||||
' - [x] Child 1.1', // Updated
|
||||
' - [x] Child 1.2', // Updated
|
||||
' - [x] Grandchild 1.2.1', // Updated
|
||||
'- [x] Parent 2', // Unchanged
|
||||
' - [x] Child 2.1', // Unchanged
|
||||
].join('\n');
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should uncheck children when parent is unchecked', () => {
|
||||
// Имитируем снятие отметки с Parent 2 (индекс 4)
|
||||
const lines = text.split('\n');
|
||||
lines[4] = '- [ ] Parent 2'; // Manually uncheck parent for the test input
|
||||
const initialTextModified = lines.join('\n');
|
||||
|
||||
const result = utilsDefault.propagateStateToChildren(initialTextModified, 4);
|
||||
const expected = [
|
||||
'- [ ] Parent 1', // Unchanged
|
||||
' - [ ] Child 1.1', // Unchanged
|
||||
' - [x] Child 1.2', // Unchanged
|
||||
' - [ ] Grandchild 1.2.1', // Unchanged
|
||||
'- [ ] Parent 2', // Unchecked
|
||||
' - [ ] Child 2.1', // Updated
|
||||
].join('\n');
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should do nothing if the target line is not a checkbox', () => {
|
||||
const textWithNonCheckbox = [
|
||||
'# Header',
|
||||
'- [ ] Item 1'
|
||||
].join('\n');
|
||||
const result = utilsDefault.propagateStateToChildren(textWithNonCheckbox, 0);
|
||||
expect(result).toBe(textWithNonCheckbox);
|
||||
});
|
||||
|
||||
it('should stop only when indent is less than or equal to the original parent', () => {
|
||||
const textSibling = [
|
||||
'- [ ] Parent', // 0, indent 0
|
||||
' - [ ] Child 1', // 1, indent 2
|
||||
' - [ ] Sibling C1', // 2, indent 2 (still > 0)
|
||||
'- [ ] Parent 2' // 3, indent 0 (<= 0, stop here)
|
||||
].join('\n');
|
||||
const lines = textSibling.split('\n');
|
||||
lines[0] = '- [x] Parent'; // Make parent checked
|
||||
const modifiedText = lines.join('\n');
|
||||
const result = utilsDefault.propagateStateToChildren(modifiedText, 0);
|
||||
const expected = [
|
||||
'- [x] Parent',
|
||||
' - [x] Child 1', // Changed (indent 2 > 0)
|
||||
' - [x] Sibling C1', // <<-- ALSO Changed (indent 2 > 0)
|
||||
'- [ ] Parent 2' // Unchanged (indent 0 <= 0)
|
||||
].join('\n');
|
||||
expect(result).toBe(expected); // Проверяем обновленное ожидание
|
||||
});
|
||||
});
|
||||
|
||||
// --- propagateStateFromChildren ---
|
||||
describe('propagateStateFromChildren', () => {
|
||||
it('should check parent if all children are checked', () => {
|
||||
const text = [
|
||||
'- [ ] Parent', // Should become [x]
|
||||
' - [x] Child 1',
|
||||
' - [x] Child 2',
|
||||
' - [x] Grandchild 2.1', // Needs to be checked too
|
||||
].join('\n');
|
||||
const result = utilsDefault.propagateStateFromChildren(text);
|
||||
const expected = [
|
||||
'- [x] Parent', // Updated
|
||||
' - [x] Child 1',
|
||||
' - [x] Child 2',
|
||||
' - [x] Grandchild 2.1',
|
||||
].join('\n');
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should uncheck parent if any child is unchecked', () => {
|
||||
const text = [
|
||||
'- [x] Parent', // Should become [ ]
|
||||
' - [x] Child 1',
|
||||
' - [ ] Child 2', // This one makes parent unchecked
|
||||
].join('\n');
|
||||
const result = utilsDefault.propagateStateFromChildren(text);
|
||||
const expected = [
|
||||
'- [ ] Parent', // Updated
|
||||
' - [x] Child 1',
|
||||
' - [ ] Child 2',
|
||||
].join('\n');
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle nested propagation', () => {
|
||||
const text = [
|
||||
'- [ ] Grandparent', // Should become [ ]
|
||||
' - [x] Parent 1', // Should become [ ]
|
||||
' - [x] Child 1.1',
|
||||
' - [ ] Child 1.2', // Makes Parent 1 unchecked
|
||||
' - [x] Parent 2', // Stays checked
|
||||
' - [x] Child 2.1',
|
||||
].join('\n');
|
||||
const result = utilsDefault.propagateStateFromChildren(text);
|
||||
const expected = [
|
||||
'- [ ] Grandparent', // Updated (because Parent 1 is now [ ])
|
||||
' - [ ] Parent 1', // Updated
|
||||
' - [x] Child 1.1',
|
||||
' - [ ] Child 1.2',
|
||||
' - [x] Parent 2',
|
||||
' - [x] Child 2.1',
|
||||
].join('\n');
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should not change parent state if it has no children', () => {
|
||||
const text = [
|
||||
'- [ ] Parent No Children',
|
||||
'- [x] Parent No Children Checked',
|
||||
' Some other line',
|
||||
].join('\n');
|
||||
const result = utilsDefault.propagateStateFromChildren(text);
|
||||
expect(result).toBe(text); // No changes expected
|
||||
});
|
||||
|
||||
it('should work correctly with xOnlyMode', () => {
|
||||
const text = [
|
||||
'- [ ] Parent', // Should become [x] in xOnly mode
|
||||
' - [x] Child 1',
|
||||
' - [x] Child 2'
|
||||
].join('\n');
|
||||
const result = utilsXOnly.propagateStateFromChildren(text);
|
||||
expect(result).toContain('- [x] Parent'); // Parent checked
|
||||
|
||||
const text2 = [
|
||||
'- [x] Parent', // Should become [ ] because '?' is not 'x'
|
||||
' - [x] Child 1',
|
||||
' - [?] Child 2'
|
||||
].join('\n');
|
||||
const result2 = utilsXOnly.propagateStateFromChildren(text2);
|
||||
expect(result2).toContain('- [ ] Parent'); // Parent unchecked
|
||||
});
|
||||
|
||||
it('should handle multiple independent parent checklists', () => {
|
||||
const text = [
|
||||
'- [ ] Parent 1',
|
||||
' - [x] Child 1.1',
|
||||
'Some other text',
|
||||
'* [ ] Parent 2',
|
||||
' * [x] Child 2.1',
|
||||
' * [x] Child 2.2',
|
||||
].join('\n');
|
||||
const result = utilsDefault.propagateStateFromChildren(text);
|
||||
const expected = [
|
||||
'- [x] Parent 1', // Updated
|
||||
' - [x] Child 1.1',
|
||||
'Some other text',
|
||||
'* [x] Parent 2', // Updated
|
||||
' * [x] Child 2.1',
|
||||
' * [x] Child 2.2',
|
||||
].join('\n');
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should stop searching for children for a parent when a line with equal or lesser indent is encountered', () => {
|
||||
const text = [
|
||||
'- [ ] Parent 1', // indent 0
|
||||
'Some regular text', // indent 0. Stops search for Parent 1 children HERE.
|
||||
' - [x] Child 1.1', // indent 2. Ignored for Parent 1 because search stopped.
|
||||
'- [ ] Parent 2', // indent 0. Processed independently later.
|
||||
' - [x] Child 2.1', // indent 2. Child of Parent 2.
|
||||
].join('\n');
|
||||
|
||||
const result = utilsDefault.propagateStateFromChildren(text);
|
||||
|
||||
// Ожидания:
|
||||
// - Parent 1 не найдет детей (из-за "Some regular text") и останется [ ].
|
||||
// - Parent 2 найдет Child 2.1 ([x]) и станет [x].
|
||||
const expected = [
|
||||
'- [ ] Parent 1', // Unchanged
|
||||
'Some regular text', // Unchanged
|
||||
' - [x] Child 1.1', // Unchanged
|
||||
'- [x] Parent 2', // Updated because Child 2.1 is checked
|
||||
' - [x] Child 2.1', // Unchanged
|
||||
].join('\n');
|
||||
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should stop searching for children when a sibling checkbox is encountered', () => {
|
||||
const text = [
|
||||
'- [ ] Parent 1', // indent 0
|
||||
'- [ ] Sibling Parent', // indent 0. Stops search for Parent 1 children HERE.
|
||||
' - [x] Child 1.1', // indent 2. Belongs to Sibling Parent if processed.
|
||||
].join('\n');
|
||||
|
||||
const result = utilsDefault.propagateStateFromChildren(text);
|
||||
|
||||
// Ожидания:
|
||||
// - Parent 1 не найдет детей (из-за "Sibling Parent") и останется [ ].
|
||||
// - Sibling Parent найдет Child 1.1 ([x]) и станет [x].
|
||||
const expected = [
|
||||
'- [ ] Parent 1', // Unchanged
|
||||
'- [x] Sibling Parent', // Updated because Child 1.1 is checked
|
||||
' - [x] Child 1.1', // Unchanged
|
||||
].join('\n');
|
||||
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// --- propagateStateToChildrenFromSingleDiff ---
|
||||
describe('propagateStateToChildrenFromSingleDiff', () => {
|
||||
const textBefore = [
|
||||
'- [ ] Parent', // 0
|
||||
' - [ ] Child', // 1
|
||||
].join('\n');
|
||||
|
||||
it('should propagate down if single line changed and it was a checkbox toggle', () => {
|
||||
const textAfter = [
|
||||
'- [x] Parent', // Changed state
|
||||
' - [ ] Child',
|
||||
].join('\n');
|
||||
const result = utilsDefault.propagateStateToChildrenFromSingleDiff(textAfter, textBefore);
|
||||
const expected = [ // Expect children to be updated
|
||||
'- [x] Parent',
|
||||
' - [x] Child',
|
||||
].join('\n');
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should NOT propagate down if single line changed but it was NOT checkbox state', () => {
|
||||
const textAfter = [
|
||||
'- [ ] Parent Edited Text', // Text changed, not checkbox
|
||||
' - [ ] Child',
|
||||
].join('\n');
|
||||
const result = utilsDefault.propagateStateToChildrenFromSingleDiff(textAfter, textBefore);
|
||||
// Expect original textAfter, no downward propagation
|
||||
expect(result).toBe(textAfter);
|
||||
});
|
||||
|
||||
it('should NOT propagate down if single line changed into non-checkbox', () => {
|
||||
const textAfter = [
|
||||
'Parent removed checkbox', // Structure changed
|
||||
' - [ ] Child',
|
||||
].join('\n');
|
||||
const result = utilsDefault.propagateStateToChildrenFromSingleDiff(textAfter, textBefore);
|
||||
expect(result).toBe(textAfter);
|
||||
});
|
||||
|
||||
|
||||
it('should NOT propagate down if multiple lines changed', () => {
|
||||
const textAfter = [
|
||||
'- [x] Parent', // Changed
|
||||
' - [x] Child', // Changed
|
||||
].join('\n');
|
||||
const result = utilsDefault.propagateStateToChildrenFromSingleDiff(textAfter, textBefore);
|
||||
expect(result).toBe(textAfter); // Expect original textAfter
|
||||
});
|
||||
|
||||
it('should NOT propagate down if line counts differ', () => {
|
||||
const textAfter = [
|
||||
'- [x] Parent',
|
||||
' - [ ] Child',
|
||||
' - [ ] New Child',
|
||||
].join('\n');
|
||||
const result = utilsDefault.propagateStateToChildrenFromSingleDiff(textAfter, textBefore);
|
||||
expect(result).toBe(textAfter); // Expect original textAfter
|
||||
});
|
||||
|
||||
it('should NOT propagate down if textBefore is undefined', () => {
|
||||
const textAfter = [
|
||||
'- [x] Parent',
|
||||
' - [ ] Child',
|
||||
].join('\n');
|
||||
const result = utilsDefault.propagateStateToChildrenFromSingleDiff(textAfter, undefined);
|
||||
expect(result).toBe(textAfter); // Expect original textAfter
|
||||
});
|
||||
});
|
||||
|
||||
// --- findDifferentLineIndexes ---
|
||||
describe('findDifferentLineIndexes', () => {
|
||||
const lines1 = ['a', 'b', 'c'];
|
||||
|
||||
it('should return empty array for identical lines', () => {
|
||||
const lines2 = ['a', 'b', 'c'];
|
||||
expect(utilsDefault.findDifferentLineIndexes(lines1, lines2)).toEqual([]);
|
||||
});
|
||||
|
||||
it('should return index of the single different line', () => {
|
||||
const lines2 = ['a', 'B', 'c'];
|
||||
expect(utilsDefault.findDifferentLineIndexes(lines1, lines2)).toEqual([1]);
|
||||
});
|
||||
|
||||
it('should return indexes of multiple different lines', () => {
|
||||
const lines2 = ['A', 'b', 'C'];
|
||||
expect(utilsDefault.findDifferentLineIndexes(lines1, lines2)).toEqual([0, 2]);
|
||||
});
|
||||
|
||||
it('should throw error if line lengths are different', () => {
|
||||
const lines2 = ['a', 'b'];
|
||||
expect(() => utilsDefault.findDifferentLineIndexes(lines1, lines2)).toThrow(
|
||||
'the length of the lines must be equal'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// --- syncText (Integration) ---
|
||||
describe('syncText', () => {
|
||||
it('should handle user checking parent (down -> up)', () => {
|
||||
const textBefore = [
|
||||
'- [ ] Parent',
|
||||
' - [ ] Child 1',
|
||||
' - [ ] Child 2',
|
||||
].join('\n');
|
||||
const textAfterUserCheck = [
|
||||
'- [x] Parent', // User checked this
|
||||
' - [ ] Child 1',
|
||||
' - [ ] Child 2',
|
||||
].join('\n');
|
||||
|
||||
const result = utilsDefault.syncText(textAfterUserCheck, textBefore);
|
||||
const expected = [ // Down propagation first, then up (which changes nothing here)
|
||||
'- [x] Parent',
|
||||
' - [x] Child 1',
|
||||
' - [x] Child 2',
|
||||
].join('\n');
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle user unchecking parent (down -> up)', () => {
|
||||
const textBefore = [
|
||||
'- [x] Parent',
|
||||
' - [x] Child 1',
|
||||
' - [x] Child 2',
|
||||
].join('\n');
|
||||
const textAfterUserUncheck = [
|
||||
'- [ ] Parent', // User unchecked this
|
||||
' - [x] Child 1',
|
||||
' - [x] Child 2',
|
||||
].join('\n');
|
||||
|
||||
const result = utilsDefault.syncText(textAfterUserUncheck, textBefore);
|
||||
const expected = [ // Down propagation first, then up
|
||||
'- [ ] Parent',
|
||||
' - [ ] Child 1',
|
||||
' - [ ] Child 2',
|
||||
].join('\n');
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle user checking the last child, making parent checked (no down -> up)', () => {
|
||||
const textBefore = [
|
||||
'- [ ] Parent',
|
||||
' - [x] Child 1',
|
||||
' - [ ] Child 2', // User will check this
|
||||
].join('\n');
|
||||
const textAfterUserCheck = [
|
||||
'- [ ] Parent',
|
||||
' - [x] Child 1',
|
||||
' - [x] Child 2', // User checked this
|
||||
].join('\n');
|
||||
|
||||
const result = utilsDefault.syncText(textAfterUserCheck, textBefore);
|
||||
// SingleDiff won't trigger down prop. FromChildren runs.
|
||||
const expected = [
|
||||
'- [x] Parent', // Updated by FromChildren
|
||||
' - [x] Child 1',
|
||||
' - [x] Child 2',
|
||||
].join('\n');
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle user unchecking one child, making parent unchecked (no down -> up)', () => {
|
||||
const textBefore = [
|
||||
'- [x] Parent',
|
||||
' - [x] Child 1', // User will uncheck this
|
||||
' - [x] Child 2',
|
||||
].join('\n');
|
||||
const textAfterUserUncheck = [
|
||||
'- [x] Parent',
|
||||
' - [ ] Child 1', // User unchecked this
|
||||
' - [x] Child 2',
|
||||
].join('\n');
|
||||
|
||||
const result = utilsDefault.syncText(textAfterUserUncheck, textBefore);
|
||||
// SingleDiff won't trigger down prop. FromChildren runs.
|
||||
const expected = [
|
||||
'- [ ] Parent', // Updated by FromChildren
|
||||
' - [ ] Child 1',
|
||||
' - [x] Child 2',
|
||||
].join('\n');
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle multiple changes correctly (no down -> up)', () => {
|
||||
const textBefore = [
|
||||
'- [ ] Parent',
|
||||
' - [ ] Child 1',
|
||||
' - [ ] Child 2',
|
||||
' - [ ] Child 3',
|
||||
].join('\n');
|
||||
// User checks Parent AND Child 1, but leaves Child 2 unchecked
|
||||
const textAfterMultipleChanges = [
|
||||
'- [x] Parent', // Changed by user
|
||||
' - [x] Child 1', // Changed by user
|
||||
' - [ ] Child 2', // Left unchecked
|
||||
' - [ ] Child 3', // Left unchecked
|
||||
].join('\n');
|
||||
|
||||
const result = utilsDefault.syncText(textAfterMultipleChanges, textBefore);
|
||||
// SingleDiff won't run (2 changes). FromChildren runs.
|
||||
// Since Child 2/3 are unchecked, Parent should become unchecked.
|
||||
const expected = [
|
||||
'- [ ] Parent', // Corrected by FromChildren
|
||||
' - [x] Child 1',
|
||||
' - [ ] Child 2',
|
||||
' - [ ] Child 3',
|
||||
].join('\n');
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { CheckboxUtils } from "../src/checkboxUtils";
|
||||
|
||||
describe("CheckboxUtils", () => {
|
||||
describe("CheckboxUtils old", () => {
|
||||
let checkboxUtilsXOnly: CheckboxUtils;
|
||||
let checkboxUtilsSpaceOnly: CheckboxUtils;
|
||||
|
||||
|
|
@ -11,88 +11,88 @@ describe("CheckboxUtils", () => {
|
|||
|
||||
describe("findCheckboxesLine", () => {
|
||||
test("recognizes valid checkbox lines", () => {
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("- [ ] Task")).not.toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine(" - [ ] Task")).not.toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("- [x] Task")).not.toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("* [ ] Another task")).not.toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("+ [x] Task")).not.toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("1. [ ] Numbered task")).not.toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("1. [x] Numbered task")).not.toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("- [ ] Task")).not.toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine(" - [ ] Task")).not.toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("- [x] Task")).not.toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("* [ ] Another task")).not.toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("+ [x] Task")).not.toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("1. [ ] Numbered task")).not.toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("1. [x] Numbered task")).not.toBeNull();
|
||||
});
|
||||
|
||||
test("correctly handles different checkbox states", () => {
|
||||
const states = [" ", "x", "-", "?", "!", "*", "#", "@", "[", "]", "y", "X", "Y"];
|
||||
|
||||
for (const state of states) {
|
||||
const match = checkboxUtilsXOnly.findCheckboxesLine(`- [${state}] Task`);
|
||||
expect(match).not.toBeNull();
|
||||
expect(match![3]).toBe(state);
|
||||
const lineInfo = checkboxUtilsXOnly.matchCheckboxLine(`- [${state}] Task`);
|
||||
expect(lineInfo).not.toBeNull();
|
||||
expect(lineInfo?.checkChar).toBe(state);
|
||||
}
|
||||
});
|
||||
|
||||
test("correctly handles indentation", () => {
|
||||
const noIndentMatch = checkboxUtilsXOnly.findCheckboxesLine("- [ ] Task");
|
||||
const noIndentMatch = checkboxUtilsXOnly.matchCheckboxLine("- [ ] Task");
|
||||
expect(noIndentMatch).not.toBeNull();
|
||||
expect(noIndentMatch![1]).toBe("");
|
||||
expect(noIndentMatch!.indent).toBe("".length);
|
||||
|
||||
const singleSpaceMatch = checkboxUtilsXOnly.findCheckboxesLine(" - [ ] Task");
|
||||
const singleSpaceMatch = checkboxUtilsXOnly.matchCheckboxLine(" - [ ] Task");
|
||||
expect(singleSpaceMatch).not.toBeNull();
|
||||
expect(singleSpaceMatch![1]).toBe(" ");
|
||||
expect(singleSpaceMatch!.indent).toBe(" ".length);
|
||||
|
||||
const multiSpaceMatch = checkboxUtilsXOnly.findCheckboxesLine(" - [ ] Task");
|
||||
const multiSpaceMatch = checkboxUtilsXOnly.matchCheckboxLine(" - [ ] Task");
|
||||
expect(multiSpaceMatch).not.toBeNull();
|
||||
expect(multiSpaceMatch![1]).toBe(" ");
|
||||
expect(multiSpaceMatch!.indent).toBe(" ".length);
|
||||
|
||||
const tabMatch = checkboxUtilsXOnly.findCheckboxesLine("\t- [ ] Task");
|
||||
const tabMatch = checkboxUtilsXOnly.matchCheckboxLine("\t- [ ] Task");
|
||||
expect(tabMatch).not.toBeNull();
|
||||
expect(tabMatch![1]).toBe("\t");
|
||||
expect(tabMatch!.indent).toBe("\t".length);
|
||||
});
|
||||
|
||||
test("ignores invalid lines", () => {
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("-[ ] Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("*[x] Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("1.[ ] Numbered task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("- [] Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine(" - Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("Just some text")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("1. Some numbered task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("-[ ] Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("*[x] Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("1.[ ] Numbered task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("- [] Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine(" - Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("Just some text")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("1. Some numbered task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("")).toBeNull();
|
||||
});
|
||||
|
||||
test("ignores lines with missing opening or closing brackets", () => {
|
||||
// Missing opening bracket [
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("- ] Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("* ] Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("+ ] Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("1. ] Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("- ] Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("* ] Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("+ ] Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("1. ] Task")).toBeNull();
|
||||
|
||||
// Missing closing bracket ]
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("- [x Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("* [ Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("+ [- Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("1. [? Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("- [x Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("* [ Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("+ [- Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("1. [? Task")).toBeNull();
|
||||
|
||||
// Malformed with spaces
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("- [ x Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.findCheckboxesLine("- x] Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("- [ x Task")).toBeNull();
|
||||
expect(checkboxUtilsXOnly.matchCheckboxLine("- x] Task")).toBeNull();
|
||||
});
|
||||
|
||||
test("extracts list marker correctly", () => {
|
||||
const dashMatch = checkboxUtilsXOnly.findCheckboxesLine("- [ ] Task");
|
||||
const dashMatch = checkboxUtilsXOnly.matchCheckboxLine("- [ ] Task");
|
||||
expect(dashMatch).not.toBeNull();
|
||||
expect(dashMatch![2]).toBe("-");
|
||||
expect(dashMatch!.marker).toBe("-");
|
||||
|
||||
const asteriskMatch = checkboxUtilsXOnly.findCheckboxesLine("* [ ] Task");
|
||||
const asteriskMatch = checkboxUtilsXOnly.matchCheckboxLine("* [ ] Task");
|
||||
expect(asteriskMatch).not.toBeNull();
|
||||
expect(asteriskMatch![2]).toBe("*");
|
||||
expect(asteriskMatch!.marker).toBe("*");
|
||||
|
||||
const plusMatch = checkboxUtilsXOnly.findCheckboxesLine("+ [ ] Task");
|
||||
const plusMatch = checkboxUtilsXOnly.matchCheckboxLine("+ [ ] Task");
|
||||
expect(plusMatch).not.toBeNull();
|
||||
expect(plusMatch![2]).toBe("+");
|
||||
expect(plusMatch!.marker).toBe("+");
|
||||
|
||||
const numberedMatch = checkboxUtilsXOnly.findCheckboxesLine("1. [ ] Task");
|
||||
const numberedMatch = checkboxUtilsXOnly.matchCheckboxLine("1. [ ] Task");
|
||||
expect(numberedMatch).not.toBeNull();
|
||||
expect(numberedMatch![2]).toBe("1.");
|
||||
expect(numberedMatch!.marker).toBe("1.");
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -119,13 +119,15 @@ describe("CheckboxUtils", () => {
|
|||
|
||||
describe("updateSettings", () => {
|
||||
test("updates xOnlyMode setting correctly", () => {
|
||||
const utils = new CheckboxUtils({ xOnlyMode: true });
|
||||
|
||||
const settings = { xOnlyMode: true };
|
||||
const utils = new CheckboxUtils(settings);
|
||||
expect(utils.isCheckedSymbol("-")).toBe(false);
|
||||
|
||||
utils.updateSettings({ xOnlyMode: false });
|
||||
settings.xOnlyMode = false;
|
||||
expect(utils.isCheckedSymbol("-")).toBe(true);
|
||||
|
||||
utils.updateSettings({ xOnlyMode: true });
|
||||
settings.xOnlyMode = true;
|
||||
expect(utils.isCheckedSymbol("-")).toBe(false);
|
||||
});
|
||||
});
|
||||
|
|
@ -134,54 +136,54 @@ describe("CheckboxUtils", () => {
|
|||
test("updates parent checkbox if all children are checked (xOnlyMode)", () => {
|
||||
const text = "- [ ] Parent\n - [x] Child 1\n - [x] Child 2";
|
||||
const expected = "- [x] Parent\n - [x] Child 1\n - [x] Child 2";
|
||||
expect(checkboxUtilsXOnly.syncCheckboxes(text)).toEqual(expected);
|
||||
expect(checkboxUtilsXOnly.propagateStateFromChildren(text)).toEqual(expected);
|
||||
});
|
||||
|
||||
test("updates parent checkbox if all children are checked (spaceOnly)", () => {
|
||||
const text = "- [ ] Parent\n - [?] Child 1\n - [-] Child 2";
|
||||
const expected = "- [x] Parent\n - [?] Child 1\n - [-] Child 2";
|
||||
expect(checkboxUtilsSpaceOnly.syncCheckboxes(text)).toEqual(expected);
|
||||
expect(checkboxUtilsSpaceOnly.propagateStateFromChildren(text)).toEqual(expected);
|
||||
});
|
||||
|
||||
test("unchecks parent if not all children are checked", () => {
|
||||
const text = "- [x] Parent\n - [x] Child 1\n - [ ] Child 2";
|
||||
const expected = "- [ ] Parent\n - [x] Child 1\n - [ ] Child 2";
|
||||
expect(checkboxUtilsXOnly.syncCheckboxes(text)).toEqual(expected);
|
||||
expect(checkboxUtilsXOnly.propagateStateFromChildren(text)).toEqual(expected);
|
||||
});
|
||||
|
||||
test("doesn't change parent if it has no children", () => {
|
||||
const text = "- [x] Single";
|
||||
expect(checkboxUtilsXOnly.syncCheckboxes(text)).toEqual(text);
|
||||
expect(checkboxUtilsXOnly.propagateStateFromChildren(text)).toEqual(text);
|
||||
});
|
||||
|
||||
test("handles multiple levels of nesting correctly", () => {
|
||||
const text = "- [ ] Parent\n - [ ] Child\n - [x] Grandchild";
|
||||
const expected = "- [x] Parent\n - [x] Child\n - [x] Grandchild";
|
||||
expect(checkboxUtilsXOnly.syncCheckboxes(text)).toEqual(expected);
|
||||
expect(checkboxUtilsXOnly.propagateStateFromChildren(text)).toEqual(expected);
|
||||
});
|
||||
|
||||
test("handles mixed indentation levels correctly", () => {
|
||||
const text = "- [ ] Parent\n - [x] Child 1\n - [x] Grandchild 1\n - [x] Child 2";
|
||||
const expected = "- [x] Parent\n - [x] Child 1\n - [x] Grandchild 1\n - [x] Child 2";
|
||||
expect(checkboxUtilsXOnly.syncCheckboxes(text)).toEqual(expected);
|
||||
expect(checkboxUtilsXOnly.propagateStateFromChildren(text)).toEqual(expected);
|
||||
});
|
||||
|
||||
test("handles siblings with different indentation correctly", () => {
|
||||
const text = "- [ ] Parent\n - [x] Child 1\n - [x] Grandchild 1\n - [ ] Child 2";
|
||||
const expected = "- [ ] Parent\n - [x] Child 1\n - [x] Grandchild 1\n - [ ] Child 2";
|
||||
expect(checkboxUtilsXOnly.syncCheckboxes(text)).toEqual(expected);
|
||||
expect(checkboxUtilsXOnly.propagateStateFromChildren(text)).toEqual(expected);
|
||||
});
|
||||
|
||||
test("handles multiple parents correctly", () => {
|
||||
const text = "- [ ] Parent 1\n - [x] Child 1\n- [ ] Parent 2\n - [x] Child 2";
|
||||
const expected = "- [x] Parent 1\n - [x] Child 1\n- [x] Parent 2\n - [x] Child 2";
|
||||
expect(checkboxUtilsXOnly.syncCheckboxes(text)).toEqual(expected);
|
||||
expect(checkboxUtilsXOnly.propagateStateFromChildren(text)).toEqual(expected);
|
||||
});
|
||||
|
||||
test("handles mixed content with non-checkbox lines correctly", () => {
|
||||
const text = "- [ ] Parent\nSome regular text\n - [x] Child 1\nMore text\n - [x] Child 2";
|
||||
const expected = "- [ ] Parent\nSome regular text\n - [x] Child 1\nMore text\n - [x] Child 2";
|
||||
expect(checkboxUtilsXOnly.syncCheckboxes(text)).toEqual(expected);
|
||||
expect(checkboxUtilsXOnly.propagateStateFromChildren(text)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -33,6 +33,9 @@ const config: Config = {
|
|||
|
||||
testEnvironment: "node",
|
||||
|
||||
// Limit the number of workers that run tests in parallel
|
||||
maxWorkers: 1, // <--- Задает последовательное выполнение
|
||||
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
|
|||
|
|
@ -6,9 +6,10 @@
|
|||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"prebuild": "npm test",
|
||||
"prebuild": "npm run test",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json",
|
||||
"test": "jest"
|
||||
"test": "jest",
|
||||
"test:fast": "jest --no-coverage"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
|
|
@ -30,4 +31,4 @@
|
|||
"dependencies": {
|
||||
"async-mutex": "^0.5.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ export class CheckboxSyncPluginSettingTab extends PluginSettingTab {
|
|||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("X-Only Mode")
|
||||
.setDesc("When enabled, only 'x' marks a task as complete. When disabled, any character except space marks a task as complete")
|
||||
|
|
@ -20,8 +20,9 @@ export class CheckboxSyncPluginSettingTab extends PluginSettingTab {
|
|||
toggle
|
||||
.setValue(this.plugin.settings.xOnlyMode)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.xOnlyMode = value;
|
||||
await this.plugin.saveSettings();
|
||||
await this.plugin.updateSettings((settings) => {
|
||||
settings.xOnlyMode = value;
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,27 +13,26 @@ export default class FileStateHolder {
|
|||
this.mutex = new Mutex();
|
||||
}
|
||||
|
||||
async update(file: TFile) {
|
||||
await this.mutex.runExclusive(async () => {
|
||||
const text = await this.vault.read(file);
|
||||
this.set(file, text);
|
||||
});
|
||||
}
|
||||
|
||||
// возвращает понадобилась ли загрузка
|
||||
async updateIfNeeded(file: TFile, text?: string): Promise<boolean> {
|
||||
/**
|
||||
* Initializes the file's data if it hasn't been initialized yet.
|
||||
*
|
||||
* @param file - The file object to initialize.
|
||||
* @param text - Optional text content to associate with the file. If not provided, it will be read from the vault.
|
||||
* @returns A promise that resolves to `true` if the file was initialized, or `false` if it was already initialized.
|
||||
*/
|
||||
async initIfNeeded(file: TFile, text?: string): Promise<boolean> {
|
||||
if (this.has(file)) {
|
||||
return false;
|
||||
return false;
|
||||
}
|
||||
let res = await this.mutex.runExclusive<boolean>(async () => {
|
||||
if (this.has(file)) {
|
||||
return false;
|
||||
}
|
||||
console.log(`updateIfNeeded "${file.name}" start`);
|
||||
if (!text){
|
||||
// console.log(`updateIfNeeded "${file.name}" start`);
|
||||
if (!text) {
|
||||
text = await this.vault.read(file);
|
||||
}
|
||||
this.set(file, text);
|
||||
}
|
||||
this.set(file, text);
|
||||
return true;
|
||||
});
|
||||
return res;
|
||||
|
|
@ -44,11 +43,19 @@ export default class FileStateHolder {
|
|||
}
|
||||
|
||||
set(file: TFile, text: string) {
|
||||
console.log(`File "${file.name}" load to holder`);
|
||||
this.map.set(file, text);
|
||||
if (text !== this.map.get(file)) {
|
||||
console.log(`File "${file.name}" load to holder`);
|
||||
this.map.set(file, text);
|
||||
}
|
||||
}
|
||||
|
||||
get(file: TFile) {
|
||||
return this.map.get(file);
|
||||
}
|
||||
|
||||
getAllFiles(): TFile[] {
|
||||
const keysIterator = this.map.keys();
|
||||
const keysArray = Array.from(keysIterator);
|
||||
return keysArray;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,19 @@
|
|||
import { Mutex } from "async-mutex";
|
||||
import { Editor, EditorChange, MarkdownFileInfo, MarkdownView, TFile } from "obsidian";
|
||||
import CheckboxSyncPlugin from "./main";
|
||||
import { Editor, EditorChange, MarkdownFileInfo, MarkdownView, TFile, Vault } from "obsidian";
|
||||
import { CheckboxUtils } from "./checkboxUtils";
|
||||
import FileStateHolder from "./FileStateHolder";
|
||||
|
||||
export default class SyncController {
|
||||
private plugin: CheckboxSyncPlugin;//delete
|
||||
// private plugin: CheckboxSyncPlugin;//delete
|
||||
private vault: Vault;
|
||||
private checkboxUtils: CheckboxUtils;
|
||||
private fileStateHolder: FileStateHolder;
|
||||
|
||||
private mutex: Mutex;
|
||||
|
||||
constructor(plugin: CheckboxSyncPlugin, checkboxUtils: CheckboxUtils, fileStateHolder: FileStateHolder) {
|
||||
this.plugin = plugin;//delete
|
||||
constructor(vault: Vault, checkboxUtils: CheckboxUtils, fileStateHolder: FileStateHolder) {
|
||||
// this.plugin = plugin;//delete
|
||||
this.vault = vault;
|
||||
this.checkboxUtils = checkboxUtils;
|
||||
this.fileStateHolder = fileStateHolder;
|
||||
this.mutex = new Mutex();
|
||||
|
|
@ -28,15 +29,15 @@ export default class SyncController {
|
|||
const text = editor.getValue();
|
||||
const textBefore = this.fileStateHolder.get(file);
|
||||
|
||||
let newText = this.syncText(text, textBefore);
|
||||
let newText = this.checkboxUtils.syncText(text, textBefore);
|
||||
if (newText === text) {
|
||||
console.log(`sync editor "${file.basename}" stop. new text equals old text.`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.editEditor(editor, text, newText);
|
||||
this.fileStateHolder.set(file, newText);
|
||||
|
||||
this.editEditor(editor, text, newText);
|
||||
|
||||
console.log(`syncEditor "${file.basename}" stop.`);
|
||||
});
|
||||
}
|
||||
|
|
@ -47,87 +48,46 @@ export default class SyncController {
|
|||
}
|
||||
await this.mutex.runExclusive(async () => {
|
||||
console.log(`sync file "${file.basename}" start.`);
|
||||
let text = await this.plugin.app.vault.read(file);
|
||||
let textBefore = this.fileStateHolder.get(file);
|
||||
|
||||
let newText = this.syncText(text, textBefore);
|
||||
|
||||
if (newText === text) {
|
||||
console.log(`sync file "${file.basename}" stop. new text equals old text.`);
|
||||
return;
|
||||
}
|
||||
const newText = await this.vault.process(file, (text) => {
|
||||
let textBefore = this.fileStateHolder.get(file);
|
||||
let newText = this.checkboxUtils.syncText(text, textBefore);
|
||||
return newText;
|
||||
});
|
||||
this.fileStateHolder.set(file, newText);
|
||||
await this.plugin.app.vault.modify(file, newText);
|
||||
console.log(`sync file "${file.basename}" stop.`);
|
||||
});
|
||||
}
|
||||
|
||||
syncText(text: string, textBefore: string | undefined): string {
|
||||
let newText;
|
||||
newText = this.syncTextBefore(text, textBefore);
|
||||
newText = this.checkboxUtils.syncCheckboxes(newText);
|
||||
return newText;
|
||||
}
|
||||
|
||||
syncTextBefore(text: string, textBefore: string | undefined): string {
|
||||
if (!textBefore) return text;
|
||||
const beforeLines = textBefore.split('\n');
|
||||
const textLines = text.split('\n');
|
||||
|
||||
if (beforeLines.length !== textLines.length) return text;
|
||||
|
||||
const diffs = this.findDifferentLineIndexes(beforeLines, textLines);
|
||||
if (diffs.length !== 1) {
|
||||
return text;
|
||||
}
|
||||
return this.checkboxUtils.syncCheckboxesAfterDifferentLine(text, diffs[0]);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
editEditor(editor: Editor, oldText: string, newText: string){
|
||||
private editEditor(editor: Editor, oldText: string, newText: string) {
|
||||
const cursor = editor.getCursor();
|
||||
|
||||
const newLines = newText.split("\n");
|
||||
const oldLines = oldText.split("\n");
|
||||
const newLines = newText.split("\n");
|
||||
const oldLines = oldText.split("\n");
|
||||
|
||||
const diffIndexes = this.findDifferentLineIndexes(oldLines, newLines);
|
||||
const diffIndexes = this.checkboxUtils.findDifferentLineIndexes(oldLines, newLines);
|
||||
|
||||
const changes: EditorChange[] = [];
|
||||
const changes: EditorChange[] = [];
|
||||
|
||||
for (let ind of diffIndexes) {
|
||||
changes.push({
|
||||
from: { line: ind, ch: 0 },
|
||||
to: { line: ind, ch: oldLines[ind].length },
|
||||
text: newLines[ind]
|
||||
});
|
||||
}
|
||||
editor.transaction({
|
||||
changes: changes
|
||||
for (let ind of diffIndexes) {
|
||||
changes.push({
|
||||
from: { line: ind, ch: 0 },
|
||||
to: { line: ind, ch: oldLines[ind].length },
|
||||
text: newLines[ind]
|
||||
});
|
||||
}
|
||||
editor.transaction({
|
||||
changes: changes
|
||||
});
|
||||
|
||||
editor.setCursor(cursor);
|
||||
editor.setCursor(cursor);
|
||||
|
||||
const lastDifferentLineIndex = diffIndexes.length > 0 ? diffIndexes[0] : -1;
|
||||
if (lastDifferentLineIndex != -1) {
|
||||
editor.scrollIntoView({
|
||||
from: { line: lastDifferentLineIndex, ch: 0 },
|
||||
to: { line: lastDifferentLineIndex, ch: 0 }
|
||||
});
|
||||
}
|
||||
const lastDifferentLineIndex = diffIndexes.length > 0 ? diffIndexes[0] : -1;
|
||||
if (lastDifferentLineIndex != -1) {
|
||||
editor.scrollIntoView({
|
||||
from: { line: lastDifferentLineIndex, ch: 0 },
|
||||
to: { line: lastDifferentLineIndex, ch: 0 }
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,67 +1,90 @@
|
|||
import { CheckboxSyncPluginSettings } from "./types";
|
||||
|
||||
export class CheckboxUtils {
|
||||
constructor(private settings: CheckboxSyncPluginSettings) { }
|
||||
export interface CheckboxLineInfo {
|
||||
indent: number;
|
||||
marker: string;
|
||||
checkChar: string;
|
||||
checkboxCharPosition: number;
|
||||
}
|
||||
|
||||
updateSettings(settings: CheckboxSyncPluginSettings) {
|
||||
export class CheckboxUtils {
|
||||
private settings: Readonly<CheckboxSyncPluginSettings>;
|
||||
constructor(settings: Readonly<CheckboxSyncPluginSettings>) {
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
findCheckboxesLine(line: string): RegExpMatchArray | null {
|
||||
return line.match(/^(\s*)([*+-]|\d+\.) \[(.)\]\s/);
|
||||
matchCheckboxLine(line: string): CheckboxLineInfo | null {
|
||||
const match = line.match(/^(\s*)([*+-]|\d+\.) \[(.)\]\s/);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const indent = match[1].length;
|
||||
const marker = match[2];
|
||||
const checkChar = match[3];
|
||||
const checkboxCharPosition = indent + marker.length + 2;
|
||||
|
||||
return {
|
||||
indent,
|
||||
marker,
|
||||
checkChar,
|
||||
checkboxCharPosition
|
||||
};
|
||||
}
|
||||
|
||||
isCheckedSymbol(text: string): boolean {
|
||||
return this.settings.xOnlyMode ? text === "x" : text !== " ";
|
||||
}
|
||||
|
||||
syncCheckboxesAfterDifferentLine(text: string, line: number): string {
|
||||
console.log("syncCheckboxesAfterDifferentLine");
|
||||
updateLineCheckboxStateWithInfo(line: string, shouldBeChecked: boolean, lineInfo: CheckboxLineInfo): string {
|
||||
const newCheckChar = shouldBeChecked ? "x" : " ";
|
||||
const pos = lineInfo.checkboxCharPosition;
|
||||
if (pos >= 0 && pos < line.length) {
|
||||
return line.substring(0, pos) + newCheckChar + line.substring(pos + 1);
|
||||
}
|
||||
console.warn("updateLineCheckboxStateWithInfo: Invalid checkbox position in lineInfo for line:", line);
|
||||
return line;
|
||||
}
|
||||
|
||||
propagateStateToChildren(text: string, parentLine: number): string {
|
||||
// console.log("propagateStateToChildren");
|
||||
|
||||
const lines = text.split("\n");
|
||||
|
||||
const match = this.findCheckboxesLine(lines[line]);
|
||||
if (!match) {
|
||||
console.log(`checkbox not found in line ${line}`)
|
||||
const parentLineInfo = this.matchCheckboxLine(lines[parentLine]);
|
||||
if (!parentLineInfo) {
|
||||
console.log(`checkbox not found in line ${parentLine}`)
|
||||
return text;
|
||||
}
|
||||
|
||||
const indent = match[1].length;
|
||||
const isChecked = this.isCheckedSymbol(match[3]);
|
||||
let j = line + 1;
|
||||
const isChecked = this.isCheckedSymbol(parentLineInfo.checkChar);
|
||||
let j = parentLine + 1;
|
||||
|
||||
while (j < lines.length) {
|
||||
const childMatch = this.findCheckboxesLine(lines[j]);
|
||||
if (!childMatch || childMatch[1].length <= indent) break;
|
||||
const checkboxPos = childMatch[1].length + childMatch[2].length + 2;
|
||||
if (isChecked) {
|
||||
lines[j] = lines[j].substring(0, checkboxPos) + "x" + lines[j].substring(checkboxPos + 1);
|
||||
} else {
|
||||
lines[j] = lines[j].substring(0, checkboxPos) + " " + lines[j].substring(checkboxPos + 1);
|
||||
}
|
||||
const childLineInfo = this.matchCheckboxLine(lines[j]);
|
||||
if (!childLineInfo || childLineInfo.indent <= parentLineInfo.indent) break;
|
||||
lines[j] = this.updateLineCheckboxStateWithInfo(lines[j], isChecked, childLineInfo);
|
||||
j++;
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
syncCheckboxes(text: string): string {
|
||||
propagateStateFromChildren(text: string): string {
|
||||
const lines = text.split("\n");
|
||||
|
||||
for (let i = lines.length - 1; i >= 0; i--) {
|
||||
const match = this.findCheckboxesLine(lines[i]);
|
||||
if (!match) continue;
|
||||
const parentLineInfo = this.matchCheckboxLine(lines[i]);
|
||||
if (!parentLineInfo) continue;
|
||||
|
||||
const indent = match[1].length;
|
||||
const isChecked = this.isCheckedSymbol(match[3]);
|
||||
const isChecked = this.isCheckedSymbol(parentLineInfo.checkChar);
|
||||
let allChildrenChecked = true;
|
||||
let hasChildren = false;
|
||||
let j = i + 1;
|
||||
|
||||
while (j < lines.length) {
|
||||
const childMatch = this.findCheckboxesLine(lines[j]);
|
||||
if (!childMatch || childMatch[1].length <= indent) break;
|
||||
const childLineInfo = this.matchCheckboxLine(lines[j]);
|
||||
if (!childLineInfo || childLineInfo.indent <= parentLineInfo.indent) break;
|
||||
hasChildren = true;
|
||||
const childrenIsChecked = this.isCheckedSymbol(childMatch[3]);
|
||||
const childrenIsChecked = this.isCheckedSymbol(childLineInfo.checkChar);
|
||||
if (!childrenIsChecked) {
|
||||
allChildrenChecked = false;
|
||||
break;
|
||||
|
|
@ -69,15 +92,68 @@ export class CheckboxUtils {
|
|||
j++;
|
||||
}
|
||||
|
||||
if (hasChildren) {
|
||||
const checkboxPos = match[1].length + match[2].length + 2;
|
||||
if (allChildrenChecked && !isChecked) {
|
||||
lines[i] = lines[i].substring(0, checkboxPos) + "x" + lines[i].substring(checkboxPos + 1);
|
||||
} else if (!allChildrenChecked && isChecked) {
|
||||
lines[i] = lines[i].substring(0, checkboxPos) + " " + lines[i].substring(checkboxPos + 1);
|
||||
}
|
||||
if (hasChildren && isChecked !== allChildrenChecked) {
|
||||
lines[i] = this.updateLineCheckboxStateWithInfo(lines[i], allChildrenChecked, parentLineInfo);
|
||||
}
|
||||
}
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
syncText(text: string, textBefore: string | undefined): string {
|
||||
let newText;
|
||||
newText = this.propagateStateToChildrenFromSingleDiff(text, textBefore);
|
||||
newText = this.propagateStateFromChildren(newText);
|
||||
return newText;
|
||||
}
|
||||
|
||||
propagateStateToChildrenFromSingleDiff(text: string, textBefore: string | undefined): string {
|
||||
if (!textBefore) return text;
|
||||
const textBeforeLines = textBefore.split('\n');
|
||||
const textLines = text.split('\n');
|
||||
|
||||
if (textBeforeLines.length !== textLines.length) return text;
|
||||
|
||||
const diffIndexes = this.findDifferentLineIndexes(textBeforeLines, textLines);
|
||||
if (diffIndexes.length !== 1) {
|
||||
return text;
|
||||
}
|
||||
|
||||
const index = diffIndexes[0];
|
||||
|
||||
const lineBefore = textBeforeLines[index];
|
||||
const lineAfter = textLines[index];
|
||||
// Получаем информацию о чекбоксе ДО и ПОСЛЕ изменения
|
||||
const lineInfoBefore = this.matchCheckboxLine(lineBefore);
|
||||
const lineInfoAfter = this.matchCheckboxLine(lineAfter);
|
||||
// Проверяем, что:
|
||||
// 1. Обе строки (до и после) являются валидными строками с чекбоксом.
|
||||
// 2. Отступ и маркер списка не изменились (т.е. структура строки та же).
|
||||
// 3. Изменился именно символ внутри скобок [ ].
|
||||
if (
|
||||
lineInfoBefore && lineInfoAfter &&
|
||||
lineInfoBefore.indent === lineInfoAfter.indent &&
|
||||
lineInfoBefore.marker === lineInfoAfter.marker &&
|
||||
lineInfoBefore.checkChar !== lineInfoAfter.checkChar
|
||||
) {
|
||||
return this.propagateStateToChildren(text, diffIndexes[0]);
|
||||
}
|
||||
return text;
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
34
src/events/FileChangeEventHandler.ts
Normal file
34
src/events/FileChangeEventHandler.ts
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { App, TFile } from "obsidian";
|
||||
import CheckboxSyncPlugin from "src/main";
|
||||
import SyncController from "src/SyncController";
|
||||
|
||||
export class FileChangeEventHandler {
|
||||
private plugin: CheckboxSyncPlugin;
|
||||
private app: App;
|
||||
private syncController: SyncController;
|
||||
|
||||
constructor(plugin: CheckboxSyncPlugin, app: App, syncController: SyncController) {
|
||||
this.plugin = plugin;
|
||||
this.app = app;
|
||||
this.syncController = syncController;
|
||||
}
|
||||
|
||||
registerEvents() {
|
||||
//запуск плагина при модификации файла(для обработки в режиме просмотра)
|
||||
this.plugin.registerEvent(
|
||||
this.app.vault.on("modify", async (file) => {
|
||||
if (!(file instanceof TFile)) return;
|
||||
if (file.extension !== "md") return;
|
||||
console.log(`modify file ${file.path}`);
|
||||
await this.syncController.syncFile(file);
|
||||
})
|
||||
);
|
||||
//запуск плагина при изменении в режиме редактора
|
||||
this.plugin.registerEvent(
|
||||
this.app.workspace.on("editor-change", async (editor, info) => {
|
||||
console.log(`editor-change file ${info.file?.path}`);
|
||||
await this.syncController.syncEditor(editor, info);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,23 @@
|
|||
import { Mutex } from "async-mutex";
|
||||
import { App, MarkdownPostProcessorContext, MarkdownRenderer, MarkdownView, TFile, WorkspaceLeaf } from "obsidian";
|
||||
import CheckboxSyncPlugin from "./main";
|
||||
import SyncController from "./SyncController";
|
||||
import FileStateHolder from "./FileStateHolder";
|
||||
import { text } from "stream/consumers";
|
||||
import FileStateHolder from "src/FileStateHolder";
|
||||
import CheckboxSyncPlugin from "src/main";
|
||||
import SyncController from "src/SyncController";
|
||||
|
||||
export default class EventService {
|
||||
|
||||
export class FileLoadEventHandler {
|
||||
private plugin: CheckboxSyncPlugin;
|
||||
private app: App;
|
||||
private syncController: SyncController;
|
||||
private fileStateHolder: FileStateHolder;
|
||||
private markdownPostProcessorMutex: Mutex;//для более последовательной обработки файлов в постпроцессоре. но вообще это лишь замедляет плагин
|
||||
|
||||
constructor(plugin: CheckboxSyncPlugin, app: App, syncController: SyncController, fileStateHolder: FileStateHolder) {
|
||||
this.plugin = plugin;
|
||||
this.app = app;
|
||||
this.syncController = syncController;
|
||||
this.fileStateHolder = fileStateHolder;
|
||||
|
||||
this.markdownPostProcessorMutex = new Mutex();
|
||||
}
|
||||
|
||||
registerEvents() {
|
||||
|
|
@ -25,44 +26,30 @@ export default class EventService {
|
|||
this.app.workspace.on("file-open", async (file) => {
|
||||
if (file) {
|
||||
console.log(`file-open ${file.path}`);
|
||||
const isUpdate = await this.fileStateHolder.updateIfNeeded(file);
|
||||
const isUpdate = await this.fileStateHolder.initIfNeeded(file);
|
||||
if (isUpdate) {
|
||||
await this.syncController.syncFile(file);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
//запуск плагина при модификации файла(для обработки в режиме просмотра)
|
||||
this.plugin.registerEvent(
|
||||
this.app.vault.on("modify", async (file) => {
|
||||
if (!(file instanceof TFile)) return;
|
||||
if (file.extension !== "md") return;
|
||||
console.log(`modify file ${file.path}`);
|
||||
await this.syncController.syncFile(file);
|
||||
})
|
||||
);
|
||||
//запуск плагина при изменении в режиме редактора
|
||||
this.plugin.registerEvent(
|
||||
this.app.workspace.on("editor-change", async (editor, info) => {
|
||||
console.log(`editor-change file ${info.file?.path}`);
|
||||
await this.syncController.syncEditor(editor, info);
|
||||
})
|
||||
);
|
||||
|
||||
this.plugin.registerMarkdownPostProcessor(async (el: HTMLElement, ctx: MarkdownPostProcessorContext) => {
|
||||
console.log(`MarkdownPostProcessor "${ctx.sourcePath}"`);
|
||||
const file = this.app.vault.getAbstractFileByPath(ctx.sourcePath);
|
||||
if (file instanceof TFile) {
|
||||
const isUpdate = await this.fileStateHolder.updateIfNeeded(file);
|
||||
if (isUpdate) {
|
||||
await this.syncController.syncFile(file);
|
||||
// console.log(`MarkdownPostProcessor "${ctx.sourcePath}"`);
|
||||
await this.markdownPostProcessorMutex.runExclusive(async () => {
|
||||
const file = this.app.vault.getAbstractFileByPath(ctx.sourcePath);
|
||||
if (file instanceof TFile) {
|
||||
const isUpdate = await this.fileStateHolder.initIfNeeded(file);
|
||||
if (isUpdate) {
|
||||
await this.syncController.syncFile(file);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.plugin.registerEvent(
|
||||
this.app.workspace.on('quick-preview', async (file: TFile, data: string) => {
|
||||
const isUpdate = await this.fileStateHolder.updateIfNeeded(file, data);
|
||||
const isUpdate = await this.fileStateHolder.initIfNeeded(file, data);
|
||||
if (isUpdate) {
|
||||
await this.syncController.syncFile(file);
|
||||
}
|
||||
|
|
@ -77,15 +64,12 @@ export default class EventService {
|
|||
//загрузка в кеш и синхронизация открытых файлов
|
||||
async loadAndSyncActiveFiles() {
|
||||
const markdownLeaves = this.app.workspace.getLeavesOfType('markdown');
|
||||
markdownLeaves.forEach(async (leaf: WorkspaceLeaf) => {
|
||||
await this.loadAndSyncActiveFile(leaf);
|
||||
});
|
||||
await Promise.all(markdownLeaves.map(async (leaf) => await this.loadAndSyncActiveFile(leaf)));
|
||||
}
|
||||
|
||||
private async loadAndSyncActiveFile(leaf: WorkspaceLeaf) {
|
||||
const view = leaf.view as MarkdownView;
|
||||
if (!view.file) return;
|
||||
|
||||
//вызов ререндера, для кеширования через MarkdownPostProcessor
|
||||
await this.rerenderView(view);
|
||||
}
|
||||
43
src/main.ts
43
src/main.ts
|
|
@ -1,43 +1,56 @@
|
|||
import { Plugin } from "obsidian";
|
||||
import { Plugin, TFile } from "obsidian";
|
||||
import { CheckboxSyncPluginSettingTab } from "./CheckboxSyncPluginSettingTab";
|
||||
import { CheckboxUtils } from "./checkboxUtils";
|
||||
import EventService from "./EventService";
|
||||
import FileStateHolder from "./FileStateHolder";
|
||||
import SyncController from "./SyncController";
|
||||
import { CheckboxSyncPluginSettings } from "./types";
|
||||
import { FileLoadEventHandler } from "./events/FileLoadEventHandler";
|
||||
import { FileChangeEventHandler } from "./events/FileChangeEventHandler";
|
||||
|
||||
const DEFAULT_SETTINGS: CheckboxSyncPluginSettings = {
|
||||
xOnlyMode: true,
|
||||
};
|
||||
|
||||
export default class CheckboxSyncPlugin extends Plugin {
|
||||
settings: CheckboxSyncPluginSettings;
|
||||
private _settings: CheckboxSyncPluginSettings;
|
||||
|
||||
syncController: SyncController;
|
||||
checkboxUtils: CheckboxUtils;
|
||||
fileStateHolder: FileStateHolder;
|
||||
eventService: EventService;
|
||||
private syncController: SyncController;
|
||||
private checkboxUtils: CheckboxUtils;
|
||||
private fileStateHolder: FileStateHolder;
|
||||
private fileLoadEventHandler: FileLoadEventHandler;
|
||||
private fileChangeEventHandler: FileChangeEventHandler;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
this.fileStateHolder = new FileStateHolder(this.app.vault);
|
||||
this.checkboxUtils = new CheckboxUtils(this.settings);
|
||||
this.syncController = new SyncController(this, this.checkboxUtils, this.fileStateHolder);
|
||||
this.eventService = new EventService(this, this.app, this.syncController, this.fileStateHolder);
|
||||
this.syncController = new SyncController(this.app.vault, this.checkboxUtils, this.fileStateHolder);
|
||||
this.fileLoadEventHandler = new FileLoadEventHandler(this, this.app, this.syncController, this.fileStateHolder);
|
||||
this.fileChangeEventHandler = new FileChangeEventHandler(this, this.app, this.syncController);
|
||||
|
||||
this.addSettingTab(new CheckboxSyncPluginSettingTab(this.app, this));
|
||||
this.eventService.registerEvents();
|
||||
|
||||
this.fileLoadEventHandler.registerEvents();
|
||||
this.fileChangeEventHandler.registerEvents();
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
this._settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
get settings(): Readonly<CheckboxSyncPluginSettings> {
|
||||
return this._settings;
|
||||
}
|
||||
|
||||
async updateSettings(callback: (settings: CheckboxSyncPluginSettings) => void | Promise<void>) {
|
||||
await callback(this._settings);
|
||||
await this.saveData(this.settings);
|
||||
this.checkboxUtils.updateSettings(this.settings);
|
||||
await this.eventService.loadAndSyncActiveFiles();
|
||||
//надо пересинхронизировать все файлы в кеше
|
||||
const allFile = this.fileStateHolder.getAllFiles();
|
||||
await Promise.all(
|
||||
allFile.map(async (file: TFile) => {
|
||||
await this.syncController.syncFile(file);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue