mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
feat: support custom status
This commit is contained in:
parent
dc35c6a635
commit
1c3df1fc12
17 changed files with 1673 additions and 306 deletions
1
.prettierignore
Normal file
1
.prettierignore
Normal file
|
|
@ -0,0 +1 @@
|
|||
**/*.md
|
||||
|
|
@ -89,12 +89,17 @@ class ProgressBar extends Component {
|
|||
progressBarEl: HTMLSpanElement;
|
||||
progressBackGroundEl: HTMLDivElement;
|
||||
progressEl: HTMLDivElement;
|
||||
inProgressEl: HTMLDivElement;
|
||||
abandonedEl: HTMLDivElement;
|
||||
numberEl: HTMLDivElement;
|
||||
|
||||
plugin: TaskProgressBarPlugin;
|
||||
|
||||
completed: number;
|
||||
total: number;
|
||||
inProgress: number;
|
||||
abandoned: number;
|
||||
notStarted: number;
|
||||
|
||||
group: GroupElement;
|
||||
|
||||
|
|
@ -105,13 +110,23 @@ class ProgressBar extends Component {
|
|||
) {
|
||||
super();
|
||||
this.plugin = plugin;
|
||||
|
||||
this.group = group;
|
||||
this.type === "dataview" && this.updateCompletedAndTotalDataview();
|
||||
this.type === "normal" && this.updateCompletedAndTotal();
|
||||
|
||||
this.completed = 0;
|
||||
this.total = 0;
|
||||
this.inProgress = 0;
|
||||
this.abandoned = 0;
|
||||
this.notStarted = 0;
|
||||
|
||||
if (type === "dataview") {
|
||||
this.updateCompletedAndTotalDataview();
|
||||
} else {
|
||||
this.updateCompletedAndTotal();
|
||||
}
|
||||
|
||||
// Set up event handlers
|
||||
for (let el of this.group.childrenElement) {
|
||||
this.type === "normal" &&
|
||||
if (this.type === "normal") {
|
||||
el.on("click", "input", () => {
|
||||
setTimeout(() => {
|
||||
this.updateCompletedAndTotal();
|
||||
|
|
@ -119,8 +134,7 @@ class ProgressBar extends Component {
|
|||
this.changeNumber();
|
||||
}, 200);
|
||||
});
|
||||
|
||||
this.type === "dataview" &&
|
||||
} else if (this.type === "dataview") {
|
||||
this.registerDomEvent(el, "mousedown", (ev) => {
|
||||
if (!ev.target) return;
|
||||
if ((ev.target as HTMLElement).tagName === "INPUT") {
|
||||
|
|
@ -131,131 +145,314 @@ class ProgressBar extends Component {
|
|||
}, 200);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getTaskStatus(
|
||||
text: string
|
||||
): "completed" | "inProgress" | "abandoned" | "notStarted" {
|
||||
const markMatch = text.match(/\[(.)]/);
|
||||
if (!markMatch || !markMatch[1]) {
|
||||
return "notStarted";
|
||||
}
|
||||
|
||||
const mark = markMatch[1];
|
||||
|
||||
// Priority 1: If useOnlyCountMarks is enabled
|
||||
if (this.plugin?.settings.useOnlyCountMarks) {
|
||||
const onlyCountMarks =
|
||||
this.plugin?.settings.onlyCountTaskMarks.split("|");
|
||||
if (onlyCountMarks.includes(mark)) {
|
||||
return "completed";
|
||||
} else {
|
||||
// If using onlyCountMarks and the mark is not in the list,
|
||||
// determine which other status it belongs to
|
||||
return this.determineNonCompletedStatus(mark);
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 2: If the mark is in excludeTaskMarks
|
||||
if (
|
||||
this.plugin?.settings.excludeTaskMarks &&
|
||||
this.plugin.settings.excludeTaskMarks.includes(mark)
|
||||
) {
|
||||
// Excluded marks are considered not started
|
||||
return "notStarted";
|
||||
}
|
||||
|
||||
// Priority 3: Check against specific task statuses
|
||||
return this.determineTaskStatus(mark);
|
||||
}
|
||||
|
||||
determineNonCompletedStatus(
|
||||
mark: string
|
||||
): "inProgress" | "abandoned" | "notStarted" {
|
||||
const inProgressMarks =
|
||||
this.plugin?.settings.taskStatuses.inProgress.split("|");
|
||||
if (inProgressMarks.includes(mark)) {
|
||||
return "inProgress";
|
||||
}
|
||||
|
||||
const abandonedMarks =
|
||||
this.plugin?.settings.taskStatuses.abandoned.split("|");
|
||||
if (abandonedMarks.includes(mark)) {
|
||||
return "abandoned";
|
||||
}
|
||||
|
||||
return "notStarted";
|
||||
}
|
||||
|
||||
determineTaskStatus(
|
||||
mark: string
|
||||
): "completed" | "inProgress" | "abandoned" | "notStarted" {
|
||||
const completedMarks =
|
||||
this.plugin?.settings.taskStatuses.completed.split("|");
|
||||
if (completedMarks.includes(mark)) {
|
||||
return "completed";
|
||||
}
|
||||
|
||||
const inProgressMarks =
|
||||
this.plugin?.settings.taskStatuses.inProgress.split("|");
|
||||
if (inProgressMarks.includes(mark)) {
|
||||
return "inProgress";
|
||||
}
|
||||
|
||||
const abandonedMarks =
|
||||
this.plugin?.settings.taskStatuses.abandoned.split("|");
|
||||
if (abandonedMarks.includes(mark)) {
|
||||
return "abandoned";
|
||||
}
|
||||
|
||||
// If not matching any specific status, check if it's a not-started mark
|
||||
const notStartedMarks =
|
||||
this.plugin?.settings.taskStatuses.notStarted.split("|");
|
||||
if (notStartedMarks.includes(mark)) {
|
||||
return "notStarted";
|
||||
}
|
||||
|
||||
// Default fallback - any unrecognized mark is considered not started
|
||||
return "notStarted";
|
||||
}
|
||||
|
||||
isCompletedTask(text: string): boolean {
|
||||
const markMatch = text.match(/\[(.)]/);
|
||||
if (!markMatch || !markMatch[1]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const mark = markMatch[1];
|
||||
|
||||
// Priority 1: If useOnlyCountMarks is enabled, only count tasks with specified marks
|
||||
if (this.plugin?.settings.useOnlyCountMarks) {
|
||||
const onlyCountMarks =
|
||||
this.plugin?.settings.onlyCountTaskMarks.split("|");
|
||||
return onlyCountMarks.includes(mark);
|
||||
}
|
||||
|
||||
// Priority 2: If the mark is in excludeTaskMarks, don't count it
|
||||
if (
|
||||
this.plugin?.settings.excludeTaskMarks &&
|
||||
this.plugin.settings.excludeTaskMarks.includes(mark)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Priority 3: Check against the task statuses
|
||||
// We consider a task "completed" if it has a mark from the "completed" status
|
||||
const completedMarks =
|
||||
this.plugin?.settings.taskStatuses.completed.split("|");
|
||||
|
||||
// Return true if the mark is in the completedMarks array
|
||||
return completedMarks.includes(mark);
|
||||
}
|
||||
|
||||
updateCompletedAndTotalDataview() {
|
||||
// Get all task elements
|
||||
const allTasks = this.group.childrenElement;
|
||||
let checked = 0;
|
||||
let completed = 0;
|
||||
let inProgress = 0;
|
||||
let abandoned = 0;
|
||||
let notStarted = 0;
|
||||
let total = 0;
|
||||
|
||||
// Get settings for task markers
|
||||
const excludeMarks = this.plugin?.settings.excludeTaskMarks
|
||||
? this.plugin.settings.excludeTaskMarks.split("")
|
||||
: [];
|
||||
const onlyCountMarks = this.plugin?.settings.onlyCountTaskMarks
|
||||
? this.plugin.settings.onlyCountTaskMarks.split("|")
|
||||
: ["x", "X"];
|
||||
const useOnlyCountMarks = this.plugin?.settings.useOnlyCountMarks;
|
||||
for (let element of this.group.childrenElement) {
|
||||
const checkboxElement = element.querySelector(
|
||||
".task-list-item-checkbox"
|
||||
);
|
||||
if (!checkboxElement) continue;
|
||||
|
||||
// Process each task element
|
||||
for (const el of allTasks) {
|
||||
const taskMark = el.getAttribute("data-task");
|
||||
// For dataview, we need to check the text content
|
||||
const textContent = element.textContent?.trim() || "";
|
||||
|
||||
// Skip if no task mark
|
||||
if (!taskMark) continue;
|
||||
total++;
|
||||
|
||||
// Count total tasks (excluding specified markers)
|
||||
if (taskMark !== " " && !excludeMarks.includes(taskMark)) {
|
||||
total++;
|
||||
}
|
||||
// Extract the task mark
|
||||
const markMatch = textContent.match(/\[(.)]/);
|
||||
if (markMatch && markMatch[1]) {
|
||||
const status = this.getTaskStatus(textContent);
|
||||
|
||||
// Count completed tasks based on settings
|
||||
if (useOnlyCountMarks) {
|
||||
// Only count specific markers
|
||||
if (onlyCountMarks.includes(taskMark)) {
|
||||
checked++;
|
||||
// Count based on status
|
||||
if (this.isCompletedTask(textContent)) {
|
||||
completed++;
|
||||
} else if (status === "inProgress") {
|
||||
inProgress++;
|
||||
} else if (status === "abandoned") {
|
||||
abandoned++;
|
||||
} else if (status === "notStarted") {
|
||||
notStarted++;
|
||||
}
|
||||
} else {
|
||||
// Count all non-space markers (excluding specified markers)
|
||||
if (taskMark !== " " && !excludeMarks.includes(taskMark)) {
|
||||
checked++;
|
||||
// Fallback to checking if the checkbox is checked
|
||||
const checkbox = checkboxElement as HTMLInputElement;
|
||||
if (checkbox.checked) {
|
||||
completed++;
|
||||
} else {
|
||||
notStarted++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.numberEl?.detach();
|
||||
|
||||
this.completed = checked;
|
||||
this.completed = completed;
|
||||
this.inProgress = inProgress;
|
||||
this.abandoned = abandoned;
|
||||
this.notStarted = notStarted;
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
updateCompletedAndTotal() {
|
||||
// Get all task elements
|
||||
const allTasks = this.group.childrenElement;
|
||||
let checked = 0;
|
||||
let total = allTasks.length;
|
||||
let completed = 0;
|
||||
let inProgress = 0;
|
||||
let abandoned = 0;
|
||||
let notStarted = 0;
|
||||
let total = 0;
|
||||
|
||||
// Get settings for task markers
|
||||
const excludeMarks = this.plugin?.settings.excludeTaskMarks
|
||||
? this.plugin.settings.excludeTaskMarks.split("")
|
||||
: [];
|
||||
const onlyCountMarks = this.plugin?.settings.onlyCountTaskMarks
|
||||
? this.plugin.settings.onlyCountTaskMarks.split("|")
|
||||
: ["x", "X"];
|
||||
const useOnlyCountMarks = this.plugin?.settings.useOnlyCountMarks;
|
||||
for (let element of this.group.childrenElement) {
|
||||
const checkboxElement = element.querySelector(
|
||||
".task-list-item-checkbox"
|
||||
);
|
||||
if (!checkboxElement) continue;
|
||||
|
||||
// In normal mode, we can only distinguish between checked and unchecked
|
||||
// So we'll only implement the "only count specific markers" feature
|
||||
// The exclude feature is not applicable here since we can't determine the specific marker
|
||||
const checkbox = checkboxElement as HTMLInputElement;
|
||||
const textContent = element.textContent?.trim() || "";
|
||||
|
||||
if (useOnlyCountMarks) {
|
||||
// For "only count specific markers" we need to check if the default checkbox is checked
|
||||
// This is a limitation since we can't determine the specific marker in normal mode
|
||||
checked = allTasks.filter((el) => el.hasClass("is-checked")).length;
|
||||
} else {
|
||||
checked = allTasks.filter((el) => el.hasClass("is-checked")).length;
|
||||
// Extract the task mark
|
||||
const markMatch = textContent.match(/\[(.)]/);
|
||||
|
||||
total++;
|
||||
|
||||
if (markMatch && markMatch[1]) {
|
||||
const status = this.getTaskStatus(textContent);
|
||||
|
||||
// Count based on status
|
||||
if (this.isCompletedTask(textContent)) {
|
||||
completed++;
|
||||
} else if (status === "inProgress") {
|
||||
inProgress++;
|
||||
} else if (status === "abandoned") {
|
||||
abandoned++;
|
||||
} else if (status === "notStarted") {
|
||||
notStarted++;
|
||||
}
|
||||
} else if (checkbox.checked) {
|
||||
completed++;
|
||||
}
|
||||
}
|
||||
|
||||
this.numberEl?.detach();
|
||||
|
||||
this.completed = checked;
|
||||
this.completed = completed;
|
||||
this.inProgress = inProgress;
|
||||
this.abandoned = abandoned;
|
||||
this.notStarted = notStarted;
|
||||
this.total = total;
|
||||
}
|
||||
|
||||
changePercentage() {
|
||||
const percentage =
|
||||
if (this.total === 0) return;
|
||||
|
||||
const completedPercentage =
|
||||
Math.round((this.completed / this.total) * 10000) / 100;
|
||||
this.progressEl.style.width = percentage + "%";
|
||||
const inProgressPercentage =
|
||||
Math.round((this.inProgress / this.total) * 10000) / 100;
|
||||
const abandonedPercentage =
|
||||
Math.round((this.abandoned / this.total) * 10000) / 100;
|
||||
|
||||
// Set the completed part
|
||||
this.progressEl.style.width = completedPercentage + "%";
|
||||
|
||||
// Set the in-progress part (if it exists)
|
||||
if (this.inProgressEl) {
|
||||
this.inProgressEl.style.width = inProgressPercentage + "%";
|
||||
this.inProgressEl.style.left = completedPercentage + "%";
|
||||
}
|
||||
|
||||
// Set the abandoned part (if it exists)
|
||||
if (this.abandonedEl) {
|
||||
this.abandonedEl.style.width = abandonedPercentage + "%";
|
||||
this.abandonedEl.style.left =
|
||||
completedPercentage + inProgressPercentage + "%";
|
||||
}
|
||||
|
||||
// Update the class based on progress percentage
|
||||
let progressClass = "progress-bar-inline";
|
||||
|
||||
switch (true) {
|
||||
case percentage >= 0 && percentage < 25:
|
||||
this.progressEl.className =
|
||||
"progress-bar-inline progress-bar-inline-0";
|
||||
case completedPercentage === 0:
|
||||
progressClass += " progress-bar-inline-empty";
|
||||
break;
|
||||
case percentage >= 25 && percentage < 50:
|
||||
this.progressEl.className =
|
||||
"progress-bar-inline progress-bar-inline-1";
|
||||
case completedPercentage > 0 && completedPercentage < 25:
|
||||
progressClass += " progress-bar-inline-0";
|
||||
break;
|
||||
case percentage >= 50 && percentage < 75:
|
||||
this.progressEl.className =
|
||||
"progress-bar-inline progress-bar-inline-2";
|
||||
case completedPercentage >= 25 && completedPercentage < 50:
|
||||
progressClass += " progress-bar-inline-1";
|
||||
break;
|
||||
case percentage >= 75 && percentage < 100:
|
||||
this.progressEl.className =
|
||||
"progress-bar-inline progress-bar-inline-3";
|
||||
case completedPercentage >= 50 && completedPercentage < 75:
|
||||
progressClass += " progress-bar-inline-2";
|
||||
break;
|
||||
case percentage >= 100:
|
||||
this.progressEl.className =
|
||||
"progress-bar-inline progress-bar-inline-4";
|
||||
case completedPercentage >= 75 && completedPercentage < 100:
|
||||
progressClass += " progress-bar-inline-3";
|
||||
break;
|
||||
case completedPercentage >= 100:
|
||||
progressClass += " progress-bar-inline-complete";
|
||||
break;
|
||||
}
|
||||
|
||||
// Add classes for special states
|
||||
if (inProgressPercentage > 0) {
|
||||
progressClass += " has-in-progress";
|
||||
}
|
||||
if (abandonedPercentage > 0) {
|
||||
progressClass += " has-abandoned";
|
||||
}
|
||||
|
||||
this.progressEl.className = progressClass;
|
||||
}
|
||||
|
||||
changeNumber() {
|
||||
if (this.plugin?.settings.addNumberToProgressBar) {
|
||||
const text = this.plugin?.settings.showPercentage
|
||||
? `${Math.round((this.completed / this.total) * 10000) / 100}%`
|
||||
: `[${this.completed}/${this.total}]`;
|
||||
let text;
|
||||
if (this.plugin?.settings.showPercentage) {
|
||||
// Calculate percentage of completed tasks
|
||||
const percentage =
|
||||
Math.round((this.completed / this.total) * 10000) / 100;
|
||||
text = `${percentage}%`;
|
||||
} else {
|
||||
// Show detailed counts if we have in-progress or abandoned tasks
|
||||
if (this.inProgress > 0 || this.abandoned > 0) {
|
||||
text = `[${this.completed}✓ ${this.inProgress}⟳ ${this.abandoned}✗ / ${this.total}]`;
|
||||
} else {
|
||||
text = `[${this.completed}/${this.total}]`;
|
||||
}
|
||||
}
|
||||
|
||||
this.numberEl = this.progressBarEl.createEl("div", {
|
||||
cls: "progress-status",
|
||||
text: text,
|
||||
});
|
||||
|
||||
return;
|
||||
if (!this.numberEl) {
|
||||
this.numberEl = this.progressBarEl.createEl("div", {
|
||||
cls: "progress-status",
|
||||
text: text,
|
||||
});
|
||||
} else {
|
||||
this.numberEl.innerText = text;
|
||||
}
|
||||
} else if (this.numberEl) {
|
||||
this.numberEl.innerText = `[${this.completed}/${this.total}]`;
|
||||
}
|
||||
this.numberEl.innerText = `[${this.completed}/${this.total}]`;
|
||||
}
|
||||
|
||||
onload() {
|
||||
|
|
@ -267,12 +464,39 @@ class ProgressBar extends Component {
|
|||
this.progressBackGroundEl = this.progressBarEl.createEl("div", {
|
||||
cls: "progress-bar-inline-background",
|
||||
});
|
||||
this.progressEl = this.progressBackGroundEl.createEl("div");
|
||||
|
||||
// Create elements for each status type
|
||||
this.progressEl = this.progressBackGroundEl.createEl("div", {
|
||||
cls: "progress-bar-inline progress-completed",
|
||||
});
|
||||
|
||||
// Only create these elements if we have tasks of these types
|
||||
if (this.inProgress > 0) {
|
||||
this.inProgressEl = this.progressBackGroundEl.createEl("div", {
|
||||
cls: "progress-bar-inline progress-in-progress",
|
||||
});
|
||||
}
|
||||
|
||||
if (this.abandoned > 0) {
|
||||
this.abandonedEl = this.progressBackGroundEl.createEl("div", {
|
||||
cls: "progress-bar-inline progress-abandoned",
|
||||
});
|
||||
}
|
||||
|
||||
if (this.plugin?.settings.addNumberToProgressBar && this.total) {
|
||||
const text = this.plugin?.settings.showPercentage
|
||||
? `${Math.round((this.completed / this.total) * 10000) / 100}%`
|
||||
: `[${this.completed}/${this.total}]`;
|
||||
let text;
|
||||
if (this.plugin?.settings.showPercentage) {
|
||||
const percentage =
|
||||
Math.round((this.completed / this.total) * 10000) / 100;
|
||||
text = `${percentage}%`;
|
||||
} else {
|
||||
// Show detailed counts if we have in-progress or abandoned tasks
|
||||
if (this.inProgress > 0 || this.abandoned > 0) {
|
||||
text = `[${this.completed}✓ ${this.inProgress}⟳ ${this.abandoned}✗ / ${this.total}]`;
|
||||
} else {
|
||||
text = `[${this.completed}/${this.total}]`;
|
||||
}
|
||||
}
|
||||
|
||||
this.numberEl = this.progressBarEl.createEl("div", {
|
||||
cls: "progress-status",
|
||||
|
|
|
|||
42
src/task-status/AnuPpuccinThemeCollection.ts
Normal file
42
src/task-status/AnuPpuccinThemeCollection.ts
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
// Code from https://github.com/obsidian-tasks-group/obsidian-tasks/tree/main/src/Config/Themes
|
||||
// Original code is licensed under the MIT License.
|
||||
|
||||
import type { StatusCollection } from "./StatusCollections";
|
||||
|
||||
/**
|
||||
* Status supported by the AnuPpuccin theme. {@link https://github.com/AnubisNekhet/AnuPpuccin}
|
||||
* @see {@link StatusSettings.bulkAddStatusCollection}
|
||||
*/
|
||||
export function anuppuccinSupportedStatuses() {
|
||||
const zzz: StatusCollection = [
|
||||
[" ", "Unchecked", "notStarted"],
|
||||
["x", "Checked", "completed"],
|
||||
[">", "Rescheduled", "planned"],
|
||||
["<", "Scheduled", "planned"],
|
||||
["!", "Important", "notStarted"],
|
||||
["-", "Cancelled", "abandoned"],
|
||||
["/", "In Progress", "inProgress"],
|
||||
["?", "Question", "notStarted"],
|
||||
["*", "Star", "notStarted"],
|
||||
["n", "Note", "notStarted"],
|
||||
["l", "Location", "notStarted"],
|
||||
["i", "Information", "notStarted"],
|
||||
["I", "Idea", "notStarted"],
|
||||
["S", "Amount", "notStarted"],
|
||||
["p", "Pro", "notStarted"],
|
||||
["c", "Con", "notStarted"],
|
||||
["b", "Bookmark", "notStarted"],
|
||||
['"', "Quote", "notStarted"],
|
||||
["0", "Speech bubble 0", "notStarted"],
|
||||
["1", "Speech bubble 1", "notStarted"],
|
||||
["2", "Speech bubble 2", "notStarted"],
|
||||
["3", "Speech bubble 3", "notStarted"],
|
||||
["4", "Speech bubble 4", "notStarted"],
|
||||
["5", "Speech bubble 5", "notStarted"],
|
||||
["6", "Speech bubble 6", "notStarted"],
|
||||
["7", "Speech bubble 7", "notStarted"],
|
||||
["8", "Speech bubble 8", "notStarted"],
|
||||
["9", "Speech bubble 9", "notStarted"],
|
||||
];
|
||||
return zzz;
|
||||
}
|
||||
33
src/task-status/AuraThemeCollection.ts
Normal file
33
src/task-status/AuraThemeCollection.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Code from https://github.com/obsidian-tasks-group/obsidian-tasks/tree/main/src/Config/Themes
|
||||
// Original code is licensed under the MIT License.
|
||||
|
||||
import type { StatusCollection } from "./StatusCollections";
|
||||
|
||||
/**
|
||||
* Status supported by the Aura theme. {@link https://github.com/ashwinjadhav818/obsidian-aura}
|
||||
* @see {@link StatusSettings.bulkAddStatusCollection}
|
||||
*/
|
||||
export function auraSupportedStatuses() {
|
||||
const zzz: StatusCollection = [
|
||||
[" ", "incomplete", "notStarted"],
|
||||
["x", "complete / done", "completed"],
|
||||
["-", "cancelled", "abandoned"],
|
||||
[">", "deferred", "planned"],
|
||||
["/", "in progress, or half-done", "inProgress"],
|
||||
["!", "Important", "notStarted"],
|
||||
["?", "question", "notStarted"],
|
||||
["R", "review", "notStarted"],
|
||||
["+", "Inbox / task that should be processed later", "notStarted"],
|
||||
["b", "bookmark", "notStarted"],
|
||||
["B", "brainstorm", "notStarted"],
|
||||
["D", "deferred or scheduled", "planned"],
|
||||
["I", "Info", "notStarted"],
|
||||
["i", "idea", "notStarted"],
|
||||
["N", "note", "notStarted"],
|
||||
["Q", "quote", "notStarted"],
|
||||
["W", "win / success / reward", "notStarted"],
|
||||
["P", "pro", "notStarted"],
|
||||
["C", "con", "notStarted"],
|
||||
];
|
||||
return zzz;
|
||||
}
|
||||
33
src/task-status/BorderThemeCollection.ts
Normal file
33
src/task-status/BorderThemeCollection.ts
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
// Code from https://github.com/obsidian-tasks-group/obsidian-tasks/tree/main/src/Config/Themes
|
||||
// Original code is licensed under the MIT License.
|
||||
import type { StatusCollection } from "./StatusCollections";
|
||||
|
||||
/**
|
||||
* Statuses supported by the Border theme. {@link https://github.com/Akifyss/obsidian-border?tab=readme-ov-file#alternate-checkboxes}
|
||||
* @see {@link StatusSettings.bulkAddStatusCollection}
|
||||
*/
|
||||
export function borderSupportedStatuses() {
|
||||
const zzz: StatusCollection = [
|
||||
[" ", "To Do", "notStarted"],
|
||||
["/", "In Progress", "inProgress"],
|
||||
["x", "Done", "completed"],
|
||||
["-", "Cancelled", "abandoned"],
|
||||
[">", "Rescheduled", "planned"],
|
||||
["<", "Scheduled", "planned"],
|
||||
["!", "Important", "notStarted"],
|
||||
["?", "Question", "notStarted"],
|
||||
["i", "Infomation", "notStarted"],
|
||||
["S", "Amount", "notStarted"],
|
||||
["*", "Star", "notStarted"],
|
||||
["b", "Bookmark", "notStarted"],
|
||||
["“", "Quote", "notStarted"],
|
||||
["n", "Note", "notStarted"],
|
||||
["l", "Location", "notStarted"],
|
||||
["I", "Idea", "notStarted"],
|
||||
["p", "Pro", "notStarted"],
|
||||
["c", "Con", "notStarted"],
|
||||
["u", "Up", "notStarted"],
|
||||
["d", "Down", "notStarted"],
|
||||
];
|
||||
return zzz;
|
||||
}
|
||||
21
src/task-status/EbullientworksThemeCollection.ts
Normal file
21
src/task-status/EbullientworksThemeCollection.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// Code from https://github.com/obsidian-tasks-group/obsidian-tasks/tree/main/src/Config/Themes
|
||||
// Original code is licensed under the MIT License.
|
||||
import type { StatusCollection } from "./StatusCollections";
|
||||
|
||||
/**
|
||||
* Status supported by the Ebullientworks theme. {@link https://github.com/ebullient/obsidian-theme-ebullientworks}
|
||||
* @see {@link StatusSettings.bulkAddStatusCollection}
|
||||
*/
|
||||
export function ebullientworksSupportedStatuses() {
|
||||
const zzz: StatusCollection = [
|
||||
[" ", "Unchecked", "notStarted"],
|
||||
["x", "Checked", "completed"],
|
||||
["-", "Cancelled", "abandoned"],
|
||||
["/", "In Progress", "inProgress"],
|
||||
[">", "Deferred", "planned"],
|
||||
["!", "Important", "notStarted"],
|
||||
["?", "Question", "planned"],
|
||||
["r", "Review", "notStarted"],
|
||||
];
|
||||
return zzz;
|
||||
}
|
||||
52
src/task-status/ITSThemeCollection.ts
Normal file
52
src/task-status/ITSThemeCollection.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
// Code from https://github.com/obsidian-tasks-group/obsidian-tasks/tree/main/src/Config/Themes
|
||||
// Original code is licensed under the MIT License.
|
||||
|
||||
import type { StatusCollection } from "./StatusCollections";
|
||||
|
||||
/**
|
||||
* Status supported by the ITS theme. {@link https://github.com/SlRvb/Obsidian--ITS-Theme}
|
||||
* Values recognised by Tasks are excluded.
|
||||
* @see {@link StatusSettings.bulkAddStatusCollection}
|
||||
*/
|
||||
export function itsSupportedStatuses() {
|
||||
const zzz: StatusCollection = [
|
||||
[" ", "Unchecked", "notStarted"],
|
||||
["x", "Regular", "completed"],
|
||||
["X", "Checked", "completed"],
|
||||
["-", "Dropped", "abandoned"],
|
||||
[">", "Forward", "planned"],
|
||||
["D", "Date", "notStarted"],
|
||||
["?", "Question", "planned"],
|
||||
["/", "Half Done", "inProgress"],
|
||||
["+", "Add", "notStarted"],
|
||||
["R", "Research", "notStarted"],
|
||||
["!", "Important", "notStarted"],
|
||||
["i", "Idea", "notStarted"],
|
||||
["B", "Brainstorm", "notStarted"],
|
||||
["P", "Pro", "notStarted"],
|
||||
["C", "Con", "notStarted"],
|
||||
["Q", "Quote", "notStarted"],
|
||||
["N", "Note", "notStarted"],
|
||||
["b", "Bookmark", "notStarted"],
|
||||
["I", "Information", "notStarted"],
|
||||
["p", "Paraphrase", "notStarted"],
|
||||
["L", "Location", "notStarted"],
|
||||
["E", "Example", "notStarted"],
|
||||
["A", "Answer", "notStarted"],
|
||||
["r", "Reward", "notStarted"],
|
||||
["c", "Choice", "notStarted"],
|
||||
["d", "Doing", "inProgress"],
|
||||
["T", "Time", "notStarted"],
|
||||
["@", "Character / Person", "notStarted"],
|
||||
["t", "Talk", "notStarted"],
|
||||
["O", "Outline / Plot", "notStarted"],
|
||||
["~", "Conflict", "notStarted"],
|
||||
["W", "World", "notStarted"],
|
||||
["f", "Clue / Find", "notStarted"],
|
||||
["F", "Foreshadow", "notStarted"],
|
||||
["H", "Favorite / Health", "notStarted"],
|
||||
["&", "Symbolism", "notStarted"],
|
||||
["s", "Secret", "notStarted"],
|
||||
];
|
||||
return zzz;
|
||||
}
|
||||
36
src/task-status/LYTModeThemeCollection.ts
Normal file
36
src/task-status/LYTModeThemeCollection.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Code from https://github.com/obsidian-tasks-group/obsidian-tasks/tree/main/src/Config/Themes
|
||||
// Original code is licensed under the MIT License.
|
||||
|
||||
import type { StatusCollection } from './StatusCollections';
|
||||
|
||||
/**
|
||||
* Status supported by the LYT Mode theme. {@link https://github.com/nickmilo/LYT-Mode}
|
||||
* @see {@link StatusSettings.bulkAddStatusCollection}
|
||||
*/
|
||||
export function lytModeSupportedStatuses() {
|
||||
const zzz: StatusCollection = [
|
||||
[' ', 'Unchecked', 'notStarted'],
|
||||
['x', 'Checked', 'completed'],
|
||||
['>', 'Rescheduled', 'planned'],
|
||||
['<', 'Scheduled', 'planned'],
|
||||
['!', 'Important', 'notStarted'],
|
||||
['-', 'Cancelled', 'abandoned'],
|
||||
['/', 'In Progress', 'inProgress'],
|
||||
['?', 'Question', 'notStarted'],
|
||||
['*', 'Star', 'notStarted'],
|
||||
['n', 'Note', 'notStarted'],
|
||||
['l', 'Location', 'notStarted'],
|
||||
['i', 'Information', 'notStarted'],
|
||||
['I', 'Idea', 'notStarted'],
|
||||
['S', 'Amount', 'notStarted'],
|
||||
['p', 'Pro', 'notStarted'],
|
||||
['c', 'Con', 'notStarted'],
|
||||
['b', 'Bookmark', 'notStarted'],
|
||||
['f', 'Fire', 'notStarted'],
|
||||
['k', 'Key', 'notStarted'],
|
||||
['w', 'Win', 'notStarted'],
|
||||
['u', 'Up', 'notStarted'],
|
||||
['d', 'Down', 'notStarted'],
|
||||
];
|
||||
return zzz;
|
||||
}
|
||||
37
src/task-status/MinimalThemeCollection.ts
Normal file
37
src/task-status/MinimalThemeCollection.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// Code from https://github.com/obsidian-tasks-group/obsidian-tasks/tree/main/src/Config/Themes
|
||||
// Original code is licensed under the MIT License.
|
||||
|
||||
import type { StatusCollection } from './StatusCollections';
|
||||
|
||||
/**
|
||||
* Status supported by the Minimal theme. {@link https://github.com/kepano/obsidian-minimal}
|
||||
* Values recognised by Tasks are excluded.
|
||||
* @see {@link StatusSettings.bulkAddStatusCollection}
|
||||
*/
|
||||
export function minimalSupportedStatuses() {
|
||||
const zzz: StatusCollection = [
|
||||
[' ', 'to-do', 'notStarted'],
|
||||
['/', 'incomplete', 'inProgress'],
|
||||
['x', 'done', 'completed'],
|
||||
['-', 'canceled', 'abandoned'],
|
||||
['>', 'forwarded', 'planned'],
|
||||
['<', 'scheduling', 'planned'],
|
||||
['?', 'question', 'notStarted'],
|
||||
['!', 'important', 'notStarted'],
|
||||
['*', 'star', 'notStarted'],
|
||||
['"', 'quote', 'notStarted'],
|
||||
['l', 'location', 'notStarted'],
|
||||
['b', 'bookmark', 'notStarted'],
|
||||
['i', 'information', 'notStarted'],
|
||||
['S', 'savings', 'notStarted'],
|
||||
['I', 'idea', 'notStarted'],
|
||||
['p', 'pros', 'notStarted'],
|
||||
['c', 'cons', 'notStarted'],
|
||||
['f', 'fire', 'notStarted'],
|
||||
['k', 'key', 'notStarted'],
|
||||
['w', 'win', 'notStarted'],
|
||||
['u', 'up', 'notStarted'],
|
||||
['d', 'down', 'notStarted'],
|
||||
];
|
||||
return zzz;
|
||||
}
|
||||
14
src/task-status/StatusCollections.d.ts
vendored
Normal file
14
src/task-status/StatusCollections.d.ts
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// Code from https://github.com/obsidian-tasks-group/obsidian-tasks/tree/main/src/Statuses
|
||||
// Original code is licensed under the MIT License.
|
||||
|
||||
/**
|
||||
* The type used for a single entry in bulk imports of pre-created sets of statuses, such as for Themes or CSS Snippets.
|
||||
* The values are: symbol, name, status type (must be one of the values in {@link StatusType}
|
||||
*/
|
||||
export type StatusCollectionEntry = [string, string, string];
|
||||
|
||||
/**
|
||||
* The type used for bulk imports of pre-created sets of statuses, such as for Themes or CSS Snippets.
|
||||
* See {@link Status.createFromImportedValue}
|
||||
*/
|
||||
export type StatusCollection = Array<StatusCollectionEntry>;
|
||||
38
src/task-status/ThingsThemeCollection.ts
Normal file
38
src/task-status/ThingsThemeCollection.ts
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Code from https://github.com/obsidian-tasks-group/obsidian-tasks/tree/main/src/Config/Themes
|
||||
// Original code is licensed under the MIT License.
|
||||
|
||||
import type { StatusCollection } from './StatusCollections';
|
||||
|
||||
/**
|
||||
* Status supported by the Things theme. {@link https://github.com/colineckert/obsidian-things}
|
||||
* @see {@link StatusSettings.bulkAddStatusCollection}
|
||||
*/
|
||||
export function thingsSupportedStatuses() {
|
||||
const zzz: StatusCollection = [
|
||||
// Basic
|
||||
[' ', 'to-do', 'notStarted'],
|
||||
['/', 'incomplete', 'inProgress'],
|
||||
['x', 'done', 'completed'],
|
||||
['-', 'canceled', 'abandoned'],
|
||||
['>', 'forwarded', 'planned'],
|
||||
['<', 'scheduling', 'planned'],
|
||||
// Extras
|
||||
['?', 'question', 'notStarted'],
|
||||
['!', 'important', 'notStarted'],
|
||||
['*', 'star', 'notStarted'],
|
||||
['"', 'quote', 'notStarted'],
|
||||
['l', 'location', 'notStarted'],
|
||||
['b', 'bookmark', 'notStarted'],
|
||||
['i', 'information', 'notStarted'],
|
||||
['S', 'savings', 'notStarted'],
|
||||
['I', 'idea', 'notStarted'],
|
||||
['p', 'pros', 'notStarted'],
|
||||
['c', 'cons', 'notStarted'],
|
||||
['f', 'fire', 'notStarted'],
|
||||
['k', 'key', 'notStarted'],
|
||||
['w', 'win', 'notStarted'],
|
||||
['u', 'up', 'notStarted'],
|
||||
['d', 'down', 'notStarted'],
|
||||
];
|
||||
return zzz;
|
||||
}
|
||||
22
src/task-status/index.ts
Normal file
22
src/task-status/index.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// Code from https://github.com/obsidian-tasks-group/obsidian-tasks/tree/main/src/Config/Themes
|
||||
// Original code is licensed under the MIT License.
|
||||
|
||||
export * from "./AnuPpuccinThemeCollection";
|
||||
export * from "./AuraThemeCollection";
|
||||
export * from "./BorderThemeCollection";
|
||||
export * from "./EbullientworksThemeCollection";
|
||||
export * from "./ITSThemeCollection";
|
||||
export * from "./LYTModeThemeCollection";
|
||||
export * from "./MinimalThemeCollection";
|
||||
export * from "./ThingsThemeCollection";
|
||||
|
||||
export const allStatusCollections: string[] = [
|
||||
"AnuPpuccin",
|
||||
"Aura",
|
||||
"Border",
|
||||
"Ebullientworks",
|
||||
"ITS",
|
||||
"LYTMode",
|
||||
"Minimal",
|
||||
"Things",
|
||||
];
|
||||
11
src/task-status/readme.md
Normal file
11
src/task-status/readme.md
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# Task Status
|
||||
|
||||
This folder contains the code for the task statuses that are supported by the plugin.
|
||||
|
||||
## Claim
|
||||
|
||||
This code is originally from [obsidian-tasks](https://github.com/obsidian-tasks-group/obsidian-tasks) plugin.
|
||||
|
||||
## License
|
||||
|
||||
This code is licensed under the MIT License.
|
||||
|
|
@ -1,11 +1,100 @@
|
|||
import { App, Editor, Plugin, PluginSettingTab, Setting } from "obsidian";
|
||||
import { taskProgressBarExtension } from "./widget";
|
||||
import {
|
||||
App,
|
||||
debounce,
|
||||
Editor,
|
||||
editorEditorField,
|
||||
editorInfoField,
|
||||
HoverParent,
|
||||
HoverPopover,
|
||||
MarkdownRenderer,
|
||||
Plugin,
|
||||
PluginSettingTab,
|
||||
Setting,
|
||||
} from "obsidian";
|
||||
import { HTMLElementWithView, taskProgressBarExtension } from "./widget";
|
||||
import { updateProgressBarInElement } from "./readModeWidget";
|
||||
import {
|
||||
DEFAULT_SETTINGS,
|
||||
TaskProgressBarSettings,
|
||||
TaskProgressBarSettingTab,
|
||||
} from "./taskProgressBarSetting";
|
||||
import { EditorView } from "@codemirror/view";
|
||||
|
||||
class TaskProgressBarPopover extends HoverPopover {
|
||||
plugin: TaskProgressBarPlugin;
|
||||
data: {
|
||||
completed: string;
|
||||
total: string;
|
||||
inProgress: string;
|
||||
abandoned: string;
|
||||
notStarted: string;
|
||||
planned: string;
|
||||
};
|
||||
|
||||
constructor(
|
||||
plugin: TaskProgressBarPlugin,
|
||||
data: {
|
||||
completed: string;
|
||||
total: string;
|
||||
inProgress: string;
|
||||
abandoned: string;
|
||||
notStarted: string;
|
||||
planned: string;
|
||||
},
|
||||
parent: HoverParent,
|
||||
targetEl: HTMLElement,
|
||||
waitTime: number = 1000
|
||||
) {
|
||||
super(parent, targetEl, waitTime);
|
||||
|
||||
this.hoverEl.toggleClass("task-progress-bar-popover", true);
|
||||
this.plugin = plugin;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
onload(): void {
|
||||
MarkdownRenderer.render(
|
||||
this.plugin.app,
|
||||
`
|
||||
| Status | Count |
|
||||
| --- | --- |
|
||||
| Total | ${this.data.total} |
|
||||
| Completed | ${this.data.completed} |
|
||||
| In Progress | ${this.data.inProgress} |
|
||||
| Abandoned | ${this.data.abandoned} |
|
||||
| Not Started | ${this.data.notStarted} |
|
||||
| Planned | ${this.data.planned} |
|
||||
`,
|
||||
this.hoverEl,
|
||||
"",
|
||||
this.plugin
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const showPopoverWithProgressBar = (
|
||||
plugin: TaskProgressBarPlugin,
|
||||
{
|
||||
progressBar,
|
||||
data,
|
||||
view,
|
||||
}: {
|
||||
progressBar: HTMLElement;
|
||||
data: {
|
||||
completed: string;
|
||||
total: string;
|
||||
inProgress: string;
|
||||
abandoned: string;
|
||||
notStarted: string;
|
||||
planned: string;
|
||||
};
|
||||
view: EditorView;
|
||||
}
|
||||
) => {
|
||||
const editor = view.state.field(editorInfoField);
|
||||
if (!editor) return;
|
||||
new TaskProgressBarPopover(plugin, data, editor, progressBar);
|
||||
};
|
||||
|
||||
export default class TaskProgressBarPlugin extends Plugin {
|
||||
settings: TaskProgressBarSettings;
|
||||
|
|
|
|||
|
|
@ -1,39 +1,61 @@
|
|||
import { App, PluginSettingTab, Setting } from "obsidian";
|
||||
import { App, PluginSettingTab, Setting, Modal } from "obsidian";
|
||||
import TaskProgressBarPlugin from "./taskProgressBarIndex";
|
||||
import { allStatusCollections } from "./task-status";
|
||||
|
||||
export interface TaskProgressBarSettings {
|
||||
addTaskProgressBarToHeading: boolean;
|
||||
addNumberToProgressBar: boolean;
|
||||
showPercentage: boolean;
|
||||
allowAlternateTaskStatus: boolean;
|
||||
alternativeMarks: string;
|
||||
countSubLevel: boolean;
|
||||
hideProgressBarBasedOnConditions: boolean;
|
||||
hideProgressBarTags: string;
|
||||
hideProgressBarFolders: string;
|
||||
hideProgressBarMetadata: string;
|
||||
|
||||
// Task state settings
|
||||
taskStatuses: {
|
||||
completed: string;
|
||||
inProgress: string;
|
||||
abandoned: string;
|
||||
notStarted: string;
|
||||
planned: string;
|
||||
};
|
||||
|
||||
countOtherStatusesAs: string;
|
||||
|
||||
// Control which tasks to count
|
||||
excludeTaskMarks: string;
|
||||
onlyCountTaskMarks: string;
|
||||
useOnlyCountMarks: boolean;
|
||||
onlyCountTaskMarks: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: TaskProgressBarSettings = {
|
||||
addTaskProgressBarToHeading: false,
|
||||
addNumberToProgressBar: false,
|
||||
showPercentage: false,
|
||||
allowAlternateTaskStatus: false,
|
||||
alternativeMarks: "(x|X|-)",
|
||||
countSubLevel: true,
|
||||
hideProgressBarBasedOnConditions: false,
|
||||
hideProgressBarTags: "no-progress-bar",
|
||||
hideProgressBarFolders: "",
|
||||
hideProgressBarMetadata: "hide-progress-bar",
|
||||
|
||||
// Default task statuses
|
||||
taskStatuses: {
|
||||
completed: "x|X",
|
||||
inProgress: ">|/",
|
||||
abandoned: "-",
|
||||
notStarted: " ",
|
||||
planned: "?",
|
||||
},
|
||||
|
||||
countOtherStatusesAs: "notStarted",
|
||||
|
||||
// Control which tasks to count
|
||||
excludeTaskMarks: "",
|
||||
onlyCountTaskMarks: "x|X",
|
||||
useOnlyCountMarks: false,
|
||||
};
|
||||
|
||||
|
||||
export class TaskProgressBarSettingTab extends PluginSettingTab {
|
||||
plugin: TaskProgressBarPlugin;
|
||||
private applyDebounceTimer: number = 0;
|
||||
|
|
@ -87,43 +109,203 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
})
|
||||
);
|
||||
|
||||
// Task Status Settings
|
||||
new Setting(containerEl)
|
||||
.setName("Allow alternate task status")
|
||||
.setName("Task Status Settings")
|
||||
.setDesc(
|
||||
"Toggle this to allow this plugin to treat different tasks mark as completed or uncompleted tasks."
|
||||
"Select a predefined task status collection or customize your own"
|
||||
)
|
||||
.addToggle((toggle) =>
|
||||
toggle
|
||||
.setValue(this.plugin.settings.allowAlternateTaskStatus)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.allowAlternateTaskStatus = value;
|
||||
this.applySettingsUpdate();
|
||||
.setHeading()
|
||||
.addDropdown((dropdown) => {
|
||||
dropdown.addOption("custom", "Custom");
|
||||
for (const statusCollection of allStatusCollections) {
|
||||
dropdown.addOption(statusCollection, statusCollection);
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
this.display();
|
||||
}, 200);
|
||||
// Set default value to custom
|
||||
dropdown.setValue("custom");
|
||||
|
||||
dropdown.onChange(async (value) => {
|
||||
if (value === "custom") {
|
||||
return;
|
||||
}
|
||||
|
||||
// Confirm before applying the theme
|
||||
const modal = new Modal(this.app);
|
||||
modal.titleEl.setText(`Apply ${value} Theme?`);
|
||||
|
||||
const content = modal.contentEl.createDiv();
|
||||
content.setText(
|
||||
`This will override your current task status settings with the ${value} theme. Do you want to continue?`
|
||||
);
|
||||
|
||||
const buttonContainer = modal.contentEl.createDiv();
|
||||
buttonContainer.addClass("modal-button-container");
|
||||
|
||||
const cancelButton = buttonContainer.createEl("button");
|
||||
cancelButton.setText("Cancel");
|
||||
cancelButton.addEventListener("click", () => {
|
||||
dropdown.setValue("custom");
|
||||
modal.close();
|
||||
});
|
||||
|
||||
const confirmButton = buttonContainer.createEl("button");
|
||||
confirmButton.setText("Apply Theme");
|
||||
confirmButton.addClass("mod-cta");
|
||||
confirmButton.addEventListener("click", async () => {
|
||||
modal.close();
|
||||
|
||||
// Apply the selected theme's task statuses
|
||||
try {
|
||||
// Import the function dynamically based on the selected theme
|
||||
const functionName =
|
||||
value.toLowerCase() + "SupportedStatuses";
|
||||
const statusesModule = await import(
|
||||
"./task-status"
|
||||
);
|
||||
|
||||
// Use type assertion for the dynamic function access
|
||||
const getStatuses = (statusesModule as any)[
|
||||
functionName
|
||||
];
|
||||
|
||||
if (typeof getStatuses === "function") {
|
||||
const statuses = getStatuses();
|
||||
|
||||
// Create a map to collect all statuses of each type
|
||||
const statusMap: Record<string, string[]> = {
|
||||
completed: [],
|
||||
inProgress: [],
|
||||
abandoned: [],
|
||||
notStarted: [],
|
||||
planned: [],
|
||||
};
|
||||
|
||||
// Group statuses by their type
|
||||
for (const [symbol, _, type] of statuses) {
|
||||
if (type in statusMap) {
|
||||
statusMap[
|
||||
type as keyof typeof statusMap
|
||||
].push(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the settings with the collected statuses
|
||||
for (const type of Object.keys(
|
||||
this.plugin.settings.taskStatuses
|
||||
)) {
|
||||
if (
|
||||
statusMap[type] &&
|
||||
statusMap[type].length > 0
|
||||
) {
|
||||
(
|
||||
this.plugin.settings
|
||||
.taskStatuses as Record<
|
||||
string,
|
||||
string
|
||||
>
|
||||
)[type] = statusMap[type].join("|");
|
||||
}
|
||||
}
|
||||
|
||||
// Save settings and refresh the display
|
||||
this.applySettingsUpdate();
|
||||
this.display();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Failed to apply task status theme:",
|
||||
error
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
modal.open();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Completed task markers")
|
||||
.setDesc(
|
||||
'Characters in square brackets that represent completed tasks. Example: "x|X"'
|
||||
)
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder(DEFAULT_SETTINGS.taskStatuses.completed)
|
||||
.setValue(this.plugin.settings.taskStatuses.completed)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.taskStatuses.completed =
|
||||
value || DEFAULT_SETTINGS.taskStatuses.completed;
|
||||
this.applySettingsUpdate();
|
||||
})
|
||||
);
|
||||
|
||||
if (this.plugin.settings.allowAlternateTaskStatus) {
|
||||
new Setting(containerEl)
|
||||
.setName("Completed alternative marks")
|
||||
.setDesc('Set completed alternative marks here. Like "x|X|-"')
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder(DEFAULT_SETTINGS.alternativeMarks)
|
||||
.setValue(this.plugin.settings.alternativeMarks)
|
||||
.onChange(async (value) => {
|
||||
if (value.length === 0) {
|
||||
this.plugin.settings.alternativeMarks =
|
||||
DEFAULT_SETTINGS.alternativeMarks;
|
||||
} else {
|
||||
this.plugin.settings.alternativeMarks = value;
|
||||
}
|
||||
this.applySettingsUpdate();
|
||||
})
|
||||
);
|
||||
}
|
||||
new Setting(containerEl)
|
||||
.setName("In progress task markers")
|
||||
.setDesc(
|
||||
'Characters in square brackets that represent tasks in progress. Example: ">|/"'
|
||||
)
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder(DEFAULT_SETTINGS.taskStatuses.inProgress)
|
||||
.setValue(this.plugin.settings.taskStatuses.inProgress)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.taskStatuses.inProgress =
|
||||
value || DEFAULT_SETTINGS.taskStatuses.inProgress;
|
||||
this.applySettingsUpdate();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Abandoned task markers")
|
||||
.setDesc(
|
||||
'Characters in square brackets that represent abandoned tasks. Example: "-"'
|
||||
)
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder(DEFAULT_SETTINGS.taskStatuses.abandoned)
|
||||
.setValue(this.plugin.settings.taskStatuses.abandoned)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.taskStatuses.abandoned =
|
||||
value || DEFAULT_SETTINGS.taskStatuses.abandoned;
|
||||
this.applySettingsUpdate();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Not started task markers")
|
||||
.setDesc(
|
||||
'Characters in square brackets that represent not started tasks. Default is space " "'
|
||||
)
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder(DEFAULT_SETTINGS.taskStatuses.notStarted)
|
||||
.setValue(this.plugin.settings.taskStatuses.notStarted)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.taskStatuses.notStarted =
|
||||
value || DEFAULT_SETTINGS.taskStatuses.notStarted;
|
||||
this.applySettingsUpdate();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Count other statuses as")
|
||||
.setDesc(
|
||||
'Select the status to count other statuses as. Default is "Not Started".'
|
||||
)
|
||||
.addDropdown((dropdown) => {
|
||||
dropdown.addOption("notStarted", "Not Started");
|
||||
dropdown.addOption("abandoned", "Abandoned");
|
||||
dropdown.addOption("planned", "Planned");
|
||||
dropdown.addOption("completed", "Completed");
|
||||
dropdown.addOption("inProgress", "In Progress");
|
||||
});
|
||||
|
||||
// Task Counting Settings
|
||||
new Setting(containerEl)
|
||||
.setName("Task Counting Settings")
|
||||
.setDesc("Toggle this to allow this plugin to count sub tasks.")
|
||||
.setHeading();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Exclude specific task markers")
|
||||
|
|
|
|||
751
src/widget.ts
751
src/widget.ts
|
|
@ -12,12 +12,18 @@ import { EditorState, Range, Text } from "@codemirror/state";
|
|||
// @ts-ignore - This import is necessary but TypeScript can't find it
|
||||
import { foldable, syntaxTree, tokenClassNodeProp } from "@codemirror/language";
|
||||
import { RegExpCursor } from "./regexp-cursor";
|
||||
import TaskProgressBarPlugin from "./taskProgressBarIndex";
|
||||
import TaskProgressBarPlugin, {
|
||||
showPopoverWithProgressBar,
|
||||
} from "./taskProgressBarIndex";
|
||||
import { shouldHideProgressBarInLivePriview } from "./utils";
|
||||
|
||||
interface Tasks {
|
||||
completed: number;
|
||||
total: number;
|
||||
inProgress?: number;
|
||||
abandoned?: number;
|
||||
notStarted?: number;
|
||||
planned?: number;
|
||||
}
|
||||
|
||||
// Type to represent a text range for safe access
|
||||
|
|
@ -26,10 +32,17 @@ interface TextRange {
|
|||
to: number;
|
||||
}
|
||||
|
||||
export interface HTMLElementWithView extends HTMLElement {
|
||||
view: EditorView;
|
||||
}
|
||||
|
||||
class TaskProgressBarWidget extends WidgetType {
|
||||
progressBarEl: HTMLSpanElement;
|
||||
progressBackGroundEl: HTMLDivElement;
|
||||
progressEl: HTMLDivElement;
|
||||
inProgressEl: HTMLDivElement;
|
||||
abandonedEl: HTMLDivElement;
|
||||
plannedEl: HTMLDivElement;
|
||||
numberEl: HTMLDivElement;
|
||||
|
||||
constructor(
|
||||
|
|
@ -39,68 +52,135 @@ class TaskProgressBarWidget extends WidgetType {
|
|||
readonly from: number,
|
||||
readonly to: number,
|
||||
readonly completed: number,
|
||||
readonly total: number
|
||||
readonly total: number,
|
||||
readonly inProgress: number = 0,
|
||||
readonly abandoned: number = 0,
|
||||
readonly notStarted: number = 0,
|
||||
readonly planned: number = 0
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
eq(other: TaskProgressBarWidget) {
|
||||
const markdownView = app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (!markdownView) {
|
||||
return false;
|
||||
}
|
||||
if (this.completed === other.completed && this.total === other.total) {
|
||||
return true;
|
||||
}
|
||||
const editor = markdownView.editor;
|
||||
const offset = editor.offsetToPos(this.from);
|
||||
const originalOffset = editor.offsetToPos(other.from);
|
||||
if (this.completed !== other.completed || this.total !== other.total) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
offset.line === originalOffset.line &&
|
||||
this.from === other.from &&
|
||||
this.to === other.to &&
|
||||
this.inProgress === other.inProgress &&
|
||||
this.abandoned === other.abandoned &&
|
||||
this.notStarted === other.notStarted &&
|
||||
this.planned === other.planned &&
|
||||
this.completed === other.completed &&
|
||||
this.total === other.total
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return other.completed === this.completed && other.total === this.total;
|
||||
return (
|
||||
other.completed === this.completed &&
|
||||
other.total === this.total &&
|
||||
other.inProgress === this.inProgress &&
|
||||
other.abandoned === this.abandoned &&
|
||||
other.notStarted === this.notStarted &&
|
||||
other.planned === this.planned
|
||||
);
|
||||
}
|
||||
|
||||
changePercentage() {
|
||||
const percentage =
|
||||
if (this.total === 0) return;
|
||||
|
||||
const completedPercentage =
|
||||
Math.round((this.completed / this.total) * 10000) / 100;
|
||||
this.progressEl.style.width = percentage + "%";
|
||||
const inProgressPercentage =
|
||||
Math.round((this.inProgress / this.total) * 10000) / 100;
|
||||
const abandonedPercentage =
|
||||
Math.round((this.abandoned / this.total) * 10000) / 100;
|
||||
const plannedPercentage =
|
||||
Math.round((this.planned / this.total) * 10000) / 100;
|
||||
|
||||
// Set the completed part
|
||||
this.progressEl.style.width = completedPercentage + "%";
|
||||
|
||||
// Set the in-progress part (if it exists)
|
||||
if (this.inProgressEl) {
|
||||
this.inProgressEl.style.width = inProgressPercentage + "%";
|
||||
this.inProgressEl.style.left = completedPercentage + "%";
|
||||
}
|
||||
|
||||
// Set the abandoned part (if it exists)
|
||||
if (this.abandonedEl) {
|
||||
this.abandonedEl.style.width = abandonedPercentage + "%";
|
||||
this.abandonedEl.style.left =
|
||||
completedPercentage + inProgressPercentage + "%";
|
||||
}
|
||||
|
||||
// Set the planned part (if it exists)
|
||||
if (this.plannedEl) {
|
||||
this.plannedEl.style.width = plannedPercentage + "%";
|
||||
this.plannedEl.style.left =
|
||||
completedPercentage +
|
||||
inProgressPercentage +
|
||||
abandonedPercentage +
|
||||
"%";
|
||||
}
|
||||
|
||||
// Update the class based on progress percentage
|
||||
// This allows for CSS styling based on progress level
|
||||
let progressClass = "progress-bar-inline";
|
||||
|
||||
switch (true) {
|
||||
case percentage >= 0 && percentage < 25:
|
||||
this.progressEl.className =
|
||||
"progress-bar-inline progress-bar-inline-0";
|
||||
case completedPercentage === 0:
|
||||
progressClass += " progress-bar-inline-empty";
|
||||
break;
|
||||
case percentage >= 25 && percentage < 50:
|
||||
this.progressEl.className =
|
||||
"progress-bar-inline progress-bar-inline-1";
|
||||
case completedPercentage > 0 && completedPercentage < 25:
|
||||
progressClass += " progress-bar-inline-0";
|
||||
break;
|
||||
case percentage >= 50 && percentage < 75:
|
||||
this.progressEl.className =
|
||||
"progress-bar-inline progress-bar-inline-2";
|
||||
case completedPercentage >= 25 && completedPercentage < 50:
|
||||
progressClass += " progress-bar-inline-1";
|
||||
break;
|
||||
case percentage >= 75 && percentage < 100:
|
||||
this.progressEl.className =
|
||||
"progress-bar-inline progress-bar-inline-3";
|
||||
case completedPercentage >= 50 && completedPercentage < 75:
|
||||
progressClass += " progress-bar-inline-2";
|
||||
break;
|
||||
case percentage >= 100:
|
||||
this.progressEl.className =
|
||||
"progress-bar-inline progress-bar-inline-4";
|
||||
case completedPercentage >= 75 && completedPercentage < 100:
|
||||
progressClass += " progress-bar-inline-3";
|
||||
break;
|
||||
case completedPercentage >= 100:
|
||||
progressClass += " progress-bar-inline-complete";
|
||||
break;
|
||||
}
|
||||
|
||||
// Add classes for special states
|
||||
if (inProgressPercentage > 0) {
|
||||
progressClass += " has-in-progress";
|
||||
}
|
||||
if (abandonedPercentage > 0) {
|
||||
progressClass += " has-abandoned";
|
||||
}
|
||||
if (plannedPercentage > 0) {
|
||||
progressClass += " has-planned";
|
||||
}
|
||||
|
||||
this.progressEl.className = progressClass;
|
||||
}
|
||||
|
||||
changeNumber() {
|
||||
if (this.plugin?.settings.addNumberToProgressBar) {
|
||||
const text = this.plugin?.settings.showPercentage
|
||||
? `${Math.round((this.completed / this.total) * 10000) / 100}%`
|
||||
: `[${this.completed}/${this.total}]`;
|
||||
let text;
|
||||
if (this.plugin?.settings.showPercentage) {
|
||||
// Calculate percentage of completed tasks
|
||||
const percentage =
|
||||
Math.round((this.completed / this.total) * 10000) / 100;
|
||||
text = `${percentage}%`;
|
||||
} else {
|
||||
// Show detailed counts if we have in-progress, abandoned, or planned tasks
|
||||
if (
|
||||
this.inProgress > 0 ||
|
||||
this.abandoned > 0 ||
|
||||
this.planned > 0
|
||||
) {
|
||||
text = `[${this.completed}✓ ${this.inProgress}⟳ ${this.abandoned}✗ ${this.planned}? / ${this.total}]`;
|
||||
} else {
|
||||
text = `[${this.completed}/${this.total}]`;
|
||||
}
|
||||
}
|
||||
|
||||
if (!this.numberEl) {
|
||||
this.numberEl = this.progressBarEl.createEl("div", {
|
||||
|
|
@ -109,10 +189,8 @@ class TaskProgressBarWidget extends WidgetType {
|
|||
});
|
||||
} else {
|
||||
this.numberEl.innerText = text;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this.numberEl) {
|
||||
} else if (this.numberEl) {
|
||||
this.numberEl.innerText = `[${this.completed}/${this.total}]`;
|
||||
}
|
||||
}
|
||||
|
|
@ -121,8 +199,9 @@ class TaskProgressBarWidget extends WidgetType {
|
|||
if (
|
||||
!this.plugin?.settings.addNumberToProgressBar &&
|
||||
this.numberEl !== undefined
|
||||
)
|
||||
) {
|
||||
this.numberEl.detach();
|
||||
}
|
||||
|
||||
if (this.progressBarEl !== undefined) {
|
||||
this.changePercentage();
|
||||
|
|
@ -133,17 +212,73 @@ class TaskProgressBarWidget extends WidgetType {
|
|||
this.progressBarEl = createSpan(
|
||||
this.plugin?.settings.addNumberToProgressBar
|
||||
? "cm-task-progress-bar with-number"
|
||||
: "cm-task-progress-bar"
|
||||
: "cm-task-progress-bar",
|
||||
(el) => {
|
||||
el.dataset.completed = this.completed.toString();
|
||||
el.dataset.total = this.total.toString();
|
||||
el.dataset.inProgress = this.inProgress.toString();
|
||||
el.dataset.abandoned = this.abandoned.toString();
|
||||
el.dataset.notStarted = this.notStarted.toString();
|
||||
el.dataset.planned = this.planned.toString();
|
||||
|
||||
el.onmouseover = () => {
|
||||
showPopoverWithProgressBar(this.plugin, {
|
||||
progressBar: el,
|
||||
data: {
|
||||
completed: this.completed.toString(),
|
||||
total: this.total.toString(),
|
||||
inProgress: this.inProgress.toString(),
|
||||
abandoned: this.abandoned.toString(),
|
||||
notStarted: this.notStarted.toString(),
|
||||
planned: this.planned.toString(),
|
||||
},
|
||||
view: this.view,
|
||||
});
|
||||
};
|
||||
}
|
||||
);
|
||||
this.progressBackGroundEl = this.progressBarEl.createEl("div", {
|
||||
cls: "progress-bar-inline-background",
|
||||
});
|
||||
this.progressEl = this.progressBackGroundEl.createEl("div");
|
||||
|
||||
// Create elements for each status type
|
||||
this.progressEl = this.progressBackGroundEl.createEl("div", {
|
||||
cls: "progress-bar-inline progress-completed",
|
||||
});
|
||||
|
||||
// Only create these elements if we have tasks of these types
|
||||
if (this.inProgress > 0) {
|
||||
this.inProgressEl = this.progressBackGroundEl.createEl("div", {
|
||||
cls: "progress-bar-inline progress-in-progress",
|
||||
});
|
||||
}
|
||||
|
||||
if (this.abandoned > 0) {
|
||||
this.abandonedEl = this.progressBackGroundEl.createEl("div", {
|
||||
cls: "progress-bar-inline progress-abandoned",
|
||||
});
|
||||
}
|
||||
|
||||
if (this.planned > 0) {
|
||||
this.plannedEl = this.progressBackGroundEl.createEl("div", {
|
||||
cls: "progress-bar-inline progress-planned",
|
||||
});
|
||||
}
|
||||
|
||||
if (this.plugin?.settings.addNumberToProgressBar && this.total) {
|
||||
const text = this.plugin?.settings.showPercentage
|
||||
? `${Math.round((this.completed / this.total) * 10000) / 100}%`
|
||||
: `[${this.completed}/${this.total}]`;
|
||||
let text;
|
||||
if (this.plugin?.settings.showPercentage) {
|
||||
const percentage =
|
||||
Math.round((this.completed / this.total) * 10000) / 100;
|
||||
text = `${percentage}%`;
|
||||
} else {
|
||||
// Show detailed counts if we have in-progress or abandoned tasks
|
||||
if (this.inProgress > 0 || this.abandoned > 0) {
|
||||
text = `[${this.completed}✓ ${this.inProgress}⟳ ${this.abandoned}✗ / ${this.total}]`;
|
||||
} else {
|
||||
text = `[${this.completed}/${this.total}]`;
|
||||
}
|
||||
}
|
||||
|
||||
this.numberEl = this.progressBarEl.createEl("div", {
|
||||
cls: "progress-status",
|
||||
|
|
@ -279,7 +414,11 @@ export function taskProgressBarExtension(
|
|||
headingLine.to,
|
||||
headingLine.to,
|
||||
tasksNum.completed,
|
||||
tasksNum.total
|
||||
tasksNum.total,
|
||||
tasksNum.inProgress || 0,
|
||||
tasksNum.abandoned || 0,
|
||||
tasksNum.notStarted || 0,
|
||||
tasksNum.planned || 0
|
||||
),
|
||||
});
|
||||
|
||||
|
|
@ -358,7 +497,11 @@ export function taskProgressBarExtension(
|
|||
line.to,
|
||||
line.to,
|
||||
tasksNum.completed,
|
||||
tasksNum.total
|
||||
tasksNum.total,
|
||||
tasksNum.inProgress || 0,
|
||||
tasksNum.abandoned || 0,
|
||||
tasksNum.notStarted || 0,
|
||||
tasksNum.planned || 0
|
||||
),
|
||||
});
|
||||
|
||||
|
|
@ -436,15 +579,48 @@ export function taskProgressBarExtension(
|
|||
level: number = 0,
|
||||
tabSize: number = 4
|
||||
): RegExp {
|
||||
// 获取排除的任务标记
|
||||
// Check if we're using only specific marks for counting
|
||||
if (plugin?.settings.useOnlyCountMarks) {
|
||||
const onlyCountMarks =
|
||||
plugin?.settings.onlyCountTaskMarks || "";
|
||||
// If onlyCountMarks is empty, return a regex that won't match anything
|
||||
if (!onlyCountMarks.trim()) {
|
||||
return new RegExp("^$"); // This won't match any tasks
|
||||
}
|
||||
|
||||
// Include the specified marks and space (for not started tasks)
|
||||
const markPattern = `\\[([ ${onlyCountMarks}])\\]`;
|
||||
|
||||
if (isHeading) {
|
||||
// For headings, we'll still match any task format, but filter by indentation level later
|
||||
return new RegExp(
|
||||
`^[\\t|\\s]*([-*+]|\\d+\\.)\\s${markPattern}`
|
||||
);
|
||||
} else {
|
||||
// If counting sublevels, use a more relaxed regex that matches any indentation
|
||||
if (plugin?.settings.countSubLevel) {
|
||||
return new RegExp(
|
||||
`^[\\t|\\s]*?([-*+]|\\d+\\.)\\s${markPattern}`
|
||||
);
|
||||
} else {
|
||||
// When not counting sublevels, we'll check the actual indentation level separately
|
||||
// So the regex should match tasks at any indentation level
|
||||
return new RegExp(
|
||||
`^[\\t|\\s]*([-*+]|\\d+\\.)\\s${markPattern}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get excluded task marks
|
||||
const excludePattern = plugin?.settings.excludeTaskMarks || "";
|
||||
|
||||
// 构建识别任务标记的正则表达式部分
|
||||
// Build the task marker pattern
|
||||
let markPattern = "\\[(.)\\]";
|
||||
|
||||
// 如果存在排除的标记,修改标记匹配模式
|
||||
// If there are excluded marks, modify the pattern
|
||||
if (excludePattern && excludePattern.length > 0) {
|
||||
// 构建一个不匹配被排除标记的模式
|
||||
// Build a pattern that doesn't match excluded marks
|
||||
const excludeChars = excludePattern
|
||||
.split("")
|
||||
.map((c) => "\\" + c)
|
||||
|
|
@ -453,19 +629,21 @@ export function taskProgressBarExtension(
|
|||
}
|
||||
|
||||
if (isHeading) {
|
||||
return new RegExp(`^([-*+]|\\d+\\.)\\s${markPattern}`);
|
||||
// For headings, we'll still match any task format, but filter by indentation level later
|
||||
return new RegExp(
|
||||
`^[\\t|\\s]*([-*+]|\\d+\\.)\\s${markPattern}`
|
||||
);
|
||||
} else {
|
||||
// 如果是子级计数模式,使用更宽松的正则表达式匹配任何缩进级别的任务
|
||||
// If counting sublevels, use a more relaxed regex
|
||||
if (plugin?.settings.countSubLevel) {
|
||||
return new RegExp(
|
||||
`^[\\t|\\s]*?([-*+]|\\d+\\.)\\s${markPattern}`
|
||||
);
|
||||
} else {
|
||||
// 否则使用精确缩进级别匹配
|
||||
// When not counting sublevels, we'll check the actual indentation level separately
|
||||
// So the regex should match tasks at any indentation level
|
||||
return new RegExp(
|
||||
`^[\\t|\\s]{${
|
||||
tabSize * (level + 1)
|
||||
}}([-*+]|\\d+\\.)\\s${markPattern}`
|
||||
`^[\\t|\\s]*([-*+]|\\d+\\.)\\s${markPattern}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -484,25 +662,30 @@ export function taskProgressBarExtension(
|
|||
const useOnlyCountMarks = plugin?.settings.useOnlyCountMarks;
|
||||
const onlyCountPattern =
|
||||
plugin?.settings.onlyCountTaskMarks || "x|X";
|
||||
const excludePattern = plugin?.settings.excludeTaskMarks || "";
|
||||
const alternativeMarks = plugin?.settings.alternativeMarks;
|
||||
|
||||
// Default patterns - 对子级计数的情况调整缩进匹配
|
||||
// If onlyCountMarks is enabled but the pattern is empty, return a regex that won't match anything
|
||||
if (useOnlyCountMarks && !onlyCountPattern.trim()) {
|
||||
return new RegExp("^$"); // This won't match any tasks
|
||||
}
|
||||
|
||||
const excludePattern = plugin?.settings.excludeTaskMarks || "";
|
||||
const completedMarks =
|
||||
plugin?.settings.taskStatuses?.completed || "x|X";
|
||||
|
||||
// Default patterns - adjust for sublevel counting
|
||||
const basePattern = isHeading
|
||||
? "^([-*+]|\\d+\\.)\\s"
|
||||
? "^[\\t|\\s]*" // For headings, match any indentation (will be filtered later)
|
||||
: plugin?.settings.countSubLevel
|
||||
? "^[\\t|\\s]*?" // 如果是子级计数,使用非贪婪模式匹配任意缩进
|
||||
: "^[\\t|\\s]+";
|
||||
? "^[\\t|\\s]*?" // For sublevel counting, use non-greedy match for any indentation
|
||||
: "^[\\t|\\s]*"; // For no sublevel counting, still match any indentation level
|
||||
|
||||
const bulletPrefix = isHeading
|
||||
? ""
|
||||
? "([-*+]|\\d+\\.)\\s" // For headings, just match the bullet
|
||||
: plugin?.settings.countSubLevel
|
||||
? "([-*+]|\\d+\\.)\\s" // 子级计数时简化前缀
|
||||
: level !== 0
|
||||
? `{${tabSize * (level + 1)}}([-*+]|\\d+\\.)\\s`
|
||||
: "([-*+]|\\d+\\.)\\s";
|
||||
? "([-*+]|\\d+\\.)\\s" // Simplified prefix for sublevel counting
|
||||
: "([-*+]|\\d+\\.)\\s"; // For no sublevel counting, just match the bullet
|
||||
|
||||
// 如果启用了仅统计特定标记功能
|
||||
// If "only count specific marks" is enabled
|
||||
if (useOnlyCountMarks) {
|
||||
return new RegExp(
|
||||
basePattern +
|
||||
|
|
@ -513,53 +696,29 @@ export function taskProgressBarExtension(
|
|||
);
|
||||
}
|
||||
|
||||
// Handle alternative marks option - 仅在未启用仅统计特定标记时才考虑替代标记
|
||||
if (
|
||||
!useOnlyCountMarks &&
|
||||
plugin?.settings.allowAlternateTaskStatus &&
|
||||
alternativeMarks &&
|
||||
alternativeMarks.length > 0
|
||||
) {
|
||||
if (excludePattern) {
|
||||
// Filter alternative marks based on exclusions
|
||||
const alternativeMarksArray = alternativeMarks
|
||||
.replace(/[()]/g, "")
|
||||
.split("|");
|
||||
const excludeMarksArray = excludePattern.split("");
|
||||
const filteredMarks = alternativeMarksArray
|
||||
.filter((mark) => !excludeMarksArray.includes(mark))
|
||||
.join("|");
|
||||
|
||||
return new RegExp(
|
||||
basePattern +
|
||||
bulletPrefix +
|
||||
"\\[(" +
|
||||
filteredMarks +
|
||||
")\\]"
|
||||
);
|
||||
} else {
|
||||
return new RegExp(
|
||||
basePattern +
|
||||
bulletPrefix +
|
||||
"\\[" +
|
||||
alternativeMarks +
|
||||
"\\]"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle standard case - 如果没有启用特殊选项
|
||||
// When using the completed task marks
|
||||
if (excludePattern) {
|
||||
// Filter completed marks based on exclusions
|
||||
const completedMarksArray = completedMarks.split("|");
|
||||
const excludeMarksArray = excludePattern.split("");
|
||||
const filteredMarks = completedMarksArray
|
||||
.filter((mark) => !excludeMarksArray.includes(mark))
|
||||
.join("|");
|
||||
|
||||
return new RegExp(
|
||||
basePattern +
|
||||
bulletPrefix +
|
||||
"\\[[^ " +
|
||||
excludePattern +
|
||||
"]\\]"
|
||||
"\\[(" +
|
||||
filteredMarks +
|
||||
")\\]"
|
||||
);
|
||||
} else {
|
||||
return new RegExp(
|
||||
basePattern + bulletPrefix + "\\[[^ ]\\]"
|
||||
basePattern +
|
||||
bulletPrefix +
|
||||
"\\[(" +
|
||||
completedMarks +
|
||||
")\\]"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -568,59 +727,193 @@ export function taskProgressBarExtension(
|
|||
* Check if a task should be counted as completed
|
||||
*/
|
||||
private isCompletedTask(text: string): boolean {
|
||||
// 如果启用了仅统计特定标记
|
||||
if (plugin?.settings.useOnlyCountMarks) {
|
||||
const onlyCountPattern =
|
||||
plugin?.settings.onlyCountTaskMarks || "x|X";
|
||||
const markMatch = text.match(/\[(.)]/);
|
||||
console.log(markMatch, text);
|
||||
if (markMatch && markMatch[1]) {
|
||||
const mark = markMatch[1];
|
||||
// 检查标记是否在仅统计列表中
|
||||
const onlyCountMarks = onlyCountPattern.split("|");
|
||||
return onlyCountMarks.includes(mark);
|
||||
}
|
||||
const markMatch = text.match(/\[(.)]/);
|
||||
if (!markMatch || !markMatch[1]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 如果启用了替代标记
|
||||
const mark = markMatch[1];
|
||||
|
||||
// Priority 1: If useOnlyCountMarks is enabled, only count tasks with specified marks
|
||||
if (plugin?.settings.useOnlyCountMarks) {
|
||||
const onlyCountMarks =
|
||||
plugin?.settings.onlyCountTaskMarks.split("|");
|
||||
return onlyCountMarks.includes(mark);
|
||||
}
|
||||
|
||||
// Priority 2: If the mark is in excludeTaskMarks, don't count it
|
||||
if (
|
||||
plugin?.settings.allowAlternateTaskStatus &&
|
||||
plugin?.settings.alternativeMarks
|
||||
plugin?.settings.excludeTaskMarks &&
|
||||
plugin.settings.excludeTaskMarks.includes(mark)
|
||||
) {
|
||||
const alternativeMarks = plugin?.settings.alternativeMarks
|
||||
.replace(/[()]/g, "")
|
||||
.split("|");
|
||||
const markMatch = text.match(/\[(.)]/);
|
||||
if (markMatch && markMatch[1]) {
|
||||
const mark = markMatch[1];
|
||||
return alternativeMarks.includes(mark);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 标准检查 - 非空格的字符
|
||||
// Priority 3: Check against the task statuses
|
||||
// We consider a task "completed" if it has a mark from the "completed" status
|
||||
const completedMarks =
|
||||
plugin?.settings.taskStatuses?.completed?.split("|") || [
|
||||
"x",
|
||||
"X",
|
||||
];
|
||||
|
||||
// Return true if the mark is in the completedMarks array
|
||||
return completedMarks.includes(mark);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the task status of a task
|
||||
*/
|
||||
private getTaskStatus(
|
||||
text: string
|
||||
):
|
||||
| "completed"
|
||||
| "inProgress"
|
||||
| "abandoned"
|
||||
| "notStarted"
|
||||
| "planned" {
|
||||
const markMatch = text.match(/\[(.)]/);
|
||||
if (markMatch && markMatch[1]) {
|
||||
const mark = markMatch[1];
|
||||
// 排除需要排除的标记
|
||||
if (
|
||||
plugin?.settings.excludeTaskMarks &&
|
||||
plugin.settings.excludeTaskMarks.includes(mark)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
// 标准完成是非空格
|
||||
return mark !== " ";
|
||||
console.log(markMatch, text);
|
||||
if (!markMatch || !markMatch[1]) {
|
||||
return "notStarted";
|
||||
}
|
||||
|
||||
return false;
|
||||
const mark = markMatch[1];
|
||||
// Priority 1: If useOnlyCountMarks is enabled
|
||||
if (plugin?.settings.useOnlyCountMarks) {
|
||||
const onlyCountMarks =
|
||||
plugin?.settings.onlyCountTaskMarks.split("|");
|
||||
if (onlyCountMarks.includes(mark)) {
|
||||
return "completed";
|
||||
} else {
|
||||
// If using onlyCountMarks and the mark is not in the list,
|
||||
// determine which other status it belongs to
|
||||
return this.determineNonCompletedStatus(mark);
|
||||
}
|
||||
}
|
||||
|
||||
// Priority 2: If the mark is in excludeTaskMarks
|
||||
if (
|
||||
plugin?.settings.excludeTaskMarks &&
|
||||
plugin.settings.excludeTaskMarks.includes(mark)
|
||||
) {
|
||||
// Excluded marks are considered not started
|
||||
return "notStarted";
|
||||
}
|
||||
|
||||
// Priority 3: Check against specific task statuses
|
||||
return this.determineTaskStatus(mark);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to determine the non-completed status of a task mark
|
||||
*/
|
||||
private determineNonCompletedStatus(
|
||||
mark: string
|
||||
): "inProgress" | "abandoned" | "notStarted" | "planned" {
|
||||
const inProgressMarks =
|
||||
plugin?.settings.taskStatuses?.inProgress?.split("|") || [
|
||||
"-",
|
||||
"/",
|
||||
];
|
||||
|
||||
if (inProgressMarks.includes(mark)) {
|
||||
return "inProgress";
|
||||
}
|
||||
|
||||
const abandonedMarks =
|
||||
plugin?.settings.taskStatuses?.abandoned?.split("|") || [
|
||||
">",
|
||||
];
|
||||
if (abandonedMarks.includes(mark)) {
|
||||
return "abandoned";
|
||||
}
|
||||
|
||||
const plannedMarks =
|
||||
plugin?.settings.taskStatuses?.planned?.split("|") || ["?"];
|
||||
if (plannedMarks.includes(mark)) {
|
||||
return "planned";
|
||||
}
|
||||
|
||||
// If the mark doesn't match any specific category, use the countOtherStatusesAs setting
|
||||
return (
|
||||
(plugin?.settings.countOtherStatusesAs as
|
||||
| "inProgress"
|
||||
| "abandoned"
|
||||
| "notStarted"
|
||||
| "planned") || "notStarted"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to determine the specific status of a task mark
|
||||
*/
|
||||
private determineTaskStatus(
|
||||
mark: string
|
||||
):
|
||||
| "completed"
|
||||
| "inProgress"
|
||||
| "abandoned"
|
||||
| "notStarted"
|
||||
| "planned" {
|
||||
const completedMarks =
|
||||
plugin?.settings.taskStatuses?.completed?.split("|") || [
|
||||
"x",
|
||||
"X",
|
||||
];
|
||||
if (completedMarks.includes(mark)) {
|
||||
return "completed";
|
||||
}
|
||||
|
||||
const inProgressMarks =
|
||||
plugin?.settings.taskStatuses?.inProgress?.split("|") || [
|
||||
"-",
|
||||
"/",
|
||||
];
|
||||
if (inProgressMarks.includes(mark)) {
|
||||
return "inProgress";
|
||||
}
|
||||
|
||||
const abandonedMarks =
|
||||
plugin?.settings.taskStatuses?.abandoned?.split("|") || [
|
||||
">",
|
||||
];
|
||||
if (abandonedMarks.includes(mark)) {
|
||||
return "abandoned";
|
||||
}
|
||||
|
||||
const plannedMarks =
|
||||
plugin?.settings.taskStatuses?.planned?.split("|") || ["?"];
|
||||
if (plannedMarks.includes(mark)) {
|
||||
return "planned";
|
||||
}
|
||||
|
||||
// If not matching any specific status, check if it's a not-started mark
|
||||
const notStartedMarks =
|
||||
plugin?.settings.taskStatuses?.notStarted?.split("|") || [
|
||||
" ",
|
||||
];
|
||||
if (notStartedMarks.includes(mark)) {
|
||||
return "notStarted";
|
||||
}
|
||||
|
||||
// If we get here, the mark doesn't match any of our defined categories
|
||||
// Use the countOtherStatusesAs setting to determine how to count it
|
||||
return (
|
||||
(plugin?.settings.countOtherStatusesAs as
|
||||
| "completed"
|
||||
| "inProgress"
|
||||
| "abandoned"
|
||||
| "notStarted"
|
||||
| "planned") || "notStarted"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a task marker should be excluded from counting
|
||||
*/
|
||||
private shouldExcludeTask(text: string): boolean {
|
||||
// 如果没有排除设置,返回false
|
||||
// If no exclusion settings, return false
|
||||
if (
|
||||
!plugin?.settings.excludeTaskMarks ||
|
||||
plugin.settings.excludeTaskMarks.length === 0
|
||||
|
|
@ -628,7 +921,7 @@ export function taskProgressBarExtension(
|
|||
return false;
|
||||
}
|
||||
|
||||
// 检查任务标记是否在排除列表中
|
||||
// Check if task mark is in the exclusion list
|
||||
const taskMarkMatch = text.match(/\[(.)]/);
|
||||
if (taskMarkMatch && taskMarkMatch[1]) {
|
||||
const taskMark = taskMarkMatch[1];
|
||||
|
|
@ -661,16 +954,34 @@ export function taskProgressBarExtension(
|
|||
bullet: boolean
|
||||
): Tasks {
|
||||
if (!textArray || textArray.length === 0) {
|
||||
return { completed: 0, total: 0 };
|
||||
return {
|
||||
completed: 0,
|
||||
total: 0,
|
||||
inProgress: 0,
|
||||
abandoned: 0,
|
||||
notStarted: 0,
|
||||
planned: 0,
|
||||
};
|
||||
}
|
||||
|
||||
let completed: number = 0;
|
||||
let inProgress: number = 0;
|
||||
let abandoned: number = 0;
|
||||
let notStarted: number = 0;
|
||||
let planned: number = 0;
|
||||
let total: number = 0;
|
||||
let level: number = 0;
|
||||
|
||||
// Get tab size from vault config
|
||||
const tabSize = this.getTabSize();
|
||||
|
||||
// For debugging - collect task marks and their statuses
|
||||
const taskDebug: {
|
||||
mark: string;
|
||||
status: string;
|
||||
lineText: string;
|
||||
}[] = [];
|
||||
|
||||
// Determine indentation level for bullets
|
||||
if (!plugin?.settings.countSubLevel && bullet && textArray[0]) {
|
||||
const indentMatch = textArray[0].match(/^[\s|\t]*/);
|
||||
|
|
@ -685,6 +996,7 @@ export function taskProgressBarExtension(
|
|||
level,
|
||||
tabSize
|
||||
);
|
||||
|
||||
const bulletCompleteRegex = this.createCompletedTaskRegex(
|
||||
plugin,
|
||||
false,
|
||||
|
|
@ -702,41 +1014,150 @@ export function taskProgressBarExtension(
|
|||
if (i === 0) continue; // Skip the first line
|
||||
|
||||
if (bullet) {
|
||||
// 确保每个任务只被计算一次
|
||||
const lineText = textArray[i].trim();
|
||||
// 首先检查是否匹配任务格式,然后检查是否是应该被排除的任务类型
|
||||
const lineText = textArray[i];
|
||||
const lineTextTrimmed = lineText.trim();
|
||||
|
||||
// If countSubLevel is false, check the indentation level directly
|
||||
if (!plugin?.settings.countSubLevel) {
|
||||
const indentMatch = lineText.match(/^[\s|\t]*/);
|
||||
const lineLevel = indentMatch
|
||||
? indentMatch[0].length / tabSize
|
||||
: 0;
|
||||
|
||||
// Only count this task if it's exactly one level deeper
|
||||
if (lineLevel !== level + 1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// First check if it matches task format, then check if it should be excluded
|
||||
if (
|
||||
lineText &&
|
||||
lineTextTrimmed &&
|
||||
lineText.match(bulletTotalRegex) &&
|
||||
!this.shouldExcludeTask(lineText)
|
||||
!this.shouldExcludeTask(lineTextTrimmed)
|
||||
) {
|
||||
total++;
|
||||
// 使用新方法判断是否为已完成任务
|
||||
if (this.isCompletedTask(lineText)) {
|
||||
// Get the task status
|
||||
const status = this.getTaskStatus(lineTextTrimmed);
|
||||
|
||||
// Extract the mark for debugging
|
||||
const markMatch = lineTextTrimmed.match(/\[(.)]/);
|
||||
if (markMatch && markMatch[1]) {
|
||||
taskDebug.push({
|
||||
mark: markMatch[1],
|
||||
status: status,
|
||||
lineText: lineTextTrimmed,
|
||||
});
|
||||
}
|
||||
|
||||
// Count based on status
|
||||
if (status === "completed") {
|
||||
completed++;
|
||||
} else if (status === "inProgress") {
|
||||
inProgress++;
|
||||
} else if (status === "abandoned") {
|
||||
abandoned++;
|
||||
} else if (status === "planned") {
|
||||
planned++;
|
||||
} else if (status === "notStarted") {
|
||||
notStarted++;
|
||||
}
|
||||
}
|
||||
} else if (plugin?.settings.addTaskProgressBarToHeading) {
|
||||
const lineText = textArray[i].trim();
|
||||
// 同样使用shouldExcludeTask函数进行额外的验证
|
||||
const lineText = textArray[i];
|
||||
const lineTextTrimmed = lineText.trim();
|
||||
|
||||
// For headings, if countSubLevel is false, only count top-level tasks (no indentation)
|
||||
if (!plugin?.settings.countSubLevel) {
|
||||
const indentMatch = lineText.match(/^[\s|\t]*/);
|
||||
const lineLevel = indentMatch
|
||||
? indentMatch[0].length / tabSize
|
||||
: 0;
|
||||
|
||||
// For headings, only count tasks with no indentation when countSubLevel is false
|
||||
if (lineLevel !== 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Also use shouldExcludeTask for additional validation
|
||||
if (
|
||||
lineText &&
|
||||
lineTextTrimmed &&
|
||||
lineText.match(headingTotalRegex) &&
|
||||
!this.shouldExcludeTask(lineText)
|
||||
!this.shouldExcludeTask(lineTextTrimmed)
|
||||
) {
|
||||
total++;
|
||||
// 使用新方法判断是否为已完成任务
|
||||
if (this.isCompletedTask(lineText)) {
|
||||
// Get the task status
|
||||
const status = this.getTaskStatus(lineTextTrimmed);
|
||||
|
||||
// Extract the mark for debugging
|
||||
const markMatch = lineTextTrimmed.match(/\[(.)]/);
|
||||
if (markMatch && markMatch[1]) {
|
||||
taskDebug.push({
|
||||
mark: markMatch[1],
|
||||
status: status,
|
||||
lineText: lineTextTrimmed,
|
||||
});
|
||||
}
|
||||
|
||||
// Count based on status
|
||||
if (status === "completed") {
|
||||
completed++;
|
||||
} else if (status === "inProgress") {
|
||||
inProgress++;
|
||||
} else if (status === "abandoned") {
|
||||
abandoned++;
|
||||
} else if (status === "planned") {
|
||||
planned++;
|
||||
} else if (status === "notStarted") {
|
||||
notStarted++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 确保完成数不超过总数
|
||||
completed = Math.min(completed, total);
|
||||
// Log debug information
|
||||
if (taskDebug.length > 0) {
|
||||
console.log("Task counting debug:", {
|
||||
taskDebug,
|
||||
completedCount: completed,
|
||||
inProgressCount: inProgress,
|
||||
abandonedCount: abandoned,
|
||||
notStartedCount: notStarted,
|
||||
plannedCount: planned,
|
||||
totalCount: total,
|
||||
settings: {
|
||||
useOnlyCountMarks:
|
||||
plugin?.settings.useOnlyCountMarks,
|
||||
onlyCountTaskMarks:
|
||||
plugin?.settings.onlyCountTaskMarks,
|
||||
excludeTaskMarks: plugin?.settings.excludeTaskMarks,
|
||||
taskStatuses: plugin?.settings.taskStatuses,
|
||||
countOtherStatusesAs:
|
||||
plugin?.settings.countOtherStatusesAs,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return { completed, total };
|
||||
// Ensure counts don't exceed total
|
||||
completed = Math.min(completed, total);
|
||||
inProgress = Math.min(inProgress, total - completed);
|
||||
abandoned = Math.min(abandoned, total - completed - inProgress);
|
||||
planned = Math.min(
|
||||
planned,
|
||||
total - completed - inProgress - abandoned
|
||||
);
|
||||
notStarted =
|
||||
total - completed - inProgress - abandoned - planned;
|
||||
|
||||
return {
|
||||
completed,
|
||||
total,
|
||||
inProgress,
|
||||
abandoned,
|
||||
notStarted,
|
||||
planned,
|
||||
};
|
||||
}
|
||||
},
|
||||
{
|
||||
|
|
|
|||
131
styles.css
131
styles.css
|
|
@ -1,3 +1,33 @@
|
|||
/* Define theme variables */
|
||||
:root {
|
||||
/* Task status colors - light theme */
|
||||
--task-completed-color: #4caf50;
|
||||
--task-in-progress-color: #f9d923;
|
||||
--task-abandoned-color: #eb5353;
|
||||
--task-planned-color: #9c27b0; /* Added planned task color */
|
||||
|
||||
/* Progress bar gradient colors - light theme */
|
||||
--progress-0-color: #ae431e;
|
||||
--progress-25-color: #e5890a;
|
||||
--progress-50-color: #b4c6a6;
|
||||
--progress-75-color: #6bcb77;
|
||||
--progress-100-color: #4d96ff;
|
||||
}
|
||||
|
||||
/* Dark theme color adjustments */
|
||||
.theme-dark {
|
||||
--task-completed-color: #4caf50;
|
||||
--task-in-progress-color: #ffc107;
|
||||
--task-abandoned-color: #f44336;
|
||||
--task-planned-color: #ce93d8; /* Added planned task color for dark theme */
|
||||
|
||||
--progress-0-color: #ae431e;
|
||||
--progress-25-color: #e5890a;
|
||||
--progress-50-color: #b4c6a6;
|
||||
--progress-75-color: #6bcb77;
|
||||
--progress-100-color: #4d96ff;
|
||||
}
|
||||
|
||||
/* Set Default Progress Bar For Plugin */
|
||||
.cm-task-progress-bar {
|
||||
display: inline-block;
|
||||
|
|
@ -14,28 +44,63 @@
|
|||
}
|
||||
|
||||
.progress-bar-inline {
|
||||
border-radius: 10px;
|
||||
height: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Progress bar colors for different percentages of completion */
|
||||
.progress-bar-inline-empty {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.progress-bar-inline-0 {
|
||||
background-color: #AE431E;
|
||||
background-color: var(--progress-0-color);
|
||||
}
|
||||
|
||||
.progress-bar-inline-1 {
|
||||
background-color: #E5890A;
|
||||
background-color: var(--progress-25-color);
|
||||
}
|
||||
|
||||
.progress-bar-inline-2 {
|
||||
background-color: #B4C6A6;
|
||||
background-color: var(--progress-50-color);
|
||||
}
|
||||
|
||||
.progress-bar-inline-3 {
|
||||
background-color: #6BCB77;
|
||||
background-color: var(--progress-75-color);
|
||||
}
|
||||
|
||||
.progress-bar-inline-4 {
|
||||
background-color: #4D96FF;
|
||||
.progress-bar-inline-complete {
|
||||
background-color: var(--progress-100-color);
|
||||
}
|
||||
|
||||
/* Colors for different task statuses */
|
||||
.progress-completed {
|
||||
background-color: var(--task-completed-color);
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.progress-in-progress {
|
||||
background-color: var(--task-in-progress-color);
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.progress-abandoned {
|
||||
background-color: var(--task-abandoned-color);
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.progress-planned {
|
||||
background-color: var(--task-planned-color);
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.progress-bar-inline-background {
|
||||
|
|
@ -46,20 +111,45 @@
|
|||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
width: 85px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Status indicators in the task number display */
|
||||
.cm-task-progress-bar .task-status-indicator {
|
||||
display: inline-block;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.cm-task-progress-bar .completed-indicator {
|
||||
color: var(--task-completed-color);
|
||||
}
|
||||
|
||||
.cm-task-progress-bar .in-progress-indicator {
|
||||
color: var(--task-in-progress-color);
|
||||
}
|
||||
|
||||
.cm-task-progress-bar .abandoned-indicator {
|
||||
color: var(--task-abandoned-color);
|
||||
}
|
||||
|
||||
.cm-task-progress-bar .planned-indicator {
|
||||
color: var(--task-planned-color);
|
||||
}
|
||||
|
||||
/* Set Default Progress Bar With Number For Plugin */
|
||||
|
||||
.cm-task-progress-bar.with-number {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.HyperMD-header .cm-task-progress-bar.with-number .progress-bar-inline-background, .HyperMD-header .cm-task-progress-bar.with-number .progress-status {
|
||||
.HyperMD-header
|
||||
.cm-task-progress-bar.with-number
|
||||
.progress-bar-inline-background,
|
||||
.HyperMD-header .cm-task-progress-bar.with-number .progress-status {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
|
||||
.cm-task-progress-bar.with-number .progress-bar-inline-background {
|
||||
margin-bottom: -2px;
|
||||
width: 42px;
|
||||
|
|
@ -69,3 +159,24 @@
|
|||
font-size: 13px;
|
||||
margin-left: 3px;
|
||||
}
|
||||
|
||||
/* Adaptations for dark theme */
|
||||
.theme-dark .progress-completed {
|
||||
background-color: var(--task-completed-color);
|
||||
}
|
||||
|
||||
.theme-dark .progress-in-progress {
|
||||
background-color: var(--task-in-progress-color);
|
||||
}
|
||||
|
||||
.theme-dark .progress-abandoned {
|
||||
background-color: var(--task-abandoned-color);
|
||||
}
|
||||
|
||||
.theme-dark .progress-planned {
|
||||
background-color: var(--task-planned-color);
|
||||
}
|
||||
|
||||
.task-progress-bar-popover {
|
||||
width: 400px;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue