mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
feat(onboarding): enhance setup with modal-driven file filter editing
Refactor onboarding system with improved component lifecycle and enhanced file filter management. Replace prompt-based interactions with modal dialogs and add comprehensive autocomplete support. Key changes: - Add OnboardingStepComponent base class for consistent lifecycle management - Introduce FileFilterRuleEditorModal for interactive rule editing - Replace prompts with modal-based file/folder selection with autocomplete - Add per-rule scope configuration and inline editing capabilities - Update view title from "Onboarding" to "Setup" with custom icon - Extend OnboardingLayout to Component class for better resource management - Improve UI polish with toggleVisibility and enhanced styling
This commit is contained in:
parent
22865d135c
commit
e17caaff8f
18 changed files with 1089 additions and 523 deletions
|
|
@ -1,4 +1,8 @@
|
|||
import { OnboardingConfig, OnboardingConfigMode } from "@/managers/onboarding-manager";
|
||||
import {
|
||||
OnboardingConfig,
|
||||
OnboardingConfigMode,
|
||||
} from "@/managers/onboarding-manager";
|
||||
import { Component } from "obsidian";
|
||||
|
||||
export enum OnboardingStep {
|
||||
INTRO = 0,
|
||||
|
|
@ -26,16 +30,16 @@ export interface OnboardingState {
|
|||
isCompleting: boolean;
|
||||
userHasChanges: boolean;
|
||||
changesSummary: string[];
|
||||
uiMode: 'fluent' | 'legacy';
|
||||
uiMode: "fluent" | "legacy";
|
||||
useSideLeaves: boolean;
|
||||
settingsCheckAction?: 'wizard' | 'keep';
|
||||
settingsCheckAction?: "wizard" | "keep";
|
||||
}
|
||||
|
||||
export type OnboardingEventType =
|
||||
| 'step-changed'
|
||||
| 'state-updated'
|
||||
| 'navigation-blocked'
|
||||
| 'completed';
|
||||
| "step-changed"
|
||||
| "state-updated"
|
||||
| "navigation-blocked"
|
||||
| "completed";
|
||||
|
||||
export interface OnboardingEvent {
|
||||
type: OnboardingEventType;
|
||||
|
|
@ -53,7 +57,10 @@ export interface OnboardingEvent {
|
|||
*/
|
||||
export class OnboardingController {
|
||||
private state: OnboardingState;
|
||||
private listeners: Map<OnboardingEventType, ((event: OnboardingEvent) => void)[]> = new Map();
|
||||
private listeners: Map<
|
||||
OnboardingEventType,
|
||||
((event: OnboardingEvent) => void)[]
|
||||
> = new Map();
|
||||
|
||||
constructor(initialState?: Partial<OnboardingState>) {
|
||||
this.state = {
|
||||
|
|
@ -62,7 +69,7 @@ export class OnboardingController {
|
|||
isCompleting: false,
|
||||
userHasChanges: false,
|
||||
changesSummary: [],
|
||||
uiMode: 'fluent',
|
||||
uiMode: "fluent",
|
||||
useSideLeaves: true,
|
||||
...initialState,
|
||||
};
|
||||
|
|
@ -89,7 +96,7 @@ export class OnboardingController {
|
|||
*/
|
||||
updateState(updates: Partial<OnboardingState>) {
|
||||
this.state = { ...this.state, ...updates };
|
||||
this.emit('state-updated', { state: updates });
|
||||
this.emit("state-updated", { state: updates });
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -102,7 +109,7 @@ export class OnboardingController {
|
|||
/**
|
||||
* Set UI mode
|
||||
*/
|
||||
setUIMode(mode: 'fluent' | 'legacy') {
|
||||
setUIMode(mode: "fluent" | "legacy") {
|
||||
this.updateState({ uiMode: mode });
|
||||
}
|
||||
|
||||
|
|
@ -128,7 +135,7 @@ export class OnboardingController {
|
|||
async next(): Promise<boolean> {
|
||||
// Validate current step
|
||||
if (!this.validateCurrentStep()) {
|
||||
this.emit('navigation-blocked', { step: this.state.currentStep });
|
||||
this.emit("navigation-blocked", { step: this.state.currentStep });
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -143,7 +150,7 @@ export class OnboardingController {
|
|||
break;
|
||||
|
||||
case OnboardingStep.MODE_SELECT:
|
||||
if (this.state.uiMode === 'fluent') {
|
||||
if (this.state.uiMode === "fluent") {
|
||||
nextStep = OnboardingStep.FLUENT_OVERVIEW;
|
||||
} else {
|
||||
nextStep = OnboardingStep.SETTINGS_CHECK;
|
||||
|
|
@ -171,11 +178,11 @@ export class OnboardingController {
|
|||
|
||||
case OnboardingStep.SETTINGS_CHECK:
|
||||
// User decided to continue wizard
|
||||
if (this.state.settingsCheckAction === 'wizard') {
|
||||
if (this.state.settingsCheckAction === "wizard") {
|
||||
nextStep = OnboardingStep.USER_LEVEL_SELECT;
|
||||
} else {
|
||||
// User chose to keep settings, exit onboarding
|
||||
this.emit('completed', { step: currentStep });
|
||||
this.emit("completed", { step: currentStep });
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
|
@ -203,7 +210,7 @@ export class OnboardingController {
|
|||
|
||||
case OnboardingStep.COMPLETE:
|
||||
// Trigger completion
|
||||
this.emit('completed', { step: currentStep });
|
||||
this.emit("completed", { step: currentStep });
|
||||
return true;
|
||||
|
||||
default:
|
||||
|
|
@ -255,7 +262,7 @@ export class OnboardingController {
|
|||
|
||||
case OnboardingStep.SETTINGS_CHECK:
|
||||
// Go back to last fluent step or mode select based on UI mode
|
||||
if (this.state.uiMode === 'fluent') {
|
||||
if (this.state.uiMode === "fluent") {
|
||||
prevStep = OnboardingStep.FLUENT_TOPNAV;
|
||||
} else {
|
||||
prevStep = OnboardingStep.MODE_SELECT;
|
||||
|
|
@ -264,9 +271,12 @@ export class OnboardingController {
|
|||
|
||||
case OnboardingStep.USER_LEVEL_SELECT:
|
||||
// Go back based on whether we went through settings check
|
||||
if (this.state.userHasChanges && this.state.settingsCheckAction === 'wizard') {
|
||||
if (
|
||||
this.state.userHasChanges &&
|
||||
this.state.settingsCheckAction === "wizard"
|
||||
) {
|
||||
prevStep = OnboardingStep.SETTINGS_CHECK;
|
||||
} else if (this.state.uiMode === 'fluent') {
|
||||
} else if (this.state.uiMode === "fluent") {
|
||||
prevStep = OnboardingStep.FLUENT_TOPNAV;
|
||||
} else {
|
||||
prevStep = OnboardingStep.MODE_SELECT;
|
||||
|
|
@ -307,14 +317,14 @@ export class OnboardingController {
|
|||
*/
|
||||
setStep(step: OnboardingStep) {
|
||||
this.state.currentStep = step;
|
||||
this.emit('step-changed', { step });
|
||||
this.emit("step-changed", { step });
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip onboarding
|
||||
*/
|
||||
skip() {
|
||||
this.emit('completed', { step: this.state.currentStep });
|
||||
this.emit("completed", { step: this.state.currentStep });
|
||||
}
|
||||
|
||||
// ==================== Validation ====================
|
||||
|
|
@ -396,7 +406,10 @@ export class OnboardingController {
|
|||
/**
|
||||
* Unregister event listener
|
||||
*/
|
||||
off(event: OnboardingEventType, callback: (event: OnboardingEvent) => void) {
|
||||
off(
|
||||
event: OnboardingEventType,
|
||||
callback: (event: OnboardingEvent) => void
|
||||
) {
|
||||
const callbacks = this.listeners.get(event);
|
||||
if (callbacks) {
|
||||
const index = callbacks.indexOf(callback);
|
||||
|
|
@ -409,7 +422,10 @@ export class OnboardingController {
|
|||
/**
|
||||
* Emit event to all listeners
|
||||
*/
|
||||
private emit(type: OnboardingEventType, data: Partial<OnboardingEvent> = {}) {
|
||||
private emit(
|
||||
type: OnboardingEventType,
|
||||
data: Partial<OnboardingEvent> = {}
|
||||
) {
|
||||
const event: OnboardingEvent = { type, ...data };
|
||||
const callbacks = this.listeners.get(type);
|
||||
if (callbacks) {
|
||||
|
|
@ -417,3 +433,17 @@ export class OnboardingController {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class OnboardingStepComponent extends Component {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
abstract render(
|
||||
headerEl: HTMLElement,
|
||||
contentEl: HTMLElement,
|
||||
controller: OnboardingController
|
||||
): void;
|
||||
abstract clear(headerEl: HTMLElement, contentEl: HTMLElement): void;
|
||||
abstract getStep(): OnboardingStep;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { ButtonComponent } from "obsidian";
|
||||
import { ButtonComponent, Component } from "obsidian";
|
||||
import { t } from "@/translations/helper";
|
||||
import { OnboardingController, OnboardingStep } from "./OnboardingController";
|
||||
|
||||
|
|
@ -15,7 +15,7 @@ export interface OnboardingLayoutCallbacks {
|
|||
* - Content section (for steps to render into)
|
||||
* - Footer with navigation buttons
|
||||
*/
|
||||
export class OnboardingLayout {
|
||||
export class OnboardingLayout extends Component {
|
||||
private container: HTMLElement;
|
||||
private controller: OnboardingController;
|
||||
private callbacks: OnboardingLayoutCallbacks;
|
||||
|
|
@ -35,6 +35,7 @@ export class OnboardingLayout {
|
|||
controller: OnboardingController,
|
||||
callbacks: OnboardingLayoutCallbacks
|
||||
) {
|
||||
super();
|
||||
this.container = container;
|
||||
this.controller = controller;
|
||||
this.callbacks = callbacks;
|
||||
|
|
@ -63,7 +64,7 @@ export class OnboardingLayout {
|
|||
|
||||
// Shadow element
|
||||
this.container.createEl("div", {
|
||||
cls: "onboarding-shadow"
|
||||
cls: "onboarding-shadow",
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -102,11 +103,11 @@ export class OnboardingLayout {
|
|||
* Setup listeners for controller events
|
||||
*/
|
||||
private setupListeners() {
|
||||
this.controller.on('step-changed', () => {
|
||||
this.controller.on("step-changed", () => {
|
||||
this.updateButtons();
|
||||
});
|
||||
|
||||
this.controller.on('state-updated', () => {
|
||||
this.controller.on("state-updated", () => {
|
||||
this.updateButtons();
|
||||
});
|
||||
}
|
||||
|
|
@ -119,21 +120,15 @@ export class OnboardingLayout {
|
|||
const state = this.controller.getState();
|
||||
|
||||
// Skip button visibility
|
||||
this.skipButton.buttonEl.style.display = this.controller.canSkip()
|
||||
? "inline-block"
|
||||
: "none";
|
||||
this.skipButton.buttonEl.toggleVisibility(this.controller.canSkip());
|
||||
|
||||
// Back button visibility
|
||||
this.backButton.buttonEl.style.display = this.controller.canGoBack()
|
||||
? "inline-block"
|
||||
: "none";
|
||||
this.backButton.buttonEl.toggleVisibility(this.controller.canGoBack());
|
||||
|
||||
// Next button
|
||||
const isLastStep = step === OnboardingStep.COMPLETE;
|
||||
const isSettingsCheck = step === OnboardingStep.SETTINGS_CHECK;
|
||||
|
||||
// Show next button for all steps
|
||||
this.nextButton.buttonEl.style.display = "inline-block";
|
||||
this.nextButton.buttonEl.toggleVisibility(true);
|
||||
|
||||
// Update button text based on step and selection
|
||||
if (isSettingsCheck) {
|
||||
|
|
@ -181,29 +176,21 @@ export class OnboardingLayout {
|
|||
* Clear header content
|
||||
*/
|
||||
clearHeader() {
|
||||
// Remove all children and event listeners
|
||||
while (this.headerEl.firstChild) {
|
||||
this.headerEl.removeChild(this.headerEl.firstChild);
|
||||
}
|
||||
this.headerEl.empty();
|
||||
this.headerEl?.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear content
|
||||
*/
|
||||
clearContent() {
|
||||
// Remove all children and event listeners
|
||||
while (this.contentEl.firstChild) {
|
||||
this.contentEl.removeChild(this.contentEl.firstChild);
|
||||
}
|
||||
this.contentEl.empty();
|
||||
this.contentEl?.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show/hide footer
|
||||
*/
|
||||
setFooterVisible(visible: boolean) {
|
||||
this.footerEl.style.display = visible ? '' : 'none';
|
||||
this.footerEl.toggleVisibility(visible);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -77,24 +77,20 @@ export class OnboardingView extends ItemView {
|
|||
}
|
||||
|
||||
getDisplayText(): string {
|
||||
return t("Task Genius Onboarding");
|
||||
return t("Task Genius Setup");
|
||||
}
|
||||
|
||||
getIcon(): string {
|
||||
return "zap";
|
||||
return "task-genius";
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
// Create layout
|
||||
this.layout = new OnboardingLayout(
|
||||
this.containerEl,
|
||||
this.controller,
|
||||
{
|
||||
onNext: async () => this.handleNext(),
|
||||
onBack: async () => this.handleBack(),
|
||||
onSkip: async () => this.handleSkip(),
|
||||
}
|
||||
);
|
||||
this.layout = new OnboardingLayout(this.containerEl, this.controller, {
|
||||
onNext: async () => this.handleNext(),
|
||||
onBack: async () => this.handleBack(),
|
||||
onSkip: async () => this.handleSkip(),
|
||||
});
|
||||
|
||||
// Render initial step
|
||||
this.renderCurrentStep();
|
||||
|
|
@ -148,26 +144,54 @@ export class OnboardingView extends ItemView {
|
|||
break;
|
||||
|
||||
case OnboardingStep.MODE_SELECT:
|
||||
ModeSelectionStep.render(headerEl, contentEl, this.controller);
|
||||
ModeSelectionStep.render(
|
||||
headerEl,
|
||||
contentEl,
|
||||
this.controller
|
||||
);
|
||||
break;
|
||||
|
||||
case OnboardingStep.FLUENT_OVERVIEW:
|
||||
FluentOverviewStep.render(headerEl, contentEl, this.controller);
|
||||
FluentOverviewStep.render(
|
||||
headerEl,
|
||||
contentEl,
|
||||
this.controller
|
||||
);
|
||||
break;
|
||||
case OnboardingStep.FLUENT_WS_SELECTOR:
|
||||
FluentWorkspaceSelectorStep.render(headerEl, contentEl, this.controller);
|
||||
FluentWorkspaceSelectorStep.render(
|
||||
headerEl,
|
||||
contentEl,
|
||||
this.controller
|
||||
);
|
||||
break;
|
||||
case OnboardingStep.FLUENT_MAIN_NAV:
|
||||
FluentMainNavigationStep.render(headerEl, contentEl, this.controller);
|
||||
FluentMainNavigationStep.render(
|
||||
headerEl,
|
||||
contentEl,
|
||||
this.controller
|
||||
);
|
||||
break;
|
||||
case OnboardingStep.FLUENT_PROJECTS:
|
||||
FluentProjectSectionStep.render(headerEl, contentEl, this.controller);
|
||||
FluentProjectSectionStep.render(
|
||||
headerEl,
|
||||
contentEl,
|
||||
this.controller
|
||||
);
|
||||
break;
|
||||
case OnboardingStep.FLUENT_OTHER_VIEWS:
|
||||
FluentOtherViewsStep.render(headerEl, contentEl, this.controller);
|
||||
FluentOtherViewsStep.render(
|
||||
headerEl,
|
||||
contentEl,
|
||||
this.controller
|
||||
);
|
||||
break;
|
||||
case OnboardingStep.FLUENT_TOPNAV:
|
||||
FluentTopNavigationStep.render(headerEl, contentEl, this.controller);
|
||||
FluentTopNavigationStep.render(
|
||||
headerEl,
|
||||
contentEl,
|
||||
this.controller
|
||||
);
|
||||
break;
|
||||
|
||||
case OnboardingStep.SETTINGS_CHECK:
|
||||
|
|
@ -282,7 +306,10 @@ export class OnboardingView extends ItemView {
|
|||
// Navigate to next step
|
||||
const success = await this.controller.next();
|
||||
console.log("handleNext - Navigation result:", success);
|
||||
console.log("handleNext - New step:", OnboardingStep[this.controller.getCurrentStep()]);
|
||||
console.log(
|
||||
"handleNext - New step:",
|
||||
OnboardingStep[this.controller.getCurrentStep()]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -357,8 +384,9 @@ export class OnboardingView extends ItemView {
|
|||
}
|
||||
|
||||
if (isFluent) {
|
||||
(this.plugin.settings.experimental as any).v2Config.useWorkspaceSideLeaves =
|
||||
!!state.useSideLeaves;
|
||||
(
|
||||
this.plugin.settings.experimental as any
|
||||
).v2Config.useWorkspaceSideLeaves = !!state.useSideLeaves;
|
||||
}
|
||||
|
||||
await this.plugin.saveSettings();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,308 @@
|
|||
import { App, DropdownComponent, Modal, Setting, setIcon } from "obsidian";
|
||||
import { t } from "@/translations/helper";
|
||||
import TaskProgressBarPlugin from "@/index";
|
||||
import { FilterMode, FileFilterRule } from "@/common/setting-definition";
|
||||
import {
|
||||
FolderSuggest,
|
||||
SimpleFileSuggest as FileSuggest,
|
||||
} from "@/components/ui/inputs/AutoComplete";
|
||||
import "@/styles/file-filter-settings.css";
|
||||
|
||||
interface FileFilterRuleEditorModalOptions {
|
||||
autoAddRuleType?: FileFilterRule["type"];
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
export class FileFilterRuleEditorModal extends Modal {
|
||||
private rulesContainer: HTMLElement | null = null;
|
||||
private statsContainer: HTMLElement | null = null;
|
||||
private pendingRule?: FileFilterRule;
|
||||
private readonly plugin: TaskProgressBarPlugin;
|
||||
private readonly options: FileFilterRuleEditorModalOptions;
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
plugin: TaskProgressBarPlugin,
|
||||
options: FileFilterRuleEditorModalOptions = {}
|
||||
) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
this.options = options;
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
this.modalEl.addClass("file-filter-rule-editor-modal");
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
|
||||
contentEl.createEl("h2", { text: t("Edit File Filter Rules") });
|
||||
contentEl.createEl("p", {
|
||||
text: t(
|
||||
"Configure which files, folders, or patterns are included during task indexing."
|
||||
),
|
||||
cls: "setting-item-description",
|
||||
});
|
||||
|
||||
new Setting(contentEl)
|
||||
.setName(t("Filter Mode"))
|
||||
.setDesc(
|
||||
t(
|
||||
"Whitelist: include only specified paths. Blacklist: exclude specified paths."
|
||||
)
|
||||
)
|
||||
.addDropdown((dropdown) =>
|
||||
dropdown
|
||||
.addOption(
|
||||
FilterMode.WHITELIST,
|
||||
t("Whitelist (Include only)")
|
||||
)
|
||||
.addOption(FilterMode.BLACKLIST, t("Blacklist (Exclude)"))
|
||||
.setValue(this.plugin.settings.fileFilter.mode)
|
||||
.onChange(async (value: FilterMode) => {
|
||||
this.plugin.settings.fileFilter.mode = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.updateStats();
|
||||
})
|
||||
);
|
||||
|
||||
const actionSetting = new Setting(contentEl);
|
||||
actionSetting.settingEl.addClass("file-filter-rule-actions");
|
||||
|
||||
actionSetting
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText(t("Add File Rule"))
|
||||
.setCta()
|
||||
.onClick(() => {
|
||||
void this.addRule("file");
|
||||
})
|
||||
)
|
||||
.addButton((button) =>
|
||||
button.setButtonText(t("Add Folder Rule")).onClick(() => {
|
||||
void this.addRule("folder");
|
||||
})
|
||||
)
|
||||
.addButton((button) =>
|
||||
button.setButtonText(t("Add Pattern Rule")).onClick(() => {
|
||||
void this.addRule("pattern");
|
||||
})
|
||||
);
|
||||
|
||||
this.rulesContainer = contentEl.createDiv({
|
||||
cls: "file-filter-rules-container",
|
||||
});
|
||||
|
||||
this.statsContainer = contentEl.createDiv({
|
||||
cls: "file-filter-stats-preview",
|
||||
});
|
||||
|
||||
new Setting(contentEl).addButton((button) =>
|
||||
button
|
||||
.setButtonText(t("Done"))
|
||||
.setCta()
|
||||
.onClick(() => this.close())
|
||||
);
|
||||
|
||||
this.renderRules();
|
||||
this.updateStats();
|
||||
|
||||
if (this.options.autoAddRuleType) {
|
||||
void this.addRule(this.options.autoAddRuleType).then((rule) => {
|
||||
this.pendingRule = rule;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onClose() {
|
||||
if (this.pendingRule && !this.pendingRule.path.trim()) {
|
||||
const index = this.plugin.settings.fileFilter.rules.indexOf(
|
||||
this.pendingRule
|
||||
);
|
||||
if (index !== -1) {
|
||||
this.plugin.settings.fileFilter.rules.splice(index, 1);
|
||||
void this.plugin.saveSettings();
|
||||
}
|
||||
}
|
||||
|
||||
this.pendingRule = undefined;
|
||||
this.contentEl.empty();
|
||||
this.options.onClose?.();
|
||||
}
|
||||
|
||||
private async addRule(
|
||||
type: FileFilterRule["type"]
|
||||
): Promise<FileFilterRule> {
|
||||
const newRule: FileFilterRule = {
|
||||
type,
|
||||
path: "",
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
this.plugin.settings.fileFilter.rules.push(newRule);
|
||||
await this.plugin.saveSettings();
|
||||
this.renderRules();
|
||||
this.updateStats();
|
||||
return newRule;
|
||||
}
|
||||
|
||||
private renderRules() {
|
||||
if (!this.rulesContainer) return;
|
||||
|
||||
const container = this.rulesContainer;
|
||||
container.empty();
|
||||
|
||||
if (this.plugin.settings.fileFilter.rules.length === 0) {
|
||||
container.createEl("p", {
|
||||
text: t("No filter rules configured yet"),
|
||||
cls: "setting-item-description",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.plugin.settings.fileFilter.rules.forEach((rule, index) => {
|
||||
const ruleContainer = container.createDiv({
|
||||
cls: "file-filter-rule",
|
||||
});
|
||||
|
||||
const typeContainer = ruleContainer.createDiv({
|
||||
cls: "file-filter-rule-type",
|
||||
});
|
||||
typeContainer.createEl("label", { text: t("Type:") });
|
||||
|
||||
new DropdownComponent(typeContainer)
|
||||
.addOption("file", t("File"))
|
||||
.addOption("folder", t("Folder"))
|
||||
.addOption("pattern", t("Pattern"))
|
||||
.setValue(rule.type)
|
||||
.onChange(async (value: FileFilterRule["type"]) => {
|
||||
rule.type = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.renderRules();
|
||||
this.updateStats();
|
||||
});
|
||||
|
||||
const scopeContainer = ruleContainer.createDiv({
|
||||
cls: "file-filter-rule-scope",
|
||||
});
|
||||
scopeContainer.createEl("label", { text: t("Scope:") });
|
||||
new DropdownComponent(scopeContainer)
|
||||
.addOption("both", t("Both"))
|
||||
.addOption("inline", t("Inline"))
|
||||
.addOption("file", t("File"))
|
||||
.setValue((rule as any).scope || "both")
|
||||
.onChange(async (value: "both" | "inline" | "file") => {
|
||||
(rule as any).scope = value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
|
||||
const pathContainer = ruleContainer.createDiv({
|
||||
cls: "file-filter-rule-path",
|
||||
});
|
||||
pathContainer.createEl("label", { text: t("Path:") });
|
||||
|
||||
const pathInput = pathContainer.createEl("input", {
|
||||
type: "text",
|
||||
value: rule.path,
|
||||
placeholder:
|
||||
rule.type === "pattern"
|
||||
? "*.tmp, temp/*"
|
||||
: rule.type === "folder"
|
||||
? "path/to/folder"
|
||||
: "path/to/file.md",
|
||||
});
|
||||
|
||||
if (rule.type === "folder") {
|
||||
new FolderSuggest(
|
||||
this.plugin.app,
|
||||
pathInput,
|
||||
this.plugin,
|
||||
"single"
|
||||
);
|
||||
} else if (rule.type === "file") {
|
||||
new FileSuggest(pathInput, this.plugin, (file) => {
|
||||
rule.path = file.path;
|
||||
pathInput.value = file.path;
|
||||
this.plugin.saveSettings();
|
||||
});
|
||||
}
|
||||
|
||||
pathInput.addEventListener("input", async () => {
|
||||
rule.path = pathInput.value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
|
||||
const enabledContainer = ruleContainer.createDiv({
|
||||
cls: "file-filter-rule-enabled",
|
||||
});
|
||||
enabledContainer.createEl("label", { text: t("Enabled:") });
|
||||
|
||||
const enabledCheckbox = enabledContainer.createEl("input", {
|
||||
type: "checkbox",
|
||||
});
|
||||
enabledCheckbox.checked = rule.enabled;
|
||||
|
||||
enabledCheckbox.addEventListener("change", async () => {
|
||||
rule.enabled = enabledCheckbox.checked;
|
||||
await this.plugin.saveSettings();
|
||||
this.updateStats();
|
||||
});
|
||||
|
||||
const deleteButton = ruleContainer.createEl("button", {
|
||||
cls: "file-filter-rule-delete mod-destructive",
|
||||
});
|
||||
setIcon(deleteButton, "trash");
|
||||
deleteButton.title = t("Delete rule");
|
||||
|
||||
deleteButton.addEventListener("click", async () => {
|
||||
this.plugin.settings.fileFilter.rules.splice(index, 1);
|
||||
await this.plugin.saveSettings();
|
||||
this.renderRules();
|
||||
this.updateStats();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private updateStats() {
|
||||
if (!this.statsContainer) return;
|
||||
|
||||
const container = this.statsContainer;
|
||||
container.empty();
|
||||
|
||||
const activeRules = this.plugin.settings.fileFilter.rules.filter(
|
||||
(rule) => rule.enabled
|
||||
).length;
|
||||
|
||||
const stats = [
|
||||
{
|
||||
label: t("Active Rules"),
|
||||
value: activeRules.toString(),
|
||||
},
|
||||
{
|
||||
label: t("Filter Mode"),
|
||||
value:
|
||||
this.plugin.settings.fileFilter.mode ===
|
||||
FilterMode.WHITELIST
|
||||
? t("Whitelist")
|
||||
: t("Blacklist"),
|
||||
},
|
||||
{
|
||||
label: t("Status"),
|
||||
value: this.plugin.settings.fileFilter.enabled
|
||||
? t("Enabled")
|
||||
: t("Disabled"),
|
||||
},
|
||||
];
|
||||
|
||||
stats.forEach((stat) => {
|
||||
const statItem = container.createDiv({ cls: "filter-stat-item" });
|
||||
statItem.createSpan({
|
||||
text: stat.value,
|
||||
cls: "filter-stat-value",
|
||||
});
|
||||
statItem.createSpan({
|
||||
text: stat.label,
|
||||
cls: "filter-stat-label",
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,11 @@ import { t } from "@/translations/helper";
|
|||
import { OnboardingController } from "../OnboardingController";
|
||||
import { FilterMode, FileFilterRule } from "@/common/setting-definition";
|
||||
import TaskProgressBarPlugin from "@/index";
|
||||
import {
|
||||
FolderSuggest,
|
||||
SimpleFileSuggest as FileSuggest,
|
||||
} from "@/components/ui/inputs/AutoComplete";
|
||||
import { FileFilterRuleEditorModal } from "@/components/features/onboarding/modals/FileFilterRuleEditorModal";
|
||||
import "@/styles/onboarding-components.css";
|
||||
import "@/styles/file-filter-settings.css";
|
||||
|
||||
|
|
@ -117,23 +122,55 @@ export class FileFilterStep {
|
|||
cls: "setting-item-control",
|
||||
});
|
||||
|
||||
// Add file rule button
|
||||
const addFileBtn = buttonsContainer.createEl("button", {
|
||||
text: t("Add File"),
|
||||
cls: "mod-cta",
|
||||
});
|
||||
addFileBtn.addEventListener("click", () => {
|
||||
const modal = new FileFilterRuleEditorModal(plugin.app, plugin, {
|
||||
autoAddRuleType: "file",
|
||||
onClose: () => {
|
||||
const rulesEl = container.querySelector(
|
||||
".file-filter-rules-container"
|
||||
) as HTMLElement | null;
|
||||
const statsEl = container.querySelector(
|
||||
".file-filter-stats-preview"
|
||||
) as HTMLElement | null;
|
||||
if (rulesEl) {
|
||||
this.renderRules(rulesEl, plugin);
|
||||
}
|
||||
if (statsEl) {
|
||||
this.updateStats(statsEl, plugin);
|
||||
}
|
||||
},
|
||||
});
|
||||
modal.open();
|
||||
});
|
||||
|
||||
// Add folder rule button
|
||||
const addFolderBtn = buttonsContainer.createEl("button", {
|
||||
text: t("Add Folder"),
|
||||
cls: "mod-cta",
|
||||
});
|
||||
addFolderBtn.addEventListener("click", () => {
|
||||
const folderPath = prompt(t("Enter folder path:"), "");
|
||||
if (folderPath) {
|
||||
plugin.settings.fileFilter.rules.push({
|
||||
type: "folder",
|
||||
path: folderPath,
|
||||
enabled: true,
|
||||
});
|
||||
plugin.saveSettings();
|
||||
this.updateStats(container, plugin);
|
||||
new Notice(t("Folder rule added"));
|
||||
}
|
||||
const modal = new FileFilterRuleEditorModal(plugin.app, plugin, {
|
||||
autoAddRuleType: "folder",
|
||||
onClose: () => {
|
||||
const rulesEl = container.querySelector(
|
||||
".file-filter-rules-container"
|
||||
) as HTMLElement | null;
|
||||
const statsEl = container.querySelector(
|
||||
".file-filter-stats-preview"
|
||||
) as HTMLElement | null;
|
||||
if (rulesEl) {
|
||||
this.renderRules(rulesEl, plugin);
|
||||
}
|
||||
if (statsEl) {
|
||||
this.updateStats(statsEl, plugin);
|
||||
}
|
||||
},
|
||||
});
|
||||
modal.open();
|
||||
});
|
||||
|
||||
// Add pattern rule button
|
||||
|
|
@ -141,17 +178,24 @@ export class FileFilterStep {
|
|||
text: t("Add Pattern"),
|
||||
});
|
||||
addPatternBtn.addEventListener("click", () => {
|
||||
const pattern = prompt(t("Enter file pattern (e.g., *.tmp):"), "");
|
||||
if (pattern) {
|
||||
plugin.settings.fileFilter.rules.push({
|
||||
type: "pattern",
|
||||
path: pattern,
|
||||
enabled: true,
|
||||
});
|
||||
plugin.saveSettings();
|
||||
this.updateStats(container, plugin);
|
||||
new Notice(t("Pattern rule added"));
|
||||
}
|
||||
const modal = new FileFilterRuleEditorModal(plugin.app, plugin, {
|
||||
autoAddRuleType: "pattern",
|
||||
onClose: () => {
|
||||
const rulesEl = container.querySelector(
|
||||
".file-filter-rules-container"
|
||||
) as HTMLElement | null;
|
||||
const statsEl = container.querySelector(
|
||||
".file-filter-stats-preview"
|
||||
) as HTMLElement | null;
|
||||
if (rulesEl) {
|
||||
this.renderRules(rulesEl, plugin);
|
||||
}
|
||||
if (statsEl) {
|
||||
this.updateStats(statsEl, plugin);
|
||||
}
|
||||
},
|
||||
});
|
||||
modal.open();
|
||||
});
|
||||
|
||||
// Current rules list
|
||||
|
|
@ -168,7 +212,7 @@ export class FileFilterStep {
|
|||
}
|
||||
|
||||
/**
|
||||
* Render current rules
|
||||
* Render current rules with inline editing support
|
||||
*/
|
||||
private static renderRules(
|
||||
container: HTMLElement,
|
||||
|
|
@ -185,29 +229,117 @@ export class FileFilterStep {
|
|||
}
|
||||
|
||||
plugin.settings.fileFilter.rules.forEach((rule, index) => {
|
||||
const ruleEl = container.createDiv({ cls: "file-filter-rule" });
|
||||
const ruleContainer = container.createDiv({
|
||||
cls: "file-filter-rule",
|
||||
});
|
||||
|
||||
// Rule type icon
|
||||
const typeIcon = ruleEl.createSpan({ cls: "file-filter-rule-type" });
|
||||
setIcon(
|
||||
typeIcon,
|
||||
rule.type === "folder"
|
||||
? "folder"
|
||||
: rule.type === "file"
|
||||
? "file"
|
||||
: "regex"
|
||||
);
|
||||
// Rule type dropdown
|
||||
const typeContainer = ruleContainer.createDiv({
|
||||
cls: "file-filter-rule-type",
|
||||
});
|
||||
typeContainer.createEl("label", { text: t("Type:") });
|
||||
|
||||
// Rule path
|
||||
ruleEl.createSpan({
|
||||
text: rule.path,
|
||||
new DropdownComponent(typeContainer)
|
||||
.addOption("file", t("File"))
|
||||
.addOption("folder", t("Folder"))
|
||||
.addOption("pattern", t("Pattern"))
|
||||
.setValue(rule.type)
|
||||
.onChange(async (value: "file" | "folder" | "pattern") => {
|
||||
rule.type = value;
|
||||
await plugin.saveSettings();
|
||||
// Only re-render rules container, not the whole component
|
||||
this.renderRules(container, plugin);
|
||||
this.updateStats(
|
||||
container.parentElement?.querySelector(
|
||||
".file-filter-stats-preview"
|
||||
) as HTMLElement,
|
||||
plugin
|
||||
);
|
||||
});
|
||||
|
||||
// Rule scope dropdown (per-rule)
|
||||
const scopeContainer = ruleContainer.createDiv({
|
||||
cls: "file-filter-rule-scope",
|
||||
});
|
||||
scopeContainer.createEl("label", { text: t("Scope:") });
|
||||
new DropdownComponent(scopeContainer)
|
||||
.addOption("both", t("Both"))
|
||||
.addOption("inline", t("Inline"))
|
||||
.addOption("file", t("File"))
|
||||
.setValue((rule as any).scope || "both")
|
||||
.onChange(async (value: "both" | "inline" | "file") => {
|
||||
(rule as any).scope = value;
|
||||
await plugin.saveSettings();
|
||||
});
|
||||
|
||||
// Path input with autocomplete
|
||||
const pathContainer = ruleContainer.createDiv({
|
||||
cls: "file-filter-rule-path",
|
||||
});
|
||||
pathContainer.createEl("label", { text: t("Path:") });
|
||||
|
||||
const pathInput = pathContainer.createEl("input", {
|
||||
type: "text",
|
||||
value: rule.path,
|
||||
placeholder:
|
||||
rule.type === "pattern"
|
||||
? "*.tmp, temp/*"
|
||||
: rule.type === "folder"
|
||||
? "path/to/folder"
|
||||
: "path/to/file.md",
|
||||
});
|
||||
|
||||
// Add appropriate autocomplete based on rule type
|
||||
if (rule.type === "folder") {
|
||||
new FolderSuggest(
|
||||
plugin.app,
|
||||
pathInput,
|
||||
plugin,
|
||||
"single"
|
||||
);
|
||||
} else if (rule.type === "file") {
|
||||
new FileSuggest(pathInput, plugin, (file) => {
|
||||
rule.path = file.path;
|
||||
pathInput.value = file.path;
|
||||
plugin.saveSettings();
|
||||
});
|
||||
}
|
||||
|
||||
pathInput.addEventListener("input", async () => {
|
||||
rule.path = pathInput.value;
|
||||
await plugin.saveSettings();
|
||||
});
|
||||
|
||||
// Enabled toggle
|
||||
const enabledContainer = ruleContainer.createDiv({
|
||||
cls: "file-filter-rule-enabled",
|
||||
});
|
||||
enabledContainer.createEl("label", { text: t("Enabled:") });
|
||||
|
||||
const enabledCheckbox = enabledContainer.createEl("input", {
|
||||
type: "checkbox",
|
||||
});
|
||||
enabledCheckbox.checked = rule.enabled;
|
||||
|
||||
enabledCheckbox.addEventListener("change", async () => {
|
||||
rule.enabled = enabledCheckbox.checked;
|
||||
await plugin.saveSettings();
|
||||
this.updateStats(
|
||||
container.parentElement?.querySelector(
|
||||
".file-filter-stats-preview"
|
||||
) as HTMLElement,
|
||||
plugin
|
||||
);
|
||||
});
|
||||
|
||||
// Delete button
|
||||
const deleteBtn = ruleEl.createSpan({ cls: "clickable-icon" });
|
||||
setIcon(deleteBtn, "trash");
|
||||
deleteBtn.addEventListener("click", async () => {
|
||||
const deleteButton = ruleContainer.createEl("button", {
|
||||
cls: "file-filter-rule-delete mod-destructive",
|
||||
});
|
||||
setIcon(deleteButton, "trash");
|
||||
deleteButton.title = t("Delete rule");
|
||||
|
||||
deleteButton.addEventListener("click", async () => {
|
||||
plugin.settings.fileFilter.rules.splice(index, 1);
|
||||
await plugin.saveSettings();
|
||||
this.renderRules(container, plugin);
|
||||
|
|
|
|||
|
|
@ -4,43 +4,52 @@ import { ComponentPreviewFactory } from "../previews/ComponentPreviewFactory";
|
|||
import "@/styles/onboarding-components.css";
|
||||
|
||||
export class FluentMainNavigationStep {
|
||||
static render(
|
||||
headerEl: HTMLElement,
|
||||
contentEl: HTMLElement,
|
||||
controller: OnboardingController
|
||||
) {
|
||||
headerEl.empty();
|
||||
contentEl.empty();
|
||||
static render(
|
||||
headerEl: HTMLElement,
|
||||
contentEl: HTMLElement,
|
||||
controller: OnboardingController
|
||||
) {
|
||||
headerEl.empty();
|
||||
contentEl.empty();
|
||||
|
||||
headerEl.createEl("h1", { text: t("Main Navigation") });
|
||||
headerEl.createEl("p", {
|
||||
text: t("Access Inbox, Today, Upcoming and more from the primary section"),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
headerEl.createEl("h1", { text: t("Main Navigation") });
|
||||
headerEl.createEl("p", {
|
||||
text: t(
|
||||
"Access Inbox, Today, Upcoming and more from the primary section"
|
||||
),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
|
||||
const showcase = contentEl.createDiv({ cls: "component-showcase" });
|
||||
const preview = showcase.createDiv({ cls: "component-showcase-preview focus-mode" });
|
||||
const desc = showcase.createDiv({ cls: "component-showcase-description" });
|
||||
const showcase = contentEl.createDiv({ cls: "component-showcase" });
|
||||
const preview = showcase.createDiv({
|
||||
cls: "component-showcase-preview focus-mode",
|
||||
});
|
||||
const desc = showcase.createDiv({
|
||||
cls: "component-showcase-description",
|
||||
});
|
||||
|
||||
ComponentPreviewFactory.createSidebarPreview(preview);
|
||||
ComponentPreviewFactory.createSidebarPreview(preview);
|
||||
|
||||
const primary = preview.querySelector<HTMLElement>(".v2-sidebar-section-primary");
|
||||
primary?.classList.add("is-focused");
|
||||
const primary = preview.querySelector<HTMLElement>(
|
||||
".v2-sidebar-section-primary"
|
||||
);
|
||||
primary?.classList.add("is-focused");
|
||||
|
||||
const dimTargets = preview.querySelectorAll<HTMLElement>(
|
||||
".v2-sidebar-section-projects, .v2-sidebar-section-other"
|
||||
);
|
||||
dimTargets.forEach((el) => el.classList.add("is-dimmed"));
|
||||
const dimTargets = preview.querySelectorAll<HTMLElement>(
|
||||
".v2-sidebar-section-projects, .v2-sidebar-section-other"
|
||||
);
|
||||
dimTargets.forEach((el) => el.classList.add("is-dimmed"));
|
||||
|
||||
desc.createEl("h3", { text: t("Navigate core views") });
|
||||
desc.createEl("p", {
|
||||
text: t("Quickly jump to core views like Inbox, Today, Upcoming and Flagged."),
|
||||
});
|
||||
const ul = desc.createEl("ul", { cls: "component-feature-list" });
|
||||
[
|
||||
t("Unread counts and indicators keep you informed"),
|
||||
t("Keyboard-ready with clear selection states"),
|
||||
].forEach((txt) => ul.createEl("li", { text: txt }));
|
||||
}
|
||||
desc.createEl("h3", { text: t("Navigate core views") });
|
||||
desc.createEl("p", {
|
||||
text: t(
|
||||
"Quickly jump to core views like Inbox, Today, Upcoming and Flagged."
|
||||
),
|
||||
});
|
||||
const ul = desc.createEl("ul", { cls: "component-feature-list" });
|
||||
[
|
||||
t("Unread counts and indicators keep you informed"),
|
||||
t("Keyboard-ready with clear selection states"),
|
||||
].forEach((txt) => ul.createEl("li", { text: txt }));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,45 +4,52 @@ import { ComponentPreviewFactory } from "../previews/ComponentPreviewFactory";
|
|||
import "@/styles/onboarding-components.css";
|
||||
|
||||
export class FluentOtherViewsStep {
|
||||
static render(
|
||||
headerEl: HTMLElement,
|
||||
contentEl: HTMLElement,
|
||||
controller: OnboardingController
|
||||
) {
|
||||
headerEl.empty();
|
||||
contentEl.empty();
|
||||
static render(
|
||||
headerEl: HTMLElement,
|
||||
contentEl: HTMLElement,
|
||||
controller: OnboardingController
|
||||
) {
|
||||
headerEl.empty();
|
||||
contentEl.empty();
|
||||
|
||||
headerEl.createEl("h1", { text: t("Other Views") });
|
||||
headerEl.createEl("p", {
|
||||
text: t("Access Calendar, Gantt and Tags from the other views section"),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
headerEl.createEl("h1", { text: t("Other Views") });
|
||||
headerEl.createEl("p", {
|
||||
text: t(
|
||||
"Access Calendar, Gantt and Tags from the other views section"
|
||||
),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
|
||||
const showcase = contentEl.createDiv({ cls: "component-showcase" });
|
||||
const preview = showcase.createDiv({ cls: "component-showcase-preview focus-mode" });
|
||||
const desc = showcase.createDiv({ cls: "component-showcase-description" });
|
||||
const showcase = contentEl.createDiv({ cls: "component-showcase" });
|
||||
const preview = showcase.createDiv({
|
||||
cls: "component-showcase-preview focus-mode",
|
||||
});
|
||||
const desc = showcase.createDiv({
|
||||
cls: "component-showcase-description",
|
||||
});
|
||||
|
||||
ComponentPreviewFactory.createSidebarPreview(preview);
|
||||
ComponentPreviewFactory.createSidebarPreview(preview);
|
||||
|
||||
const other = preview.querySelector<HTMLElement>(".v2-sidebar-section-other");
|
||||
other?.classList.add("is-focused");
|
||||
const other = preview.querySelector<HTMLElement>(
|
||||
".v2-sidebar-section-other"
|
||||
);
|
||||
other?.classList.add("is-focused");
|
||||
|
||||
const dimTargets = preview.querySelectorAll<HTMLElement>(
|
||||
".v2-sidebar-section-primary, .v2-sidebar-section-projects"
|
||||
);
|
||||
dimTargets.forEach((el) => el.classList.add("is-dimmed"));
|
||||
const dimTargets = preview.querySelectorAll<HTMLElement>(
|
||||
".v2-sidebar-section-primary, .v2-sidebar-section-projects"
|
||||
);
|
||||
dimTargets.forEach((el) => el.classList.add("is-dimmed"));
|
||||
|
||||
desc.createEl("h3", { text: t("Specialized views") });
|
||||
desc.createEl("p", {
|
||||
text: t(
|
||||
"Quickly reach Calendar scheduling, Gantt timelines and tag-based filtering."
|
||||
),
|
||||
});
|
||||
const ul = desc.createEl("ul", { cls: "component-feature-list" });
|
||||
[
|
||||
t("Compact list with clear icons"),
|
||||
t("Consistent selection feedback"),
|
||||
].forEach((txt) => ul.createEl("li", { text: txt }));
|
||||
}
|
||||
desc.createEl("h3", { text: t("Specialized views") });
|
||||
desc.createEl("p", {
|
||||
text: t(
|
||||
"Quickly reach views like Calendar, Gantt, Flagged and Tags, etc."
|
||||
),
|
||||
});
|
||||
const ul = desc.createEl("ul", { cls: "component-feature-list" });
|
||||
[
|
||||
t("Compact list with clear icons"),
|
||||
t("Right click for more options"),
|
||||
].forEach((txt) => ul.createEl("li", { text: txt }));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,46 +4,54 @@ import { ComponentPreviewFactory } from "../previews/ComponentPreviewFactory";
|
|||
import "@/styles/onboarding-components.css";
|
||||
|
||||
export class FluentProjectSectionStep {
|
||||
static render(
|
||||
headerEl: HTMLElement,
|
||||
contentEl: HTMLElement,
|
||||
controller: OnboardingController
|
||||
) {
|
||||
headerEl.empty();
|
||||
contentEl.empty();
|
||||
static render(
|
||||
headerEl: HTMLElement,
|
||||
contentEl: HTMLElement,
|
||||
controller: OnboardingController
|
||||
) {
|
||||
headerEl.empty();
|
||||
contentEl.empty();
|
||||
|
||||
headerEl.createEl("h1", { text: t("Projects Section") });
|
||||
headerEl.createEl("p", {
|
||||
text: t("Organize your work with projects and hierarchies"),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
headerEl.createEl("h1", { text: t("Projects Section") });
|
||||
headerEl.createEl("p", {
|
||||
text: t("Organize your work with projects and hierarchies"),
|
||||
cls: "onboarding-subtitle",
|
||||
});
|
||||
|
||||
const showcase = contentEl.createDiv({ cls: "component-showcase" });
|
||||
const preview = showcase.createDiv({ cls: "component-showcase-preview focus-mode" });
|
||||
const desc = showcase.createDiv({ cls: "component-showcase-description" });
|
||||
const showcase = contentEl.createDiv({ cls: "component-showcase" });
|
||||
const preview = showcase.createDiv({
|
||||
cls: "component-showcase-preview focus-mode",
|
||||
});
|
||||
const desc = showcase.createDiv({
|
||||
cls: "component-showcase-description",
|
||||
});
|
||||
|
||||
ComponentPreviewFactory.createSidebarPreview(preview);
|
||||
ComponentPreviewFactory.createSidebarPreview(preview);
|
||||
|
||||
const projects = preview.querySelector<HTMLElement>(".v2-sidebar-section-projects");
|
||||
projects?.classList.add("is-focused");
|
||||
const projects = preview.querySelector<HTMLElement>(
|
||||
".v2-sidebar-section-projects"
|
||||
);
|
||||
projects?.classList.add("is-focused");
|
||||
|
||||
const dimTargets = preview.querySelectorAll<HTMLElement>(
|
||||
".v2-sidebar-section-primary, .v2-sidebar-section-other"
|
||||
);
|
||||
dimTargets.forEach((el) => el.classList.add("is-dimmed"));
|
||||
const dimTargets = preview.querySelectorAll<HTMLElement>(
|
||||
".v2-sidebar-section-primary, .v2-sidebar-section-other"
|
||||
);
|
||||
dimTargets.forEach((el) => el.classList.add("is-dimmed"));
|
||||
|
||||
desc.createEl("h3", { text: t("Project organization") });
|
||||
desc.createEl("p", {
|
||||
text: t(
|
||||
"Group related tasks under projects. Build nested hierarchies and get quick stats."
|
||||
),
|
||||
});
|
||||
const ul = desc.createEl("ul", { cls: "component-feature-list" });
|
||||
[
|
||||
t("Color-coded projects with counts"),
|
||||
t("Supports nested structures for complex work"),
|
||||
t("Quick actions via project popover (preview)"),
|
||||
].forEach((txt) => ul.createEl("li", { text: txt }));
|
||||
}
|
||||
desc.createEl("h3", { text: t("Project organization") });
|
||||
desc.createEl("p", {
|
||||
text: t(
|
||||
"Group related tasks under projects. Build nested hierarchies and get quick stats."
|
||||
),
|
||||
});
|
||||
const ul = desc.createEl("ul", { cls: "component-feature-list" });
|
||||
[
|
||||
t(
|
||||
"Color-coded projects with counts (You can change the color in the settings)"
|
||||
),
|
||||
t("Supports nested structures for complex work"),
|
||||
t("Right click for more options"),
|
||||
t("Sort projects by name or tasks count, etc."),
|
||||
].forEach((txt) => ul.createEl("li", { text: txt }));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ export class IntroStep {
|
|||
contentEl.empty();
|
||||
|
||||
// Hide footer during intro animation
|
||||
footerEl.style.display = "none";
|
||||
footerEl.hide();
|
||||
|
||||
// Create wrapper for typing animation
|
||||
const introWrapper = contentEl.createDiv({
|
||||
|
|
@ -55,9 +55,9 @@ export class IntroStep {
|
|||
className: "intro-line-3",
|
||||
speed: 20,
|
||||
fadeOut: true,
|
||||
pauseAfter: 3000, // Wait 3s for user to read
|
||||
pauseAfter: 2000, // Wait 3s for user to read
|
||||
fadeOutDelay: 0, // Start fading out immediately after pause
|
||||
fadeOutDuration: 2000, // 2s fade out animation
|
||||
fadeOutDuration: 1000, // 2s fade out animation
|
||||
delayNext: 0, // No extra delay before next message
|
||||
},
|
||||
{
|
||||
|
|
@ -73,7 +73,7 @@ export class IntroStep {
|
|||
// Start typing animation
|
||||
new TypingAnimation(typingContainer, messages, () => {
|
||||
// Typing completed: show footer and move to Mode Selection step
|
||||
footerEl.style.display = "";
|
||||
footerEl.show();
|
||||
controller.setStep(OnboardingStep.MODE_SELECT);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import { t } from "@/translations/helper";
|
||||
import { SelectableCard, SelectableCardConfig } from "@/components/features/onboarding/ui/SelectableCard";
|
||||
import {
|
||||
SelectableCard,
|
||||
SelectableCardConfig,
|
||||
} from "@/components/features/onboarding/ui/SelectableCard";
|
||||
import { OnboardingController } from "@/components/features/onboarding/OnboardingController";
|
||||
import { Alert } from "@/components/features/onboarding/ui/Alert";
|
||||
|
||||
export type UIMode = 'fluent' | 'legacy';
|
||||
export type UIMode = "fluent" | "legacy";
|
||||
|
||||
/**
|
||||
* Mode Selection Step - Choose between Fluent and Legacy UI
|
||||
|
|
@ -21,14 +24,16 @@ export class ModeSelectionStep {
|
|||
headerEl.empty();
|
||||
contentEl.empty();
|
||||
|
||||
headerEl.toggleClass("intro-typing-wrapper", true);
|
||||
contentEl.toggleClass("intro-typing-wrapper", true);
|
||||
|
||||
// Intro guidance text (same as intro-line-4)
|
||||
headerEl.createEl("p", {
|
||||
cls: "intro-line intro-line-4",
|
||||
text: t(
|
||||
"In the current version, Task Genius provides a brand new visual and interactive experience: Fluent; while also providing the option to return to the previous interface. Which one do you prefer?"
|
||||
),
|
||||
});
|
||||
// Intro guidance text (same as intro-line-4)
|
||||
headerEl.createEl("p", {
|
||||
cls: "intro-line intro-line-4",
|
||||
text: t(
|
||||
"In the current version, Task Genius provides a brand new visual and interactive experience: Fluent; while also providing the option to return to the previous interface. Which one do you prefer?"
|
||||
),
|
||||
});
|
||||
|
||||
// Get current state
|
||||
const currentMode = controller.getState().uiMode;
|
||||
|
|
@ -85,83 +90,29 @@ export class ModeSelectionStep {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render mode selection inline (for intro step)
|
||||
* This version doesn't clear the container and calls a custom callback
|
||||
*/
|
||||
static renderInline(
|
||||
containerEl: HTMLElement,
|
||||
controller: OnboardingController,
|
||||
onSelect: (mode: UIMode) => void
|
||||
) {
|
||||
// Get current state
|
||||
const currentMode = controller.getState().uiMode;
|
||||
|
||||
// Create cards configuration
|
||||
const cardConfigs: SelectableCardConfig<UIMode>[] = [
|
||||
{
|
||||
id: "fluent",
|
||||
title: t("Fluent"),
|
||||
subtitle: t("Modern & Sleek"),
|
||||
description: t(
|
||||
"New visual design with elegant animations and modern interactions"
|
||||
),
|
||||
preview: this.createFluentPreview(),
|
||||
},
|
||||
{
|
||||
id: "legacy",
|
||||
title: t("Legacy"),
|
||||
subtitle: t("Classic & Familiar"),
|
||||
description: t(
|
||||
"Keep the familiar interface and interaction style you know"
|
||||
),
|
||||
preview: this.createLegacyPreview(),
|
||||
},
|
||||
];
|
||||
|
||||
// Render selectable cards
|
||||
const card = new SelectableCard<UIMode>(
|
||||
containerEl,
|
||||
cardConfigs,
|
||||
{
|
||||
containerClass: "selectable-cards-container",
|
||||
cardClass: "selectable-card",
|
||||
showPreview: true,
|
||||
},
|
||||
(mode) => {
|
||||
onSelect(mode);
|
||||
}
|
||||
);
|
||||
|
||||
// Set initial selection
|
||||
if (currentMode) {
|
||||
card.setSelected(currentMode);
|
||||
}
|
||||
|
||||
// Add info alert
|
||||
Alert.create(
|
||||
containerEl,
|
||||
t("You can change this option later in settings"),
|
||||
{
|
||||
variant: "info",
|
||||
className: "mode-selection-tip",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Fluent mode preview
|
||||
*/
|
||||
private static createFluentPreview(): HTMLElement {
|
||||
const preview = document.createElement("div");
|
||||
preview.addClass("mode-preview", "mode-preview-fluent");
|
||||
const preview = createDiv({
|
||||
cls: ["mode-preview", "mode-preview-fluent"],
|
||||
});
|
||||
|
||||
// Check theme
|
||||
const isDark = document.body.classList.contains("theme-dark");
|
||||
const theme = isDark ? "" : "-light";
|
||||
const imageUrl = `https://raw.githubusercontent.com/Quorafind/Obsidian-Task-Progress-Bar/master/media/fluent${theme}.png`;
|
||||
|
||||
preview.innerHTML = `<img src="${imageUrl}" alt="Fluent mode preview" style="max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 4px;">`;
|
||||
const img = preview.createEl("img", {
|
||||
attr: {
|
||||
src: imageUrl,
|
||||
alt: "Fluent mode preview",
|
||||
},
|
||||
});
|
||||
img.style.maxWidth = "100%";
|
||||
img.style.maxHeight = "100%";
|
||||
img.style.objectFit = "contain";
|
||||
img.style.borderRadius = "4px";
|
||||
|
||||
return preview;
|
||||
}
|
||||
|
|
@ -170,15 +121,25 @@ export class ModeSelectionStep {
|
|||
* Create Legacy mode preview
|
||||
*/
|
||||
private static createLegacyPreview(): HTMLElement {
|
||||
const preview = document.createElement("div");
|
||||
preview.addClass("mode-preview", "mode-preview-legacy");
|
||||
const preview = createDiv({
|
||||
cls: ["mode-preview", "mode-preview-legacy"],
|
||||
});
|
||||
|
||||
// Check theme
|
||||
const isDark = document.body.classList.contains("theme-dark");
|
||||
const theme = isDark ? "" : "-light";
|
||||
const imageUrl = `https://raw.githubusercontent.com/Quorafind/Obsidian-Task-Progress-Bar/master/media/legacy${theme}.png`;
|
||||
|
||||
preview.innerHTML = `<img src="${imageUrl}" alt="Legacy mode preview" style="max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 4px;">`;
|
||||
const img = preview.createEl("img", {
|
||||
attr: {
|
||||
src: imageUrl,
|
||||
alt: "Legacy mode preview",
|
||||
},
|
||||
});
|
||||
img.style.maxWidth = "100%";
|
||||
img.style.maxHeight = "100%";
|
||||
img.style.objectFit = "contain";
|
||||
img.style.borderRadius = "4px";
|
||||
|
||||
return preview;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,17 +77,17 @@ export class SettingsCheckStep {
|
|||
"settings-check-actions"
|
||||
);
|
||||
|
||||
// Action 2: Keep current settings (secondary)
|
||||
const keepCard = actionsContainer.createDiv(
|
||||
"settings-check-action-card settings-check-action-keep"
|
||||
);
|
||||
|
||||
// Action 1: Continue with wizard (prominent)
|
||||
const wizardCard = actionsContainer.createDiv(
|
||||
"settings-check-action-card settings-check-action-wizard"
|
||||
);
|
||||
wizardCard.addEventListener("click", () => {
|
||||
// Select this option
|
||||
this.selectedAction = "wizard";
|
||||
this.updateCardSelection(wizardCard, keepCard);
|
||||
controller.updateState({ settingsCheckAction: "wizard" });
|
||||
});
|
||||
|
||||
// 先渲染卡片内容,再绑定事件,避免 keepCard/wizardCard 未定义
|
||||
const wizardHeader = wizardCard.createDiv("action-card-header");
|
||||
const wizardIcon = wizardHeader.createDiv("action-card-icon");
|
||||
setIcon(wizardIcon, "wand-2");
|
||||
|
|
@ -114,17 +114,6 @@ export class SettingsCheckStep {
|
|||
item.createSpan({ text: feature });
|
||||
});
|
||||
|
||||
// Action 2: Keep current settings (secondary)
|
||||
const keepCard = actionsContainer.createDiv(
|
||||
"settings-check-action-card settings-check-action-keep"
|
||||
);
|
||||
keepCard.addEventListener("click", () => {
|
||||
// Select this option
|
||||
this.selectedAction = "keep";
|
||||
this.updateCardSelection(keepCard, wizardCard);
|
||||
controller.updateState({ settingsCheckAction: "keep" });
|
||||
});
|
||||
|
||||
const keepHeader = keepCard.createDiv("action-card-header");
|
||||
const keepIcon = keepHeader.createDiv("action-card-icon");
|
||||
setIcon(keepIcon, "shield-check");
|
||||
|
|
@ -143,6 +132,21 @@ export class SettingsCheckStep {
|
|||
keepNote.createSpan({
|
||||
text: t("Your customizations will be preserved"),
|
||||
});
|
||||
|
||||
// 事件绑定前,确保两个卡片都已渲染
|
||||
wizardCard.addEventListener("click", () => {
|
||||
if (this.selectedAction === "wizard") return;
|
||||
this.selectedAction = "wizard";
|
||||
this.updateCardSelection(wizardCard, keepCard);
|
||||
controller.updateState({ settingsCheckAction: "wizard" });
|
||||
});
|
||||
|
||||
keepCard.addEventListener("click", () => {
|
||||
if (this.selectedAction === "keep") return;
|
||||
this.selectedAction = "keep";
|
||||
this.updateCardSelection(keepCard, wizardCard);
|
||||
controller.updateState({ settingsCheckAction: "keep" });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -62,25 +62,22 @@ export class UserLevelStep {
|
|||
contentEl,
|
||||
cardConfigs,
|
||||
{
|
||||
containerClass: "selectable-cards-container",
|
||||
containerClass: [
|
||||
"selectable-cards-container",
|
||||
"user-level-cards",
|
||||
],
|
||||
cardClass: "selectable-card",
|
||||
showIcon: true,
|
||||
showFeatures: true,
|
||||
showPreview: false,
|
||||
},
|
||||
(mode) => {
|
||||
// Find the selected config
|
||||
const selectedConfig = configs.find((c) => c.mode === mode);
|
||||
controller.setSelectedConfig(selectedConfig);
|
||||
controller.setSelectedConfig(
|
||||
configs.find((c) => c.mode === mode)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// Set initial selection
|
||||
if (currentConfig) {
|
||||
card.setSelected(currentConfig.mode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get icon for configuration mode
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export interface SelectableCardConfig<T> {
|
|||
}
|
||||
|
||||
export interface SelectableCardOptions {
|
||||
containerClass?: string;
|
||||
containerClass?: string | string[];
|
||||
cardClass?: string;
|
||||
showIcon?: boolean;
|
||||
showPreview?: boolean;
|
||||
|
|
@ -66,11 +66,11 @@ export class SelectableCard<T = string> {
|
|||
});
|
||||
|
||||
// Header
|
||||
const header = card.createDiv({cls: `${cardClass}-header`});
|
||||
const header = card.createDiv({ cls: `${cardClass}-header` });
|
||||
|
||||
// Icon
|
||||
if (showIcon && config.icon) {
|
||||
const iconEl = header.createDiv({cls: `${cardClass}-icon`});
|
||||
const iconEl = header.createDiv({ cls: `${cardClass}-icon` });
|
||||
setIcon(iconEl, config.icon);
|
||||
}
|
||||
|
||||
|
|
@ -100,7 +100,7 @@ export class SelectableCard<T = string> {
|
|||
}
|
||||
|
||||
// Body
|
||||
const body = card.createDiv({cls: `${cardClass}-body`});
|
||||
const body = card.createDiv({ cls: `${cardClass}-body` });
|
||||
|
||||
// Preview (optional)
|
||||
if (showPreview && config.preview) {
|
||||
|
|
@ -128,7 +128,7 @@ export class SelectableCard<T = string> {
|
|||
});
|
||||
const featuresList = featuresContainer.createEl("ul");
|
||||
config.features.forEach((feature) => {
|
||||
featuresList.createEl("li", {text: feature});
|
||||
featuresList.createEl("li", { text: feature });
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
277
src/index.ts
277
src/index.ts
|
|
@ -119,9 +119,15 @@ import { createDataflow, isDataflowEnabled } from "./dataflow/createDataflow";
|
|||
import type { DataflowOrchestrator } from "./dataflow/Orchestrator";
|
||||
import { WriteAPI } from "./dataflow/api/WriteAPI";
|
||||
import { Events } from "./dataflow/events/Events";
|
||||
import { installWorkspaceDragMonitor, registerRestrictedDnDViewTypes } from "./patches/workspace-dnd-patch";
|
||||
import {
|
||||
installWorkspaceDragMonitor,
|
||||
registerRestrictedDnDViewTypes,
|
||||
} from "./patches/workspace-dnd-patch";
|
||||
import { TASK_VIEW_V2_TYPE } from "./experimental/v2/TaskViewV2";
|
||||
import { setPriorityAtCursor, removePriorityAtCursor } from "./utils/task/curosr-priority-utils";
|
||||
import {
|
||||
setPriorityAtCursor,
|
||||
removePriorityAtCursor,
|
||||
} from "./utils/task/curosr-priority-utils";
|
||||
|
||||
class TaskProgressBarPopover extends HoverPopover {
|
||||
plugin: TaskProgressBarPlugin;
|
||||
|
|
@ -146,7 +152,7 @@ class TaskProgressBarPopover extends HoverPopover {
|
|||
},
|
||||
parent: HoverParent,
|
||||
targetEl: HTMLElement,
|
||||
waitTime: number = 1000,
|
||||
waitTime: number = 1000
|
||||
) {
|
||||
super(parent, targetEl, waitTime);
|
||||
|
||||
|
|
@ -170,7 +176,7 @@ class TaskProgressBarPopover extends HoverPopover {
|
|||
`,
|
||||
this.hoverEl,
|
||||
"",
|
||||
this.plugin,
|
||||
this.plugin
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -192,7 +198,7 @@ export const showPopoverWithProgressBar = (
|
|||
planned: string;
|
||||
};
|
||||
view: EditorView;
|
||||
},
|
||||
}
|
||||
) => {
|
||||
const editor = view.state.field(editorInfoField);
|
||||
if (!editor) return;
|
||||
|
|
@ -265,7 +271,6 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
// V2 Integration instance
|
||||
v2Integration?: V2Integration;
|
||||
|
||||
|
||||
// Uninstaller for workspace drag-and-drop monkey patch
|
||||
private uninstallWorkspaceDnD?: () => void;
|
||||
|
||||
|
|
@ -296,7 +301,9 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this.globalSuggestManager = new SuggestManager(this.app, this);
|
||||
|
||||
// Initialize workspace manager
|
||||
const {WorkspaceManager} = await import("./experimental/v2/managers/WorkspaceManager");
|
||||
const { WorkspaceManager } = await import(
|
||||
"./experimental/v2/managers/WorkspaceManager"
|
||||
);
|
||||
this.workspaceManager = new WorkspaceManager(this);
|
||||
await this.workspaceManager.migrateToV2();
|
||||
this.workspaceManager.ensureDefaultWorkspaceInvariant();
|
||||
|
|
@ -308,11 +315,6 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
// Initialize rebuild progress manager
|
||||
this.rebuildProgressManager = new RebuildProgressManager();
|
||||
|
||||
// Initialize V2 Integration
|
||||
this.v2Integration = new V2Integration(this);
|
||||
await this.v2Integration.migrateSettings();
|
||||
this.v2Integration.register();
|
||||
|
||||
// Initialize task management systems
|
||||
if (this.settings.enableIndexer) {
|
||||
// Initialize indexer-dependent features
|
||||
|
|
@ -326,28 +328,35 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this.initializeDataflowWithVersionCheck().catch((error) => {
|
||||
console.error(
|
||||
"Failed to initialize dataflow with version check:",
|
||||
error,
|
||||
error
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
console.time("[TPB] registerViewsAndCommands");
|
||||
// Register the TaskView
|
||||
this.registerView(
|
||||
TASK_VIEW_TYPE,
|
||||
(leaf) => new TaskView(leaf, this),
|
||||
);
|
||||
if (!this.settings.onboarding?.completed) {
|
||||
// Initialize V2 Integration
|
||||
this.v2Integration = new V2Integration(this);
|
||||
await this.v2Integration.migrateSettings();
|
||||
this.v2Integration.register();
|
||||
|
||||
this.registerView(
|
||||
TASK_SPECIFIC_VIEW_TYPE,
|
||||
(leaf) => new TaskSpecificView(leaf, this),
|
||||
);
|
||||
this.registerView(
|
||||
TASK_VIEW_TYPE,
|
||||
(leaf) => new TaskView(leaf, this)
|
||||
);
|
||||
|
||||
// Register the Timeline Sidebar View
|
||||
this.registerView(
|
||||
TIMELINE_SIDEBAR_VIEW_TYPE,
|
||||
(leaf) => new TimelineSidebarView(leaf, this),
|
||||
);
|
||||
this.registerView(
|
||||
TASK_SPECIFIC_VIEW_TYPE,
|
||||
(leaf) => new TaskSpecificView(leaf, this)
|
||||
);
|
||||
|
||||
// Register the Timeline Sidebar View
|
||||
this.registerView(
|
||||
TIMELINE_SIDEBAR_VIEW_TYPE,
|
||||
(leaf) => new TimelineSidebarView(leaf, this)
|
||||
);
|
||||
}
|
||||
|
||||
// Register the Onboarding View
|
||||
this.registerView(
|
||||
|
|
@ -357,7 +366,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
console.log("Onboarding completed successfully");
|
||||
// Close the onboarding view and refresh views
|
||||
leaf.detach();
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
// Add a command to open the TaskView
|
||||
|
|
@ -400,7 +409,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
t("Open Task Genius view"),
|
||||
() => {
|
||||
this.activateTaskView();
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
console.timeEnd("[TPB] registerViewsAndCommands");
|
||||
|
|
@ -446,22 +455,22 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
detectionMethods:
|
||||
this.settings.projectConfig?.metadataConfig
|
||||
?.detectionMethods || [],
|
||||
},
|
||||
}
|
||||
);
|
||||
console.timeEnd("[TPB] createDataflow");
|
||||
console.log(
|
||||
"[Plugin] Dataflow orchestrator initialized successfully",
|
||||
"[Plugin] Dataflow orchestrator initialized successfully"
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[Plugin] Failed to initialize dataflow orchestrator:",
|
||||
error,
|
||||
error
|
||||
);
|
||||
// Fatal error - cannot continue without dataflow
|
||||
new Notice(
|
||||
t(
|
||||
"Failed to initialize task system. Please restart Obsidian.",
|
||||
),
|
||||
"Failed to initialize task system. Please restart Obsidian."
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -486,7 +495,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this.app.vault,
|
||||
this.app.metadataCache,
|
||||
this,
|
||||
getTaskById,
|
||||
getTaskById
|
||||
);
|
||||
|
||||
// Initialize OnCompletionManager
|
||||
|
|
@ -511,11 +520,12 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this.registerEditorExt();
|
||||
console.timeEnd("[TPB] registerEditorExt");
|
||||
|
||||
|
||||
// Install workspace DnD monkey patch (blocks dragging restricted views to center)
|
||||
installWorkspaceDragMonitor(this);
|
||||
// Also restrict V2 main view from being dropped to center, as it is sidebar-managed
|
||||
try { registerRestrictedDnDViewTypes(TASK_VIEW_V2_TYPE); } catch {}
|
||||
try {
|
||||
registerRestrictedDnDViewTypes(TASK_VIEW_V2_TYPE);
|
||||
} catch {}
|
||||
|
||||
this.settingTab = new TaskProgressBarSettingTab(this.app, this);
|
||||
this.addSettingTab(this.settingTab);
|
||||
|
|
@ -536,18 +546,18 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
item.setTitle(
|
||||
`${t("Set priority")}: ${
|
||||
priority.text
|
||||
}`,
|
||||
}`
|
||||
);
|
||||
item.setIcon("arrow-big-up-dash");
|
||||
item.onClick(() => {
|
||||
setPriorityAtCursor(
|
||||
editor,
|
||||
priority.emoji,
|
||||
priority.emoji
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
submenu.addSeparator();
|
||||
|
|
@ -557,17 +567,17 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
([key, priority]) => {
|
||||
submenu.addItem((item) => {
|
||||
item.setTitle(
|
||||
`${t("Set priority")}: ${key}`,
|
||||
`${t("Set priority")}: ${key}`
|
||||
);
|
||||
item.setIcon("a-arrow-up");
|
||||
item.onClick(() => {
|
||||
setPriorityAtCursor(
|
||||
editor,
|
||||
`[#${key}]`,
|
||||
`[#${key}]`
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
// Remove priority command
|
||||
|
|
@ -587,18 +597,23 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
if (this.settings.workflow.enableWorkflow) {
|
||||
updateWorkflowContextMenu(menu, editor, this);
|
||||
}
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
this.app.workspace.onLayoutReady(() => {
|
||||
console.time("[TPB] onLayoutReady");
|
||||
|
||||
// Update workspace leaves when layout is ready
|
||||
const deferWorkspaceLeaves = this.app.workspace.getLeavesOfType(TASK_VIEW_TYPE);
|
||||
const deferSpecificLeaves = this.app.workspace.getLeavesOfType(TASK_SPECIFIC_VIEW_TYPE);
|
||||
[...deferWorkspaceLeaves, ...deferSpecificLeaves].forEach(leaf => {
|
||||
leaf.loadIfDeferred();
|
||||
});
|
||||
const deferWorkspaceLeaves =
|
||||
this.app.workspace.getLeavesOfType(TASK_VIEW_TYPE);
|
||||
const deferSpecificLeaves = this.app.workspace.getLeavesOfType(
|
||||
TASK_SPECIFIC_VIEW_TYPE
|
||||
);
|
||||
[...deferWorkspaceLeaves, ...deferSpecificLeaves].forEach(
|
||||
(leaf) => {
|
||||
leaf.loadIfDeferred();
|
||||
}
|
||||
);
|
||||
// Initialize Task Genius Icon Manager
|
||||
this.taskGeniusIconManager = new TaskGeniusIconManager(this);
|
||||
this.addChild(this.taskGeniusIconManager);
|
||||
|
|
@ -616,8 +631,8 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this.registerEvent(
|
||||
this.app.workspace.on(
|
||||
Events.TASK_CACHE_UPDATED as any,
|
||||
() => this.notificationManager?.onTaskCacheUpdated(),
|
||||
),
|
||||
() => this.notificationManager?.onTaskCacheUpdated()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -669,7 +684,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this.icsManager = new IcsManager(
|
||||
this.settings.icsIntegration,
|
||||
this.settings,
|
||||
this,
|
||||
this
|
||||
);
|
||||
this.addChild(this.icsManager);
|
||||
|
||||
|
|
@ -689,7 +704,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this.activateTimelineSidebarView().catch((error) => {
|
||||
console.error(
|
||||
"Failed to auto-open timeline sidebar:",
|
||||
error,
|
||||
error
|
||||
);
|
||||
});
|
||||
}, 1000);
|
||||
|
|
@ -708,11 +723,11 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
(preset: any) => {
|
||||
if (preset.options) {
|
||||
preset.options = migrateOldFilterOptions(
|
||||
preset.options,
|
||||
preset.options
|
||||
);
|
||||
}
|
||||
return preset;
|
||||
},
|
||||
}
|
||||
);
|
||||
await this.saveSettings();
|
||||
console.timeEnd("[TPB] migratePresetTaskFilters");
|
||||
|
|
@ -763,7 +778,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
if (view) {
|
||||
view.dispatch({
|
||||
effects: toggleTaskFilter.of(
|
||||
!view.state.field(taskFilterState),
|
||||
!view.state.field(taskFilterState)
|
||||
),
|
||||
});
|
||||
}
|
||||
|
|
@ -785,14 +800,14 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
const changes = sortTasksInDocument(
|
||||
editorView,
|
||||
this,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
if (changes) {
|
||||
new Notice(
|
||||
t(
|
||||
"Tasks sorted (using settings). Change application needs refinement.",
|
||||
),
|
||||
"Tasks sorted (using settings). Change application needs refinement."
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Notice is already handled within sortTasksInDocument if no changes or sorting disabled
|
||||
|
|
@ -816,11 +831,11 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
return changes;
|
||||
});
|
||||
new Notice(
|
||||
t("Entire document sorted (using settings)."),
|
||||
t("Entire document sorted (using settings).")
|
||||
);
|
||||
} else {
|
||||
new Notice(
|
||||
t("Tasks already sorted or no tasks found."),
|
||||
t("Tasks already sorted or no tasks found.")
|
||||
);
|
||||
}
|
||||
},
|
||||
|
|
@ -861,7 +876,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
) {
|
||||
// Use dataflow orchestrator for refresh
|
||||
console.log(
|
||||
"[Command] Refreshing task index via dataflow",
|
||||
"[Command] Refreshing task index via dataflow"
|
||||
);
|
||||
|
||||
// Re-scan all files to refresh the index
|
||||
|
|
@ -883,8 +898,8 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
batch.map((file) =>
|
||||
(
|
||||
this.dataflowOrchestrator as any
|
||||
).processFileImmediate(file),
|
||||
),
|
||||
).processFileImmediate(file)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -921,12 +936,10 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
) {
|
||||
// Use dataflow orchestrator for force reindex
|
||||
console.log(
|
||||
"[Command] Force reindexing via dataflow",
|
||||
"[Command] Force reindexing via dataflow"
|
||||
);
|
||||
new Notice(
|
||||
t(
|
||||
"Clearing task cache and rebuilding index...",
|
||||
),
|
||||
t("Clearing task cache and rebuilding index...")
|
||||
);
|
||||
|
||||
// Clear all caches and rebuild from scratch
|
||||
|
|
@ -1026,7 +1039,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"allCompleted",
|
||||
"allCompleted"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1041,7 +1054,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"directChildren",
|
||||
"directChildren"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1056,7 +1069,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"all",
|
||||
"all"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1072,7 +1085,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"allCompleted",
|
||||
"allCompleted"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1080,7 +1093,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this.addCommand({
|
||||
id: "auto-move-direct-completed-subtasks",
|
||||
name: t(
|
||||
"Auto-move direct completed subtasks to default file",
|
||||
"Auto-move direct completed subtasks to default file"
|
||||
),
|
||||
editorCheckCallback: (checking, editor, ctx) => {
|
||||
return autoMoveCompletedTasksCommand(
|
||||
|
|
@ -1088,7 +1101,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"directChildren",
|
||||
"directChildren"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1102,7 +1115,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"all",
|
||||
"all"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1121,7 +1134,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"allIncompleted",
|
||||
"allIncompleted"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1136,7 +1149,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"directIncompletedChildren",
|
||||
"directIncompletedChildren"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1152,7 +1165,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"allIncompleted",
|
||||
"allIncompleted"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1160,7 +1173,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this.addCommand({
|
||||
id: "auto-move-direct-incomplete-subtasks",
|
||||
name: t(
|
||||
"Auto-move direct incomplete subtasks to default file",
|
||||
"Auto-move direct incomplete subtasks to default file"
|
||||
),
|
||||
editorCheckCallback: (checking, editor, ctx) => {
|
||||
return autoMoveCompletedTasksCommand(
|
||||
|
|
@ -1168,7 +1181,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
editor,
|
||||
ctx,
|
||||
this,
|
||||
"directIncompletedChildren",
|
||||
"directIncompletedChildren"
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1234,8 +1247,8 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
} catch (e) {
|
||||
new Notice(
|
||||
t(
|
||||
"Could not open quick capture panel in the current editor",
|
||||
),
|
||||
"Could not open quick capture panel in the current editor"
|
||||
)
|
||||
);
|
||||
}
|
||||
}, 100);
|
||||
|
|
@ -1254,7 +1267,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
checking,
|
||||
editor,
|
||||
ctx,
|
||||
this,
|
||||
this
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1267,7 +1280,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
checking,
|
||||
editor,
|
||||
ctx,
|
||||
this,
|
||||
this
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1280,7 +1293,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
checking,
|
||||
editor,
|
||||
ctx,
|
||||
this,
|
||||
this
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1293,7 +1306,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
checking,
|
||||
editor,
|
||||
ctx,
|
||||
this,
|
||||
this
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1306,7 +1319,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
checking,
|
||||
editor,
|
||||
ctx,
|
||||
this,
|
||||
this
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1319,7 +1332,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
checking,
|
||||
editor,
|
||||
ctx,
|
||||
this,
|
||||
this
|
||||
);
|
||||
},
|
||||
});
|
||||
|
|
@ -1357,7 +1370,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
URL.revokeObjectURL(url);
|
||||
|
||||
new Notice(
|
||||
`Exported ${stats.activeTimers} timer records`,
|
||||
`Exported ${stats.activeTimers} timer records`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error exporting timer data:", error);
|
||||
|
|
@ -1388,17 +1401,17 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
if (success) {
|
||||
new Notice(
|
||||
"Timer data imported successfully",
|
||||
"Timer data imported successfully"
|
||||
);
|
||||
} else {
|
||||
new Notice(
|
||||
"Failed to import timer data - invalid format",
|
||||
"Failed to import timer data - invalid format"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Error importing timer data:",
|
||||
error,
|
||||
error
|
||||
);
|
||||
new Notice("Failed to import timer data");
|
||||
}
|
||||
|
|
@ -1442,12 +1455,12 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
URL.revokeObjectURL(url);
|
||||
|
||||
new Notice(
|
||||
`Exported ${stats.activeTimers} timer records to YAML`,
|
||||
`Exported ${stats.activeTimers} timer records to YAML`
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Error exporting timer data to YAML:",
|
||||
error,
|
||||
error
|
||||
);
|
||||
new Notice("Failed to export timer data to YAML");
|
||||
}
|
||||
|
|
@ -1495,7 +1508,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
let message = `Task Timer Statistics:\n`;
|
||||
message += `Active timers: ${stats.activeTimers}\n`;
|
||||
message += `Total duration: ${Math.round(
|
||||
stats.totalDuration / 60000,
|
||||
stats.totalDuration / 60000
|
||||
)} minutes\n`;
|
||||
|
||||
if (stats.oldestTimer) {
|
||||
|
|
@ -1525,12 +1538,12 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
// Initialize task timer manager and exporter
|
||||
if (!this.taskTimerManager) {
|
||||
this.taskTimerManager = new TaskTimerManager(
|
||||
this.settings.taskTimer,
|
||||
this.settings.taskTimer
|
||||
);
|
||||
}
|
||||
if (!this.taskTimerExporter) {
|
||||
this.taskTimerExporter = new TaskTimerExporter(
|
||||
this.taskTimerManager,
|
||||
this.taskTimerManager
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1538,12 +1551,12 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
}
|
||||
|
||||
this.settings.taskGutter.enableTaskGutter &&
|
||||
this.registerEditorExtension([taskGutterExtension(this.app, this)]);
|
||||
this.registerEditorExtension([taskGutterExtension(this.app, this)]);
|
||||
this.settings.enableTaskStatusSwitcher &&
|
||||
this.settings.enableCustomTaskMarks &&
|
||||
this.registerEditorExtension([
|
||||
taskStatusSwitcherExtension(this.app, this),
|
||||
]);
|
||||
this.settings.enableCustomTaskMarks &&
|
||||
this.registerEditorExtension([
|
||||
taskStatusSwitcherExtension(this.app, this),
|
||||
]);
|
||||
|
||||
// Add priority picker extension
|
||||
if (this.settings.enablePriorityPicker) {
|
||||
|
|
@ -1579,7 +1592,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
if (this.settings.quickCapture.enableMinimalMode) {
|
||||
this.minimalQuickCaptureSuggest = new MinimalQuickCaptureSuggest(
|
||||
this.app,
|
||||
this,
|
||||
this
|
||||
);
|
||||
this.registerEditorSuggest(this.minimalQuickCaptureSuggest);
|
||||
}
|
||||
|
|
@ -1611,7 +1624,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
this.dataflowOrchestrator.cleanup().catch((error) => {
|
||||
console.error(
|
||||
"Error cleaning up dataflow orchestrator:",
|
||||
error,
|
||||
error
|
||||
);
|
||||
});
|
||||
// Set to undefined to prevent any further access
|
||||
|
|
@ -1657,7 +1670,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
* Open the onboarding view in a new leaf
|
||||
*/
|
||||
async openOnboardingView(): Promise<void> {
|
||||
const {workspace} = this.app;
|
||||
const { workspace } = this.app;
|
||||
|
||||
// Check if onboarding view is already open
|
||||
const existingLeaf = workspace.getLeavesOfType(ONBOARDING_VIEW_TYPE)[0];
|
||||
|
|
@ -1669,7 +1682,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
// Create a new leaf in the main area and open the onboarding view
|
||||
const leaf = workspace.getLeaf("tab");
|
||||
await leaf.setViewState({type: ONBOARDING_VIEW_TYPE});
|
||||
await leaf.setViewState({ type: ONBOARDING_VIEW_TYPE });
|
||||
workspace.revealLeaf(leaf);
|
||||
}
|
||||
|
||||
|
|
@ -1679,14 +1692,13 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
try {
|
||||
console.debug(
|
||||
"[Plugin][loadSettings] fileMetadataInheritance (raw):",
|
||||
savedData?.fileMetadataInheritance,
|
||||
savedData?.fileMetadataInheritance
|
||||
);
|
||||
console.debug(
|
||||
"[Plugin][loadSettings] fileMetadataInheritance (effective):",
|
||||
this.settings.fileMetadataInheritance,
|
||||
this.settings.fileMetadataInheritance
|
||||
);
|
||||
} catch {
|
||||
}
|
||||
} catch {}
|
||||
|
||||
// Migrate old inheritance settings to new structure
|
||||
this.migrateInheritanceSettings(savedData);
|
||||
|
|
@ -1726,10 +1738,9 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
try {
|
||||
console.debug(
|
||||
"[Plugin][saveSettings] fileMetadataInheritance:",
|
||||
this.settings?.fileMetadataInheritance,
|
||||
this.settings?.fileMetadataInheritance
|
||||
);
|
||||
} catch {
|
||||
}
|
||||
} catch {}
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
|
||||
|
|
@ -1744,10 +1755,10 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
// Add any missing default views to user settings
|
||||
defaultViews.forEach((defaultView) => {
|
||||
const existingView = this.settings.viewConfiguration.find(
|
||||
(v) => v.id === defaultView.id,
|
||||
(v) => v.id === defaultView.id
|
||||
);
|
||||
if (!existingView) {
|
||||
this.settings.viewConfiguration.push({...defaultView});
|
||||
this.settings.viewConfiguration.push({ ...defaultView });
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -1766,7 +1777,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
this.isActivatingView = true;
|
||||
try {
|
||||
const {workspace} = this.app;
|
||||
const { workspace } = this.app;
|
||||
|
||||
// Check if view is already open
|
||||
const existingLeaves = workspace.getLeavesOfType(TASK_VIEW_TYPE);
|
||||
|
|
@ -1784,7 +1795,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
// Otherwise, create a new leaf and open the view
|
||||
const leaf = workspace.getLeaf("tab");
|
||||
await leaf.setViewState({type: TASK_VIEW_TYPE});
|
||||
await leaf.setViewState({ type: TASK_VIEW_TYPE });
|
||||
workspace.revealLeaf(leaf);
|
||||
} finally {
|
||||
this.isActivatingView = false;
|
||||
|
|
@ -1801,11 +1812,11 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
this.isActivatingSidebar = true;
|
||||
try {
|
||||
const {workspace} = this.app;
|
||||
const { workspace } = this.app;
|
||||
|
||||
// Check if view is already open
|
||||
const existingLeaves = workspace.getLeavesOfType(
|
||||
TIMELINE_SIDEBAR_VIEW_TYPE,
|
||||
TIMELINE_SIDEBAR_VIEW_TYPE
|
||||
);
|
||||
|
||||
if (existingLeaves.length > 0) {
|
||||
|
|
@ -1822,7 +1833,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
// Open in the right sidebar
|
||||
const leaf = workspace.getRightLeaf(false);
|
||||
if (leaf) {
|
||||
await leaf.setViewState({type: TIMELINE_SIDEBAR_VIEW_TYPE});
|
||||
await leaf.setViewState({ type: TIMELINE_SIDEBAR_VIEW_TYPE });
|
||||
workspace.revealLeaf(leaf);
|
||||
}
|
||||
} finally {
|
||||
|
|
@ -1851,7 +1862,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
// Update Timeline Sidebar Views
|
||||
const timelineViewLeaves = this.app.workspace.getLeavesOfType(
|
||||
TIMELINE_SIDEBAR_VIEW_TYPE,
|
||||
TIMELINE_SIDEBAR_VIEW_TYPE
|
||||
);
|
||||
if (timelineViewLeaves.length > 0) {
|
||||
for (const leaf of timelineViewLeaves) {
|
||||
|
|
@ -1885,7 +1896,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
if (!diagnosticInfo.canWrite) {
|
||||
throw new Error(
|
||||
"Cannot write to version storage - storage may be corrupted",
|
||||
"Cannot write to version storage - storage may be corrupted"
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -1894,7 +1905,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
diagnosticInfo.previousVersion
|
||||
) {
|
||||
console.warn(
|
||||
"Invalid version data detected, attempting recovery",
|
||||
"Invalid version data detected, attempting recovery"
|
||||
);
|
||||
await this.versionManager.recoverFromCorruptedVersion();
|
||||
}
|
||||
|
|
@ -1905,7 +1916,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
|
||||
if (versionResult.requiresRebuild) {
|
||||
console.log(
|
||||
`Task Genius (Dataflow): ${versionResult.rebuildReason}`,
|
||||
`Task Genius (Dataflow): ${versionResult.rebuildReason}`
|
||||
);
|
||||
|
||||
// Get all supported files for progress tracking
|
||||
|
|
@ -1914,13 +1925,13 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
.filter(
|
||||
(file) =>
|
||||
file.extension === "md" ||
|
||||
file.extension === "canvas",
|
||||
file.extension === "canvas"
|
||||
);
|
||||
|
||||
// Start rebuild progress tracking
|
||||
this.rebuildProgressManager.startRebuild(
|
||||
allFiles.length,
|
||||
versionResult.rebuildReason,
|
||||
versionResult.rebuildReason
|
||||
);
|
||||
|
||||
// After dataflow rebuild, refresh habits to keep in sync
|
||||
|
|
@ -1946,20 +1957,20 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
} else {
|
||||
// No rebuild needed, dataflow already initialized during creation
|
||||
console.log(
|
||||
"Task Genius (Dataflow): No rebuild needed, using existing cache",
|
||||
"Task Genius (Dataflow): No rebuild needed, using existing cache"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"Error during dataflow initialization with version check:",
|
||||
error,
|
||||
error
|
||||
);
|
||||
|
||||
// Trigger emergency rebuild for dataflow
|
||||
try {
|
||||
const emergencyResult =
|
||||
await this.versionManager.handleEmergencyRebuild(
|
||||
`Dataflow initialization failed: ${error.message}`,
|
||||
`Dataflow initialization failed: ${error.message}`
|
||||
);
|
||||
|
||||
// Get all supported files for progress tracking
|
||||
|
|
@ -1968,13 +1979,13 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
.filter(
|
||||
(file) =>
|
||||
file.extension === "md" ||
|
||||
file.extension === "canvas",
|
||||
file.extension === "canvas"
|
||||
);
|
||||
|
||||
// Start emergency rebuild
|
||||
this.rebuildProgressManager.startRebuild(
|
||||
allFiles.length,
|
||||
emergencyResult.rebuildReason,
|
||||
emergencyResult.rebuildReason
|
||||
);
|
||||
|
||||
// Force rebuild dataflow
|
||||
|
|
@ -1992,12 +2003,12 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
await this.versionManager.markVersionProcessed();
|
||||
|
||||
console.log(
|
||||
"Emergency dataflow rebuild completed successfully",
|
||||
"Emergency dataflow rebuild completed successfully"
|
||||
);
|
||||
} catch (emergencyError) {
|
||||
console.error(
|
||||
"Emergency dataflow rebuild failed:",
|
||||
emergencyError,
|
||||
emergencyError
|
||||
);
|
||||
throw emergencyError;
|
||||
}
|
||||
|
|
@ -2012,7 +2023,7 @@ export default class TaskProgressBarPlugin extends Plugin {
|
|||
private async initializeTaskManagerWithVersionCheck(): Promise<void> {
|
||||
// This method is deprecated and should not be called
|
||||
console.warn(
|
||||
"initializeTaskManagerWithVersionCheck is deprecated and should not be used",
|
||||
"initializeTaskManagerWithVersionCheck is deprecated and should not be used"
|
||||
);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 4px;
|
||||
background: var(--background-primary);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.file-filter-rule:last-child {
|
||||
|
|
@ -247,3 +248,8 @@
|
|||
background: var(--color-green-rgb);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.file-filter-rule-editor-modal {
|
||||
max-width: 800px;
|
||||
width: 90vw;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,45 +4,67 @@
|
|||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.component-preview .v2-nav-left {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.onboarding-view:has(.component-showcase) .onboarding-header {
|
||||
padding: calc(var(--onboarding-spacing) * 2) var(--onboarding-spacing) var(--size-4-2) var(--onboarding-spacing);
|
||||
padding: calc(var(--onboarding-spacing) * 2) var(--onboarding-spacing)
|
||||
var(--size-4-2) var(--onboarding-spacing);
|
||||
}
|
||||
|
||||
.v2-top-navigation.component-preview {
|
||||
overflow-x: auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.v2-sidebar.component-preview {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.component-showcase:has(
|
||||
.tg-v2-container.component-preview-sidebar
|
||||
+ .tg-v2-container.component-preview-topnav
|
||||
)
|
||||
.tg-v2-container {
|
||||
background: var(--background-primary);
|
||||
}
|
||||
|
||||
.tg-v2-container.component-preview-sidebar {
|
||||
width: 200px;
|
||||
min-width: 200px;
|
||||
border-right: 1px solid var(--background-modifier-border);
|
||||
width: 200px;
|
||||
min-width: 200px;
|
||||
border-right: 1px solid var(--background-modifier-border);
|
||||
border-top: unset;
|
||||
}
|
||||
|
||||
.tg-v2-container.component-preview-topnav {
|
||||
background: var(--background-primary);
|
||||
}
|
||||
|
||||
.component-showcase:has(
|
||||
.tg-v2-container.component-preview-sidebar
|
||||
+ .tg-v2-container.component-preview-topnav
|
||||
)
|
||||
.tg-v2-container.component-preview-topnav {
|
||||
border-top: unset;
|
||||
background: var(--background-primary);
|
||||
}
|
||||
|
||||
/* Component showcase container */
|
||||
.component-showcase {
|
||||
display: flex;
|
||||
gap: var(--size-4-6);
|
||||
margin: var(--size-4-4) 0;
|
||||
min-height: 400px;
|
||||
min-width: 90%;
|
||||
}
|
||||
|
||||
.component-showcase-preview {
|
||||
flex: 1;
|
||||
background: var(--background-secondary);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--size-4-4);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow-x: auto;
|
||||
flex: 1;
|
||||
background: var(--background-secondary);
|
||||
border-radius: var(--radius-m);
|
||||
padding: var(--size-4-4);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.component-showcase-description {
|
||||
|
|
@ -231,41 +253,50 @@
|
|||
font-size: var(--font-ui-small);
|
||||
}
|
||||
|
||||
.component-showcase-preview.tg-v2-container.component-preview-sidebar .v2-sidebar {
|
||||
width: 100%;
|
||||
.component-showcase-preview.tg-v2-container.component-preview-sidebar
|
||||
.v2-sidebar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Preview interactivity (scoped to preview only) */
|
||||
.component-showcase-preview .component-preview-sidebar .v2-navigation-item,
|
||||
.component-showcase-preview .component-preview-sidebar .v2-project-item {
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
pointer-events: auto;
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.component-showcase-preview .component-preview-sidebar .v2-navigation-item.is-active,
|
||||
.component-showcase-preview .component-preview-sidebar .v2-project-item.is-active {
|
||||
background: var(--background-modifier-hover);
|
||||
border-radius: var(--radius-s);
|
||||
.component-showcase-preview
|
||||
.component-preview-sidebar
|
||||
.v2-navigation-item.is-active,
|
||||
.component-showcase-preview
|
||||
.component-preview-sidebar
|
||||
.v2-project-item.is-active {
|
||||
background: var(--background-modifier-hover);
|
||||
border-radius: var(--radius-s);
|
||||
}
|
||||
|
||||
.component-showcase-preview .component-preview-sidebar .v2-navigation-item:focus-visible,
|
||||
.component-showcase-preview .component-preview-sidebar .v2-project-item:focus-visible {
|
||||
box-shadow: 0 0 0 2px var(--interactive-accent);
|
||||
border-radius: var(--radius-s);
|
||||
.component-showcase-preview
|
||||
.component-preview-sidebar
|
||||
.v2-navigation-item:focus-visible,
|
||||
.component-showcase-preview
|
||||
.component-preview-sidebar
|
||||
.v2-project-item:focus-visible {
|
||||
box-shadow: 0 0 0 2px var(--interactive-accent);
|
||||
border-radius: var(--radius-s);
|
||||
}
|
||||
|
||||
/* Focus/Dim mode for step highlights */
|
||||
.component-showcase-preview.focus-mode .is-dimmed {
|
||||
opacity: 0.5;
|
||||
filter: grayscale(15%);
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
filter: grayscale(15%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.component-showcase-preview.focus-mode .is-focused {
|
||||
position: relative;
|
||||
outline: 1px solid var(--color-accent);
|
||||
outline-offset: -1px;
|
||||
sborder-radius: var(--radius-s);
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
outline: 1px solid var(--color-accent);
|
||||
outline-offset: -1px;
|
||||
sborder-radius: var(--radius-s);
|
||||
z-index: 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,10 @@
|
|||
/* shadcn design tokens */
|
||||
--shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
|
||||
--shadow-md: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||
--shadow-lg: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
--shadow-xl: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
--shadow-lg: 0 4px 6px -1px rgb(0 0 0 / 0.1),
|
||||
0 2px 4px -2px rgb(0 0 0 / 0.1);
|
||||
--shadow-xl: 0 10px 15px -3px rgb(0 0 0 / 0.1),
|
||||
0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
/* Modal specific styles */
|
||||
|
|
@ -49,7 +51,8 @@
|
|||
|
||||
/* Header section */
|
||||
.onboarding-view .onboarding-header {
|
||||
padding: calc(var(--onboarding-spacing) * 4) var(--onboarding-spacing) var(--size-4-2) var(--onboarding-spacing);
|
||||
padding: calc(var(--onboarding-spacing) * 4) var(--onboarding-spacing)
|
||||
var(--size-4-2) var(--onboarding-spacing);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
|
@ -72,7 +75,7 @@
|
|||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Footer section - Modern design */
|
||||
|
|
@ -88,7 +91,7 @@
|
|||
|
||||
/* Glass morphism effect for modern look */
|
||||
.onboarding-view .onboarding-footer::before {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
|
@ -139,7 +142,7 @@
|
|||
|
||||
/* Ripple effect on button click */
|
||||
.onboarding-view .onboarding-buttons button::after {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
|
|
@ -262,7 +265,8 @@
|
|||
rgba(var(--interactive-accent-rgb), 0.05) 0%,
|
||||
var(--background-primary) 100%
|
||||
);
|
||||
box-shadow: var(--shadow-md), 0 0 0 3px rgba(var(--interactive-accent-rgb), 0.1);
|
||||
box-shadow: var(--shadow-md),
|
||||
0 0 0 3px rgba(var(--interactive-accent-rgb), 0.1);
|
||||
}
|
||||
|
||||
.onboarding-view .settings-check-action-card:hover {
|
||||
|
|
@ -1303,7 +1307,11 @@
|
|||
}
|
||||
|
||||
.mode-fluent .mode-card-preview {
|
||||
background: linear-gradient(135deg, var(--background-secondary) 0%, var(--background-modifier-hover) 100%);
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
var(--background-secondary) 0%,
|
||||
var(--background-modifier-hover) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.mode-legacy .mode-card-preview {
|
||||
|
|
@ -1497,9 +1505,16 @@
|
|||
/* Description text - Responsive body text */
|
||||
font-size: clamp(1rem, 2vw, 1.4rem);
|
||||
color: var(--text-muted);
|
||||
line-height: 1.8;
|
||||
margin-bottom: var(--size-4-5);
|
||||
text-align: left;s
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.intro-line-4 {
|
||||
padding-top: calc(var(--onboarding-spacing) * 4);
|
||||
}
|
||||
|
||||
.onboarding-header > .intro-line-4 {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
/* AI-style streaming characters */
|
||||
|
|
@ -1509,8 +1524,8 @@
|
|||
filter: blur(4px);
|
||||
transform: translateY(-2px);
|
||||
transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
filter 0.4s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
filter 0.4s cubic-bezier(0.4, 0, 0.2, 1),
|
||||
transform 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
|
|
@ -1537,10 +1552,12 @@
|
|||
}
|
||||
|
||||
@keyframes cursorBlink {
|
||||
0%, 49% {
|
||||
0%,
|
||||
49% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
50%, 100% {
|
||||
50%,
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1734,6 +1751,12 @@
|
|||
margin: var(--size-4-4) 0;
|
||||
}
|
||||
|
||||
.selectable-cards-container.user-level-cards {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--size-4-4);
|
||||
}
|
||||
|
||||
.selectable-card {
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--onboarding-border-radius);
|
||||
|
|
|
|||
38
styles.css
38
styles.css
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue