feat(onboarding): refactor onboarding flow with new placement configuration

- Add new PlacementStep for configuring Fluent layout placement
- Consolidate onboarding steps into unified step-based architecture
- Remove obsolete components (ConfigPreview, FluentPlacement, OnboardingComplete, UserLevelSelector)
- Enhance step navigation with improved back/skip logic
- Add internationalization support to changelog view
- Update changelog branding (icon, URL to taskgenius.md)
- Add experimental TaskViewV2 with enhanced Fluent interface
- Improve onboarding UI with better visual hierarchy and styling
- Update translations across en, zh-cn, zh-tw locales
This commit is contained in:
Quorafind 2025-10-10 17:21:07 +08:00
parent 2323767b32
commit 1b9768f5af
32 changed files with 4458 additions and 1095 deletions

View file

@ -1,6 +1,7 @@
import { ItemView, MarkdownRenderer, WorkspaceLeaf } from "obsidian";
import type TaskProgressBarPlugin from "@/index";
import { getCachedChangelog } from "@/utils/changelog-cache";
import { t } from "@/translations/helper";
export const CHANGELOG_VIEW_TYPE = "task-genius-changelog";
@ -26,11 +27,11 @@ export class ChangelogView extends ItemView {
}
getDisplayText(): string {
return "Task Genius Changelog";
return t("Changelog");
}
getIcon(): string {
return "list";
return "task-genius";
}
async onOpen() {
@ -97,7 +98,7 @@ export class ChangelogView extends ItemView {
});
headerEl.createEl("h2", {
text: "Task Genius Changelog",
text: t("Task Genius Changelog"),
});
if (this.content?.version) {
@ -113,7 +114,7 @@ export class ChangelogView extends ItemView {
metaEl.createSpan({ text: " • " });
metaEl.createEl("a", {
text: "View full changelog",
href: this.content.sourceUrl,
href: "https://taskgenius.md/changelog",
attr: {
target: "_blank",
rel: "noopener noreferrer",

View file

@ -1,320 +0,0 @@
import {
OnboardingConfig,
OnboardingConfigManager,
} from "@/managers/onboarding-manager";
import { t } from "@/translations/helper";
import { setIcon } from "obsidian";
export class ConfigPreview {
private configManager: OnboardingConfigManager;
constructor(configManager: OnboardingConfigManager) {
this.configManager = configManager;
}
/**
* Render configuration preview
*/
render(containerEl: HTMLElement, config: OnboardingConfig) {
containerEl.empty();
// Configuration overview
const overviewSection = containerEl.createDiv("config-overview");
const selectedModeEl = overviewSection.createDiv("selected-mode");
selectedModeEl.createEl("h3", { text: t("Selected Mode") });
const modeCard = selectedModeEl.createDiv("mode-card");
const modeIcon = modeCard.createDiv("mode-icon");
setIcon(modeIcon, this.getConfigIcon(config.mode));
const modeContent = modeCard.createDiv("mode-content");
modeContent.createEl("h4", { text: config.name });
modeContent.createEl("p", { text: config.description });
// Features that will be enabled
const featuresSection = containerEl.createDiv("config-features");
featuresSection.createEl("h3", {
text: t("Features that will be enabled"),
});
const featuresList = featuresSection.createEl("ul", {
cls: "enabled-features-list",
});
config.features.forEach((feature) => {
const featureItem = featuresList.createEl("li");
const checkIcon = featureItem.createSpan("feature-check");
setIcon(checkIcon, "check");
featureItem.createSpan("feature-text").setText(feature);
});
// Views that will be available
this.renderViewsPreview(containerEl, config);
// Settings summary
this.renderSettingsSummary(containerEl, config);
// Note about customization
const customizationNote = containerEl.createDiv("customization-note");
customizationNote.createEl("p", {
text: t(
"Don't worry! You can customize any of these settings later in the plugin settings."
),
cls: "note-text",
});
}
/**
* Render views preview
*/
private renderViewsPreview(
containerEl: HTMLElement,
config: OnboardingConfig
) {
if (!config.settings.viewConfiguration) return;
const viewsSection = containerEl.createDiv("config-views");
viewsSection.createEl("h3", { text: t("Available views") });
const viewsGrid = viewsSection.createDiv("views-grid");
config.settings.viewConfiguration.forEach((view) => {
const viewItem = viewsGrid.createDiv("view-item");
const viewIcon = viewItem.createDiv("view-icon");
// Use native Obsidian icon from view.icon
setIcon(viewIcon, view.icon || "list");
const viewName = viewItem.createDiv("view-name");
viewName.setText(view.name);
});
}
/**
* Render settings summary
*/
private renderSettingsSummary(
containerEl: HTMLElement,
config: OnboardingConfig
) {
const settingsSection = containerEl.createDiv("config-settings");
settingsSection.createEl("h3", { text: t("Key settings") });
const settingsList = settingsSection.createEl("ul", {
cls: "settings-summary-list",
});
// Progress bars
if (config.settings.progressBarDisplayMode) {
const item = settingsList.createEl("li");
item.createSpan("setting-label").setText(t("Progress bars") + ":");
item.createSpan("setting-value").setText(
config.settings.progressBarDisplayMode === "both"
? t("Enabled (both graphical and text)")
: config.settings.progressBarDisplayMode
);
}
// Task status switching
if (config.settings.enableTaskStatusSwitcher !== undefined) {
const item = settingsList.createEl("li");
item.createSpan("setting-label").setText(
t("Task status switching") + ":"
);
item.createSpan("setting-value").setText(
config.settings.enableTaskStatusSwitcher
? t("Enabled")
: t("Disabled")
);
}
// Quick capture
if (config.settings.quickCapture?.enableQuickCapture !== undefined) {
const item = settingsList.createEl("li");
item.createSpan("setting-label").setText(t("Quick capture") + ":");
item.createSpan("setting-value").setText(
config.settings.quickCapture.enableQuickCapture
? t("Enabled")
: t("Disabled")
);
}
// Workflow
if (config.settings.workflow?.enableWorkflow !== undefined) {
const item = settingsList.createEl("li");
item.createSpan("setting-label").setText(
t("Workflow management") + ":"
);
item.createSpan("setting-value").setText(
config.settings.workflow.enableWorkflow
? t("Enabled")
: t("Disabled")
);
}
// Rewards
if (config.settings.rewards?.enableRewards !== undefined) {
const item = settingsList.createEl("li");
item.createSpan("setting-label").setText(t("Reward system") + ":");
item.createSpan("setting-value").setText(
config.settings.rewards.enableRewards
? t("Enabled")
: t("Disabled")
);
}
// Habits
if (config.settings.habit?.enableHabits !== undefined) {
const item = settingsList.createEl("li");
item.createSpan("setting-label").setText(t("Habit tracking") + ":");
item.createSpan("setting-value").setText(
config.settings.habit.enableHabits
? t("Enabled")
: t("Disabled")
);
}
// Performance features
if (
config.settings.fileParsingConfig?.enableWorkerProcessing !==
undefined
) {
const item = settingsList.createEl("li");
item.createSpan("setting-label").setText(
t("Performance optimization") + ":"
);
item.createSpan("setting-value").setText(
config.settings.fileParsingConfig.enableWorkerProcessing
? t("Enabled")
: t("Disabled")
);
}
// Show configuration change preview
this.renderConfigurationChanges(containerEl, config);
}
/**
* Render configuration changes preview
*/
private renderConfigurationChanges(
containerEl: HTMLElement,
config: OnboardingConfig
) {
try {
const preview = this.configManager.getConfigurationPreview(
config.mode
);
// Show change summary section
const changesSection = containerEl.createDiv(
"config-changes-summary"
);
changesSection.createEl("h3", { text: t("Configuration Changes") });
// User custom views preserved
if (preview.userCustomViewsPreserved.length > 0) {
const preservedSection =
changesSection.createDiv("preserved-views");
const preservedHeader =
preservedSection.createDiv("preserved-header");
const preservedIcon =
preservedHeader.createSpan("preserved-icon");
setIcon(preservedIcon, "shield-check");
preservedHeader
.createSpan("preserved-text")
.setText(
t("Your custom views will be preserved") +
` (${preview.userCustomViewsPreserved.length})`
);
const preservedList = preservedSection.createEl("ul", {
cls: "preserved-views-list",
});
preview.userCustomViewsPreserved.forEach((view) => {
const item = preservedList.createEl("li");
const viewIcon = item.createSpan();
setIcon(viewIcon, view.icon || "list");
item.createSpan().setText(" " + view.name);
});
}
// Views to be added
if (preview.viewsToAdd.length > 0) {
const addedSection = changesSection.createDiv("added-views");
const addedIcon = addedSection.createSpan("change-icon");
setIcon(addedIcon, "plus-circle");
addedSection
.createSpan("change-text")
.setText(
t("New views to be added") +
` (${preview.viewsToAdd.length})`
);
}
// Views to be updated
if (preview.viewsToUpdate.length > 0) {
const updatedSection =
changesSection.createDiv("updated-views");
const updatedIcon = updatedSection.createSpan("change-icon");
setIcon(updatedIcon, "refresh-cw");
updatedSection
.createSpan("change-text")
.setText(
t("Existing views to be updated") +
` (${preview.viewsToUpdate.length})`
);
}
// Settings changes
if (preview.settingsChanges.length > 0) {
const settingsChangesSection =
changesSection.createDiv("settings-changes");
const settingsIcon =
settingsChangesSection.createSpan("change-icon");
setIcon(settingsIcon, "settings");
settingsChangesSection
.createSpan("change-text")
.setText(t("Feature changes"));
const changesList = settingsChangesSection.createEl("ul", {
cls: "settings-changes-list",
});
preview.settingsChanges.forEach((change) => {
const item = changesList.createEl("li");
item.setText(change);
});
}
// Safety note
const safetyNote = changesSection.createDiv("safety-note");
const safetyIcon = safetyNote.createSpan("safety-icon");
setIcon(safetyIcon, "info");
safetyNote
.createSpan("safety-text")
.setText(
t(
"Only template settings will be applied. Your existing custom configurations will be preserved."
)
);
} catch (error) {
console.warn("Could not generate configuration preview:", error);
}
}
/**
* Get icon for configuration mode
*/
private getConfigIcon(mode: string): string {
switch (mode) {
case "beginner":
return "edit-3"; // Lucide edit icon
case "advanced":
return "settings"; // Lucide settings icon
case "power":
return "zap"; // Lucide lightning bolt icon
default:
return "clipboard-list"; // Lucide clipboard icon
}
}
}

View file

@ -1,75 +0,0 @@
import { t } from "@/translations/helper";
export type Placement = "sideleaves" | "inline";
export class FluentPlacement {
render(
container: HTMLElement,
current: Placement | null,
onSelect: (p: Placement) => void
) {
container.empty();
const section = container.createDiv({ cls: "placement-selection" });
section.createEl("h2", { text: t("Fluent Layout"), cls: "section-title" });
section.createEl("p", {
text: t("Choose how to display Fluent: use Sideleaves for enhanced multi-column collaboration, or Inline for an immersive single-page experience."),
cls: "section-desc"
});
const options = section.createDiv({ cls: "placement-options" });
const makeCard = (
id: Placement,
titleText: string,
descText: string
) => {
const el = options.createDiv({ cls: `placement-card placement-${id}` });
const header = el.createDiv({ cls: "placement-card-header" });
header.createDiv({ cls: "placement-card-title", text: titleText });
const body = el.createDiv({ cls: "placement-card-body" });
const preview = body.createDiv({ cls: "placement-card-preview" });
// Visual representation for each placement mode
if (id === "sideleaves") {
// Three column layout visual
const col1 = preview.createDiv({ cls: "placement-preview-col" });
const col2 = preview.createDiv({ cls: "placement-preview-col placement-preview-main" });
const col3 = preview.createDiv({ cls: "placement-preview-col" });
} else {
// Single column layout visual
const single = preview.createDiv({ cls: "placement-preview-single" });
}
body.createDiv({ cls: "placement-card-desc", text: descText });
if (current === id) el.addClass("is-selected");
el.addEventListener("click", () => {
// Remove previous selection
options.querySelectorAll('.placement-card').forEach(card => {
card.removeClass('is-selected');
});
el.addClass('is-selected');
onSelect(id);
});
el.addClass("clickable-icon");
};
makeCard(
"inline",
t("Inline (Single-Page Immersion)"),
t("All content in one page, focusing on the main view and reducing interface distractions.")
);
makeCard(
"sideleaves",
t("Sideleaves (Multi-Column Collaboration)"),
t("Left navigation and right details as separate workspace sidebars, ideal for simultaneous browsing and editing.")
);
const tips = section.createDiv({ cls: "placement-tips" });
tips.createEl("p", {
text: t("You can change this option later in settings. We'll strive to maintain fluidity and elegance."),
cls: "text-muted",
});
}
}

View file

@ -1,213 +0,0 @@
import { OnboardingConfig } from "@/managers/onboarding-manager";
import { t } from "@/translations/helper";
import { setIcon } from "obsidian";
export class OnboardingComplete {
/**
* Render onboarding completion page
*/
render(containerEl: HTMLElement, config: OnboardingConfig) {
containerEl.empty();
// Success message
const successSection = containerEl.createDiv("completion-success");
const successIcon = successSection.createDiv("success-icon");
successIcon.setText("🎉");
successSection.createEl("h2", { text: t("Congratulations!") });
successSection.createEl("p", {
text: t(
"Task Genius has been configured with your selected preferences"
),
cls: "success-message",
});
// Configuration summary
const summarySection = containerEl.createDiv("completion-summary");
summarySection.createEl("h3", { text: t("Your Configuration") });
const configCard = summarySection.createDiv("config-summary-card");
const configHeader = configCard.createDiv("config-header");
const iconEl = configHeader.createDiv("config-icon");
setIcon(iconEl, this.getConfigIcon(config.mode));
configHeader.createDiv("config-name").setText(config.name);
const configDescription = configCard.createDiv("config-description");
configDescription.setText(config.description);
// Quick start guide
const quickStartSection = containerEl.createDiv("quick-start-section");
quickStartSection.createEl("h3", { text: t("Quick Start Guide") });
const stepsContainer = quickStartSection.createDiv("quick-start-steps");
const quickStartSteps = this.getQuickStartSteps(config.mode);
quickStartSteps.forEach((step, index) => {
const stepEl = stepsContainer.createDiv("quick-start-step");
stepEl.createDiv("step-number").setText((index + 1).toString());
stepEl.createDiv("step-content").setText(step);
});
// Next steps
const nextStepsSection = containerEl.createDiv("next-steps-section");
nextStepsSection.createEl("h3", { text: t("What's next?") });
const nextStepsList = nextStepsSection.createEl("ul", {
cls: "next-steps-list",
});
const nextSteps = [
t("Open Task Genius view from the left ribbon"),
t("Create your first task using Quick Capture"),
t("Explore different views to organize your tasks"),
t("Customize settings anytime in plugin settings"),
];
nextSteps.forEach((step) => {
const item = nextStepsList.createEl("li");
const checkIcon = item.createSpan("step-check");
setIcon(checkIcon, "arrow-right");
item.createSpan("step-text").setText(step);
});
// Helpful resources
const resourcesSection = containerEl.createDiv("resources-section");
resourcesSection.createEl("h3", { text: t("Helpful Resources") });
const resourcesList = resourcesSection.createDiv("resources-list");
const resources = [
{
icon: "book-open", // Lucide book icon
title: t("Documentation"),
description: t("Complete guide to all features"),
url: "https://taskgenius.md",
},
{
icon: "message-circle", // Lucide message circle icon
title: t("Community"),
description: t("Get help and share tips"),
url: "https://discord.gg/ARR2rHHX6b",
},
{
icon: "settings", // Lucide settings icon
title: t("Settings"),
description: t("Customize Task Genius"),
action: "open-settings",
},
];
resources.forEach((resource) => {
const resourceEl = resourcesList.createDiv("resource-item");
const resourceContent = resourceEl.createDiv("resource-content");
resourceContent.createEl("h4", { text: resource.title });
resourceContent.createEl("p", { text: resource.description });
if (resource.url) {
resourceEl.addEventListener("click", () => {
window.open(resource.url, "_blank");
});
resourceEl.addClass("resource-clickable");
} else if (resource.action === "open-settings") {
resourceEl.addEventListener("click", () => {
// Open plugin settings
// This will be handled by the main plugin
const event = new CustomEvent("task-genius-open-settings");
document.dispatchEvent(event);
});
resourceEl.addClass("resource-clickable");
}
});
}
/**
* Get configuration icon
*/
private getConfigIcon(mode: string): string {
switch (mode) {
case "beginner":
return "edit-3"; // Lucide edit icon
case "advanced":
return "settings"; // Lucide settings icon
case "power":
return "zap"; // Lucide lightning bolt icon
default:
return "clipboard-list"; // Lucide clipboard icon
}
}
/**
* Get quick start steps based on configuration mode
*/
private getQuickStartSteps(mode: string): string[] {
switch (mode) {
case "beginner":
return [
t("Click the Task Genius icon in the left sidebar"),
t("Start with the Inbox view to see all your tasks"),
t("Use quick capture panel to quickly add your first task"),
t("Try the Forecast view to see tasks by date"),
];
case "advanced":
return [
t("Open Task Genius and explore the available views"),
t("Set up a project using the Projects view"),
t("Try the Kanban board for visual task management"),
t("Use workflow stages to track task progress"),
];
case "power":
return [
t("Explore all available views and their configurations"),
t("Set up complex workflows for your projects"),
t("Configure habits and rewards to stay motivated"),
t("Integrate with external calendars and systems"),
];
default:
return [
t("Open Task Genius from the left sidebar"),
t("Create your first task"),
t("Explore the different views available"),
t("Customize settings as needed"),
];
}
}
/**
* Handle feedback submission
*/
private handleFeedback(
type: "positive" | "negative",
feedbackSection: HTMLElement
) {
// Find and remove existing feedback buttons
const buttonsEl = feedbackSection.querySelector(".feedback-buttons");
if (buttonsEl) {
buttonsEl.remove();
}
// Show thank you message
const thankYouEl = feedbackSection.createDiv("feedback-thanks");
thankYouEl.createEl("p", {
text:
type === "positive"
? t("Thank you for your positive feedback!")
: t(
"Thank you for your feedback. We'll continue improving the experience."
),
cls: "feedback-thanks-message",
});
// For negative feedback, could add a link to feedback form
if (type === "negative") {
const feedbackLink = thankYouEl.createEl("a", {
text: t("Share detailed feedback"),
href: "https://github.com/obsidian-task-genius/feedback/issues/new",
});
feedbackLink.setAttribute("target", "_blank");
}
// Log feedback (could be sent to analytics in the future)
console.log(`Onboarding feedback: ${type}`);
}
}

View file

@ -14,13 +14,14 @@ export enum OnboardingStep {
FLUENT_PROJECTS = 5,
FLUENT_OTHER_VIEWS = 6,
FLUENT_TOPNAV = 7,
FLUENT_PLACEMENT = 8,
// Config & rest
SETTINGS_CHECK = 8,
USER_LEVEL_SELECT = 9,
FILE_FILTER = 10,
CONFIG_PREVIEW = 11,
TASK_CREATION_GUIDE = 12,
COMPLETE = 13,
SETTINGS_CHECK = 9,
USER_LEVEL_SELECT = 10,
FILE_FILTER = 11,
CONFIG_PREVIEW = 12,
TASK_CREATION_GUIDE = 13,
COMPLETE = 14,
}
export interface OnboardingState {
@ -173,6 +174,9 @@ export class OnboardingController {
nextStep = OnboardingStep.FLUENT_TOPNAV;
break;
case OnboardingStep.FLUENT_TOPNAV:
nextStep = OnboardingStep.FLUENT_PLACEMENT;
break;
case OnboardingStep.FLUENT_PLACEMENT:
nextStep = OnboardingStep.SETTINGS_CHECK;
break;
@ -259,11 +263,14 @@ export class OnboardingController {
case OnboardingStep.FLUENT_TOPNAV:
prevStep = OnboardingStep.FLUENT_OTHER_VIEWS;
break;
case OnboardingStep.FLUENT_PLACEMENT:
prevStep = OnboardingStep.FLUENT_TOPNAV;
break;
case OnboardingStep.SETTINGS_CHECK:
// Go back to last fluent step or mode select based on UI mode
if (this.state.uiMode === "fluent") {
prevStep = OnboardingStep.FLUENT_TOPNAV;
prevStep = OnboardingStep.FLUENT_PLACEMENT;
} else {
prevStep = OnboardingStep.MODE_SELECT;
}
@ -277,7 +284,7 @@ export class OnboardingController {
) {
prevStep = OnboardingStep.SETTINGS_CHECK;
} else if (this.state.uiMode === "fluent") {
prevStep = OnboardingStep.FLUENT_TOPNAV;
prevStep = OnboardingStep.FLUENT_PLACEMENT;
} else {
prevStep = OnboardingStep.MODE_SELECT;
}
@ -383,11 +390,15 @@ export class OnboardingController {
* Check if skip button should be shown
*/
canSkip(): boolean {
const step = this.state.currentStep;
if (step === OnboardingStep.COMPLETE) {
return false;
}
return (
this.state.currentStep === OnboardingStep.INTRO ||
this.state.currentStep === OnboardingStep.MODE_SELECT ||
this.state.currentStep === OnboardingStep.SETTINGS_CHECK ||
this.state.currentStep === OnboardingStep.FILE_FILTER
step === OnboardingStep.INTRO ||
this.canGoBack()
);
}

View file

@ -33,7 +33,7 @@ export class OnboardingLayout extends Component {
constructor(
container: HTMLElement,
controller: OnboardingController,
callbacks: OnboardingLayoutCallbacks
callbacks: OnboardingLayoutCallbacks,
) {
super();
this.container = container;
@ -50,7 +50,7 @@ export class OnboardingLayout extends Component {
*/
private createLayout() {
this.container.empty();
this.container.addClass("onboarding-view");
this.container.toggleClass(["onboarding-view"], true);
// Header section
this.headerEl = this.container.createDiv("onboarding-header");
@ -66,6 +66,10 @@ export class OnboardingLayout extends Component {
this.container.createEl("div", {
cls: "onboarding-shadow",
});
this.container.createEl("div", {
cls: "tg-noise-layer",
});
}
/**
@ -128,6 +132,7 @@ export class OnboardingLayout extends Component {
// Next button
const isLastStep = step === OnboardingStep.COMPLETE;
const isSettingsCheck = step === OnboardingStep.SETTINGS_CHECK;
const isModeSelect = step === OnboardingStep.MODE_SELECT;
this.nextButton.buttonEl.toggleVisibility(true);
// Update button text based on step and selection
@ -139,15 +144,17 @@ export class OnboardingLayout extends Component {
} else {
this.nextButton.setButtonText(t("Continue"));
}
} else if (isModeSelect && state.uiMode === "fluent") {
this.nextButton.setButtonText(t("Next to Introduction"));
} else {
this.nextButton.setButtonText(
isLastStep ? t("Start Using Task Genius") : t("Next")
isLastStep ? t("Start Using Task Genius") : t("Next"),
);
}
// Enable/disable next based on validation
this.nextButton.setDisabled(
!this.controller.canGoNext() || state.isCompleting
!this.controller.canGoNext() || state.isCompleting,
);
}

View file

@ -15,6 +15,7 @@ import { FluentMainNavigationStep } from "./steps/FluentMainNavigationStep";
import { FluentProjectSectionStep } from "./steps/FluentProjectSectionStep";
import { FluentOtherViewsStep } from "./steps/FluentOtherViewsStep";
import { FluentTopNavigationStep } from "./steps/FluentTopNavigationStep";
import { PlacementStep } from "./steps/PlacementStep";
import { UserLevelStep } from "./steps/UserLevelStep";
import { FileFilterStep } from "./steps/FileFilterStep";
import { ConfigPreviewStep } from "./steps/ConfigPreviewStep";
@ -51,7 +52,7 @@ export class OnboardingView extends ItemView {
constructor(
leaf: WorkspaceLeaf,
plugin: TaskProgressBarPlugin,
onComplete: () => void
onComplete: () => void,
) {
super(leaf);
this.plugin = plugin;
@ -78,7 +79,7 @@ export class OnboardingView extends ItemView {
}
getDisplayText(): string {
return t("Task Genius Setup");
return t("Setup");
}
getIcon(): string {
@ -140,7 +141,7 @@ export class OnboardingView extends ItemView {
headerEl,
contentEl,
footerEl,
this.controller
this.controller,
);
break;
@ -148,7 +149,7 @@ export class OnboardingView extends ItemView {
ModeSelectionStep.render(
headerEl,
contentEl,
this.controller
this.controller,
);
break;
@ -156,50 +157,53 @@ export class OnboardingView extends ItemView {
FluentOverviewStep.render(
headerEl,
contentEl,
this.controller
this.controller,
);
break;
case OnboardingStep.FLUENT_WS_SELECTOR:
FluentWorkspaceSelectorStep.render(
headerEl,
contentEl,
this.controller
this.controller,
);
break;
case OnboardingStep.FLUENT_MAIN_NAV:
FluentMainNavigationStep.render(
headerEl,
contentEl,
this.controller
this.controller,
);
break;
case OnboardingStep.FLUENT_PROJECTS:
FluentProjectSectionStep.render(
headerEl,
contentEl,
this.controller
this.controller,
);
break;
case OnboardingStep.FLUENT_OTHER_VIEWS:
FluentOtherViewsStep.render(
headerEl,
contentEl,
this.controller
this.controller,
);
break;
case OnboardingStep.FLUENT_TOPNAV:
FluentTopNavigationStep.render(
headerEl,
contentEl,
this.controller
this.controller,
);
break;
case OnboardingStep.FLUENT_PLACEMENT:
PlacementStep.render(headerEl, contentEl, this.controller);
break;
case OnboardingStep.SETTINGS_CHECK:
SettingsCheckStep.render(
headerEl,
contentEl,
this.controller
this.controller,
);
break;
@ -208,7 +212,7 @@ export class OnboardingView extends ItemView {
headerEl,
contentEl,
this.controller,
this.configManager
this.configManager,
);
break;
@ -217,7 +221,7 @@ export class OnboardingView extends ItemView {
headerEl,
contentEl,
this.controller,
this.plugin
this.plugin,
);
break;
@ -226,7 +230,7 @@ export class OnboardingView extends ItemView {
headerEl,
contentEl,
this.controller,
this.configManager
this.configManager,
);
break;
@ -235,7 +239,7 @@ export class OnboardingView extends ItemView {
headerEl,
contentEl,
this.controller,
this.plugin
this.plugin,
);
break;
@ -253,13 +257,9 @@ export class OnboardingView extends ItemView {
const step = this.controller.getCurrentStep();
const state = this.controller.getState();
console.log("handleNext - Current step:", OnboardingStep[step]);
console.log("handleNext - UI Mode:", state.uiMode);
console.log("handleNext - User has changes:", state.userHasChanges);
// Show config check transition only when entering Settings Check from:
// - Mode Select with Legacy
// - The last Fluent step (Top Navigation)
// - The final Fluent step (Placement selection)
if (step === OnboardingStep.MODE_SELECT) {
if (state.uiMode === "legacy" && state.userHasChanges) {
// Clear header before showing transition to avoid residual UI
@ -267,7 +267,7 @@ export class OnboardingView extends ItemView {
await this.showConfigCheckTransition();
}
}
if (step === OnboardingStep.FLUENT_TOPNAV) {
if (step === OnboardingStep.FLUENT_PLACEMENT) {
if (state.userHasChanges) {
// Clear header before showing transition to avoid residual UI
this.layout.clearHeader();
@ -309,7 +309,7 @@ export class OnboardingView extends ItemView {
console.log("handleNext - Navigation result:", success);
console.log(
"handleNext - New step:",
OnboardingStep[this.controller.getCurrentStep()]
OnboardingStep[this.controller.getCurrentStep()],
);
}
@ -330,7 +330,7 @@ export class OnboardingView extends ItemView {
() => {
resolve();
},
state.userHasChanges
state.userHasChanges,
);
});
}
@ -381,9 +381,8 @@ export class OnboardingView extends ItemView {
}
if (isFluent && this.plugin.settings.fluentView) {
(
this.plugin.settings.fluentView
).useWorkspaceSideLeaves = !!state.useSideLeaves;
this.plugin.settings.fluentView.useWorkspaceSideLeaves =
!!state.useSideLeaves;
}
await this.plugin.saveSettings();
@ -398,7 +397,7 @@ export class OnboardingView extends ItemView {
if (!config || state.isCompleting) return;
this.controller.updateState({isCompleting: true});
this.controller.updateState({ isCompleting: true });
try {
// Mark onboarding as completed
@ -409,7 +408,7 @@ export class OnboardingView extends ItemView {
this.leaf.detach();
} catch (error) {
console.error("Failed to complete onboarding:", error);
this.controller.updateState({isCompleting: false});
this.controller.updateState({ isCompleting: false });
}
}
}

View file

@ -20,7 +20,7 @@ export class TaskCreationGuide {
const introSection = containerEl.createDiv("task-guide-intro");
introSection.createEl("p", {
text: t(
"Learn the different ways to create and format tasks in Task Genius. You can use either emoji-based or Dataview-style syntax."
"Learn the different ways to create and format tasks in Task Genius. You can use either emoji-based or Dataview-style syntax.",
),
cls: "guide-description",
});
@ -37,18 +37,18 @@ export class TaskCreationGuide {
*/
private renderTaskFormats(containerEl: HTMLElement) {
const formatsSection = containerEl.createDiv("task-formats-section");
formatsSection.createEl("h3", {text: t("Task Format Examples")});
formatsSection.createEl("h3", { text: t("Task Format Examples") });
// Basic task format
const basicFormat = formatsSection.createDiv("format-example");
basicFormat.createEl("h4", {text: t("Basic Task")});
basicFormat.createEl("h4", { text: t("Basic Task") });
basicFormat.createEl("code", {
text: "- [ ] Complete project documentation",
});
// Emoji format
const emojiFormat = formatsSection.createDiv("format-example");
emojiFormat.createEl("h4", {text: t("With Emoji Metadata")});
emojiFormat.createEl("h4", { text: t("With Emoji Metadata") });
emojiFormat.createEl("code", {
text: "- [ ] Complete project documentation 📅 2024-01-15 🔺 #project/docs",
});
@ -56,20 +56,20 @@ export class TaskCreationGuide {
const emojiLegend = emojiFormat.createDiv("format-legend");
emojiLegend.createEl("small", {
text: t(
"📅 = Due date, 🔺 = High priority, #project/ = Docs project tag"
"📅 = Due date, 🔺 = High priority, #project/ = Docs project tag",
),
});
// Dataview format
const dataviewFormat = formatsSection.createDiv("format-example");
dataviewFormat.createEl("h4", {text: t("With Dataview Metadata")});
dataviewFormat.createEl("h4", { text: t("With Dataview Metadata") });
dataviewFormat.createEl("code", {
text: "- [ ] Complete project documentation [due:: 2024-01-15] [priority:: high] [project:: docs]",
});
// Mixed format
const mixedFormat = formatsSection.createDiv("format-example");
mixedFormat.createEl("h4", {text: t("Mixed Format")});
mixedFormat.createEl("h4", { text: t("Mixed Format") });
mixedFormat.createEl("code", {
text: "- [ ] Complete project documentation 📅 2024-01-15 [priority:: high] @work",
});
@ -81,48 +81,48 @@ export class TaskCreationGuide {
// Status markers
const statusSection = formatsSection.createDiv("status-markers");
statusSection.createEl("h4", {text: t("Task Status Markers")});
statusSection.createEl("h4", { text: t("Task Status Markers") });
const statusList = statusSection.createEl("ul", {cls: "status-list"});
const statusList = statusSection.createEl("ul", { cls: "status-list" });
const statusMarkers = [
{marker: "[ ]", description: t("Not started")},
{marker: "[x]", description: t("Completed")},
{marker: "[/]", description: t("In progress")},
{marker: "[?]", description: t("Planned")},
{marker: "[-]", description: t("Abandoned")},
{ marker: "[ ]", description: t("Not started") },
{ marker: "[x]", description: t("Completed") },
{ marker: "[/]", description: t("In progress") },
{ marker: "[?]", description: t("Planned") },
{ marker: "[-]", description: t("Abandoned") },
];
statusMarkers.forEach((status) => {
const item = statusList.createEl("li");
item.createEl("code", {text: status.marker});
item.createEl("code", { text: status.marker });
item.createSpan().setText(" - " + status.description);
});
// Metadata symbols
const metadataSection = formatsSection.createDiv("metadata-symbols");
metadataSection.createEl("h4", {text: t("Common Metadata Symbols")});
metadataSection.createEl("h4", { text: t("Common Metadata Symbols") });
const symbolsList = metadataSection.createEl("ul", {
cls: "symbols-list",
});
const symbols = [
{symbol: "📅", description: t("Due date")},
{symbol: "🛫", description: t("Start date")},
{symbol: "⏳", description: t("Scheduled date")},
{symbol: "🔺", description: t("High priority")},
{symbol: "⏫", description: t("Higher priority")},
{symbol: "🔼", description: t("Medium priority")},
{symbol: "🔽", description: t("Lower priority")},
{symbol: "⏬", description: t("Lowest priority")},
{symbol: "🔁", description: t("Recurring task")},
{symbol: "#", description: t("Project/tag")},
{symbol: "@", description: t("Context")},
{ symbol: "📅", description: t("Due date") },
{ symbol: "🛫", description: t("Start date") },
{ symbol: "⏳", description: t("Scheduled date") },
{ symbol: "🔺", description: t("High priority") },
{ symbol: "⏫", description: t("Higher priority") },
{ symbol: "🔼", description: t("Medium priority") },
{ symbol: "🔽", description: t("Lower priority") },
{ symbol: "⏬", description: t("Lowest priority") },
{ symbol: "🔁", description: t("Recurring task") },
{ symbol: "#", description: t("Project/tag") },
{ symbol: "@", description: t("Context") },
];
symbols.forEach((symbol) => {
const item = symbolsList.createEl("li");
item.createSpan().setText(
symbol.symbol + " - " + symbol.description
symbol.symbol + " - " + symbol.description,
);
});
}
@ -132,14 +132,14 @@ export class TaskCreationGuide {
*/
private renderQuickCaptureDemo(containerEl: HTMLElement) {
const quickCaptureSection = containerEl.createDiv(
"quick-capture-section"
"quick-capture-section",
);
quickCaptureSection.createEl("h3", {text: t("Quick Capture")});
quickCaptureSection.createEl("h3", { text: t("Quick Capture") });
const demoContent = quickCaptureSection.createDiv("demo-content");
demoContent.createEl("p", {
text: t(
"Use quick capture panel to quickly capture tasks from anywhere in Obsidian."
"Use quick capture panel to quickly capture tasks from anywhere in Obsidian.",
),
});
@ -159,16 +159,16 @@ export class TaskCreationGuide {
// Show info that quick capture will be enabled
new Notice(
t(
"Quick capture is now enabled in your configuration!"
"Quick capture is now enabled in your configuration!",
),
3000
3000,
);
}
} catch (error) {
console.error("Failed to open quick capture:", error);
new Notice(
t("Failed to open quick capture. Please try again later."),
3000
3000,
);
}
});
@ -179,7 +179,7 @@ export class TaskCreationGuide {
*/
private renderInteractivePractice(containerEl: HTMLElement) {
const practiceSection = containerEl.createDiv("practice-section");
practiceSection.createEl("h3", {text: t("Try It Yourself")});
practiceSection.createEl("h3", { text: t("Try It Yourself") });
practiceSection.createEl("p", {
text: t("Practice creating a task with the format you prefer:"),
@ -236,7 +236,7 @@ export class TaskCreationGuide {
if (!isValidTask) {
feedbackEl.createEl("div", {
text: t(
"This doesn't look like a valid task. Tasks should start with '- [ ]'"
"This doesn't look like a valid task. Tasks should start with '- [ ]'",
),
cls: "validation-message validation-error",
});

View file

@ -1,134 +0,0 @@
import {
OnboardingConfigManager,
OnboardingConfig,
} from "@/managers/onboarding-manager";
import { setIcon } from "obsidian";
export class UserLevelSelector {
private configManager: OnboardingConfigManager;
private selectedConfig: OnboardingConfig | null = null;
private onSelectionChange: (config: OnboardingConfig) => void = () => {};
constructor(configManager: OnboardingConfigManager) {
this.configManager = configManager;
}
/**
* Render the user level selector
*/
render(
containerEl: HTMLElement,
onSelectionChange: (config: OnboardingConfig) => void
) {
this.onSelectionChange = onSelectionChange;
containerEl.empty();
const configs = this.configManager.getOnboardingConfigs();
// Create card container
const cardsContainer = containerEl.createDiv("user-level-cards");
// Create cards for each configuration
configs.forEach((config) => {
this.createConfigCard(cardsContainer, config);
});
}
/**
* Create a configuration card
*/
private createConfigCard(container: HTMLElement, config: OnboardingConfig) {
const card = container.createDiv("user-level-card");
card.setAttribute("data-mode", config.mode);
// Card header with icon and title
const cardHeader = card.createDiv("card-header");
const iconEl = cardHeader.createDiv("card-icon");
setIcon(iconEl, this.getConfigIcon(config.mode));
const titleEl = cardHeader.createEl("h3", {
text: config.name,
cls: "card-title",
});
// Card description
const descEl = card.createEl("p", {
text: config.description,
cls: "card-description",
});
// Features list
const featuresEl = card.createDiv("card-features");
const featuresList = featuresEl.createEl("ul");
config.features.forEach((feature) => {
const featureItem = featuresList.createEl("li");
featureItem.setText(feature);
});
// Recommendation badge for beginner
// if (config.mode === 'beginner') {
// const badge = card.createDiv("recommendation-badge");
// badge.setText(t("Recommended for new users"));
// }
// Click handler
card.addEventListener("click", () => {
this.selectConfig(config);
});
// Hover effects
card.addEventListener("mouseenter", () => {
card.addClass("card-hover");
});
card.addEventListener("mouseleave", () => {
card.removeClass("card-hover");
});
}
/**
* Get icon for configuration mode
*/
private getConfigIcon(mode: string): string {
switch (mode) {
case "beginner":
return "edit-3"; // Lucide edit icon
case "advanced":
return "settings"; // Lucide settings icon
case "power":
return "zap"; // Lucide lightning bolt icon
default:
return "clipboard-list"; // Lucide clipboard icon
}
}
/**
* Select a configuration
*/
private selectConfig(config: OnboardingConfig) {
// Remove previous selection
if (this.selectedConfig) {
const prevCard = document.querySelector(
`[data-mode="${this.selectedConfig.mode}"]`
);
prevCard?.removeClass("selected");
}
// Select new config
this.selectedConfig = config;
const newCard = document.querySelector(`[data-mode="${config.mode}"]`);
newCard?.addClass("selected");
// Trigger callback
this.onSelectionChange(config);
}
/**
* Get selected configuration
*/
getSelectedConfig(): OnboardingConfig | null {
return this.selectedConfig;
}
}

View file

@ -8,9 +8,9 @@ import { OnboardingConfig } from "@/managers/onboarding-manager";
*/
export class CompleteStep {
private static readonly ICON_MAP: Record<string, string> = {
beginner: "seedling",
advanced: "zap",
power: "rocket",
beginner: "edit-3",
advanced: "settings",
power: "zap",
};
/**
@ -29,20 +29,21 @@ export class CompleteStep {
if (!config) return;
// Header
headerEl.createEl("h1", { text: t("Setup Complete! 🎉") });
headerEl.createEl("h1", { text: t("Task Genius is ready!") });
headerEl.createEl("p", {
text: t("Task Genius is ready to use"),
text: t("You're all set to start managing your tasks"),
cls: "onboarding-subtitle",
});
// Success section
const successSection = contentEl.createDiv("completion-success");
const successIcon = successSection.createDiv("success-icon");
successIcon.setText("");
successIcon.setText("🎉");
successSection.createEl("h2", { text: t("Congratulations!") });
successSection.createEl("p", {
text: t(
"Your task management workspace has been configured successfully"
"Task Genius has been configured with your selected preferences"
),
cls: "success-message",
});
@ -53,9 +54,6 @@ export class CompleteStep {
// Quick start guide
this.renderQuickStart(contentEl, config);
// Next steps
this.renderNextSteps(contentEl);
// Resources
this.renderResources(contentEl);
}
@ -101,36 +99,12 @@ export class CompleteStep {
});
}
/**
* Render next steps
*/
private static renderNextSteps(container: HTMLElement) {
const section = container.createDiv("next-steps-section");
section.createEl("h3", { text: t("What's next?") });
const list = section.createEl("ul", { cls: "next-steps-list" });
const steps = [
t("Open Task Genius view from the left sidebar"),
t("Create your first task using Quick Capture"),
t("Explore different views to organize your tasks"),
t("Customize settings anytime in plugin settings"),
];
steps.forEach((step) => {
const item = list.createEl("li");
const checkIcon = item.createSpan("step-check");
setIcon(checkIcon, "arrow-right");
item.createSpan("step-text").setText(step);
});
}
/**
* Render resources
*/
private static renderResources(container: HTMLElement) {
const section = container.createDiv("resources-section");
section.createEl("h3", { text: t("Resources") });
section.createEl("h3", { text: t("Helpful Resources") });
const list = section.createDiv("resources-list");
@ -138,7 +112,7 @@ export class CompleteStep {
{
icon: "book-open",
title: t("Documentation"),
desc: t("Learn all features"),
desc: t("Complete guide to all features"),
url: "https://taskgenius.md",
},
{
@ -147,19 +121,35 @@ export class CompleteStep {
desc: t("Get help and share tips"),
url: "https://discord.gg/ARR2rHHX6b",
},
{
icon: "settings",
title: t("Settings"),
desc: t("Customize Task Genius"),
action: "open-settings",
},
];
resources.forEach((r) => {
const item = list.createDiv("resource-item resource-clickable");
const item = list.createDiv("resource-item");
const icon = item.createDiv("resource-icon");
setIcon(icon, r.icon);
const content = item.createDiv("resource-content");
content.createEl("h4", { text: r.title });
content.createEl("p", { text: r.desc });
item.addEventListener("click", () => {
window.open(r.url, "_blank");
});
if (r.url) {
item.addEventListener("click", () => {
window.open(r.url, "_blank");
});
item.addClass("resource-clickable");
} else if (r.action === "open-settings") {
item.addEventListener("click", () => {
// Signal main plugin to open settings so we keep UI logic here.
const event = new CustomEvent("task-genius-open-settings");
document.dispatchEvent(event);
});
item.addClass("resource-clickable");
}
});
}
@ -172,7 +162,7 @@ export class CompleteStep {
return [
t("Click the Task Genius icon in the left sidebar"),
t("Start with the Inbox view to see all your tasks"),
t("Use Quick Capture to add your first task"),
t("Use quick capture panel to quickly add your first task"),
t("Try the Forecast view to see tasks by date"),
];
case "advanced":
@ -184,7 +174,7 @@ export class CompleteStep {
];
case "power":
return [
t("Explore all available views and configurations"),
t("Explore all available views and their configurations"),
t("Set up complex workflows for your projects"),
t("Configure habits and rewards to stay motivated"),
t("Integrate with external calendars and systems"),
@ -193,7 +183,7 @@ export class CompleteStep {
return [
t("Open Task Genius from the left sidebar"),
t("Create your first task"),
t("Explore different views"),
t("Explore the different views available"),
t("Customize settings as needed"),
];
}

View file

@ -19,7 +19,7 @@ export class ConfigPreviewStep {
headerEl: HTMLElement,
contentEl: HTMLElement,
controller: OnboardingController,
configManager: OnboardingConfigManager
configManager: OnboardingConfigManager,
) {
// Clear
headerEl.empty();
@ -38,7 +38,7 @@ export class ConfigPreviewStep {
headerEl.createEl("h1", { text: t("Review Your Configuration") });
headerEl.createEl("p", {
text: t(
"Review the settings that will be applied for your selected mode"
"Review the settings that will be applied for your selected mode",
),
cls: "onboarding-subtitle",
});
@ -81,23 +81,21 @@ export class ConfigPreviewStep {
.setName(t("Include task creation guide"))
.setDesc(t("Show a quick tutorial on creating your first task"))
.addToggle((toggle) => {
toggle
.setValue(!state.skipTaskGuide)
.onChange((value) => {
controller.setSkipTaskGuide(!value);
});
toggle.setValue(!state.skipTaskGuide).onChange((value) => {
controller.setSkipTaskGuide(!value);
});
});
// Note about customization
Alert.create(
contentEl,
t(
"You can customize any of these settings later in the plugin settings"
"You can customize any of these settings later in the plugin settings",
),
{
variant: "info",
className: "customization-note",
}
},
);
}
}

View file

@ -22,7 +22,7 @@ export class FileFilterStep {
headerEl: HTMLElement,
contentEl: HTMLElement,
controller: OnboardingController,
plugin: TaskProgressBarPlugin
plugin: TaskProgressBarPlugin,
) {
// Clear
headerEl.empty();
@ -32,7 +32,7 @@ export class FileFilterStep {
headerEl.createEl("h1", { text: t("Optimize Performance") });
headerEl.createEl("p", {
text: t(
"Configure file filtering to improve indexing performance and focus on relevant content"
"Configure file filtering to improve indexing performance and focus on relevant content",
),
cls: "onboarding-subtitle",
});
@ -62,15 +62,13 @@ export class FileFilterStep {
*/
private static renderConfiguration(
container: HTMLElement,
plugin: TaskProgressBarPlugin
plugin: TaskProgressBarPlugin,
) {
// Enable/Disable toggle
new Setting(container)
.setName(t("Enable File Filter"))
.setDesc(
t(
"Filter files during task indexing to improve performance"
)
t("Filter files during task indexing to improve performance"),
)
.addToggle((toggle) =>
toggle
@ -80,12 +78,14 @@ export class FileFilterStep {
await plugin.saveSettings();
// Re-render to show/hide configuration
this.render(
container.parentElement?.previousElementSibling as HTMLElement,
container.parentElement?.parentElement as HTMLElement,
container.parentElement
?.previousElementSibling as HTMLElement,
container.parentElement
?.parentElement as HTMLElement,
{} as OnboardingController,
plugin
plugin,
);
})
}),
);
if (!plugin.settings.fileFilter.enabled) {
@ -97,19 +97,22 @@ export class FileFilterStep {
.setName(t("Filter Mode"))
.setDesc(
t(
"Whitelist: Include only specified paths | Blacklist: Exclude specified paths"
)
"Whitelist: Include only specified paths | Blacklist: Exclude specified paths",
),
)
.addDropdown((dropdown) =>
dropdown
.addOption(FilterMode.WHITELIST, t("Whitelist (Include only)"))
.addOption(
FilterMode.WHITELIST,
t("Whitelist (Include only)"),
)
.addOption(FilterMode.BLACKLIST, t("Blacklist (Exclude)"))
.setValue(plugin.settings.fileFilter.mode)
.onChange(async (value: FilterMode) => {
plugin.settings.fileFilter.mode = value;
await plugin.saveSettings();
this.updateStats(container, plugin);
})
}),
);
// Quick add rules section
@ -132,10 +135,10 @@ export class FileFilterStep {
autoAddRuleType: "file",
onClose: () => {
const rulesEl = container.querySelector(
".file-filter-rules-container"
".file-filter-rules-container",
) as HTMLElement | null;
const statsEl = container.querySelector(
".file-filter-stats-preview"
".file-filter-stats-preview",
) as HTMLElement | null;
if (rulesEl) {
this.renderRules(rulesEl, plugin);
@ -157,10 +160,10 @@ export class FileFilterStep {
autoAddRuleType: "folder",
onClose: () => {
const rulesEl = container.querySelector(
".file-filter-rules-container"
".file-filter-rules-container",
) as HTMLElement | null;
const statsEl = container.querySelector(
".file-filter-stats-preview"
".file-filter-stats-preview",
) as HTMLElement | null;
if (rulesEl) {
this.renderRules(rulesEl, plugin);
@ -182,10 +185,10 @@ export class FileFilterStep {
autoAddRuleType: "pattern",
onClose: () => {
const rulesEl = container.querySelector(
".file-filter-rules-container"
".file-filter-rules-container",
) as HTMLElement | null;
const statsEl = container.querySelector(
".file-filter-stats-preview"
".file-filter-stats-preview",
) as HTMLElement | null;
if (rulesEl) {
this.renderRules(rulesEl, plugin);
@ -216,7 +219,7 @@ export class FileFilterStep {
*/
private static renderRules(
container: HTMLElement,
plugin: TaskProgressBarPlugin
plugin: TaskProgressBarPlugin,
) {
container.empty();
@ -251,9 +254,9 @@ export class FileFilterStep {
this.renderRules(container, plugin);
this.updateStats(
container.parentElement?.querySelector(
".file-filter-stats-preview"
".file-filter-stats-preview",
) as HTMLElement,
plugin
plugin,
);
});
@ -285,18 +288,13 @@ export class FileFilterStep {
rule.type === "pattern"
? "*.tmp, temp/*"
: rule.type === "folder"
? "path/to/folder"
: "path/to/file.md",
? "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"
);
new FolderSuggest(plugin.app, pathInput, plugin, "single");
} else if (rule.type === "file") {
new FileSuggest(pathInput, plugin, (file) => {
rule.path = file.path;
@ -326,9 +324,9 @@ export class FileFilterStep {
await plugin.saveSettings();
this.updateStats(
container.parentElement?.querySelector(
".file-filter-stats-preview"
".file-filter-stats-preview",
) as HTMLElement,
plugin
plugin,
);
});
@ -345,9 +343,9 @@ export class FileFilterStep {
this.renderRules(container, plugin);
this.updateStats(
container.parentElement?.querySelector(
".file-filter-stats-preview"
".file-filter-stats-preview",
) as HTMLElement,
plugin
plugin,
);
});
});
@ -356,13 +354,16 @@ export class FileFilterStep {
/**
* Update statistics display
*/
private static updateStats(container: HTMLElement, plugin: TaskProgressBarPlugin) {
private static updateStats(
container: HTMLElement,
plugin: TaskProgressBarPlugin,
) {
if (!container) return;
container.empty();
const activeRules = plugin.settings.fileFilter.rules.filter(
(r) => r.enabled
(r) => r.enabled,
).length;
const stats = [
@ -403,29 +404,15 @@ export class FileFilterStep {
*/
private static renderDescription(
container: HTMLElement,
plugin: TaskProgressBarPlugin
plugin: TaskProgressBarPlugin,
) {
container.createEl("h3", { text: t("Why File Filtering?") });
container.createEl("p", {
text: t(
"File filtering helps you focus on relevant content while improving performance, especially in large vaults."
"File filtering helps you focus on relevant content while improving performance, especially in large vaults.",
),
});
const benefits = container.createEl("ul", {
cls: "component-feature-list",
});
[
t("Faster task indexing in large vaults"),
t("Exclude temporary or archive files"),
t("Focus on specific project folders"),
t("Reduce memory usage"),
t("Improve search performance"),
].forEach((benefit) => {
benefits.createEl("li", { text: benefit });
});
// Recommended configurations
const recsContainer = container.createDiv({
cls: "recommended-configs",
@ -473,7 +460,7 @@ export class FileFilterStep {
rec.rules.forEach((rule) => {
// Check if rule already exists
const exists = plugin.settings.fileFilter.rules.some(
(r) => r.path === rule.path && r.type === rule.type
(r) => r.path === rule.path && r.type === rule.type,
);
if (!exists) {
plugin.settings.fileFilter.rules.push({
@ -486,18 +473,17 @@ export class FileFilterStep {
await plugin.saveSettings();
new Notice(
t("Applied recommended configuration: ") + rec.title
t("Applied recommended configuration: ") + rec.title,
);
// Re-render configuration section
const configSection =
container.parentElement?.querySelector(
".file-filter-preview"
);
const configSection = container.parentElement?.querySelector(
".file-filter-preview",
);
if (configSection) {
this.renderConfiguration(
configSection as HTMLElement,
plugin
plugin,
);
}
});

View file

@ -18,7 +18,7 @@ export class ModeSelectionStep {
static render(
headerEl: HTMLElement,
contentEl: HTMLElement,
controller: OnboardingController
controller: OnboardingController,
) {
// Clear
headerEl.empty();
@ -31,7 +31,7 @@ export class ModeSelectionStep {
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?"
"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?",
),
});
@ -45,7 +45,7 @@ export class ModeSelectionStep {
title: t("Fluent"),
subtitle: t("Modern & Sleek"),
description: t(
"New visual design with elegant animations and modern interactions"
"New visual design with elegant animations and modern interactions",
),
preview: this.createFluentPreview(),
},
@ -54,7 +54,7 @@ export class ModeSelectionStep {
title: t("Legacy"),
subtitle: t("Classic & Familiar"),
description: t(
"Keep the familiar interface and interaction style you know"
"Keep the familiar interface and interaction style you know",
),
preview: this.createLegacyPreview(),
},
@ -71,7 +71,7 @@ export class ModeSelectionStep {
},
(mode) => {
controller.setUIMode(mode);
}
},
);
// Set initial selection
@ -82,11 +82,11 @@ export class ModeSelectionStep {
// Add info alert
Alert.create(
contentEl,
t("You can change this option later in settings"),
t("You can change this option later in interface settings"),
{
variant: "info",
className: "mode-selection-tip",
}
},
);
}

View file

@ -16,7 +16,7 @@ export class PlacementStep {
static render(
headerEl: HTMLElement,
contentEl: HTMLElement,
controller: OnboardingController
controller: OnboardingController,
) {
// Clear
headerEl.empty();
@ -25,9 +25,7 @@ export class PlacementStep {
// Header
headerEl.createEl("h1", { text: t("Fluent Layout") });
headerEl.createEl("p", {
text: t(
"Choose how to display Fluent views in your workspace"
),
text: t("Choose how to display Fluent views in your workspace"),
cls: "onboarding-subtitle",
});
@ -43,7 +41,7 @@ export class PlacementStep {
title: t("Sideleaves"),
subtitle: t("Multi-Column Collaboration"),
description: t(
"Left navigation and right details as separate workspace sidebars, ideal for simultaneous browsing and editing"
"Left navigation and right details as separate workspace sidebars, ideal for simultaneous browsing and editing",
),
preview: this.createSideleavesPreview(),
},
@ -52,7 +50,7 @@ export class PlacementStep {
title: t("Inline"),
subtitle: t("Single-Page Immersion"),
description: t(
"All content in one page, focusing on the main view and reducing interface distractions"
"All content in one page, focusing on the main view and reducing interface distractions",
),
preview: this.createInlinePreview(),
},
@ -68,12 +66,12 @@ export class PlacementStep {
showPreview: true,
},
(placement) => {
controller.setUseSideLeaves(placement === "inline");
}
controller.setUseSideLeaves(placement === "sideleaves");
},
);
// Set initial selection
card.setSelected(currentPlacement);
card.setSelected("inline");
// Add info alert
Alert.create(
@ -82,7 +80,7 @@ export class PlacementStep {
{
variant: "info",
className: "placement-selection-tip",
}
},
);
}

View file

@ -1,8 +1,7 @@
import { t } from "@/translations/helper";
import type TaskProgressBarPlugin from "@/index";
import { OnboardingController } from "../OnboardingController";
import { FormatExamples } from "./guide/FormatExamples";
import { QuickCaptureDemo } from "./guide/QuickCaptureDemo";
import type { OnboardingController } from "../OnboardingController";
import { TaskCreationGuide } from "../TaskCreationGuide";
/**
* Task Guide Step - Learn how to create tasks
@ -14,7 +13,7 @@ export class TaskGuideStep {
static render(
headerEl: HTMLElement,
contentEl: HTMLElement,
controller: OnboardingController,
_controller: OnboardingController,
plugin: TaskProgressBarPlugin
) {
// Clear
@ -22,27 +21,16 @@ export class TaskGuideStep {
contentEl.empty();
// Header
headerEl.createEl("h1", { text: t("Creating Tasks") });
headerEl.createEl("h1", { text: t("Create Your First Task") });
headerEl.createEl("p", {
text: t(
"Learn different ways to create and format tasks in Task Genius"
"Learn the fastest ways to capture and format tasks inside Task Genius"
),
cls: "onboarding-subtitle",
});
// Introduction
const intro = contentEl.createDiv("task-guide-intro");
intro.createEl("p", {
text: t(
"You can use either emoji-based or Dataview-style syntax for task metadata"
),
cls: "guide-description",
});
// Format examples
FormatExamples.render(contentEl);
// Quick capture demo
QuickCaptureDemo.render(contentEl, plugin);
// Use the shared task creation guide to render examples and demos
const guide = new TaskCreationGuide(plugin);
guide.render(contentEl);
}
}

View file

@ -35,7 +35,7 @@ export class SelectableCard<T = string> {
container: HTMLElement,
configs: SelectableCardConfig<T>[],
options: SelectableCardOptions = {},
onSelect: (id: T) => void
onSelect: (id: T) => void,
) {
this.container = container;
this.onSelect = onSelect;
@ -44,7 +44,7 @@ export class SelectableCard<T = string> {
private render(
configs: SelectableCardConfig<T>[],
options: SelectableCardOptions
options: SelectableCardOptions,
) {
const {
containerClass = "selectable-cards-container",

View file

@ -1,11 +1,7 @@
import { App, Component, setIcon } from "obsidian";
import { Task } from "@/types/task";
import { TaskListItemComponent } from "./listItem"; // Re-import needed components
import {
ViewMode,
getViewSettingOrDefault,
SortCriterion,
} from "@/common/setting-definition"; // 导入 SortCriterion
import { ViewMode, getViewSettingOrDefault } from "@/common/setting-definition"; // 导入 SortCriterion
import { tasksToTree } from "@/utils/ui/tree-view-utils"; // Re-import needed utils
import { TaskTreeItemComponent } from "./treeItem"; // Re-import needed components
import { t } from "@/translations/helper";

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,799 @@
/* Task Genius V2 Enhanced Styles - Shadcn-inspired */
/* ========== Base Variables ========== */
:root {
/* Shadows */
--tg-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--tg-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--tg-shadow-md:
0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--tg-shadow-lg:
0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--tg-shadow-xl:
0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
/* Animations */
--tg-transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
--tg-transition-base: 200ms cubic-bezier(0.4, 0, 0.2, 1);
--tg-transition-slow: 300ms cubic-bezier(0.4, 0, 0.2, 1);
/* Border radius */
--tg-radius-sm: 0.25rem;
--tg-radius: 0.375rem;
--tg-radius-md: 0.5rem;
--tg-radius-lg: 0.75rem;
--tg-radius-full: 9999px;
}
/* ========== Task List View Styles ========== */
.task-list-container {
height: 100%;
overflow-y: auto;
padding: 1.5rem;
animation: fadeIn var(--tg-transition-base);
}
.task-list-wrapper {
max-width: 1200px;
margin: 0 auto;
}
.tg-task-list-item {
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
border-radius: var(--tg-radius-md);
margin-bottom: 0.75rem;
padding: 1rem 1.25rem;
transition: all var(--tg-transition-fast);
position: relative;
overflow: hidden;
}
.tg-task-list-item::before {
content: "";
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 3px;
background: transparent;
transition: background var(--tg-transition-fast);
}
.tg-task-list-item:hover {
box-shadow: var(--tg-shadow);
transform: translateY(-1px);
border-color: var(--interactive-accent);
}
.tg-task-list-item:hover::before {
background: var(--interactive-accent);
}
.tg-task-list-item.is-completed {
opacity: 0.6;
background: var(--background-secondary);
}
.tg-task-list-item.is-overdue::before {
background: var(--text-error);
}
.task-list-empty {
text-align: center;
padding: 3rem 1.5rem;
color: var(--text-muted);
font-size: 0.9rem;
}
/* ========== Task Tree View Styles ========== */
.task-tree-container {
height: 100%;
overflow-y: auto;
padding: 1.5rem;
animation: fadeIn var(--tg-transition-base);
}
.task-tree-wrapper {
max-width: 1200px;
margin: 0 auto;
}
.task-tree-project {
margin-bottom: 1.5rem;
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
border-radius: var(--tg-radius-lg);
overflow: hidden;
transition: all var(--tg-transition-base);
}
.task-tree-project:hover {
box-shadow: var(--tg-shadow-md);
}
.task-tree-project-header {
padding: 1rem 1.25rem;
background: var(--background-secondary);
border-bottom: 1px solid var(--background-modifier-border);
font-weight: 600;
font-size: 0.95rem;
color: var(--text-normal);
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
transition: background var(--tg-transition-fast);
}
.task-tree-project-header:hover {
background: var(--background-modifier-hover);
}
.task-tree-project-header::before {
content: "▶";
display: inline-block;
margin-right: 0.5rem;
transition: transform var(--tg-transition-fast);
font-size: 0.75rem;
}
.task-tree-project.is-expanded .task-tree-project-header::before {
transform: rotate(90deg);
}
.task-tree-tasks {
padding: 0.5rem;
}
.task-tree-item {
padding: 0.75rem 1rem;
margin: 0.25rem;
border-radius: var(--tg-radius);
transition: all var(--tg-transition-fast);
position: relative;
padding-left: 2rem;
}
.task-tree-item::before {
content: "";
position: absolute;
left: 1rem;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 4px;
background: var(--text-muted);
border-radius: var(--tg-radius-full);
}
.task-tree-item:hover {
background: var(--background-modifier-hover);
}
.task-tree-empty {
text-align: center;
padding: 3rem 1.5rem;
color: var(--text-muted);
font-size: 0.9rem;
}
/* ========== Filter Panel Styles ========== */
.tg-v2-filter-panel {
position: fixed;
right: -380px;
top: 0;
bottom: 0;
width: 380px;
background: var(--background-primary);
border-left: 1px solid var(--background-modifier-border);
box-shadow: var(--tg-shadow-xl);
transition: right var(--tg-transition-slow);
z-index: 1000;
overflow-y: auto;
display: flex;
flex-direction: column;
}
.tg-v2-filter-panel.is-open {
right: 0;
}
.filter-panel-header {
padding: 1.25rem;
border-bottom: 1px solid var(--background-modifier-border);
display: flex;
align-items: center;
justify-content: space-between;
}
.filter-panel-title {
font-size: 1.1rem;
font-weight: 600;
color: var(--text-normal);
}
.filter-panel-close {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--tg-radius);
cursor: pointer;
transition: background var(--tg-transition-fast);
color: var(--text-muted);
}
.filter-panel-close:hover {
background: var(--background-modifier-hover);
color: var(--text-normal);
}
.filter-panel-content {
padding: 1.25rem;
}
.filter-group {
margin-bottom: 1.5rem;
}
.filter-group-label {
font-size: 0.875rem;
font-weight: 500;
color: var(--text-muted);
margin-bottom: 0.5rem;
text-transform: uppercase;
letter-spacing: 0.025em;
}
.filter-options {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.filter-option {
display: flex;
align-items: center;
padding: 0.5rem 0.75rem;
border-radius: var(--tg-radius);
cursor: pointer;
transition: background var(--tg-transition-fast);
}
.filter-option:hover {
background: var(--background-modifier-hover);
}
.filter-option.is-selected {
background: var(--interactive-accent);
color: var(--text-on-accent);
}
.filter-checkbox {
margin-right: 0.75rem;
}
/* Filter specific styles */
.filter-select {
width: 100%;
padding: 0.5rem 0.75rem;
background: var(--background-modifier-form-field);
border: 1px solid var(--background-modifier-border);
border-radius: var(--tg-radius);
color: var(--text-normal);
font-size: 0.875rem;
transition: all var(--tg-transition-fast);
cursor: pointer;
}
.filter-select:hover {
border-color: var(--interactive-accent);
}
.filter-select:focus {
outline: none;
border-color: var(--interactive-accent);
box-shadow: 0 0 0 2px var(--interactive-accent-hover);
}
.filter-tags-container {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
padding: 0.5rem 0;
}
.filter-tag {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.375rem 0.75rem;
background: var(--background-secondary);
border: 1px solid var(--background-modifier-border);
border-radius: var(--tg-radius-full);
font-size: 0.875rem;
cursor: pointer;
transition: all var(--tg-transition-fast);
}
.filter-tag:hover {
background: var(--background-modifier-hover);
}
.filter-tag.is-selected {
background: var(--interactive-accent);
color: var(--text-on-accent);
border-color: var(--interactive-accent);
}
.filter-date-container {
display: flex;
flex-direction: column;
gap: 0.75rem;
}
.filter-date-field {
display: flex;
align-items: center;
gap: 0.5rem;
}
.filter-date-field span {
min-width: 50px;
font-size: 0.875rem;
color: var(--text-muted);
}
.filter-date-input {
flex: 1;
padding: 0.5rem 0.75rem;
background: var(--background-modifier-form-field);
border: 1px solid var(--background-modifier-border);
border-radius: var(--tg-radius);
color: var(--text-normal);
font-size: 0.875rem;
transition: all var(--tg-transition-fast);
}
.filter-date-input:hover {
border-color: var(--interactive-accent);
}
.filter-date-input:focus {
outline: none;
border-color: var(--interactive-accent);
}
.filter-action-buttons {
display: flex;
gap: 0.75rem;
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--background-modifier-border);
}
.filter-action-buttons button {
flex: 1;
}
.priority-color-dot {
width: 12px;
height: 12px;
border-radius: var(--tg-radius-full);
flex-shrink: 0;
}
/* ========== Loading States ========== */
.tg-v2-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
gap: 1rem;
}
.tg-v2-spinner {
width: 32px;
height: 32px;
border: 3px solid var(--background-modifier-border);
border-top-color: var(--interactive-accent);
border-radius: var(--tg-radius-full);
animation: spin 1s linear infinite;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
.tg-v2-loading-text {
color: var(--text-muted);
font-size: 0.9rem;
}
/* ========== Empty States ========== */
.tg-v2-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
padding: 2rem;
text-align: center;
}
.tg-v2-empty-icon {
width: 64px;
height: 64px;
margin-bottom: 1rem;
color: var(--text-muted);
opacity: 0.5;
}
.tg-v2-empty-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-normal);
margin-bottom: 0.5rem;
}
.tg-v2-empty-description {
color: var(--text-muted);
margin-bottom: 1.5rem;
max-width: 400px;
}
.tg-v2-empty-action {
padding: 0.75rem 1.5rem;
background: var(--interactive-accent);
color: var(--text-on-accent);
border: none;
border-radius: var(--tg-radius-md);
font-weight: 500;
cursor: pointer;
transition: all var(--tg-transition-fast);
}
.tg-v2-empty-action:hover {
background: var(--interactive-accent-hover);
transform: translateY(-1px);
box-shadow: var(--tg-shadow);
}
/* ========== Tooltips ========== */
.tg-v2-tooltip {
position: absolute;
background: var(--background-secondary);
color: var(--text-normal);
padding: 0.5rem 0.75rem;
border-radius: var(--tg-radius);
font-size: 0.875rem;
box-shadow: var(--tg-shadow-lg);
z-index: 9999;
pointer-events: none;
opacity: 0;
transform: translateY(4px);
transition: all var(--tg-transition-fast);
}
.tg-v2-tooltip.is-visible {
opacity: 1;
transform: translateY(0);
}
/* ========== Cards ========== */
.tg-v2-card {
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
border-radius: var(--tg-radius-lg);
padding: 1.25rem;
margin-bottom: 1rem;
transition: all var(--tg-transition-base);
}
.tg-v2-card:hover {
box-shadow: var(--tg-shadow-md);
border-color: var(--interactive-accent);
}
.tg-v2-card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--background-modifier-border);
}
.tg-v2-card-title {
font-size: 1rem;
font-weight: 600;
color: var(--text-normal);
}
.tg-v2-card-actions {
display: flex;
gap: 0.5rem;
}
.tg-v2-card-action {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--tg-radius);
cursor: pointer;
transition: all var(--tg-transition-fast);
color: var(--text-muted);
}
.tg-v2-card-action:hover {
background: var(--background-modifier-hover);
color: var(--text-normal);
}
/* ========== Buttons ========== */
.tg-v2-button {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.625rem 1.25rem;
font-size: 0.875rem;
font-weight: 500;
border-radius: var(--tg-radius-md);
border: none;
cursor: pointer;
transition: all var(--tg-transition-fast);
position: relative;
overflow: hidden;
}
.tg-v2-button::before {
content: "";
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-radius: var(--tg-radius-full);
background: rgba(255, 255, 255, 0.2);
transform: translate(-50%, -50%);
transition:
width var(--tg-transition-slow),
height var(--tg-transition-slow);
}
.tg-v2-button:active::before {
width: 300px;
height: 300px;
}
.tg-v2-button-primary {
background: var(--interactive-accent);
color: var(--text-on-accent);
}
.tg-v2-button-primary:hover {
background: var(--interactive-accent-hover);
transform: translateY(-1px);
box-shadow: var(--tg-shadow);
}
.tg-v2-button-secondary {
background: var(--background-secondary);
color: var(--text-normal);
border: 1px solid var(--background-modifier-border);
}
.tg-v2-button-secondary:hover {
background: var(--background-modifier-hover);
border-color: var(--interactive-accent);
}
.tg-v2-button-ghost {
background: transparent;
color: var(--text-normal);
}
.tg-v2-button-ghost:hover {
background: var(--background-modifier-hover);
}
.tg-v2-button-danger {
background: var(--text-error);
color: white;
}
.tg-v2-button-danger:hover {
background: #dc2626;
transform: translateY(-1px);
box-shadow: var(--tg-shadow);
}
/* ========== Animations ========== */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes slideIn {
from {
transform: translateX(100%);
}
to {
transform: translateX(0);
}
}
@keyframes slideUp {
from {
transform: translateY(100%);
}
to {
transform: translateY(0);
}
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
/* ========== Responsive Design ========== */
@media (max-width: 1024px) {
.task-list-container,
.task-tree-container {
padding: 1rem;
}
.tg-v2-filter-panel {
width: 100%;
right: -100%;
}
}
@media (max-width: 640px) {
.tg-task-list-item {
padding: 0.75rem 1rem;
}
.task-tree-project-header {
padding: 0.75rem 1rem;
}
.tg-v2-card {
padding: 1rem;
}
}
/* ========== Dark Mode Adjustments ========== */
.theme-dark {
--tg-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
--tg-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.4), 0 1px 2px -1px rgb(0 0 0 / 0.3);
--tg-shadow-md:
0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.3);
--tg-shadow-lg:
0 10px 15px -3px rgb(0 0 0 / 0.4), 0 4px 6px -4px rgb(0 0 0 / 0.3);
--tg-shadow-xl:
0 20px 25px -5px rgb(0 0 0 / 0.4), 0 8px 10px -6px rgb(0 0 0 / 0.3);
}
.theme-dark .tg-v2-card:hover,
.theme-dark .tg-task-list-item:hover,
.theme-dark .task-tree-project:hover {
box-shadow:
0 0 0 1px var(--interactive-accent),
var(--tg-shadow-md);
}
/* ========== Progress Bars ========== */
.tg-v2-progress {
width: 100%;
height: 6px;
background: var(--background-modifier-border);
border-radius: var(--tg-radius-full);
overflow: hidden;
margin: 0.5rem 0;
}
.tg-v2-progress-bar {
height: 100%;
background: var(--interactive-accent);
border-radius: var(--tg-radius-full);
transition: width var(--tg-transition-slow);
position: relative;
overflow: hidden;
}
.tg-v2-progress-bar::after {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(
90deg,
transparent,
rgba(255, 255, 255, 0.3),
transparent
);
animation: shimmer 2s infinite;
}
@keyframes shimmer {
0% {
transform: translateX(-100%);
}
100% {
transform: translateX(100%);
}
}
/* ========== Tags ========== */
.tg-v2-tag {
display: inline-flex;
align-items: center;
padding: 0.25rem 0.625rem;
font-size: 0.75rem;
font-weight: 500;
border-radius: var(--tg-radius-full);
background: var(--background-secondary);
color: var(--text-normal);
border: 1px solid var(--background-modifier-border);
margin-right: 0.25rem;
transition: all var(--tg-transition-fast);
}
.tg-v2-tag:hover {
background: var(--background-modifier-hover);
border-color: var(--interactive-accent);
}
.tg-v2-tag-primary {
background: var(--interactive-accent);
color: var(--text-on-accent);
border-color: var(--interactive-accent);
}
.tg-v2-tag-success {
background: #10b981;
color: white;
border-color: #10b981;
}
.tg-v2-tag-warning {
background: #f59e0b;
color: white;
border-color: #f59e0b;
}
.tg-v2-tag-danger {
background: var(--text-error);
color: white;
border-color: var(--text-error);
}
.workspace-name-with-icon {
display: flex;
align-items: center;
justify-content: flex-start;
gap: var(--size-2-2);
}

View file

@ -322,6 +322,30 @@ export default class TaskProgressBarPlugin extends Plugin {
},
});
this.addCommand({
id: "open-task-genius-changelog",
name: t("Open Task Genius changelog"),
callback: () => {
if (!this.changelogManager) {
return;
}
const targetVersion =
this.manifest?.version ||
this.settings.changelog.lastVersion;
if (!targetVersion) {
return;
}
const isBeta = targetVersion.toLowerCase().includes("beta");
void this.changelogManager.openChangelog(
targetVersion,
isBeta,
);
},
});
addIcon("task-genius", getTaskGeniusIcon());
addIcon("completed", getStatusIcon("completed"));
addIcon("inProgress", getStatusIcon("inProgress"));

View file

@ -34,7 +34,11 @@ export class ChangelogManager {
return;
}
const cached = getCachedChangelog(version, isBeta);
const cached = getCachedChangelog(
version,
isBeta,
this.plugin.app,
);
if (cached) {
this.currentVersionDisplayed = version;
await view.setContent({
@ -58,7 +62,7 @@ export class ChangelogManager {
return;
}
cacheChangelog(version, isBeta, data);
cacheChangelog(version, isBeta, data, this.plugin.app);
await view.setContent({
version,
markdown: data.markdown,

View file

@ -357,7 +357,7 @@
}
.fluent-project-list.is-tree-view
.fluent-project-item[data-level]:not([data-level="0"]):hover::before {
.fluent-project-item[data-level]:not([data-level="0"]):hover::before {
opacity: 0.3;
}
@ -597,7 +597,10 @@
/* ===== Collapsed Sidebar (Rail) ===== */
.tg-fluent-sidebar-container {
transition: width 0.2s ease, min-width 0.2s ease, max-width 0.2s ease;
transition:
width 0.2s ease,
min-width 0.2s ease,
max-width 0.2s ease;
}
/* When the sidebar element itself is collapsed */
@ -628,7 +631,9 @@
border-radius: var(--radius-s);
color: var(--text-muted);
cursor: pointer;
transition: background-color 0.15s ease, color 0.15s ease;
transition:
background-color 0.15s ease,
color 0.15s ease;
}
.fluent-rail-btn:hover {
@ -791,7 +796,9 @@
border-radius: 4px;
color: var(--text-muted);
cursor: pointer;
transition: background-color 0.15s ease, color 0.15s ease;
transition:
background-color 0.15s ease,
color 0.15s ease;
margin-right: 8px;
flex-shrink: 0;
}
@ -815,8 +822,8 @@
/* Hide sidebar toggle on desktop when in mobile drawer mode */
.tg-fluent-sidebar-container.is-mobile-drawer
~ .tg-fluent-main-container
.sidebar-toggle {
~ .tg-fluent-main-container
.sidebar-toggle {
display: none !important;
}
@ -875,6 +882,7 @@
height: 400px;
filter: blur(150px);
animation: shadow-slide infinite 4s linear alternate;
pointer-events: none;
}
@keyframes shadow-slide {

View file

@ -5,12 +5,12 @@
/* Shadows */
--tg-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
--tg-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
--tg-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1),
0 2px 4px -2px rgb(0 0 0 / 0.1);
--tg-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1),
0 4px 6px -4px rgb(0 0 0 / 0.1);
--tg-shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1),
0 8px 10px -6px rgb(0 0 0 / 0.1);
--tg-shadow-md:
0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--tg-shadow-lg:
0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--tg-shadow-xl:
0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
/* Animations */
--tg-transition-fast: 150ms cubic-bezier(0.4, 0, 0.2, 1);
@ -38,7 +38,7 @@
margin: 0 auto;
}
.task-list-item {
.tg-task-list-item {
background: var(--background-primary);
border: 1px solid var(--background-modifier-border);
border-radius: var(--tg-radius-md);
@ -60,13 +60,13 @@
transition: background var(--tg-transition-fast);
}
.task-list-item:hover {
.tg-task-list-item:hover {
box-shadow: var(--tg-shadow);
transform: translateY(-1px);
border-color: var(--interactive-accent);
}
.task-list-item:hover::before {
.tg-task-list-item:hover::before {
background: var(--interactive-accent);
}
@ -564,7 +564,8 @@
border-radius: var(--tg-radius-full);
background: rgba(255, 255, 255, 0.2);
transform: translate(-50%, -50%);
transition: width var(--tg-transition-slow),
transition:
width var(--tg-transition-slow),
height var(--tg-transition-slow);
}
@ -669,7 +670,7 @@
}
@media (max-width: 640px) {
.task-list-item {
.tg-task-list-item {
padding: 0.75rem 1rem;
}
@ -686,18 +687,20 @@
.theme-dark {
--tg-shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.3);
--tg-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.4), 0 1px 2px -1px rgb(0 0 0 / 0.3);
--tg-shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.4),
0 2px 4px -2px rgb(0 0 0 / 0.3);
--tg-shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.4),
0 4px 6px -4px rgb(0 0 0 / 0.3);
--tg-shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.4),
0 8px 10px -6px rgb(0 0 0 / 0.3);
--tg-shadow-md:
0 4px 6px -1px rgb(0 0 0 / 0.4), 0 2px 4px -2px rgb(0 0 0 / 0.3);
--tg-shadow-lg:
0 10px 15px -3px rgb(0 0 0 / 0.4), 0 4px 6px -4px rgb(0 0 0 / 0.3);
--tg-shadow-xl:
0 20px 25px -5px rgb(0 0 0 / 0.4), 0 8px 10px -6px rgb(0 0 0 / 0.3);
}
.theme-dark .tg-fluent-card:hover,
.theme-dark .task-list-item:hover,
.theme-dark .tg-task-list-item:hover,
.theme-dark .task-tree-project:hover {
box-shadow: 0 0 0 1px var(--interactive-accent), var(--tg-shadow-md);
box-shadow:
0 0 0 1px var(--interactive-accent),
var(--tg-shadow-md);
}
/* ========== Progress Bars ========== */

View file

@ -1,6 +1,11 @@
/* Noise Layer Effect */
.tg-noise-layer {
position: relative;
position: fixed;
width: 100%;
height: 100%;
left: 0;
top: 0;
pointer-events: none;
}
.tg-noise-layer::before {

View file

@ -29,22 +29,12 @@
width: 200px;
min-width: 200px;
border-right: 1px solid var(--background-modifier-border);
border-top: unset;
}
.tg-fluent-container.component-preview-topnav {
background: var(--background-primary);
}
.component-showcase:has(
.tg-fluent-container.component-preview-sidebar
+ .tg-fluent-container.component-preview-topnav
)
.tg-fluent-container.component-preview-topnav {
border-top: unset;
background: var(--background-primary);
}
/* Component showcase container */
.component-showcase {
display: flex;
@ -77,7 +67,6 @@
.component-showcase-description h3 {
margin: 0;
color: var(--text-normal);
font-size: var(--font-ui-large);
}
.component-showcase-description p {
@ -297,6 +286,41 @@
position: relative;
outline: 1px solid var(--color-accent);
outline-offset: -1px;
sborder-radius: var(--radius-s);
border-radius: var(--radius-s);
z-index: 1;
}
.component-showcase:has(.fluent-top-navigation):not(
:has(.component-preview-sidebar)
) {
flex-direction: column;
}
.component-showcase-preview.focus-mode.tg-fluent-container.component-preview-topnav {
flex: unset;
height: max-content;
}
.fluent-top-navigation.component-preview.is-focused {
outline: unset;
}
.component-showcase-preview:has(
.component-preview-sidebar + .component-preview-topnav
)
.component-preview-sidebar {
border-top: unset;
}
.component-showcase:has(
.tg-fluent-container.component-preview-sidebar
+ .tg-fluent-container.component-preview-topnav
)
.tg-fluent-container.component-preview-topnav {
border-top: unset;
background: var(--background-primary);
}
.onboarding-content .resources-section {
width: 100%;
}

View file

@ -15,10 +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,12 +49,6 @@
flex-direction: column;
}
/* Header section */
.onboarding-view .onboarding-header {
padding: calc(var(--onboarding-spacing) * 4) var(--onboarding-spacing) var(--size-4-2) var(--onboarding-spacing);
text-align: center;
}
/* Hide onboarding-header if no header content */
.onboarding-view .onboarding-header:empty {
display: none;
@ -150,7 +144,9 @@
border-radius: 50%;
background: rgba(255, 255, 255, 0.3);
transform: translate(-50%, -50%);
transition: width 0.6s, height 0.6s;
transition:
width 0.6s,
height 0.6s;
}
.onboarding-view .onboarding-buttons button:active::after {
@ -264,8 +260,9 @@
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 {
@ -694,6 +691,11 @@
display: flex;
}
.onboarding-view .change-item {
display: flex;
flex-direction: row;
}
.onboarding-view .change-icon {
color: var(--interactive-accent);
font-size: 1.1em;
@ -1466,8 +1468,13 @@
/* Wrapper to prevent layout shift */
.intro-typing-wrapper {
width: 100%;
max-width: max(900px, 70%);
margin: 0 auto;
padding-left: calc(var(--onboarding-spacing) * 3);
padding-right: calc(var(--onboarding-spacing) * 3);
}
.onboarding-view .onboarding-content.intro-typing-wrapper {
padding-left: calc(var(--onboarding-spacing) * 3);
padding-right: calc(var(--onboarding-spacing) * 3);
}
.intro-typing {
@ -1522,9 +1529,10 @@
opacity: 0;
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);
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);
position: relative;
}
@ -1582,7 +1590,9 @@
.intro-line-fadeout {
opacity: 0;
transform: translateY(-10px);
transition: opacity 0.5s ease-out, transform 0.5s ease-out;
transition:
opacity 0.5s ease-out,
transform 0.5s ease-out;
pointer-events: none;
}
@ -1686,9 +1696,10 @@
}
.config-check-typing .check-line {
margin-bottom: var(--size-4-4);
line-height: 1.6;
color: var(--text-normal);
margin-block-start: var(--size-4-2);
margin-block-end: var(--size-4-2);
}
.config-check-typing .check-line-1 {

View file

@ -82,7 +82,9 @@
.quick-capture-tab.active {
background: hsl(var(--tg-tab-background));
color: hsl(var(--tg-tab-foreground));
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
box-shadow:
0 1px 3px 0 rgb(0 0 0 / 0.1),
0 1px 2px -1px rgb(0 0 0 / 0.1);
}
.quick-capture-tabs.is-hidden {
@ -153,19 +155,12 @@
.quick-capture-continue {
padding: var(--size-2-2) var(--size-4-3);
background: transparent;
border: 1px solid var(--background-modifier-border);
border-radius: var(--radius-s);
cursor: pointer;
transition: all 0.2s ease;
color: var(--text-muted);
}
.quick-capture-continue:hover {
background-color: var(--background-modifier-hover);
color: var(--text-normal);
border-color: var(--interactive-accent);
}
/* File Name Input Component */
.file-name-input-container {
display: flex;

View file

@ -358,6 +358,7 @@ const translations = {
"Current stage": "Current stage",
"Type": "Type",
"Next": "Next",
"Next to Introduction": "Next to Introduction",
"Name and ID are required.": "Name and ID are required.",
"End of file": "End of file",
"Include in cycle": "Include in cycle",
@ -679,6 +680,7 @@ const translations = {
"No tasks with the selected tags": "No tasks with the selected tags",
"Select a tag to see related tasks": "Select a tag to see related tasks",
"Open Task Genius view": "Open Task Genius view",
"Open Task Genius changelog": "Open Task Genius changelog",
"Minimal Quick Capture": "Minimal Quick Capture",
"Refresh task index": "Refresh task index",
"Refreshing task index...": "Refreshing task index...",

View file

@ -532,6 +532,7 @@ const translations = {
"No tasks with the selected tags": "没有带有所选标签的任务",
"Select a tag to see related tasks": "选择一个标签以查看相关任务",
"Open Task Genius view": "打开 Task Genius 视图",
"Open Task Genius changelog": "打开 Task Genius 更新日志",
"Task capture with metadata": "带元数据的任务捕获",
"Refresh task index": "刷新任务索引",
"Refreshing task index...": "正在刷新任务索引...",

View file

@ -465,6 +465,7 @@ const translations = {
"No tasks with the selected tags": "沒有帶有所選標籤的任務",
"Select a tag to see related tasks": "選擇一個標籤以查看相關任務",
"Open Task Genius view": "打開 Task Genius 視圖",
"Open Task Genius changelog": "打開 Task Genius 更新日誌",
"Task capture with metadata": "帶元數據的任務捕獲",
"Refresh task index": "刷新任務索引",
"Refreshing task index...": "正在刷新任務索引...",

View file

@ -1,3 +1,5 @@
import { App } from "obsidian";
const STORAGE_KEY = "task-genius-changelog-cache";
type CacheChannel = "stable" | "beta";
@ -17,6 +19,41 @@ interface ChangelogCachePayload {
const getChannelKey = (isBeta: boolean): CacheChannel =>
isBeta ? "beta" : "stable";
const isChangelogCacheEntry = (
value: unknown,
): value is ChangelogCacheEntry => {
if (!value || typeof value !== "object") {
return false;
}
const entry = value as Partial<ChangelogCacheEntry>;
return (
typeof entry.version === "string" &&
typeof entry.markdown === "string" &&
typeof entry.sourceUrl === "string" &&
typeof entry.updatedAt === "number"
);
};
const sanitizeCachePayload = (value: unknown): ChangelogCachePayload => {
if (!value || typeof value !== "object") {
return {};
}
const payload = value as Partial<Record<CacheChannel, unknown>>;
const sanitized: ChangelogCachePayload = {};
if (isChangelogCacheEntry(payload.stable)) {
sanitized.stable = payload.stable;
}
if (isChangelogCacheEntry(payload.beta)) {
sanitized.beta = payload.beta;
}
return sanitized;
};
const getStorage = (): Storage | null => {
try {
if (typeof window === "undefined") {
@ -29,7 +66,15 @@ const getStorage = (): Storage | null => {
}
};
const loadCache = (): ChangelogCachePayload => {
const loadCache = (app?: App): ChangelogCachePayload => {
try {
if (typeof app?.loadLocalStorage === "function") {
return sanitizeCachePayload(app.loadLocalStorage(STORAGE_KEY));
}
} catch (error) {
console.warn("[ChangelogCache] Failed to load via app localStorage", error);
}
const storage = getStorage();
if (!storage) {
return {};
@ -41,36 +86,46 @@ const loadCache = (): ChangelogCachePayload => {
return {};
}
const parsed = JSON.parse(raw) as ChangelogCachePayload;
if (!parsed || typeof parsed !== "object") {
return {};
}
return parsed;
} catch {
return sanitizeCachePayload(JSON.parse(raw));
} catch (error) {
console.warn("[ChangelogCache] Failed to load via window localStorage", error);
return {};
}
};
const saveCache = (cache: ChangelogCachePayload): void => {
const saveCache = (cache: ChangelogCachePayload, app?: App): void => {
try {
if (typeof app?.saveLocalStorage === "function") {
app.saveLocalStorage(STORAGE_KEY, cache);
return;
}
} catch (error) {
console.warn("[ChangelogCache] Failed to save via app localStorage", error);
}
const storage = getStorage();
if (!storage) {
return;
}
try {
const serialized = JSON.stringify(cache);
storage.setItem(STORAGE_KEY, serialized);
} catch {
// Swallow errors silently; cache is an optimization only.
if (!cache.stable && !cache.beta) {
storage.removeItem(STORAGE_KEY);
return;
}
storage.setItem(STORAGE_KEY, JSON.stringify(cache));
} catch (error) {
console.warn("[ChangelogCache] Failed to save via window localStorage", error);
}
};
export const getCachedChangelog = (
version: string,
isBeta: boolean,
app?: App,
): ChangelogCacheEntry | null => {
const cache = loadCache();
const cache = loadCache(app);
const channel = getChannelKey(isBeta);
const entry = cache[channel];
if (!entry || entry.version !== version) {
@ -82,8 +137,9 @@ export const getCachedChangelog = (
export const getLatestCachedChangelog = (
isBeta: boolean,
app?: App,
): ChangelogCacheEntry | null => {
const cache = loadCache();
const cache = loadCache(app);
const channel = getChannelKey(isBeta);
return cache[channel] ?? null;
};
@ -92,8 +148,9 @@ export const cacheChangelog = (
version: string,
isBeta: boolean,
data: Pick<ChangelogCacheEntry, "markdown" | "sourceUrl">,
app?: App,
): void => {
const cache = loadCache();
const cache = loadCache(app);
const channel = getChannelKey(isBeta);
cache[channel] = {
version,
@ -101,6 +158,5 @@ export const cacheChangelog = (
sourceUrl: data.sourceUrl,
updatedAt: Date.now(),
};
saveCache(cache);
saveCache(cache, app);
};

File diff suppressed because one or more lines are too long