mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
feat(status): extend multi-cycle support to all task switching contexts
- Add multi-cycle status support in ReadModeTextMark with getNextStatusPrimary - Integrate getAllMarks in status-cycler for multi-cycle mark validation - Update status-switcher to use multi-cycle configuration with fallback - Maintain backward compatibility with legacy single-cycle mode - Ensure consistent status cycling behavior across reading and editing views
This commit is contained in:
parent
c82155815e
commit
689419538b
3 changed files with 156 additions and 46 deletions
|
|
@ -3,12 +3,14 @@ import {
|
|||
Component,
|
||||
debounce,
|
||||
MarkdownPostProcessorContext,
|
||||
setIcon,
|
||||
TFile,
|
||||
} from "obsidian";
|
||||
import { getTasksAPI } from "@/utils";
|
||||
import { parseTaskLine } from "@/utils/task/task-operations";
|
||||
import { getTaskStatusConfig } from "@/utils/status-cycle-resolver";
|
||||
import {
|
||||
getTaskStatusConfig,
|
||||
getNextStatusPrimary,
|
||||
} from "@/utils/status-cycle-resolver";
|
||||
|
||||
// This component replaces standard checkboxes with custom text marks in reading view
|
||||
export function applyTaskTextMarks({
|
||||
|
|
@ -207,19 +209,53 @@ class TaskTextMark extends Component {
|
|||
(state) => !excludeMarksFromCycle.includes(state),
|
||||
);
|
||||
|
||||
if (remainingCycle.length === 0) return;
|
||||
let nextMark: string;
|
||||
let nextState: string;
|
||||
let currentState: string;
|
||||
|
||||
// Find current state in cycle
|
||||
let currentState =
|
||||
Object.keys(marks).find(
|
||||
(state) => marks[state] === this.currentMark,
|
||||
) || remainingCycle[0];
|
||||
// Try to use multi-cycle configuration first
|
||||
if (
|
||||
this.plugin.settings.statusCycles &&
|
||||
this.plugin.settings.statusCycles.length > 0
|
||||
) {
|
||||
const nextStatusResult = getNextStatusPrimary(
|
||||
this.currentMark,
|
||||
this.plugin.settings.statusCycles,
|
||||
);
|
||||
|
||||
// Find next state in cycle
|
||||
const currentIndex = remainingCycle.indexOf(currentState);
|
||||
const nextIndex = (currentIndex + 1) % remainingCycle.length;
|
||||
const nextState = remainingCycle[nextIndex];
|
||||
const nextMark = marks[nextState] || " ";
|
||||
if (nextStatusResult) {
|
||||
nextState = nextStatusResult.statusName;
|
||||
nextMark = nextStatusResult.mark;
|
||||
// Find current state for Tasks API check
|
||||
currentState =
|
||||
Object.keys(marks).find(
|
||||
(state) => marks[state] === this.currentMark,
|
||||
) || nextStatusResult.cycle.cycle[0];
|
||||
} else if (remainingCycle.length > 0) {
|
||||
// If not found in multi-cycle, fall back to first status in current cycle
|
||||
currentState = remainingCycle[0];
|
||||
nextState = remainingCycle[0];
|
||||
nextMark = marks[nextState] || " ";
|
||||
} else {
|
||||
// No cycle available
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Fall back to legacy single-cycle logic
|
||||
if (remainingCycle.length === 0) return;
|
||||
|
||||
// Find current state in cycle
|
||||
currentState =
|
||||
Object.keys(marks).find(
|
||||
(state) => marks[state] === this.currentMark,
|
||||
) || remainingCycle[0];
|
||||
|
||||
// Find next state in cycle
|
||||
const currentIndex = remainingCycle.indexOf(currentState);
|
||||
const nextIndex = (currentIndex + 1) % remainingCycle.length;
|
||||
nextState = remainingCycle[nextIndex];
|
||||
nextMark = marks[nextState] || " ";
|
||||
}
|
||||
// Check if next state is DONE and Tasks plugin is available
|
||||
const tasksApi = getTasksAPI(this.plugin);
|
||||
const canToggleWithTasksApi = nextState === "DONE" && !!tasksApi;
|
||||
|
|
@ -342,7 +378,18 @@ class TaskTextMark extends Component {
|
|||
(state) => !excludeMarksFromCycle.includes(state),
|
||||
);
|
||||
|
||||
if (remainingCycle.length === 0) return null;
|
||||
// Only return null if:
|
||||
// 1. No cycle is available (remainingCycle is empty)
|
||||
// 2. AND multi-cycle mode is not enabled
|
||||
if (
|
||||
remainingCycle.length === 0 &&
|
||||
!(
|
||||
this.plugin.settings.statusCycles &&
|
||||
this.plugin.settings.statusCycles.length > 0
|
||||
)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let currentState: string =
|
||||
Object.keys(marks).find((state) => marks[state] === mark) ||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {
|
|||
getNextStatusPrimary,
|
||||
findApplicableCycles,
|
||||
getTaskStatusConfig,
|
||||
getAllMarks,
|
||||
} from "@/utils/status-cycle-resolver";
|
||||
|
||||
/**
|
||||
|
|
@ -59,9 +60,20 @@ function isValidTaskMarkerReplacement(
|
|||
return false;
|
||||
}
|
||||
|
||||
// Get valid task status marks from plugin settings
|
||||
const { marks } = getTaskStatusConfig(plugin.settings);
|
||||
const validMarks = Object.values(marks);
|
||||
// Get valid task status marks from ALL enabled cycles (multi-cycle support)
|
||||
let validMarks: string[];
|
||||
if (
|
||||
plugin.settings.statusCycles &&
|
||||
plugin.settings.statusCycles.length > 0
|
||||
) {
|
||||
// Multi-cycle mode: get all marks from all enabled cycles
|
||||
const allMarksSet = getAllMarks(plugin.settings.statusCycles);
|
||||
validMarks = Array.from(allMarksSet);
|
||||
} else {
|
||||
// Legacy single-cycle mode
|
||||
const { marks } = getTaskStatusConfig(plugin.settings);
|
||||
validMarks = Object.values(marks);
|
||||
}
|
||||
|
||||
// Check if both the original and inserted characters are valid task status marks
|
||||
const isOriginalValidMark =
|
||||
|
|
|
|||
|
|
@ -283,7 +283,7 @@ class TaskStatusWidget extends WidgetType {
|
|||
if (!currentMarkMatch) return;
|
||||
|
||||
// Find the mark for the given status, checking multi-cycle config first
|
||||
let nextMark: string = " "; // Default to space
|
||||
let nextMark = " "; // Default to space
|
||||
|
||||
if (
|
||||
this.plugin.settings.statusCycles &&
|
||||
|
|
@ -364,38 +364,81 @@ class TaskStatusWidget extends WidgetType {
|
|||
(state) => !excludeMarksFromCycle.includes(state),
|
||||
);
|
||||
|
||||
if (remainingCycle.length === 0) {
|
||||
const editor = this.view.state.field(editorInfoField);
|
||||
if (editor) {
|
||||
editor?.editor?.cm?.dispatch({
|
||||
selection: EditorSelection.range(this.to + 1, this.to + 1),
|
||||
});
|
||||
}
|
||||
// If no cycle is available, trigger the default editor:toggle-checklist-status command
|
||||
this.app.commands.executeCommandById(
|
||||
"editor:toggle-checklist-status",
|
||||
let nextMark: string;
|
||||
let nextState: string;
|
||||
|
||||
// Try to use multi-cycle configuration first
|
||||
if (
|
||||
this.plugin.settings.statusCycles &&
|
||||
this.plugin.settings.statusCycles.length > 0
|
||||
) {
|
||||
const nextStatusResult = getNextStatusPrimary(
|
||||
currentMark,
|
||||
this.plugin.settings.statusCycles,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
let currentStateIndex = -1;
|
||||
|
||||
for (let i = 0; i < remainingCycle.length; i++) {
|
||||
const state = remainingCycle[i];
|
||||
if (marks[state] === currentMark) {
|
||||
currentStateIndex = i;
|
||||
break;
|
||||
if (nextStatusResult) {
|
||||
nextState = nextStatusResult.statusName;
|
||||
nextMark = nextStatusResult.mark;
|
||||
} else if (remainingCycle.length > 0) {
|
||||
// If not found in multi-cycle, fall back to first status in current cycle
|
||||
nextState = remainingCycle[0];
|
||||
nextMark = marks[nextState] || " ";
|
||||
} else {
|
||||
// No cycle available, trigger default Obsidian command
|
||||
const editor = this.view.state.field(editorInfoField);
|
||||
if (editor) {
|
||||
editor?.editor?.cm?.dispatch({
|
||||
selection: EditorSelection.range(
|
||||
this.to + 1,
|
||||
this.to + 1,
|
||||
),
|
||||
});
|
||||
}
|
||||
this.app.commands.executeCommandById(
|
||||
"editor:toggle-checklist-status",
|
||||
);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Fall back to legacy single-cycle logic
|
||||
if (remainingCycle.length === 0) {
|
||||
// No cycle available, trigger default Obsidian command
|
||||
const editor = this.view.state.field(editorInfoField);
|
||||
if (editor) {
|
||||
editor?.editor?.cm?.dispatch({
|
||||
selection: EditorSelection.range(
|
||||
this.to + 1,
|
||||
this.to + 1,
|
||||
),
|
||||
});
|
||||
}
|
||||
this.app.commands.executeCommandById(
|
||||
"editor:toggle-checklist-status",
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentStateIndex === -1) {
|
||||
currentStateIndex = 0;
|
||||
}
|
||||
let currentStateIndex = -1;
|
||||
|
||||
// Calculate next state
|
||||
const nextStateIndex = (currentStateIndex + 1) % remainingCycle.length;
|
||||
const nextState = remainingCycle[nextStateIndex];
|
||||
const nextMark = marks[nextState] || " ";
|
||||
for (let i = 0; i < remainingCycle.length; i++) {
|
||||
const state = remainingCycle[i];
|
||||
if (marks[state] === currentMark) {
|
||||
currentStateIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentStateIndex === -1) {
|
||||
currentStateIndex = 0;
|
||||
}
|
||||
|
||||
// Calculate next state
|
||||
const nextStateIndex =
|
||||
(currentStateIndex + 1) % remainingCycle.length;
|
||||
nextState = remainingCycle[nextStateIndex];
|
||||
nextMark = marks[nextState] || " ";
|
||||
}
|
||||
|
||||
// Replace text
|
||||
const newText = currentText.replace(/\[(.)]/, `[${nextMark}]`);
|
||||
|
|
@ -446,9 +489,17 @@ export function taskStatusSwitcherExtension(
|
|||
(state) => !excludeMarksFromCycle.includes(state),
|
||||
);
|
||||
|
||||
// Only skip rendering if:
|
||||
// 1. No cycle is available (remainingCycle is empty)
|
||||
// 2. AND multi-cycle mode is not enabled
|
||||
// 3. AND custom task marks are not enabled
|
||||
if (
|
||||
remainingCycle.length === 0 &&
|
||||
!plugin.settings.enableCustomTaskMarks
|
||||
!plugin.settings.enableCustomTaskMarks &&
|
||||
!(
|
||||
plugin.settings.statusCycles &&
|
||||
plugin.settings.statusCycles.length > 0
|
||||
)
|
||||
)
|
||||
return;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue