mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
fix(editor-extensions): resolve transaction conflicts between workflow and date managers
- Add transaction annotation checks to prevent workflow/date manager conflicts - Improve bounds checking and error handling in date manager operations - Add safety checks for document position calculations - Remove verbose console logging for cleaner output - Fix code formatting and trailing comma inconsistencies - Enhance transaction filter coordination to prevent duplicate operations
This commit is contained in:
parent
37c9769593
commit
cf5334f3ba
12 changed files with 670 additions and 308 deletions
|
|
@ -232,6 +232,19 @@ export class EditorState {
|
|||
};
|
||||
},
|
||||
};
|
||||
|
||||
// Add transactionExtender mock for tests
|
||||
static transactionExtender = {
|
||||
of: (
|
||||
f: (
|
||||
tr: Transaction
|
||||
) => TransactionSpec | readonly TransactionSpec[] | null
|
||||
) => {
|
||||
return {
|
||||
extend: f,
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export class Annotation<T> {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export class FluentSidebar extends Component {
|
|||
private railEl: HTMLElement | null = null;
|
||||
|
||||
private primaryItems: FluentTaskNavigationItem[] = [
|
||||
{id: "inbox", label: t("Inbox"), icon: "inbox", type: "primary"},
|
||||
{ id: "inbox", label: t("Inbox"), icon: "inbox", type: "primary" },
|
||||
{
|
||||
id: "today",
|
||||
label: t("Today"),
|
||||
|
|
@ -43,7 +43,7 @@ export class FluentSidebar extends Component {
|
|||
icon: "calendar",
|
||||
type: "primary",
|
||||
},
|
||||
{id: "flagged", label: t("Flagged"), icon: "flag", type: "primary"},
|
||||
{ id: "flagged", label: t("Flagged"), icon: "flag", type: "primary" },
|
||||
];
|
||||
|
||||
private otherItems: FluentTaskNavigationItem[] = [
|
||||
|
|
@ -53,14 +53,14 @@ export class FluentSidebar extends Component {
|
|||
icon: "calendar",
|
||||
type: "other",
|
||||
},
|
||||
{id: "gantt", label: t("Gantt"), icon: "git-branch", type: "other"},
|
||||
{ id: "gantt", label: t("Gantt"), icon: "git-branch", type: "other" },
|
||||
{
|
||||
id: "review",
|
||||
label: t("Review"),
|
||||
icon: "check-square",
|
||||
type: "other",
|
||||
},
|
||||
{id: "tags", label: t("Tags"), icon: "tag", type: "other"},
|
||||
{ id: "tags", label: t("Tags"), icon: "tag", type: "other" },
|
||||
];
|
||||
|
||||
constructor(
|
||||
|
|
@ -68,7 +68,7 @@ export class FluentSidebar extends Component {
|
|||
plugin: TaskProgressBarPlugin,
|
||||
private onNavigate: (viewId: string) => void,
|
||||
private onProjectSelect: (projectId: string) => void,
|
||||
collapsed = false
|
||||
collapsed = false,
|
||||
) {
|
||||
super();
|
||||
this.containerEl = containerEl;
|
||||
|
|
@ -103,7 +103,8 @@ export class FluentSidebar extends Component {
|
|||
this.workspaceSelector = new WorkspaceSelector(
|
||||
workspaceSelectorEl,
|
||||
this.plugin,
|
||||
(workspaceId: string) => this.handleWorkspaceChange(workspaceId)
|
||||
(workspaceId: string) =>
|
||||
this.handleWorkspaceChange(workspaceId),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -112,10 +113,10 @@ export class FluentSidebar extends Component {
|
|||
cls: "fluent-new-task-btn",
|
||||
text: t("New Task"),
|
||||
});
|
||||
setIcon(newTaskBtn.createDiv({cls: "fluent-new-task-icon"}), "plus");
|
||||
newTaskBtn.addEventListener("click", () => {
|
||||
this.onNavigate("new-task");
|
||||
});
|
||||
setIcon(newTaskBtn.createDiv({ cls: "fluent-new-task-icon" }), "plus");
|
||||
this.registerDomEvent(newTaskBtn, "click", () =>
|
||||
this.onNavigate("new-task"),
|
||||
);
|
||||
|
||||
// Main navigation area
|
||||
const content = this.containerEl.createDiv({
|
||||
|
|
@ -136,7 +137,7 @@ export class FluentSidebar extends Component {
|
|||
cls: "fluent-section-header",
|
||||
});
|
||||
|
||||
projectHeader.createSpan({text: t("Projects")});
|
||||
projectHeader.createSpan({ text: t("Projects") });
|
||||
|
||||
// Button container for tree toggle and sort
|
||||
const buttonContainer = projectHeader.createDiv({
|
||||
|
|
@ -146,22 +147,22 @@ export class FluentSidebar extends Component {
|
|||
// Tree/List toggle button
|
||||
const treeToggleBtn = buttonContainer.createDiv({
|
||||
cls: "fluent-tree-toggle-btn",
|
||||
attr: {"aria-label": t("Toggle tree/list view")},
|
||||
attr: { "aria-label": t("Toggle tree/list view") },
|
||||
});
|
||||
// Load saved view mode preference
|
||||
this.isTreeView =
|
||||
this.plugin.app.loadLocalStorage(
|
||||
"task-genius-project-view-mode"
|
||||
"task-genius-project-view-mode",
|
||||
) === "tree";
|
||||
setIcon(treeToggleBtn, this.isTreeView ? "git-branch" : "list");
|
||||
|
||||
treeToggleBtn.addEventListener("click", () => {
|
||||
this.registerDomEvent(treeToggleBtn, "click", () => {
|
||||
this.isTreeView = !this.isTreeView;
|
||||
setIcon(treeToggleBtn, this.isTreeView ? "git-branch" : "list");
|
||||
// Save preference
|
||||
this.plugin.app.saveLocalStorage(
|
||||
"task-genius-project-view-mode",
|
||||
this.isTreeView ? "tree" : "list"
|
||||
this.isTreeView ? "tree" : "list",
|
||||
);
|
||||
// Update project list view mode
|
||||
if (this.projectList) {
|
||||
|
|
@ -172,12 +173,12 @@ export class FluentSidebar extends Component {
|
|||
// Sort button
|
||||
const sortProjectBtn = buttonContainer.createDiv({
|
||||
cls: "fluent-sort-project-btn",
|
||||
attr: {"aria-label": t("Sort projects")},
|
||||
attr: { "aria-label": t("Sort projects") },
|
||||
});
|
||||
setIcon(sortProjectBtn, "arrow-up-down");
|
||||
|
||||
// Pass sort button to project list for menu handling
|
||||
sortProjectBtn.addEventListener("click", () => {
|
||||
this.registerDomEvent(sortProjectBtn, "click", () => {
|
||||
(this.projectList as any).showSortMenu?.(sortProjectBtn);
|
||||
});
|
||||
|
||||
|
|
@ -186,7 +187,7 @@ export class FluentSidebar extends Component {
|
|||
projectListEl,
|
||||
this.plugin,
|
||||
this.onProjectSelect,
|
||||
this.isTreeView
|
||||
this.isTreeView,
|
||||
);
|
||||
// Add ProjectList as a child component
|
||||
this.addChild(this.projectList);
|
||||
|
|
@ -209,26 +210,26 @@ export class FluentSidebar extends Component {
|
|||
// Workspace menu button
|
||||
const wsBtn = this.railEl.createDiv({
|
||||
cls: "fluent-rail-btn",
|
||||
attr: {"aria-label": t("Workspace")},
|
||||
attr: { "aria-label": t("Workspace") },
|
||||
});
|
||||
setIcon(wsBtn, "layers");
|
||||
wsBtn.addEventListener("click", (e) =>
|
||||
this.showWorkspaceMenuWithManager(e as MouseEvent)
|
||||
this.registerDomEvent(wsBtn, "click", (e) =>
|
||||
this.showWorkspaceMenuWithManager(e as MouseEvent),
|
||||
);
|
||||
|
||||
// Primary view icons
|
||||
this.primaryItems.forEach((item) => {
|
||||
const btn = this.railEl!.createDiv({
|
||||
cls: "fluent-rail-btn",
|
||||
attr: {"aria-label": item.label, "data-view-id": item.id},
|
||||
attr: { "aria-label": item.label, "data-view-id": item.id },
|
||||
});
|
||||
setIcon(btn, item.icon);
|
||||
btn.addEventListener("click", () => {
|
||||
this.registerDomEvent(btn, "click", () => {
|
||||
this.setActiveItem(item.id);
|
||||
this.onNavigate(item.id);
|
||||
});
|
||||
// Add context menu handler for rail button
|
||||
btn.addEventListener("contextmenu", (e) => {
|
||||
this.registerDomEvent(btn, "contextmenu", (e) => {
|
||||
this.showViewContextMenu(e as MouseEvent, item.id);
|
||||
});
|
||||
});
|
||||
|
|
@ -240,7 +241,7 @@ export class FluentSidebar extends Component {
|
|||
?.maxOtherViewsBeforeOverflow ?? 5;
|
||||
const displayedOther: FluentTaskNavigationItem[] = allOtherItems.slice(
|
||||
0,
|
||||
visibleCount
|
||||
visibleCount,
|
||||
);
|
||||
const remainingOther: FluentTaskNavigationItem[] =
|
||||
allOtherItems.slice(visibleCount);
|
||||
|
|
@ -248,15 +249,15 @@ export class FluentSidebar extends Component {
|
|||
displayedOther.forEach((item: FluentTaskNavigationItem) => {
|
||||
const btn = this.railEl!.createDiv({
|
||||
cls: "fluent-rail-btn",
|
||||
attr: {"aria-label": item.label, "data-view-id": item.id},
|
||||
attr: { "aria-label": item.label, "data-view-id": item.id },
|
||||
});
|
||||
setIcon(btn, item.icon);
|
||||
btn.addEventListener("click", () => {
|
||||
this.registerDomEvent(btn, "click", () => {
|
||||
this.setActiveItem(item.id);
|
||||
this.onNavigate(item.id);
|
||||
});
|
||||
// Add context menu handler for rail button
|
||||
btn.addEventListener("contextmenu", (e) => {
|
||||
this.registerDomEvent(btn, "contextmenu", (e) => {
|
||||
this.showViewContextMenu(e as MouseEvent, item.id);
|
||||
});
|
||||
});
|
||||
|
|
@ -264,31 +265,31 @@ export class FluentSidebar extends Component {
|
|||
if (remainingOther.length > 0) {
|
||||
const moreBtn = this.railEl!.createDiv({
|
||||
cls: "fluent-rail-btn",
|
||||
attr: {"aria-label": t("More views")},
|
||||
attr: { "aria-label": t("More views") },
|
||||
});
|
||||
setIcon(moreBtn, "more-horizontal");
|
||||
moreBtn.addEventListener("click", (e) =>
|
||||
this.showOtherViewsMenu(e as MouseEvent, remainingOther)
|
||||
this.registerDomEvent(moreBtn, "click", (e) =>
|
||||
this.showOtherViewsMenu(e as MouseEvent, remainingOther),
|
||||
);
|
||||
}
|
||||
|
||||
// Projects menu button
|
||||
const projBtn = this.railEl!.createDiv({
|
||||
cls: "fluent-rail-btn",
|
||||
attr: {"aria-label": t("Projects")},
|
||||
attr: { "aria-label": t("Projects") },
|
||||
});
|
||||
setIcon(projBtn, "folder");
|
||||
projBtn.addEventListener("click", (e) =>
|
||||
this.showProjectMenu(e as MouseEvent)
|
||||
this.registerDomEvent(projBtn, "click", (e) =>
|
||||
this.showProjectMenu(e as MouseEvent),
|
||||
);
|
||||
|
||||
// Add (New Task) button
|
||||
const addBtn = this.railEl!.createDiv({
|
||||
cls: "fluent-rail-btn",
|
||||
attr: {"aria-label": t("New Task")},
|
||||
attr: { "aria-label": t("New Task") },
|
||||
});
|
||||
setIcon(addBtn, "plus");
|
||||
addBtn.addEventListener("click", () => this.onNavigate("new-task"));
|
||||
this.registerDomEvent(addBtn, "click", () => this.onNavigate("new-task"));
|
||||
}
|
||||
|
||||
private renderOtherViewsSection() {
|
||||
|
|
@ -310,21 +311,21 @@ export class FluentSidebar extends Component {
|
|||
?.maxOtherViewsBeforeOverflow ?? 5;
|
||||
const displayedOther: FluentTaskNavigationItem[] = allOtherItems.slice(
|
||||
0,
|
||||
visibleCount
|
||||
visibleCount,
|
||||
);
|
||||
const remainingOther: FluentTaskNavigationItem[] =
|
||||
allOtherItems.slice(visibleCount);
|
||||
|
||||
otherHeader.createSpan({text: t("Other Views")});
|
||||
otherHeader.createSpan({ text: t("Other Views") });
|
||||
|
||||
if (remainingOther.length > 0) {
|
||||
const moreBtn = otherHeader.createDiv({
|
||||
cls: "fluent-section-action",
|
||||
attr: {"aria-label": t("More views")},
|
||||
attr: { "aria-label": t("More views") },
|
||||
});
|
||||
setIcon(moreBtn, "more-horizontal");
|
||||
moreBtn.addEventListener("click", (e) =>
|
||||
this.showOtherViewsMenu(e as MouseEvent, remainingOther)
|
||||
this.registerDomEvent(moreBtn, "click", (e) =>
|
||||
this.showOtherViewsMenu(e as MouseEvent, remainingOther),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -383,19 +384,19 @@ export class FluentSidebar extends Component {
|
|||
onWorkspaceSwitched(this.plugin.app, (payload) => {
|
||||
this.currentWorkspaceId = payload.workspaceId;
|
||||
this.render();
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this.registerEvent(
|
||||
onWorkspaceDeleted(this.plugin.app, () => {
|
||||
this.render();
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
this.registerEvent(
|
||||
onWorkspaceCreated(this.plugin.app, () => {
|
||||
this.render();
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -463,14 +464,14 @@ export class FluentSidebar extends Component {
|
|||
|
||||
constructor(
|
||||
private plugin: TaskProgressBarPlugin,
|
||||
private onCreated: () => void
|
||||
private onCreated: () => void,
|
||||
) {
|
||||
super(plugin.app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const {contentEl} = this;
|
||||
contentEl.createEl("h2", {text: t("Create New Workspace")});
|
||||
const { contentEl } = this;
|
||||
contentEl.createEl("h2", { text: t("Create New Workspace") });
|
||||
|
||||
const inputContainer = contentEl.createDiv();
|
||||
inputContainer.createEl("label", {
|
||||
|
|
@ -495,14 +496,14 @@ export class FluentSidebar extends Component {
|
|||
const name = this.nameInput.value.trim();
|
||||
if (name && this.plugin.workspaceManager) {
|
||||
await this.plugin.workspaceManager.createWorkspace(
|
||||
name
|
||||
name,
|
||||
);
|
||||
new Notice(
|
||||
t('Workspace "{{name}}" created', {
|
||||
interpolation: {
|
||||
name: name,
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
this.onCreated();
|
||||
this.close();
|
||||
|
|
@ -519,7 +520,7 @@ export class FluentSidebar extends Component {
|
|||
}
|
||||
|
||||
onClose() {
|
||||
const {contentEl} = this;
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
|
@ -538,7 +539,7 @@ export class FluentSidebar extends Component {
|
|||
const tempList: any = new ProjectList(
|
||||
temp as any,
|
||||
this.plugin,
|
||||
this.onProjectSelect
|
||||
this.onProjectSelect,
|
||||
);
|
||||
if (typeof tempList.getProjects === "function") {
|
||||
projects = tempList.getProjects();
|
||||
|
|
@ -557,7 +558,10 @@ export class FluentSidebar extends Component {
|
|||
menu.showAtMouseEvent(event);
|
||||
}
|
||||
|
||||
private showOtherViewsMenu(event: MouseEvent, items: FluentTaskNavigationItem[]) {
|
||||
private showOtherViewsMenu(
|
||||
event: MouseEvent,
|
||||
items: FluentTaskNavigationItem[],
|
||||
) {
|
||||
const menu = new Menu();
|
||||
items.forEach((it: FluentTaskNavigationItem) => {
|
||||
menu.addItem((mi) => {
|
||||
|
|
@ -580,7 +584,7 @@ export class FluentSidebar extends Component {
|
|||
|
||||
// Check if this is a primary view
|
||||
const isPrimaryView = this.primaryItems.some(
|
||||
(item) => item.id === viewId
|
||||
(item) => item.id === viewId,
|
||||
);
|
||||
|
||||
// Open in new tab
|
||||
|
|
@ -607,7 +611,7 @@ export class FluentSidebar extends Component {
|
|||
if (viewId === "habit") {
|
||||
(this.plugin.app as any).setting.open();
|
||||
(this.plugin.app as any).setting.openTabById(
|
||||
this.plugin.manifest.id
|
||||
this.plugin.manifest.id,
|
||||
);
|
||||
setTimeout(() => {
|
||||
if (this.plugin.settingTab) {
|
||||
|
|
@ -619,7 +623,7 @@ export class FluentSidebar extends Component {
|
|||
|
||||
// Normal handling for other views
|
||||
const view = this.plugin.settings.viewConfiguration.find(
|
||||
(v) => v.id === viewId
|
||||
(v) => v.id === viewId,
|
||||
);
|
||||
if (!view) {
|
||||
return;
|
||||
|
|
@ -632,16 +636,16 @@ export class FluentSidebar extends Component {
|
|||
currentRules,
|
||||
(
|
||||
updatedView: ViewConfig,
|
||||
updatedRules: ViewFilterRule
|
||||
updatedRules: ViewFilterRule,
|
||||
) => {
|
||||
const currentIndex =
|
||||
this.plugin.settings.viewConfiguration.findIndex(
|
||||
(v) => v.id === updatedView.id
|
||||
(v) => v.id === updatedView.id,
|
||||
);
|
||||
if (currentIndex !== -1) {
|
||||
this.plugin.settings.viewConfiguration[
|
||||
currentIndex
|
||||
] = {
|
||||
] = {
|
||||
...updatedView,
|
||||
filterRules: updatedRules,
|
||||
};
|
||||
|
|
@ -653,10 +657,10 @@ export class FluentSidebar extends Component {
|
|||
// Trigger view config changed event
|
||||
this.plugin.app.workspace.trigger(
|
||||
"task-genius:view-config-changed",
|
||||
{reason: "edit", viewId: viewId}
|
||||
{ reason: "edit", viewId: viewId },
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
).open();
|
||||
});
|
||||
});
|
||||
|
|
@ -670,7 +674,7 @@ export class FluentSidebar extends Component {
|
|||
.onClick(() => {
|
||||
const view =
|
||||
this.plugin.settings.viewConfiguration.find(
|
||||
(v) => v.id === viewId
|
||||
(v) => v.id === viewId,
|
||||
);
|
||||
if (!view) {
|
||||
return;
|
||||
|
|
@ -683,18 +687,18 @@ export class FluentSidebar extends Component {
|
|||
null, // null for create mode
|
||||
(
|
||||
createdView: ViewConfig,
|
||||
createdRules: ViewFilterRule
|
||||
createdRules: ViewFilterRule,
|
||||
) => {
|
||||
if (
|
||||
!this.plugin.settings.viewConfiguration.some(
|
||||
(v) => v.id === createdView.id
|
||||
(v) => v.id === createdView.id,
|
||||
)
|
||||
) {
|
||||
this.plugin.settings.viewConfiguration.push(
|
||||
{
|
||||
...createdView,
|
||||
filterRules: createdRules,
|
||||
}
|
||||
},
|
||||
);
|
||||
this.plugin.saveSettings();
|
||||
// Re-render the sidebar to show the new view
|
||||
|
|
@ -705,20 +709,20 @@ export class FluentSidebar extends Component {
|
|||
{
|
||||
reason: "create",
|
||||
viewId: createdView.id,
|
||||
}
|
||||
},
|
||||
);
|
||||
new Notice(
|
||||
t("View copied successfully: ") +
|
||||
createdView.name
|
||||
createdView.name,
|
||||
);
|
||||
} else {
|
||||
new Notice(
|
||||
t("Error: View ID already exists.")
|
||||
t("Error: View ID already exists."),
|
||||
);
|
||||
}
|
||||
},
|
||||
view, // Pass current view as copy source
|
||||
view.id
|
||||
view.id,
|
||||
).open();
|
||||
});
|
||||
});
|
||||
|
|
@ -729,7 +733,7 @@ export class FluentSidebar extends Component {
|
|||
.onClick(() => {
|
||||
const view =
|
||||
this.plugin.settings.viewConfiguration.find(
|
||||
(v) => v.id === viewId
|
||||
(v) => v.id === viewId,
|
||||
);
|
||||
if (!view) {
|
||||
return;
|
||||
|
|
@ -745,7 +749,7 @@ export class FluentSidebar extends Component {
|
|||
// Trigger view config changed event
|
||||
this.plugin.app.workspace.trigger(
|
||||
"task-genius:view-config-changed",
|
||||
{reason: "visibility", viewId: viewId}
|
||||
{ reason: "visibility", viewId: viewId },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -753,7 +757,7 @@ export class FluentSidebar extends Component {
|
|||
|
||||
// Delete (for custom views only)
|
||||
const view = this.plugin.settings.viewConfiguration.find(
|
||||
(v) => v.id === viewId
|
||||
(v) => v.id === viewId,
|
||||
);
|
||||
if (view?.type === "custom") {
|
||||
menu.addSeparator();
|
||||
|
|
@ -764,7 +768,7 @@ export class FluentSidebar extends Component {
|
|||
.onClick(() => {
|
||||
this.plugin.settings.viewConfiguration =
|
||||
this.plugin.settings.viewConfiguration.filter(
|
||||
(v) => v.id !== viewId
|
||||
(v) => v.id !== viewId,
|
||||
);
|
||||
this.plugin.saveSettings();
|
||||
// Re-render based on current mode
|
||||
|
|
@ -776,7 +780,7 @@ export class FluentSidebar extends Component {
|
|||
// Trigger view config changed event
|
||||
this.plugin.app.workspace.trigger(
|
||||
"task-genius:view-config-changed",
|
||||
{reason: "delete", viewId: viewId}
|
||||
{ reason: "delete", viewId: viewId },
|
||||
);
|
||||
new Notice(t("View deleted: ") + view.name);
|
||||
});
|
||||
|
|
@ -788,15 +792,15 @@ export class FluentSidebar extends Component {
|
|||
|
||||
private renderNavigationItems(
|
||||
containerEl: HTMLElement,
|
||||
items: FluentTaskNavigationItem[]
|
||||
items: FluentTaskNavigationItem[],
|
||||
) {
|
||||
const list = containerEl.createDiv({cls: "fluent-navigation-list"});
|
||||
const list = containerEl.createDiv({ cls: "fluent-navigation-list" });
|
||||
items.forEach((item) => {
|
||||
const itemEl = list.createDiv({
|
||||
cls: "fluent-navigation-item",
|
||||
attr: {"data-view-id": item.id},
|
||||
attr: { "data-view-id": item.id },
|
||||
});
|
||||
const icon = itemEl.createDiv({cls: "fluent-navigation-icon"});
|
||||
const icon = itemEl.createDiv({ cls: "fluent-navigation-icon" });
|
||||
setIcon(icon, item.icon);
|
||||
itemEl.createSpan({
|
||||
cls: "fluent-navigation-label",
|
||||
|
|
@ -808,12 +812,12 @@ export class FluentSidebar extends Component {
|
|||
text: String(item.badge),
|
||||
});
|
||||
}
|
||||
itemEl.addEventListener("click", () => {
|
||||
this.registerDomEvent(itemEl, "click", () => {
|
||||
this.setActiveItem(item.id);
|
||||
this.onNavigate(item.id);
|
||||
});
|
||||
// Add context menu handler
|
||||
itemEl.addEventListener("contextmenu", (e) => {
|
||||
this.registerDomEvent(itemEl, "contextmenu", (e) => {
|
||||
this.showViewContextMenu(e as MouseEvent, item.id);
|
||||
});
|
||||
});
|
||||
|
|
@ -823,14 +827,14 @@ export class FluentSidebar extends Component {
|
|||
// Clear active state from both full navigation items and rail buttons
|
||||
this.containerEl
|
||||
.querySelectorAll(
|
||||
".fluent-navigation-item, .fluent-rail-btn[data-view-id]"
|
||||
".fluent-navigation-item, .fluent-rail-btn[data-view-id]",
|
||||
)
|
||||
.forEach((el) => {
|
||||
el.removeClass("is-active");
|
||||
});
|
||||
// Apply to any element that carries this view id (works in both modes)
|
||||
const activeEls = this.containerEl.querySelectorAll(
|
||||
`[data-view-id="${viewId}"]`
|
||||
`[data-view-id="${viewId}"]`,
|
||||
);
|
||||
activeEls.forEach((el) => el.addClass("is-active"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ export class TopNavigation extends Component {
|
|||
private onSortClick: () => void,
|
||||
private onSettingsClick: () => void,
|
||||
availableModes?: ViewMode[],
|
||||
private onToggleSidebar?: () => void
|
||||
private onToggleSidebar?: () => void,
|
||||
) {
|
||||
super();
|
||||
this.containerEl = containerEl;
|
||||
|
|
@ -75,12 +75,12 @@ export class TopNavigation extends Component {
|
|||
|
||||
// Hide entire navigation if no view modes are available
|
||||
if (this.availableModes.length === 0) {
|
||||
this.containerEl.style.display = "none";
|
||||
this.containerEl.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show navigation when modes are available
|
||||
this.containerEl.style.display = "";
|
||||
this.containerEl.show();
|
||||
|
||||
// Left section - Hamburger menu (mobile) and Search
|
||||
const leftSection = this.containerEl.createDiv({
|
||||
|
|
@ -125,8 +125,8 @@ export class TopNavigation extends Component {
|
|||
if (this.notificationCount === 0) {
|
||||
badge.hide();
|
||||
}
|
||||
notificationBtn.addEventListener("click", (e) =>
|
||||
this.showNotifications(e)
|
||||
this.registerDomEvent(notificationBtn, "click", (e) =>
|
||||
this.showNotifications(e),
|
||||
);
|
||||
|
||||
// Settings button
|
||||
|
|
@ -134,14 +134,14 @@ export class TopNavigation extends Component {
|
|||
cls: "fluent-nav-icon-button",
|
||||
});
|
||||
setIcon(settingsBtn, "settings");
|
||||
settingsBtn.addEventListener("click", () => this.onSettingsClick());
|
||||
this.registerDomEvent(settingsBtn, "click", () => this.onSettingsClick());
|
||||
}
|
||||
|
||||
private createViewTab(
|
||||
container: HTMLElement,
|
||||
mode: ViewMode,
|
||||
icon: string,
|
||||
label: string
|
||||
label: string,
|
||||
) {
|
||||
const tab = container.createEl("button", {
|
||||
cls: ["fluent-view-tab", "clickable-icon"],
|
||||
|
|
@ -155,7 +155,7 @@ export class TopNavigation extends Component {
|
|||
setIcon(tab.createDiv({ cls: "fluent-view-tab-icon" }), icon);
|
||||
tab.createSpan({ text: label });
|
||||
|
||||
tab.addEventListener("click", () => {
|
||||
this.registerDomEvent(tab, "click", () => {
|
||||
this.setViewMode(mode);
|
||||
this.onViewModeChange(mode);
|
||||
});
|
||||
|
|
@ -169,7 +169,7 @@ export class TopNavigation extends Component {
|
|||
});
|
||||
|
||||
const activeTab = this.containerEl.querySelector(
|
||||
`[data-mode="${mode}"]`
|
||||
`[data-mode="${mode}"]`,
|
||||
);
|
||||
if (activeTab) {
|
||||
activeTab.addClass("is-active");
|
||||
|
|
@ -206,7 +206,7 @@ export class TopNavigation extends Component {
|
|||
} else {
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(
|
||||
`${overdueTasks.length} overdue tasks`
|
||||
`${overdueTasks.length} overdue tasks`,
|
||||
).setDisabled(true);
|
||||
});
|
||||
|
||||
|
|
@ -220,7 +220,7 @@ export class TopNavigation extends Component {
|
|||
new Notice(
|
||||
t("Task: {{content}}", {
|
||||
content: task.content || "",
|
||||
})
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -232,7 +232,7 @@ export class TopNavigation extends Component {
|
|||
|
||||
private updateNotificationBadge() {
|
||||
const badge = this.containerEl.querySelector(
|
||||
".fluent-notification-badge"
|
||||
".fluent-notification-badge",
|
||||
);
|
||||
if (badge instanceof HTMLElement) {
|
||||
badge.textContent = String(this.notificationCount);
|
||||
|
|
@ -278,7 +278,7 @@ export class TopNavigation extends Component {
|
|||
this.viewTabsContainer,
|
||||
mode,
|
||||
config.icon,
|
||||
config.label
|
||||
config.label,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -289,12 +289,12 @@ export class TopNavigation extends Component {
|
|||
|
||||
// Hide entire navigation if no modes available
|
||||
if (modes.length === 0) {
|
||||
this.containerEl.style.display = "none";
|
||||
this.containerEl.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show navigation when modes are available
|
||||
this.containerEl.style.display = "";
|
||||
this.containerEl.show();
|
||||
|
||||
// If current mode is no longer available, switch to first available mode
|
||||
if (!modes.includes(this.currentViewMode)) {
|
||||
|
|
@ -305,10 +305,10 @@ export class TopNavigation extends Component {
|
|||
|
||||
// Update center section visibility (this should always be visible now since we handle empty modes above)
|
||||
const centerSection = this.containerEl.querySelector(
|
||||
".fluent-nav-center"
|
||||
".fluent-nav-center",
|
||||
) as HTMLElement;
|
||||
if (centerSection) {
|
||||
centerSection.style.display = "";
|
||||
centerSection.show();
|
||||
// Re-render the view tabs
|
||||
if (!this.viewTabsContainer) {
|
||||
this.viewTabsContainer = centerSection.createDiv({
|
||||
|
|
|
|||
|
|
@ -1,4 +1,11 @@
|
|||
import { App, ButtonComponent, Component, ItemView, Platform, WorkspaceLeaf, } from "obsidian";
|
||||
import {
|
||||
App,
|
||||
ButtonComponent,
|
||||
Component,
|
||||
ItemView,
|
||||
Platform,
|
||||
WorkspaceLeaf,
|
||||
} from "obsidian";
|
||||
import TaskProgressBarPlugin from "@/index";
|
||||
import { FluentSidebar } from "../components/FluentSidebar";
|
||||
import { TaskDetailsComponent } from "@/components/features/task/view/details";
|
||||
|
|
@ -6,7 +13,10 @@ import { Task } from "@/types/task";
|
|||
import { t } from "@/translations/helper";
|
||||
import { TG_LEFT_SIDEBAR_VIEW_TYPE } from "../../../../pages/LeftSidebarView";
|
||||
import { QuickCaptureModal } from "@/components/features/quick-capture/modals/QuickCaptureModal";
|
||||
import { ViewTaskFilterModal, ViewTaskFilterPopover, } from "@/components/features/task/filter";
|
||||
import {
|
||||
ViewTaskFilterModal,
|
||||
ViewTaskFilterPopover,
|
||||
} from "@/components/features/task/filter";
|
||||
import { RootFilterState } from "@/components/features/task/filter/ViewTaskFilter";
|
||||
|
||||
/**
|
||||
|
|
@ -47,7 +57,7 @@ export class FluentLayoutManager extends Component {
|
|||
private onTaskEdit?: (task: Task) => void;
|
||||
private onTaskUpdate?: (
|
||||
originalTask: Task,
|
||||
updatedTask: Task
|
||||
updatedTask: Task,
|
||||
) => Promise<void>;
|
||||
private onFilterReset?: () => void;
|
||||
private getLiveFilterState?: () => RootFilterState | null;
|
||||
|
|
@ -60,7 +70,7 @@ export class FluentLayoutManager extends Component {
|
|||
private rootContainerEl: HTMLElement,
|
||||
private headerEl: HTMLElement,
|
||||
private titleEl: HTMLElement,
|
||||
private getTaskCount: () => number
|
||||
private getTaskCount: () => number,
|
||||
) {
|
||||
super();
|
||||
|
||||
|
|
@ -109,7 +119,7 @@ export class FluentLayoutManager extends Component {
|
|||
* Check if using workspace side leaves mode
|
||||
*/
|
||||
private useSideLeaves(): boolean {
|
||||
return !!(this.plugin.settings.fluentView)?.useWorkspaceSideLeaves;
|
||||
return !!this.plugin.settings.fluentView?.useWorkspaceSideLeaves;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -119,7 +129,7 @@ export class FluentLayoutManager extends Component {
|
|||
if (this.useSideLeaves()) {
|
||||
containerEl.hide();
|
||||
console.log(
|
||||
"[FluentLayout] Using workspace side leaves: skip in-view sidebar"
|
||||
"[FluentLayout] Using workspace side leaves: skip in-view sidebar",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -146,7 +156,7 @@ export class FluentLayoutManager extends Component {
|
|||
this.closeMobileDrawer();
|
||||
}
|
||||
},
|
||||
initialCollapsedState
|
||||
initialCollapsedState,
|
||||
);
|
||||
|
||||
// Add sidebar as a child component for proper lifecycle management
|
||||
|
|
@ -159,7 +169,7 @@ export class FluentLayoutManager extends Component {
|
|||
initializeDetailsComponent(): void {
|
||||
if (this.useSideLeaves()) {
|
||||
console.log(
|
||||
"[FluentLayout] Using workspace side leaves: skip in-view details panel"
|
||||
"[FluentLayout] Using workspace side leaves: skip in-view details panel",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -168,7 +178,7 @@ export class FluentLayoutManager extends Component {
|
|||
this.detailsComponent = new TaskDetailsComponent(
|
||||
this.rootContainerEl,
|
||||
this.app,
|
||||
this.plugin
|
||||
this.plugin,
|
||||
);
|
||||
this.addChild(this.detailsComponent);
|
||||
this.detailsComponent.load();
|
||||
|
|
@ -180,7 +190,7 @@ export class FluentLayoutManager extends Component {
|
|||
this.onTaskEdit?.(task);
|
||||
this.detailsComponent.onTaskUpdate = async (
|
||||
originalTask: Task,
|
||||
updatedTask: Task
|
||||
updatedTask: Task,
|
||||
) => {
|
||||
await this.onTaskUpdate?.(originalTask, updatedTask);
|
||||
};
|
||||
|
|
@ -196,11 +206,11 @@ export class FluentLayoutManager extends Component {
|
|||
createSidebarToggle(): void {
|
||||
const headerBtns = !Platform.isPhone
|
||||
? (this.headerEl?.querySelector(
|
||||
".view-header-nav-buttons"
|
||||
) as HTMLElement | null)
|
||||
".view-header-nav-buttons",
|
||||
) as HTMLElement | null)
|
||||
: (this.headerEl?.querySelector(
|
||||
".view-header-left"
|
||||
) as HTMLElement);
|
||||
".view-header-left",
|
||||
) as HTMLElement);
|
||||
|
||||
if (!headerBtns) {
|
||||
console.warn("[FluentLayout] header buttons container not found");
|
||||
|
|
@ -238,7 +248,7 @@ export class FluentLayoutManager extends Component {
|
|||
interpolation: {
|
||||
num: this.getTaskCount(),
|
||||
},
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -286,7 +296,7 @@ export class FluentLayoutManager extends Component {
|
|||
this.sidebar?.setCollapsed(this.isSidebarCollapsed);
|
||||
this.rootContainerEl?.toggleClass(
|
||||
"fluent-sidebar-collapsed",
|
||||
this.isSidebarCollapsed
|
||||
this.isSidebarCollapsed,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -298,7 +308,7 @@ export class FluentLayoutManager extends Component {
|
|||
this.isMobileDrawerOpen = true;
|
||||
this.rootContainerEl?.addClass("drawer-open");
|
||||
if (this.drawerOverlay) {
|
||||
this.drawerOverlay.style.display = "block";
|
||||
this.drawerOverlay.show();
|
||||
}
|
||||
// Show the sidebar
|
||||
this.sidebar?.setCollapsed(false);
|
||||
|
|
@ -311,7 +321,7 @@ export class FluentLayoutManager extends Component {
|
|||
this.isMobileDrawerOpen = false;
|
||||
this.rootContainerEl?.removeClass("drawer-open");
|
||||
if (this.drawerOverlay) {
|
||||
this.drawerOverlay.style.display = "none";
|
||||
this.drawerOverlay.hide();
|
||||
}
|
||||
// Hide the sidebar
|
||||
this.sidebar?.setCollapsed(true);
|
||||
|
|
@ -326,10 +336,17 @@ export class FluentLayoutManager extends Component {
|
|||
this.drawerOverlay = layoutContainer.createDiv({
|
||||
cls: "drawer-overlay",
|
||||
});
|
||||
this.drawerOverlay.style.display = "none";
|
||||
this.drawerOverlay.addEventListener("click", () => {
|
||||
this.closeMobileDrawer();
|
||||
});
|
||||
this.drawerOverlay.hide();
|
||||
this.registerDomEvent(
|
||||
this.drawerOverlay,
|
||||
"click",
|
||||
() => {
|
||||
this.closeMobileDrawer();
|
||||
},
|
||||
{
|
||||
once: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -359,7 +376,7 @@ export class FluentLayoutManager extends Component {
|
|||
this.detailsToggleBtn.toggleClass("is-active", visible);
|
||||
this.detailsToggleBtn.setAttribute(
|
||||
"aria-label",
|
||||
visible ? t("Hide Details") : t("Show Details")
|
||||
visible ? t("Hide Details") : t("Show Details"),
|
||||
);
|
||||
}
|
||||
return;
|
||||
|
|
@ -377,7 +394,7 @@ export class FluentLayoutManager extends Component {
|
|||
this.detailsToggleBtn.toggleClass("is-active", visible);
|
||||
this.detailsToggleBtn.setAttribute(
|
||||
"aria-label",
|
||||
visible ? t("Hide Details") : t("Show Details")
|
||||
visible ? t("Hide Details") : t("Show Details"),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -393,7 +410,7 @@ export class FluentLayoutManager extends Component {
|
|||
this.toggleDetailsVisibility(false);
|
||||
document.removeEventListener(
|
||||
"click",
|
||||
overlayClickHandler
|
||||
overlayClickHandler,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
@ -407,7 +424,7 @@ export class FluentLayoutManager extends Component {
|
|||
) {
|
||||
document.removeEventListener(
|
||||
"click",
|
||||
this.mobileDetailsOverlayHandler
|
||||
this.mobileDetailsOverlayHandler,
|
||||
);
|
||||
delete this.mobileDetailsOverlayHandler;
|
||||
}
|
||||
|
|
@ -501,14 +518,14 @@ export class FluentLayoutManager extends Component {
|
|||
t("Details"),
|
||||
() => {
|
||||
this.toggleDetailsVisibility(!this.isDetailsVisible);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
if (this.detailsToggleBtn) {
|
||||
this.detailsToggleBtn.toggleClass("panel-toggle-btn", true);
|
||||
this.detailsToggleBtn.toggleClass(
|
||||
"is-active",
|
||||
this.isDetailsVisible
|
||||
this.isDetailsVisible,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -518,7 +535,7 @@ export class FluentLayoutManager extends Component {
|
|||
this.app,
|
||||
this.plugin,
|
||||
{},
|
||||
true
|
||||
true,
|
||||
);
|
||||
modal.open();
|
||||
});
|
||||
|
|
@ -529,7 +546,7 @@ export class FluentLayoutManager extends Component {
|
|||
const popover = new ViewTaskFilterPopover(
|
||||
this.app,
|
||||
undefined,
|
||||
this.plugin
|
||||
this.plugin,
|
||||
);
|
||||
|
||||
// Set up filter state when opening
|
||||
|
|
@ -538,18 +555,18 @@ export class FluentLayoutManager extends Component {
|
|||
const liveFilterState = this.getLiveFilterState?.();
|
||||
if (liveFilterState && popover.taskFilterComponent) {
|
||||
popover.taskFilterComponent.loadFilterState(
|
||||
liveFilterState
|
||||
liveFilterState,
|
||||
);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
|
||||
popover.showAtPosition({x: e.clientX, y: e.clientY});
|
||||
popover.showAtPosition({ x: e.clientX, y: e.clientY });
|
||||
} else {
|
||||
const modal = new ViewTaskFilterModal(
|
||||
this.app,
|
||||
this.leaf.id,
|
||||
this.plugin
|
||||
this.plugin,
|
||||
);
|
||||
|
||||
modal.open();
|
||||
|
|
@ -559,7 +576,7 @@ export class FluentLayoutManager extends Component {
|
|||
if (liveFilterState && modal.taskFilterComponent) {
|
||||
setTimeout(() => {
|
||||
modal.taskFilterComponent.loadFilterState(
|
||||
liveFilterState
|
||||
liveFilterState,
|
||||
);
|
||||
}, 100);
|
||||
}
|
||||
|
|
@ -576,7 +593,7 @@ export class FluentLayoutManager extends Component {
|
|||
updateActionButtons(): void {
|
||||
// Remove reset filter button if exists
|
||||
const resetButton = this.headerEl.querySelector(
|
||||
".view-action.task-filter-reset"
|
||||
".view-action.task-filter-reset",
|
||||
);
|
||||
if (resetButton) {
|
||||
resetButton.remove();
|
||||
|
|
@ -604,7 +621,7 @@ export class FluentLayoutManager extends Component {
|
|||
if (Platform.isPhone && (this as any).mobileDetailsOverlayHandler) {
|
||||
document.removeEventListener(
|
||||
"click",
|
||||
(this as any).mobileDetailsOverlayHandler
|
||||
(this as any).mobileDetailsOverlayHandler,
|
||||
);
|
||||
delete (this as any).mobileDetailsOverlayHandler;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -458,14 +458,29 @@ function applyDateOperations(
|
|||
operations: DateOperation[],
|
||||
plugin: TaskProgressBarPlugin
|
||||
): TransactionSpec {
|
||||
// Early validation: ensure line number is within bounds
|
||||
if (lineNumber < 1 || lineNumber > tr.newDoc.lines) {
|
||||
console.warn(
|
||||
`[AutoDateManager] Line number ${lineNumber} is out of bounds (doc has ${tr.newDoc.lines} lines)`
|
||||
);
|
||||
return tr;
|
||||
}
|
||||
|
||||
// IMPORTANT: Use the NEW document state, not the old one
|
||||
const line = tr.newDoc.line(lineNumber);
|
||||
|
||||
// Validate line boundaries
|
||||
if (line.from > tr.newDoc.length || line.to > tr.newDoc.length) {
|
||||
console.warn(
|
||||
`[AutoDateManager] Line boundaries invalid: from=${line.from}, to=${line.to}, doc length=${tr.newDoc.length}`
|
||||
);
|
||||
return tr;
|
||||
}
|
||||
|
||||
let lineText = line.text;
|
||||
const changes = [];
|
||||
|
||||
console.log(
|
||||
`[AutoDateManager] applyDateOperations - Working with line: "${lineText}"`
|
||||
);
|
||||
// Removed verbose logging
|
||||
|
||||
for (const operation of operations) {
|
||||
if (operation.type === "add") {
|
||||
|
|
@ -501,22 +516,21 @@ function applyDateOperations(
|
|||
|
||||
const absolutePosition = line.from + insertPosition;
|
||||
|
||||
console.log(
|
||||
`[AutoDateManager] Inserting ${operation.dateType} date:`
|
||||
);
|
||||
console.log(` - Insert position (relative): ${insertPosition}`);
|
||||
console.log(` - Line.from: ${line.from}`);
|
||||
console.log(` - Absolute position: ${absolutePosition}`);
|
||||
console.log(` - Date text: "${dateText}"`);
|
||||
console.log(
|
||||
` - Text at insert point: "${lineText.substring(
|
||||
insertPosition
|
||||
)}"`
|
||||
);
|
||||
// Ensure position is within document bounds
|
||||
// Clamp to document length to prevent range errors
|
||||
const safePosition = Math.min(absolutePosition, tr.newDoc.length);
|
||||
const clampedPosition = Math.max(0, safePosition);
|
||||
|
||||
// Keep minimal logging for debugging
|
||||
if (clampedPosition !== absolutePosition) {
|
||||
console.log(
|
||||
`[AutoDateManager] Position adjusted: ${absolutePosition} -> ${clampedPosition} (doc length: ${tr.newDoc.length})`
|
||||
);
|
||||
}
|
||||
|
||||
changes.push({
|
||||
from: absolutePosition,
|
||||
to: absolutePosition,
|
||||
from: clampedPosition,
|
||||
to: clampedPosition,
|
||||
insert: dateText,
|
||||
});
|
||||
|
||||
|
|
@ -574,9 +588,19 @@ function applyDateOperations(
|
|||
const absoluteFrom = line.from + matchToRemove.start;
|
||||
const absoluteTo = line.from + matchToRemove.end;
|
||||
|
||||
// Ensure positions are within document bounds
|
||||
const safeFrom = Math.min(
|
||||
Math.max(0, absoluteFrom),
|
||||
tr.newDoc.length
|
||||
);
|
||||
const safeTo = Math.min(
|
||||
Math.max(0, absoluteTo),
|
||||
tr.newDoc.length
|
||||
);
|
||||
|
||||
changes.push({
|
||||
from: absoluteFrom,
|
||||
to: absoluteTo,
|
||||
from: safeFrom,
|
||||
to: safeTo,
|
||||
insert: "",
|
||||
});
|
||||
|
||||
|
|
@ -589,8 +613,104 @@ function applyDateOperations(
|
|||
}
|
||||
|
||||
if (changes.length > 0) {
|
||||
// CRITICAL FIX: When multiple transaction filters run in sequence,
|
||||
// positions must be handled very carefully.
|
||||
|
||||
// Our positions are calculated relative to tr.newDoc (after previous changes).
|
||||
// However, when returning changes, CodeMirror expects them to be relative
|
||||
// to the state AFTER tr.changes is applied, which IS tr.newDoc.
|
||||
// So our positions should actually be correct as-is!
|
||||
|
||||
// The issue might be that we're seeing the document length at one point
|
||||
// but by the time the changes are applied, something else has modified it.
|
||||
|
||||
// Let's validate and ensure our positions don't exceed bounds
|
||||
const docLength = tr.newDoc.length;
|
||||
const validatedChanges = changes.map((change, i) => {
|
||||
// Ensure positions are within the document bounds of newDoc
|
||||
let validFrom = Math.min(Math.max(0, change.from), docLength);
|
||||
let validTo = Math.min(Math.max(0, change.to), docLength);
|
||||
|
||||
// Ensure from <= to
|
||||
if (validFrom > validTo) {
|
||||
validTo = validFrom;
|
||||
}
|
||||
|
||||
// Log only if positions changed
|
||||
if (change.from !== validFrom || change.to !== validTo) {
|
||||
console.log(
|
||||
`[AutoDateManager] Position adjusted: (${change.from},${change.to}) -> (${validFrom},${validTo})`
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
from: validFrom,
|
||||
to: validTo,
|
||||
insert: change.insert,
|
||||
};
|
||||
});
|
||||
|
||||
// Check if there are existing changes
|
||||
let existingChanges: Array<{
|
||||
fromA: number;
|
||||
toA: number;
|
||||
fromB: number;
|
||||
toB: number;
|
||||
inserted: string;
|
||||
}> = [];
|
||||
tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
|
||||
existingChanges.push({
|
||||
fromA,
|
||||
toA,
|
||||
fromB,
|
||||
toB,
|
||||
inserted: inserted.toString(),
|
||||
});
|
||||
});
|
||||
|
||||
// Only log if there are existing changes (indicating multiple filters)
|
||||
if (existingChanges.length > 0) {
|
||||
console.log(
|
||||
`[AutoDateManager] Processing with ${existingChanges.length} existing changes from other filters`
|
||||
);
|
||||
}
|
||||
|
||||
// IMPORTANT: When we return changes combined with tr.changes,
|
||||
// our change positions should be relative to the document state
|
||||
// AFTER tr.changes is applied (which is tr.newDoc).
|
||||
// This is exactly what we have, so we should be good.
|
||||
|
||||
// Let's also add an extra safety check
|
||||
const finalChanges = validatedChanges.filter((change) => {
|
||||
if (change.from > docLength || change.to > docLength) {
|
||||
console.error(
|
||||
`[AutoDateManager] ERROR: Change position exceeds document length! from=${change.from}, to=${change.to}, docLength=${docLength}`
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
// Rebuild a single combined change list relative to the ORIGINAL doc
|
||||
// 1) Take the original transaction's changes as specs (relative to startState)
|
||||
const baseChangeSpecs: { from: number; to: number; insert: string }[] =
|
||||
[];
|
||||
tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
|
||||
baseChangeSpecs.push({
|
||||
from: fromA,
|
||||
to: toA,
|
||||
insert: inserted.toString(),
|
||||
});
|
||||
});
|
||||
// 2) Map our additional changes (currently in newDoc space) back to startState
|
||||
const inverse = tr.changes.invert(tr.startState.doc);
|
||||
const mappedFinalChanges = finalChanges.map((c) => ({
|
||||
from: inverse.mapPos(c.from, -1),
|
||||
to: inverse.mapPos(c.to, -1),
|
||||
insert: c.insert,
|
||||
}));
|
||||
return {
|
||||
changes: [tr.changes, ...changes],
|
||||
changes: [...baseChangeSpecs, ...mappedFinalChanges],
|
||||
selection: tr.selection,
|
||||
annotations: [
|
||||
taskStatusChangeAnnotation.of("autoDateManager.dateUpdate"),
|
||||
|
|
@ -687,16 +807,16 @@ function findMetadataInsertPosition(
|
|||
for (let i = 0; i < remainingText.length; i++) {
|
||||
const char = remainingText[i];
|
||||
const nextChar = remainingText[i + 1];
|
||||
const twoChars = char + (nextChar || '');
|
||||
const twoChars = char + (nextChar || "");
|
||||
|
||||
// Handle [[wiki links]] - they are part of content
|
||||
if (twoChars === '[[') {
|
||||
if (twoChars === "[[") {
|
||||
inLink++;
|
||||
contentEnd = position + i + 2;
|
||||
i++; // Skip next char
|
||||
continue;
|
||||
}
|
||||
if (twoChars === ']]' && inLink > 0) {
|
||||
if (twoChars === "]]" && inLink > 0) {
|
||||
inLink--;
|
||||
contentEnd = position + i + 2;
|
||||
i++; // Skip next char
|
||||
|
|
@ -710,7 +830,7 @@ function findMetadataInsertPosition(
|
|||
}
|
||||
|
||||
// Check for dataview metadata [field:: value]
|
||||
if (char === '[' && !inDataview) {
|
||||
if (char === "[" && !inDataview) {
|
||||
const afterBracket = remainingText.slice(i + 1);
|
||||
if (afterBracket.match(/^[a-zA-Z]+::/)) {
|
||||
// This is dataview metadata, stop here
|
||||
|
|
@ -719,8 +839,12 @@ function findMetadataInsertPosition(
|
|||
}
|
||||
|
||||
// Check for tags (only if preceded by whitespace or at start)
|
||||
if (char === '#') {
|
||||
if (i === 0 || remainingText[i - 1] === ' ' || remainingText[i - 1] === '\t') {
|
||||
if (char === "#") {
|
||||
if (
|
||||
i === 0 ||
|
||||
remainingText[i - 1] === " " ||
|
||||
remainingText[i - 1] === "\t"
|
||||
) {
|
||||
// Check if this is actually a tag (followed by word characters)
|
||||
const afterHash = remainingText.slice(i + 1);
|
||||
if (afterHash.match(/^[\w-]+/)) {
|
||||
|
|
@ -731,7 +855,7 @@ function findMetadataInsertPosition(
|
|||
}
|
||||
|
||||
// Check for date emojis (these are metadata markers)
|
||||
const dateEmojis = ['📅', '🚀', '✅', '❌', '🛫', '▶️', '⏰', '🏁'];
|
||||
const dateEmojis = ["📅", "🚀", "✅", "❌", "🛫", "▶️", "⏰", "🏁"];
|
||||
if (dateEmojis.includes(char)) {
|
||||
// Check if this is followed by a date pattern
|
||||
const afterEmoji = remainingText.slice(i + 1);
|
||||
|
|
@ -748,7 +872,7 @@ function findMetadataInsertPosition(
|
|||
position = contentEnd;
|
||||
|
||||
// Trim trailing whitespace
|
||||
while (position > taskMatch[0].length && lineText[position - 1] === ' ') {
|
||||
while (position > taskMatch[0].length && lineText[position - 1] === " ") {
|
||||
position--;
|
||||
}
|
||||
|
||||
|
|
@ -827,9 +951,11 @@ function findMetadataInsertPosition(
|
|||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[AutoDateManager] Final insert position for ${dateType}: ${position}`
|
||||
);
|
||||
// Final validation: ensure position doesn't exceed line length
|
||||
position = Math.min(position, lineText.length);
|
||||
position = Math.max(0, position);
|
||||
|
||||
// Removed verbose logging
|
||||
return position;
|
||||
}
|
||||
|
||||
|
|
@ -845,18 +971,25 @@ function findCompletedDateInsertPosition(
|
|||
): number {
|
||||
// Use centralized block reference detection
|
||||
const blockRef = detectBlockReference(lineText);
|
||||
let position: number;
|
||||
|
||||
if (blockRef) {
|
||||
// Insert before the block reference ID
|
||||
// Remove trailing space if exists
|
||||
let position = blockRef.index;
|
||||
position = blockRef.index;
|
||||
if (position > 0 && lineText[position - 1] === " ") {
|
||||
position--;
|
||||
}
|
||||
return position;
|
||||
} else {
|
||||
// If no block reference, insert at the very end
|
||||
position = lineText.length;
|
||||
}
|
||||
|
||||
// If no block reference, insert at the very end
|
||||
return lineText.length;
|
||||
// Validate position is within bounds
|
||||
position = Math.min(position, lineText.length);
|
||||
position = Math.max(0, position);
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class TaskStatusWidget extends WidgetType {
|
|||
readonly from: number,
|
||||
readonly to: number,
|
||||
readonly currentState: TaskState,
|
||||
readonly listPrefix: string
|
||||
readonly listPrefix: string,
|
||||
) {
|
||||
super();
|
||||
const config = this.getStatusConfig();
|
||||
|
|
@ -69,7 +69,7 @@ class TaskStatusWidget extends WidgetType {
|
|||
let nextState = this.currentState;
|
||||
|
||||
const remainingCycle = cycle.filter(
|
||||
(state) => !excludeMarksFromCycle.includes(state)
|
||||
(state) => !excludeMarksFromCycle.includes(state),
|
||||
);
|
||||
|
||||
if (remainingCycle.length > 0) {
|
||||
|
|
@ -101,7 +101,7 @@ class TaskStatusWidget extends WidgetType {
|
|||
cls: isNumberedList ? "list-number" : "list-bullet",
|
||||
text: this.bulletText,
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +111,7 @@ class TaskStatusWidget extends WidgetType {
|
|||
"task-state",
|
||||
this.isLivePreview ? "live-preview-mode" : "source-mode",
|
||||
],
|
||||
true
|
||||
true,
|
||||
);
|
||||
|
||||
// Add a specific class based on the mode
|
||||
|
|
@ -253,7 +253,7 @@ class TaskStatusWidget extends WidgetType {
|
|||
const { cycle, marks, excludeMarksFromCycle } = this.getStatusConfig();
|
||||
|
||||
const remainingCycle = cycle.filter(
|
||||
(state) => !excludeMarksFromCycle.includes(state)
|
||||
(state) => !excludeMarksFromCycle.includes(state),
|
||||
);
|
||||
|
||||
if (remainingCycle.length === 0) {
|
||||
|
|
@ -265,7 +265,7 @@ class TaskStatusWidget extends WidgetType {
|
|||
}
|
||||
// If no cycle is available, trigger the default editor:toggle-checklist-status command
|
||||
this.app.commands.executeCommandById(
|
||||
"editor:toggle-checklist-status"
|
||||
"editor:toggle-checklist-status",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
|
@ -292,20 +292,6 @@ class TaskStatusWidget extends WidgetType {
|
|||
// Replace text
|
||||
const newText = currentText.replace(/\[(.)]/, `[${nextMark}]`);
|
||||
|
||||
// if (nextMark === "x" || nextMark === "X") {
|
||||
// const line = this.view.state.doc.lineAt(this.from);
|
||||
// const path =
|
||||
// this.view.state.field(editorInfoField)?.file?.path || "";
|
||||
// const task = parseTaskLine(
|
||||
// path,
|
||||
// line.text,
|
||||
// line.number,
|
||||
// this.plugin.settings.preferMetadataFormat
|
||||
// );
|
||||
// task &&
|
||||
// this.app.workspace.trigger("task-genius:task-completed", task);
|
||||
// }
|
||||
|
||||
this.view.dispatch({
|
||||
changes: {
|
||||
from: this.from,
|
||||
|
|
@ -320,12 +306,12 @@ class TaskStatusWidget extends WidgetType {
|
|||
|
||||
export function taskStatusSwitcherExtension(
|
||||
app: App,
|
||||
plugin: TaskProgressBarPlugin
|
||||
plugin: TaskProgressBarPlugin,
|
||||
) {
|
||||
class TaskStatusViewPluginValue implements PluginValue {
|
||||
public readonly view: EditorView;
|
||||
decorations: DecorationSet = Decoration.none;
|
||||
private lastUpdate: number = 0;
|
||||
private lastUpdate = 0;
|
||||
private readonly updateThreshold: number = 50;
|
||||
private readonly match = new MatchDecorator({
|
||||
regexp: /^(\s*)((?:[-*+]|\d+[.)])\s)(\[(.)]\s)/g,
|
||||
|
|
@ -334,7 +320,7 @@ export function taskStatusSwitcherExtension(
|
|||
from: number,
|
||||
to: number,
|
||||
match: RegExpExecArray,
|
||||
view: EditorView
|
||||
view: EditorView,
|
||||
) => {
|
||||
if (!this.shouldRender(view, from, to)) {
|
||||
return;
|
||||
|
|
@ -351,7 +337,7 @@ export function taskStatusSwitcherExtension(
|
|||
const excludeMarksFromCycle =
|
||||
plugin.settings.excludeMarksFromCycle || [];
|
||||
const remainingCycle = cycle.filter(
|
||||
(state) => !excludeMarksFromCycle.includes(state)
|
||||
(state) => !excludeMarksFromCycle.includes(state),
|
||||
);
|
||||
|
||||
if (
|
||||
|
|
@ -385,9 +371,9 @@ export function taskStatusSwitcherExtension(
|
|||
checkboxStart,
|
||||
checkboxEnd,
|
||||
currentState,
|
||||
bulletText
|
||||
bulletText,
|
||||
),
|
||||
})
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
// In Live Preview mode, replace the whole bullet point + checkbox
|
||||
|
|
@ -408,9 +394,9 @@ export function taskStatusSwitcherExtension(
|
|||
bulletWithSpace.length +
|
||||
checkbox.length,
|
||||
currentState,
|
||||
bulletText
|
||||
bulletText,
|
||||
),
|
||||
})
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
@ -449,7 +435,7 @@ export function taskStatusSwitcherExtension(
|
|||
} else {
|
||||
this.decorations = this.match.updateDeco(
|
||||
update,
|
||||
this.decorations
|
||||
this.decorations,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -461,10 +447,10 @@ export function taskStatusSwitcherExtension(
|
|||
shouldRender(
|
||||
view: EditorView,
|
||||
decorationFrom: number,
|
||||
decorationTo: number
|
||||
decorationTo: number,
|
||||
) {
|
||||
const syntaxNode = syntaxTree(view.state).resolveInner(
|
||||
decorationFrom + 1
|
||||
decorationFrom + 1,
|
||||
);
|
||||
const nodeProps = syntaxNode.type.prop(tokenClassNodeProp);
|
||||
|
||||
|
|
@ -498,7 +484,7 @@ export function taskStatusSwitcherExtension(
|
|||
filter: (
|
||||
rangeFrom: number,
|
||||
rangeTo: number,
|
||||
deco: Decoration
|
||||
deco: Decoration,
|
||||
) => {
|
||||
const widget = deco.spec?.widget;
|
||||
if ((widget as any).error) {
|
||||
|
|
@ -521,6 +507,6 @@ export function taskStatusSwitcherExtension(
|
|||
|
||||
return ViewPlugin.fromClass(
|
||||
TaskStatusViewPluginValue,
|
||||
TaskStatusViewPluginSpec
|
||||
TaskStatusViewPluginSpec,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ function getIndentation(text: string): string {
|
|||
function removeTrailingIndentation(
|
||||
indentation: string,
|
||||
app: App,
|
||||
levels: number = 1,
|
||||
levels: number = 1
|
||||
): string {
|
||||
const indentUnit = buildIndentString(app);
|
||||
const removal = indentUnit.repeat(levels);
|
||||
|
|
@ -73,7 +73,7 @@ function removeTrailingIndentation(
|
|||
if (indentation.endsWith(removal)) {
|
||||
return indentation.slice(
|
||||
0,
|
||||
Math.max(0, indentation.length - removal.length),
|
||||
Math.max(0, indentation.length - removal.length)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ function removeTrailingIndentation(
|
|||
const fallbackLength = indentUnit.length * levels;
|
||||
return indentation.slice(
|
||||
0,
|
||||
Math.max(0, indentation.length - fallbackLength),
|
||||
Math.max(0, indentation.length - fallbackLength)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ interface TextRange {
|
|||
*/
|
||||
function calculateRangeForTransform(
|
||||
state: EditorState,
|
||||
pos: number,
|
||||
pos: number
|
||||
): TextRange | null {
|
||||
const line = state.doc.lineAt(pos);
|
||||
const foldRange = foldable(state, line.from, line.to);
|
||||
|
|
@ -226,7 +226,7 @@ export function findParentWorkflow(doc: Text, lineNum: number): string | null {
|
|||
export function handleWorkflowTransaction(
|
||||
tr: Transaction,
|
||||
app: App,
|
||||
plugin: TaskProgressBarPlugin,
|
||||
plugin: TaskProgressBarPlugin
|
||||
): TransactionSpec {
|
||||
// Only process if workflow feature is enabled
|
||||
if (!plugin.settings.workflow.enableWorkflow) {
|
||||
|
|
@ -239,12 +239,14 @@ export function handleWorkflowTransaction(
|
|||
}
|
||||
|
||||
// Skip if this transaction already has a workflow or task status annotation
|
||||
const tsAnn = tr.annotation(taskStatusChangeAnnotation) as
|
||||
| string
|
||||
| undefined;
|
||||
if (
|
||||
tr.annotation(workflowChangeAnnotation) ||
|
||||
tr.annotation(priorityChangeAnnotation) ||
|
||||
(tr.annotation(taskStatusChangeAnnotation) as string)?.startsWith(
|
||||
"workflowChange",
|
||||
)
|
||||
tsAnn?.startsWith("workflowChange") ||
|
||||
tsAnn?.startsWith("autoDateManager")
|
||||
) {
|
||||
return tr;
|
||||
}
|
||||
|
|
@ -271,7 +273,7 @@ export function handleWorkflowTransaction(
|
|||
const isCompletionChange = (text: string) =>
|
||||
completedStatuses.includes(text) ||
|
||||
completedStatuses.some(
|
||||
(status) => text === `- [${status}]` || text === `[${status}]`,
|
||||
(status) => text === `- [${status}]` || text === `[${status}]`
|
||||
);
|
||||
|
||||
if (!changes.some((c) => isCompletionChange(c.text))) {
|
||||
|
|
@ -297,7 +299,7 @@ export function handleWorkflowTransaction(
|
|||
lineText,
|
||||
tr.newDoc,
|
||||
line.number,
|
||||
plugin,
|
||||
plugin
|
||||
);
|
||||
|
||||
if (resolvedInfo) {
|
||||
|
|
@ -309,6 +311,21 @@ export function handleWorkflowTransaction(
|
|||
}
|
||||
}
|
||||
|
||||
// Check if there are existing changes from other transaction filters
|
||||
// (e.g., date manager) and adjust our calculations accordingly
|
||||
let existingChangeAdjustment = 0;
|
||||
let hasExistingChanges = false;
|
||||
|
||||
tr.changes.iterChanges((fromA, toA, fromB, toB, inserted) => {
|
||||
// Calculate how much the document has grown from existing changes
|
||||
const insertedLength = inserted.length;
|
||||
const deletedLength = toA - fromA;
|
||||
existingChangeAdjustment += insertedLength - deletedLength;
|
||||
hasExistingChanges = true;
|
||||
});
|
||||
|
||||
// Logging moved to where it's used
|
||||
|
||||
const newChanges: { from: number; to: number; insert: string }[] = [];
|
||||
// Process each workflow update
|
||||
if (workflowUpdates.length > 0) {
|
||||
|
|
@ -334,22 +351,36 @@ export function handleWorkflowTransaction(
|
|||
line.from,
|
||||
line.number,
|
||||
workflowType,
|
||||
plugin,
|
||||
plugin
|
||||
);
|
||||
newChanges.push(...timeChanges);
|
||||
|
||||
// Adjust time change positions if there are existing changes
|
||||
const adjustedTimeChanges = timeChanges;
|
||||
|
||||
for (const change of adjustedTimeChanges) {
|
||||
newChanges.push(change);
|
||||
}
|
||||
|
||||
let stageRemovalChange: {
|
||||
from: number;
|
||||
to: number;
|
||||
insert: string;
|
||||
} | null = null;
|
||||
|
||||
// Remove the [stage::] marker from the current line
|
||||
if (plugin.settings.workflow.autoRemoveLastStageMarker) {
|
||||
const stageMarker = safelyFindStageMarker(line.text);
|
||||
if (stageMarker) {
|
||||
newChanges.push({
|
||||
from: line.from + stageMarker.index,
|
||||
to:
|
||||
line.from +
|
||||
stageMarker.index +
|
||||
stageMarker[0].length,
|
||||
let fromPos = line.from + stageMarker.index;
|
||||
let toPos =
|
||||
line.from + stageMarker.index + stageMarker[0].length;
|
||||
|
||||
stageRemovalChange = {
|
||||
from: fromPos,
|
||||
to: toPos,
|
||||
insert: "",
|
||||
});
|
||||
};
|
||||
newChanges.push(stageRemovalChange);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -376,9 +407,18 @@ export function handleWorkflowTransaction(
|
|||
if (rootTaskMatch) {
|
||||
const rootTaskStatus = rootTaskMatch[3];
|
||||
if (!completedStatuses.includes(rootTaskStatus)) {
|
||||
const rootTaskStart =
|
||||
let rootTaskStart =
|
||||
checkLine.from +
|
||||
rootTaskMatch[0].indexOf("[");
|
||||
|
||||
// Adjust positions if there are existing changes
|
||||
if (hasExistingChanges) {
|
||||
rootTaskStart = Math.max(
|
||||
0,
|
||||
rootTaskStart - existingChangeAdjustment
|
||||
);
|
||||
}
|
||||
|
||||
newChanges.push({
|
||||
from: rootTaskStart + 1,
|
||||
to: rootTaskStart + 2,
|
||||
|
|
@ -395,16 +435,32 @@ export function handleWorkflowTransaction(
|
|||
const { nextStageId, nextSubStageId } = determineNextStage(
|
||||
currentStage,
|
||||
workflow,
|
||||
currentSubStage,
|
||||
currentSubStage
|
||||
);
|
||||
|
||||
// Guard: If completing a parent cycle stage (no currentSubStage) and there's no explicit
|
||||
// next main stage configured (nextStageId remains current), skip auto-creating the first
|
||||
// substage to avoid unintended child looping when advancing parent via menu.
|
||||
if (
|
||||
currentStage.type === "cycle" &&
|
||||
!currentSubStage &&
|
||||
nextStageId === currentStage.id
|
||||
) {
|
||||
// Optional debug
|
||||
console.debug(
|
||||
"[WorkflowHandler] Skip auto-substage creation for parent cycle stage completion",
|
||||
{ line: line.number, stage: currentStage.id }
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const nextStage = workflow.stages.find((s) => s.id === nextStageId);
|
||||
if (!nextStage) continue;
|
||||
|
||||
let nextSubStage: WorkflowSubStage | undefined;
|
||||
if (nextSubStageId && nextStage.subStages) {
|
||||
nextSubStage = nextStage.subStages.find(
|
||||
(ss) => ss.id === nextSubStageId,
|
||||
(ss) => ss.id === nextSubStageId
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -419,15 +475,34 @@ export function handleWorkflowTransaction(
|
|||
newTaskIndentation,
|
||||
plugin,
|
||||
true,
|
||||
nextSubStage,
|
||||
nextSubStage
|
||||
);
|
||||
|
||||
const insertionPoint = determineTaskInsertionPoint(
|
||||
line,
|
||||
tr.newDoc,
|
||||
indentation,
|
||||
indentation
|
||||
);
|
||||
|
||||
const insertionAdjustment = calculateInsertionAdjustment(
|
||||
insertionPoint,
|
||||
line.from,
|
||||
[
|
||||
...timeChanges,
|
||||
...(stageRemovalChange ? [stageRemovalChange] : []),
|
||||
]
|
||||
);
|
||||
|
||||
// Calculate the adjusted insertion point
|
||||
let adjustedInsertionPoint = insertionPoint + insertionAdjustment;
|
||||
|
||||
// Ensure the insertion point is within the new document bounds (after original changes)
|
||||
adjustedInsertionPoint = Math.min(
|
||||
adjustedInsertionPoint,
|
||||
tr.newDoc.length
|
||||
);
|
||||
adjustedInsertionPoint = Math.max(0, adjustedInsertionPoint);
|
||||
|
||||
if (
|
||||
!(
|
||||
tr.annotation(taskStatusChangeAnnotation) ===
|
||||
|
|
@ -435,8 +510,8 @@ export function handleWorkflowTransaction(
|
|||
)
|
||||
) {
|
||||
newChanges.push({
|
||||
from: insertionPoint,
|
||||
to: insertionPoint,
|
||||
from: adjustedInsertionPoint,
|
||||
to: adjustedInsertionPoint,
|
||||
insert: `\n${completeTaskText}`,
|
||||
});
|
||||
}
|
||||
|
|
@ -444,8 +519,33 @@ export function handleWorkflowTransaction(
|
|||
}
|
||||
|
||||
if (newChanges.length > 0) {
|
||||
// Log only if processing with existing changes
|
||||
if (hasExistingChanges) {
|
||||
console.log(
|
||||
`[WorkflowHandler] Applied ${newChanges.length} changes (adjusted for existing changes)`
|
||||
);
|
||||
}
|
||||
|
||||
// Rebuild a single combined change list relative to the ORIGINAL doc
|
||||
// 1) Take the original transaction's changes as specs (relative to startState)
|
||||
const baseChangeSpecs: { from: number; to: number; insert: string }[] =
|
||||
[];
|
||||
tr.changes.iterChanges((fromA, toA, _fromB, _toB, inserted) => {
|
||||
baseChangeSpecs.push({
|
||||
from: fromA,
|
||||
to: toA,
|
||||
insert: inserted.toString(),
|
||||
});
|
||||
});
|
||||
// 2) Map our additional changes (currently in newDoc space) back to startState
|
||||
const inverse = tr.changes.invert(tr.startState.doc);
|
||||
const mappedNewChanges = newChanges.map((c) => ({
|
||||
from: inverse.mapPos(c.from, -1),
|
||||
to: inverse.mapPos(c.to, -1),
|
||||
insert: c.insert,
|
||||
}));
|
||||
return {
|
||||
changes: [tr.changes, ...newChanges],
|
||||
changes: [...baseChangeSpecs, ...mappedNewChanges],
|
||||
selection: tr.selection,
|
||||
annotations: workflowChangeAnnotation.of("workflowChange"),
|
||||
};
|
||||
|
|
@ -470,7 +570,7 @@ export function processTimestampAndCalculateTime(
|
|||
lineFrom: number,
|
||||
lineNumber: number,
|
||||
workflowType: string,
|
||||
plugin: TaskProgressBarPlugin,
|
||||
plugin: TaskProgressBarPlugin
|
||||
): { from: number; to: number; insert: string }[] {
|
||||
const changes: { from: number; to: number; insert: string }[] = [];
|
||||
|
||||
|
|
@ -489,7 +589,7 @@ export function processTimestampAndCalculateTime(
|
|||
const startTime = moment(
|
||||
timestampText.replace("🛫 ", ""),
|
||||
timestampFormat,
|
||||
true,
|
||||
true
|
||||
);
|
||||
|
||||
if (!startTime.isValid()) {
|
||||
|
|
@ -502,7 +602,7 @@ export function processTimestampAndCalculateTime(
|
|||
lineText,
|
||||
lineNumber,
|
||||
doc,
|
||||
plugin,
|
||||
plugin
|
||||
);
|
||||
|
||||
// Remove timestamp if enabled
|
||||
|
|
@ -518,22 +618,28 @@ export function processTimestampAndCalculateTime(
|
|||
|
||||
// Add spent time if enabled
|
||||
if (plugin.settings.workflow.calculateSpentTime) {
|
||||
const spentTime = moment
|
||||
.utc(duration.asMilliseconds())
|
||||
.format(plugin.settings.workflow.spentTimeFormat);
|
||||
|
||||
// Determine insertion position (before any stage marker)
|
||||
const stageMarkerIndex = lineText.indexOf("[stage::");
|
||||
const insertPosition =
|
||||
lineFrom +
|
||||
(stageMarkerIndex !== -1 ? stageMarkerIndex : lineText.length);
|
||||
|
||||
if (!isFinalStage || !plugin.settings.workflow.calculateFullSpentTime) {
|
||||
changes.push({
|
||||
from: insertPosition,
|
||||
to: insertPosition,
|
||||
insert: ` (⏱️ ${spentTime})`,
|
||||
});
|
||||
// Guard: avoid duplicating local spent time if it already exists on the line
|
||||
if (!TIME_SPENT_REGEX.test(lineText)) {
|
||||
const spentTime = moment
|
||||
.utc(duration.asMilliseconds())
|
||||
.format(plugin.settings.workflow.spentTimeFormat);
|
||||
|
||||
if (
|
||||
!isFinalStage ||
|
||||
!plugin.settings.workflow.calculateFullSpentTime
|
||||
) {
|
||||
changes.push({
|
||||
from: insertPosition,
|
||||
to: insertPosition,
|
||||
insert: ` (⏱️ ${spentTime})`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate and add total time for final stage if enabled
|
||||
|
|
@ -558,7 +664,7 @@ export function processTimestampAndCalculateTime(
|
|||
const taskLine = doc.line(j);
|
||||
|
||||
const indentLevel = getIndentation(
|
||||
taskLine.text,
|
||||
taskLine.text
|
||||
).length;
|
||||
|
||||
if (indentLevel > currentIndentLevel) {
|
||||
|
|
@ -634,7 +740,7 @@ export function processTimestampAndCalculateTime(
|
|||
export function updateWorkflowContextMenu(
|
||||
menu: any,
|
||||
editor: Editor,
|
||||
plugin: TaskProgressBarPlugin,
|
||||
plugin: TaskProgressBarPlugin
|
||||
) {
|
||||
if (!plugin.settings.workflow.enableWorkflow) {
|
||||
return;
|
||||
|
|
@ -701,7 +807,7 @@ export function updateWorkflowContextMenu(
|
|||
false,
|
||||
editor,
|
||||
null as any,
|
||||
plugin,
|
||||
plugin
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -715,7 +821,7 @@ export function updateWorkflowContextMenu(
|
|||
false,
|
||||
editor,
|
||||
null as any,
|
||||
plugin,
|
||||
plugin
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -729,7 +835,7 @@ export function updateWorkflowContextMenu(
|
|||
false,
|
||||
editor,
|
||||
null as any,
|
||||
plugin,
|
||||
plugin
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -743,7 +849,7 @@ export function updateWorkflowContextMenu(
|
|||
line,
|
||||
editor.cm.state.doc,
|
||||
cursor.line + 1,
|
||||
plugin,
|
||||
plugin
|
||||
);
|
||||
|
||||
if (!resolvedInfo) {
|
||||
|
|
@ -771,7 +877,7 @@ export function updateWorkflowContextMenu(
|
|||
const firstStage = workflow.stages[0];
|
||||
submenu.addItem((nextItem: any) => {
|
||||
nextItem.setTitle(
|
||||
`${t("Move to stage")} ${firstStage.name}`,
|
||||
`${t("Move to stage")} ${firstStage.name}`
|
||||
);
|
||||
nextItem.onClick(() => {
|
||||
const changes = createWorkflowStageTransition(
|
||||
|
|
@ -782,13 +888,15 @@ export function updateWorkflowContextMenu(
|
|||
firstStage,
|
||||
true,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
|
||||
editor.cm.dispatch({
|
||||
changes,
|
||||
annotations:
|
||||
annotations: [
|
||||
taskStatusChangeAnnotation.of("workflowChange"),
|
||||
workflowChangeAnnotation.of("workflowChange"),
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -796,7 +904,7 @@ export function updateWorkflowContextMenu(
|
|||
} else if (currentStage.canProceedTo) {
|
||||
currentStage.canProceedTo.forEach((nextStageId) => {
|
||||
const nextStage = workflow.stages.find(
|
||||
(s) => s.id === nextStageId,
|
||||
(s) => s.id === nextStageId
|
||||
);
|
||||
|
||||
if (nextStage) {
|
||||
|
|
@ -806,14 +914,14 @@ export function updateWorkflowContextMenu(
|
|||
line,
|
||||
cursor.line,
|
||||
editor.cm.state.doc,
|
||||
plugin,
|
||||
plugin
|
||||
);
|
||||
|
||||
// If last stage, show "Complete stage" instead of "Move to"
|
||||
nextItem.setTitle(
|
||||
isLastStage
|
||||
? `${t("Complete stage")}: ${nextStage.name}`
|
||||
: `${t("Move to stage")} ${nextStage.name}`,
|
||||
: `${t("Move to stage")} ${nextStage.name}`
|
||||
);
|
||||
nextItem.onClick(() => {
|
||||
const changes = createWorkflowStageTransition(
|
||||
|
|
@ -824,15 +932,20 @@ export function updateWorkflowContextMenu(
|
|||
nextStage,
|
||||
false,
|
||||
undefined,
|
||||
currentSubStage,
|
||||
currentSubStage
|
||||
);
|
||||
editor.cm.dispatch({
|
||||
changes,
|
||||
annotations: taskStatusChangeAnnotation.of(
|
||||
isLastStage
|
||||
? "workflowChange.completeStage"
|
||||
: "workflowChange.moveToStage",
|
||||
),
|
||||
annotations: [
|
||||
taskStatusChangeAnnotation.of(
|
||||
isLastStage
|
||||
? "workflowChange.completeStage"
|
||||
: "workflowChange.moveToStage"
|
||||
),
|
||||
workflowChangeAnnotation.of(
|
||||
"workflowChange"
|
||||
),
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -850,13 +963,15 @@ export function updateWorkflowContextMenu(
|
|||
currentStage,
|
||||
false,
|
||||
undefined,
|
||||
currentSubStage,
|
||||
currentSubStage
|
||||
);
|
||||
|
||||
editor.cm.dispatch({
|
||||
changes,
|
||||
annotations:
|
||||
annotations: [
|
||||
taskStatusChangeAnnotation.of("workflowChange"),
|
||||
workflowChangeAnnotation.of("workflowChange"),
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -865,13 +980,13 @@ export function updateWorkflowContextMenu(
|
|||
const { nextStageId } = determineNextStage(
|
||||
currentStage,
|
||||
workflow,
|
||||
currentSubStage,
|
||||
currentSubStage
|
||||
);
|
||||
|
||||
// Only add menu option if there's a valid next stage that's different from current
|
||||
if (nextStageId && nextStageId !== currentStage.id) {
|
||||
const nextStage = workflow.stages.find(
|
||||
(s) => s.id === nextStageId,
|
||||
(s) => s.id === nextStageId
|
||||
);
|
||||
if (nextStage) {
|
||||
submenu.addItem((nextItem: any) => {
|
||||
|
|
@ -885,15 +1000,19 @@ export function updateWorkflowContextMenu(
|
|||
nextStage,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
|
||||
editor.cm.dispatch({
|
||||
changes,
|
||||
annotations:
|
||||
annotations: [
|
||||
taskStatusChangeAnnotation.of(
|
||||
"workflowChange",
|
||||
"workflowChange"
|
||||
),
|
||||
workflowChangeAnnotation.of(
|
||||
"workflowChange"
|
||||
),
|
||||
],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -918,12 +1037,14 @@ export function updateWorkflowContextMenu(
|
|||
firstStage,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
editor.cm.dispatch({
|
||||
changes,
|
||||
annotations:
|
||||
annotations: [
|
||||
taskStatusChangeAnnotation.of("workflowChange"),
|
||||
workflowChangeAnnotation.of("workflowChange"),
|
||||
],
|
||||
});
|
||||
}
|
||||
} else if (currentStage.id === "_root_task_") {
|
||||
|
|
@ -937,12 +1058,14 @@ export function updateWorkflowContextMenu(
|
|||
firstStage,
|
||||
false,
|
||||
undefined,
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
editor.cm.dispatch({
|
||||
changes,
|
||||
annotations:
|
||||
annotations: [
|
||||
taskStatusChangeAnnotation.of("workflowChange"),
|
||||
workflowChangeAnnotation.of("workflowChange"),
|
||||
],
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
|
@ -954,12 +1077,14 @@ export function updateWorkflowContextMenu(
|
|||
currentStage,
|
||||
false,
|
||||
currentSubStage,
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
editor.cm.dispatch({
|
||||
changes,
|
||||
annotations:
|
||||
annotations: [
|
||||
taskStatusChangeAnnotation.of("workflowChange"),
|
||||
workflowChangeAnnotation.of("workflowChange"),
|
||||
],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
@ -980,7 +1105,7 @@ export function isLastWorkflowStageOrNotWorkflow(
|
|||
lineText: string,
|
||||
lineNumber: number,
|
||||
doc: Text,
|
||||
plugin: TaskProgressBarPlugin,
|
||||
plugin: TaskProgressBarPlugin
|
||||
): boolean {
|
||||
const workflowInfo = extractWorkflowInfo(lineText);
|
||||
if (!workflowInfo) {
|
||||
|
|
@ -1002,7 +1127,7 @@ export function isLastWorkflowStageOrNotWorkflow(
|
|||
}
|
||||
|
||||
const workflow = plugin.settings.workflow.definitions.find(
|
||||
(wf: WorkflowDefinition) => wf.id === workflowType,
|
||||
(wf: WorkflowDefinition) => wf.id === workflowType
|
||||
);
|
||||
|
||||
if (!workflow) {
|
||||
|
|
@ -1028,7 +1153,7 @@ export function isLastWorkflowStageOrNotWorkflow(
|
|||
currentSubStageId
|
||||
) {
|
||||
const currentSubStage = currentStage.subStages.find(
|
||||
(ss) => ss.id === currentSubStageId,
|
||||
(ss) => ss.id === currentSubStageId
|
||||
);
|
||||
if (!currentSubStage) {
|
||||
return true;
|
||||
|
|
@ -1047,7 +1172,7 @@ export function isLastWorkflowStageOrNotWorkflow(
|
|||
!parentStageHasLinearNext
|
||||
) {
|
||||
const currentIndex = workflow.stages.findIndex(
|
||||
(s) => s.id === currentStage.id,
|
||||
(s) => s.id === currentStage.id
|
||||
);
|
||||
if (currentIndex === workflow.stages.length - 1) {
|
||||
return true;
|
||||
|
|
@ -1064,7 +1189,7 @@ export function isLastWorkflowStageOrNotWorkflow(
|
|||
}
|
||||
|
||||
const currentIndex = workflow.stages.findIndex(
|
||||
(s) => s.id === currentStage.id,
|
||||
(s) => s.id === currentStage.id
|
||||
);
|
||||
if (currentIndex < 0) {
|
||||
return true;
|
||||
|
|
@ -1086,7 +1211,7 @@ export function isLastWorkflowStageOrNotWorkflow(
|
|||
export function determineNextStage(
|
||||
currentStage: WorkflowStage,
|
||||
workflow: WorkflowDefinition,
|
||||
currentSubStage?: WorkflowSubStage,
|
||||
currentSubStage?: WorkflowSubStage
|
||||
): { nextStageId: string; nextSubStageId?: string } {
|
||||
let nextStageId = currentStage.id;
|
||||
let nextSubStageId: string | undefined;
|
||||
|
|
@ -1135,7 +1260,7 @@ export function determineNextStage(
|
|||
nextStageId = currentStage.canProceedTo[0];
|
||||
} else {
|
||||
const currentIndex = workflow.stages.findIndex(
|
||||
(stage) => stage.id === currentStage.id,
|
||||
(stage) => stage.id === currentStage.id
|
||||
);
|
||||
if (
|
||||
currentIndex >= 0 &&
|
||||
|
|
@ -1166,7 +1291,7 @@ export function createWorkflowStageTransition(
|
|||
nextStage: WorkflowStage,
|
||||
isRootTask: boolean,
|
||||
nextSubStage?: WorkflowSubStage,
|
||||
currentSubStage?: WorkflowSubStage,
|
||||
currentSubStage?: WorkflowSubStage
|
||||
) {
|
||||
const doc = editor.cm.state.doc;
|
||||
const app = plugin.app;
|
||||
|
|
@ -1185,7 +1310,7 @@ export function createWorkflowStageTransition(
|
|||
line,
|
||||
lineNumber,
|
||||
doc,
|
||||
plugin,
|
||||
plugin
|
||||
);
|
||||
|
||||
const taskMatch = line.match(TASK_REGEX);
|
||||
|
|
@ -1214,7 +1339,7 @@ export function createWorkflowStageTransition(
|
|||
lineStart.from,
|
||||
lineNumber,
|
||||
workflowType,
|
||||
plugin,
|
||||
plugin
|
||||
);
|
||||
changes.push(...timeChanges);
|
||||
|
||||
|
|
@ -1239,13 +1364,15 @@ export function createWorkflowStageTransition(
|
|||
indentation,
|
||||
plugin,
|
||||
true,
|
||||
nextSubStage,
|
||||
nextSubStage
|
||||
);
|
||||
|
||||
// Add the new task after the current line
|
||||
// Ensure the insertion point is within document bounds
|
||||
const insertPoint = Math.min(lineStart.to, doc.length);
|
||||
changes.push({
|
||||
from: lineStart.to,
|
||||
to: lineStart.to,
|
||||
from: insertPoint,
|
||||
to: insertPoint,
|
||||
insert: `\n${newTaskText}`,
|
||||
});
|
||||
}
|
||||
|
|
@ -1277,7 +1404,7 @@ export function resolveWorkflowInfo(
|
|||
lineText: string,
|
||||
doc: Text,
|
||||
lineNumber: number,
|
||||
plugin: TaskProgressBarPlugin,
|
||||
plugin: TaskProgressBarPlugin
|
||||
): {
|
||||
workflowType: string;
|
||||
currentStage: WorkflowStage;
|
||||
|
|
@ -1305,7 +1432,7 @@ export function resolveWorkflowInfo(
|
|||
}
|
||||
|
||||
const workflow = plugin.settings.workflow.definitions.find(
|
||||
(wf: WorkflowDefinition) => wf.id === workflowType,
|
||||
(wf: WorkflowDefinition) => wf.id === workflowType
|
||||
);
|
||||
if (!workflow) {
|
||||
return null;
|
||||
|
|
@ -1337,7 +1464,7 @@ export function resolveWorkflowInfo(
|
|||
let currentSubStage: WorkflowSubStage | undefined;
|
||||
if (subStageId && currentStage.subStages) {
|
||||
currentSubStage = currentStage.subStages.find(
|
||||
(ss) => ss.id === subStageId,
|
||||
(ss) => ss.id === subStageId
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1364,14 +1491,14 @@ export function generateWorkflowTaskText(
|
|||
indentation: string,
|
||||
plugin: TaskProgressBarPlugin,
|
||||
addSubtasks: boolean = true,
|
||||
nextSubStage?: WorkflowSubStage,
|
||||
nextSubStage?: WorkflowSubStage
|
||||
): string {
|
||||
// Generate timestamp if configured
|
||||
const timestamp = plugin.settings.workflow.autoAddTimestamp
|
||||
? ` 🛫 ${moment().format(
|
||||
plugin.settings.workflow.timestampFormat ||
|
||||
"YYYY-MM-DD HH:mm:ss",
|
||||
)}`
|
||||
"YYYY-MM-DD HH:mm:ss"
|
||||
)}`
|
||||
: "";
|
||||
const defaultIndentation = buildIndentString(plugin.app);
|
||||
|
||||
|
|
@ -1406,11 +1533,16 @@ export function generateWorkflowTaskText(
|
|||
export function determineTaskInsertionPoint(
|
||||
line: { number: number; to: number; text: string },
|
||||
doc: Text,
|
||||
indentation: string,
|
||||
indentation: string
|
||||
): number {
|
||||
// Default insertion point is after the current line
|
||||
let insertionPoint = line.to;
|
||||
|
||||
// Validate that the initial insertion point is within bounds
|
||||
if (insertionPoint > doc.length) {
|
||||
insertionPoint = doc.length;
|
||||
}
|
||||
|
||||
// Check if there are child tasks by looking for lines with greater indentation
|
||||
const lineIndent = indentation.length;
|
||||
let lastChildLine = line.number;
|
||||
|
|
@ -1418,11 +1550,14 @@ export function determineTaskInsertionPoint(
|
|||
|
||||
// Look at the next 20 lines to find potential child tasks
|
||||
// This is a reasonable limit for most task hierarchies
|
||||
for (
|
||||
let i = line.number + 1;
|
||||
i <= Math.min(line.number + 20, doc.lines);
|
||||
i++
|
||||
) {
|
||||
const maxSearchLine = Math.min(line.number + 20, doc.lines);
|
||||
|
||||
for (let i = line.number + 1; i <= maxSearchLine; i++) {
|
||||
// Ensure the line number is valid
|
||||
if (i > doc.lines) {
|
||||
break;
|
||||
}
|
||||
|
||||
const checkLine = doc.line(i);
|
||||
const checkIndent = getIndentation(checkLine.text).length;
|
||||
|
||||
|
|
@ -1435,9 +1570,51 @@ export function determineTaskInsertionPoint(
|
|||
}
|
||||
|
||||
// If we found child tasks, insert after the last child
|
||||
if (foundChildren) {
|
||||
insertionPoint = doc.line(lastChildLine).to;
|
||||
if (foundChildren && lastChildLine <= doc.lines) {
|
||||
const lastChild = doc.line(lastChildLine);
|
||||
insertionPoint = lastChild.to;
|
||||
|
||||
// Ensure the insertion point doesn't exceed document bounds
|
||||
if (insertionPoint > doc.length) {
|
||||
insertionPoint = doc.length;
|
||||
}
|
||||
}
|
||||
|
||||
return insertionPoint;
|
||||
}
|
||||
|
||||
function calculateInsertionAdjustment(
|
||||
baseInsertionPoint: number,
|
||||
lineStart: number,
|
||||
changes: { from: number; to: number; insert: string }[]
|
||||
): number {
|
||||
let adjustment = 0;
|
||||
|
||||
// Sort changes by position (descending) to process from end to beginning
|
||||
const sortedChanges = [...changes].sort((a, b) => b.from - a.from);
|
||||
|
||||
for (const change of sortedChanges) {
|
||||
const changeStart = change.from;
|
||||
const changeEnd = change.to ?? change.from;
|
||||
|
||||
// Only consider changes that occur before the insertion point
|
||||
// This accounts for ALL changes that affect the insertion position
|
||||
if (changeStart >= baseInsertionPoint) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const insertedLength = change.insert ? change.insert.length : 0;
|
||||
const removedLength = changeEnd - changeStart;
|
||||
|
||||
// If the change ends before the insertion point, adjust by the full amount
|
||||
if (changeEnd <= baseInsertionPoint) {
|
||||
adjustment += insertedLength - removedLength;
|
||||
} else {
|
||||
// If the change partially overlaps, only adjust by the part before insertion point
|
||||
const effectiveRemovedLength = baseInsertionPoint - changeStart;
|
||||
adjustment += insertedLength - effectiveRemovedLength;
|
||||
}
|
||||
}
|
||||
|
||||
return adjustment;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.onboarding-view:has(.component-showcase) .onboarding-header {
|
||||
.onboarding-view:has(.component-showcase, .intro-line) .onboarding-header {
|
||||
padding: calc(var(--onboarding-spacing) * 2) var(--onboarding-spacing)
|
||||
var(--size-4-2) var(--onboarding-spacing);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -222,7 +222,6 @@
|
|||
padding-top: var(--size-4-4);
|
||||
}
|
||||
|
||||
|
||||
.fluent-view-tabs.tg-index-task-source-switcher {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
|
@ -274,3 +273,11 @@
|
|||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.file-name-templates-container input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.file-name-templates-container .setting-item-info {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@
|
|||
.workspace-settings-selector-button .workspace-icon svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Workspace name */
|
||||
|
|
@ -128,3 +127,9 @@
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.task-genius-settings .tg-workspace-selector-container .workspace-icon {
|
||||
width: unset;
|
||||
height: unset;
|
||||
color: inherit;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ export const getCachedChangelog = (
|
|||
|
||||
export const getLatestCachedChangelog = (
|
||||
isBeta: boolean,
|
||||
app?: App,
|
||||
app: App,
|
||||
): ChangelogCacheEntry | null => {
|
||||
const cache = loadCache(app);
|
||||
const channel = getChannelKey(isBeta);
|
||||
|
|
@ -179,7 +179,7 @@ export const cacheChangelog = (
|
|||
version: string,
|
||||
isBeta: boolean,
|
||||
data: Pick<ChangelogCacheEntry, "markdown" | "sourceUrl">,
|
||||
app?: App,
|
||||
app: App,
|
||||
): void => {
|
||||
const cache = loadCache(app);
|
||||
const channel = getChannelKey(isBeta);
|
||||
|
|
|
|||
26
styles.css
26
styles.css
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue