Skip to content

Commit 7adb6ab

Browse files
committed
fix: should cycle checkbox when click on checkbox
1 parent 3bd99e3 commit 7adb6ab

11 files changed

Lines changed: 26962 additions & 12637 deletions

File tree

src/components/settings/TaskStatusSettingsTab.ts

Lines changed: 63 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,7 @@ export function renderTaskStatusSettingsTab(
549549
}
550550

551551
// Check Switcher section
552-
new Setting(containerEl).setName(t("Checkbo x Switcher")).setHeading();
552+
new Setting(containerEl).setName(t("Checkbox Switcher")).setHeading();
553553

554554
new Setting(containerEl)
555555
.setName(t("Enable checkbox status switcher"))
@@ -573,40 +573,75 @@ export function renderTaskStatusSettingsTab(
573573

574574
if (settingTab.plugin.settings.enableTaskStatusSwitcher) {
575575
new Setting(containerEl)
576-
.setName(t("Enable custom task marks"))
576+
.setName(t("Task mark display style"))
577577
.setDesc(
578578
t(
579-
"Replace default checkboxes with styled text marks that follow your checkbox status cycle when clicked."
579+
"Choose how task marks are displayed: default checkboxes, custom text marks, or Task Genius icons."
580580
)
581581
)
582-
.addToggle((toggle) => {
583-
toggle
584-
.setValue(settingTab.plugin.settings.enableCustomTaskMarks)
585-
.onChange(async (value) => {
586-
settingTab.plugin.settings.enableCustomTaskMarks =
587-
value;
588-
settingTab.applySettingsUpdate();
589-
});
582+
.addDropdown((dropdown) => {
583+
dropdown.addOption("default", t("Default checkboxes"));
584+
dropdown.addOption("textmarks", t("Custom text marks"));
585+
dropdown.addOption("icons", t("Task Genius icons"));
586+
587+
// Determine current value based on existing settings
588+
let currentValue = "default";
589+
if (settingTab.plugin.settings.enableTaskGeniusIcons) {
590+
currentValue = "icons";
591+
} else if (settingTab.plugin.settings.enableCustomTaskMarks) {
592+
currentValue = "textmarks";
593+
}
594+
595+
dropdown.setValue(currentValue);
596+
597+
dropdown.onChange(async (value) => {
598+
// Reset all options first
599+
settingTab.plugin.settings.enableCustomTaskMarks = false;
600+
settingTab.plugin.settings.enableTaskGeniusIcons = false;
601+
602+
// Set the selected option
603+
if (value === "textmarks") {
604+
settingTab.plugin.settings.enableCustomTaskMarks = true;
605+
} else if (value === "icons") {
606+
settingTab.plugin.settings.enableTaskGeniusIcons = true;
607+
}
608+
609+
settingTab.applySettingsUpdate();
610+
611+
// Update Task Genius Icon Manager
612+
if (settingTab.plugin.taskGeniusIconManager) {
613+
settingTab.plugin.taskGeniusIconManager.update();
614+
}
615+
616+
// Refresh display to show/hide dependent options
617+
setTimeout(() => {
618+
settingTab.display();
619+
}, 200);
620+
});
590621
});
591622

592-
new Setting(containerEl)
593-
.setName(t("Enable text mark in source mode"))
594-
.setDesc(
595-
t(
596-
"Make the text mark in source mode follow the checkbox status cycle when clicked."
597-
)
598-
)
599-
.addToggle((toggle) => {
600-
toggle
601-
.setValue(
602-
settingTab.plugin.settings.enableTextMarkInSourceMode
623+
// Show text mark source mode option only when custom text marks are enabled
624+
if (settingTab.plugin.settings.enableCustomTaskMarks) {
625+
new Setting(containerEl)
626+
.setName(t("Enable text mark in source mode"))
627+
.setDesc(
628+
t(
629+
"Make the text mark in source mode follow the checkbox status cycle when clicked."
603630
)
604-
.onChange(async (value) => {
605-
settingTab.plugin.settings.enableTextMarkInSourceMode =
606-
value;
607-
settingTab.applySettingsUpdate();
608-
});
609-
});
631+
)
632+
.addToggle((toggle) => {
633+
toggle
634+
.setValue(
635+
settingTab.plugin.settings
636+
.enableTextMarkInSourceMode
637+
)
638+
.onChange(async (value) => {
639+
settingTab.plugin.settings.enableTextMarkInSourceMode =
640+
value;
641+
settingTab.applySettingsUpdate();
642+
});
643+
});
644+
}
610645
}
611646

612647
new Setting(containerEl)
@@ -1028,24 +1063,4 @@ export function renderTaskStatusSettingsTab(
10281063
})
10291064
);
10301065
}
1031-
1032-
// Use Task Genius icons
1033-
new Setting(containerEl).setName(t("Other settings")).setHeading();
1034-
1035-
new Setting(containerEl)
1036-
.setName(t("Use Task Genius icons"))
1037-
.setDesc(t("Use Task Genius icons for task statuses"))
1038-
.addToggle((toggle) =>
1039-
toggle
1040-
.setValue(settingTab.plugin.settings.enableTaskGeniusIcons)
1041-
.onChange(async (value) => {
1042-
settingTab.plugin.settings.enableTaskGeniusIcons = value;
1043-
settingTab.applySettingsUpdate();
1044-
1045-
// Update Task Genius Icon Manager
1046-
if (settingTab.plugin.taskGeniusIconManager) {
1047-
settingTab.plugin.taskGeniusIconManager.update();
1048-
}
1049-
})
1050-
);
10511066
}

src/editor-ext/cycleCompleteStatus.ts

Lines changed: 105 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,85 @@ function getTaskStatusConfig(plugin: TaskProgressBarPlugin) {
3838
};
3939
}
4040

41+
/**
42+
* Checks if a replacement operation is a valid task marker replacement
43+
* @param tr The transaction containing selection and change information
44+
* @param fromA Start position of the replacement
45+
* @param toA End position of the replacement
46+
* @param insertedText The text being inserted
47+
* @param originalText The text being replaced
48+
* @param pos The position in the new document
49+
* @param newLineText The full line text after the change
50+
* @param plugin The plugin instance for accessing settings
51+
* @returns true if this is a valid task marker replacement, false otherwise
52+
*/
53+
function isValidTaskMarkerReplacement(
54+
tr: Transaction,
55+
fromA: number,
56+
toA: number,
57+
insertedText: string,
58+
originalText: string,
59+
pos: number,
60+
newLineText: string,
61+
plugin: TaskProgressBarPlugin
62+
): boolean {
63+
// Only single character replacements are considered valid task marker operations
64+
if (toA - fromA !== 1 || insertedText.length !== 1) {
65+
return false;
66+
}
67+
68+
// Check if user actively selected text before replacement
69+
const startSelection = tr.startState.selection.main;
70+
const hasUserSelection = !startSelection.empty;
71+
72+
// If user had a selection that covers the replacement range, this is intentional replacement
73+
if (hasUserSelection && fromA >= startSelection.from && toA <= startSelection.to) {
74+
console.log(
75+
`User selection detected (${startSelection.from}-${startSelection.to}) covering replacement range (${fromA}-${toA}). Skipping automatic cycling as this is user-intended replacement.`
76+
);
77+
return false;
78+
}
79+
80+
// Get valid task status marks from plugin settings
81+
const { marks } = getTaskStatusConfig(plugin);
82+
const validMarks = Object.values(marks);
83+
84+
// Check if both the original and inserted characters are valid task status marks
85+
const isOriginalValidMark = validMarks.includes(originalText) || originalText === ' ';
86+
const isInsertedValidMark = validMarks.includes(insertedText) || insertedText === ' ';
87+
88+
// If either character is not a valid task mark, this is likely manual input
89+
if (!isOriginalValidMark || !isInsertedValidMark) {
90+
return false;
91+
}
92+
93+
// Check if the replacement position is at a task marker location
94+
const taskRegex = /^[\s|\t]*([-*+]|\d+\.)\s+\[(.)]/;
95+
const match = newLineText.match(taskRegex);
96+
97+
if (!match) {
98+
return false;
99+
}
100+
101+
// Log successful validation for debugging
102+
console.log(
103+
`Valid task marker replacement detected. No user selection or selection doesn't cover replacement range. Original: '${originalText}' -> New: '${insertedText}' at position ${fromA}-${toA}`
104+
);
105+
106+
return true;
107+
}
108+
41109
/**
42110
* Finds a task status change event in the transaction
43111
* @param tr The transaction to check
44112
* @param tasksPluginLoaded Whether the Obsidian Tasks plugin is loaded
113+
* @param plugin The plugin instance (optional for backwards compatibility)
45114
* @returns Information about all changed task statuses or empty array if no status was changed
46115
*/
47116
export function findTaskStatusChanges(
48117
tr: Transaction,
49-
tasksPluginLoaded: boolean
118+
tasksPluginLoaded: boolean,
119+
plugin?: TaskProgressBarPlugin
50120
): {
51121
position: number;
52122
currentMark: string;
@@ -257,15 +327,40 @@ export function findTaskStatusChanges(
257327
pos === newLine.from + markIndex &&
258328
insertedText !== "["
259329
) {
260-
// NEW: Check if this is a replacement operation (user selected and replaced text)
261-
// If fromA != toA, it means user deleted existing text and replaced it
262-
// This indicates user has explicit intent for the specific character
263-
// In this case, we should NOT trigger automatic cycling
330+
// Check if this is a replacement operation and validate if it's a valid task marker replacement
264331
if (fromA !== toA) {
265-
console.log(
266-
`Detected replacement operation (fromA=${fromA}, toA=${toA}). User manually input '${insertedText}', skipping automatic cycling.`
267-
);
268-
return; // Skip this change, don't add to taskChanges
332+
const originalText = tr.startState.doc.sliceString(fromA, toA);
333+
334+
// Only perform validation if plugin is provided
335+
if (plugin) {
336+
const isValidReplacement = isValidTaskMarkerReplacement(
337+
tr,
338+
fromA,
339+
toA,
340+
insertedText,
341+
originalText,
342+
pos,
343+
newLineText,
344+
plugin
345+
);
346+
347+
if (!isValidReplacement) {
348+
console.log(
349+
`Detected invalid task marker replacement (fromA=${fromA}, toA=${toA}). User manually input '${insertedText}' (original: '${originalText}'), skipping automatic cycling.`
350+
);
351+
return; // Skip this change, don't add to taskChanges
352+
}
353+
354+
console.log(
355+
`Detected valid task marker replacement (fromA=${fromA}, toA=${toA}). Original: '${originalText}' -> New: '${insertedText}', proceeding with automatic cycling.`
356+
);
357+
} else {
358+
// Fallback to original logic for backwards compatibility
359+
console.log(
360+
`Detected replacement operation (fromA=${fromA}, toA=${toA}). User manually input '${insertedText}', skipping automatic cycling.`
361+
);
362+
return; // Skip this change, don't add to taskChanges
363+
}
269364
}
270365

271366
changedPosition = pos;
@@ -425,7 +520,7 @@ export function handleCycleCompleteStatusTransaction(
425520
}
426521

427522
// Check if any task statuses were changed in this transaction
428-
const taskStatusChanges = findTaskStatusChanges(tr, !!getTasksAPI(plugin));
523+
const taskStatusChanges = findTaskStatusChanges(tr, !!getTasksAPI(plugin), plugin);
429524
if (taskStatusChanges.length === 0) {
430525
return tr;
431526
}

0 commit comments

Comments
 (0)