mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
feat(quick-capture): add mode persistence and file capture to minimal modal
- Store last used mode in localStorage instead of plugin settings - Add file mode with frontmatter generation to MinimalQuickCaptureModal - Include tab switching UI for checkbox/file modes - Extract tags from content for frontmatter when in file mode - Persist mode selection across app sessions using localStorage key
This commit is contained in:
parent
8bed404489
commit
22865d135c
3 changed files with 171 additions and 16 deletions
|
|
@ -49,6 +49,7 @@ export interface TaskMetadata {
|
|||
* Base class for all Quick Capture modals
|
||||
* Provides shared functionality and state management
|
||||
*/
|
||||
const LAST_USED_MODE_KEY = "task-genius.lastUsedQuickCaptureMode";
|
||||
export abstract class BaseQuickCaptureModal extends Modal {
|
||||
plugin: TaskProgressBarPlugin;
|
||||
protected markdownEditor: EmbeddableMarkdownEditor | null = null;
|
||||
|
|
@ -320,6 +321,8 @@ export abstract class BaseQuickCaptureModal extends Modal {
|
|||
|
||||
// Update mode
|
||||
this.currentMode = mode;
|
||||
// Persist last used mode to local storage
|
||||
try { this.app.saveLocalStorage(LAST_USED_MODE_KEY, mode); } catch {}
|
||||
|
||||
// Update modal classes
|
||||
this.modalEl.removeClass(
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import {
|
|||
EmbeddableMarkdownEditor,
|
||||
} from "@/editor-extensions/core/markdown-editor";
|
||||
import TaskProgressBarPlugin from "@/index";
|
||||
import { saveCapture } from "@/utils/file/file-operations";
|
||||
import { saveCapture, processDateTemplates } from "@/utils/file/file-operations";
|
||||
import { t } from "@/translations/helper";
|
||||
import { MinimalQuickCaptureSuggest } from "@/components/features/quick-capture/suggest/MinimalQuickCaptureSuggest";
|
||||
import {
|
||||
|
|
@ -22,6 +22,9 @@ import {
|
|||
} from "@/components/ui/suggest";
|
||||
import { ConfigurableTaskParser } from "@/dataflow/core/ConfigurableTaskParser";
|
||||
import { clearAllMarks } from "@/components/ui/renderers/MarkdownRenderer";
|
||||
import { FileNameInput } from "../components/FileNameInput";
|
||||
|
||||
const LAST_USED_MODE_KEY = "task-genius.lastUsedQuickCaptureMode";
|
||||
|
||||
interface TaskMetadata {
|
||||
startDate?: Date;
|
||||
|
|
@ -41,11 +44,17 @@ export class MinimalQuickCaptureModal extends Modal {
|
|||
capturedContent: string = "";
|
||||
taskMetadata: TaskMetadata = {};
|
||||
|
||||
// Mode: 'checkbox' or 'file'
|
||||
private currentMode: "checkbox" | "file" = "checkbox";
|
||||
|
||||
// UI Elements
|
||||
private dateButton: HTMLButtonElement | null = null;
|
||||
private priorityButton: HTMLButtonElement | null = null;
|
||||
private locationButton: HTMLButtonElement | null = null;
|
||||
private tagButton: HTMLButtonElement | null = null;
|
||||
private modeTabsContainer: HTMLElement | null = null;
|
||||
private fileNameSection: HTMLElement | null = null;
|
||||
private fileNameInput: FileNameInput | null = null;
|
||||
|
||||
// Suggest instances
|
||||
private minimalSuggest: MinimalQuickCaptureSuggest;
|
||||
|
|
@ -60,6 +69,12 @@ export class MinimalQuickCaptureModal extends Modal {
|
|||
// Initialize suggest manager
|
||||
this.suggestManager = new SuggestManager(app, plugin);
|
||||
|
||||
// Load last used mode from local storage
|
||||
try {
|
||||
const stored = this.app.loadLocalStorage(LAST_USED_MODE_KEY) as string | null;
|
||||
if (stored === "checkbox" || stored === "file") this.currentMode = stored;
|
||||
} catch {}
|
||||
|
||||
// Initialize default metadata with fallback
|
||||
const minimalSettings =
|
||||
this.plugin.settings.quickCapture.minimalModeSettings;
|
||||
|
|
@ -131,6 +146,35 @@ export class MinimalQuickCaptureModal extends Modal {
|
|||
// Title
|
||||
this.titleEl.setText(t("Minimal Quick Capture"));
|
||||
|
||||
// Mode tabs (checkbox | file)
|
||||
this.modeTabsContainer = contentEl.createDiv({ cls: "quick-capture-minimal-tabs" });
|
||||
const tabCheckbox = this.modeTabsContainer.createEl("button", { text: t("Task"), cls: "tab-btn" });
|
||||
const tabFile = this.modeTabsContainer.createEl("button", { text: t("File"), cls: "tab-btn" });
|
||||
const refreshTabs = () => {
|
||||
if (this.currentMode === "checkbox") {
|
||||
tabCheckbox.addClass("active");
|
||||
tabFile.removeClass("active");
|
||||
} else {
|
||||
tabFile.addClass("active");
|
||||
tabCheckbox.removeClass("active");
|
||||
}
|
||||
this.updateModeUI();
|
||||
};
|
||||
tabCheckbox.addEventListener("click", () => {
|
||||
this.currentMode = "checkbox";
|
||||
try { this.app.saveLocalStorage(LAST_USED_MODE_KEY, "checkbox"); } catch {}
|
||||
refreshTabs();
|
||||
});
|
||||
tabFile.addEventListener("click", () => {
|
||||
this.currentMode = "file";
|
||||
try { this.app.saveLocalStorage(LAST_USED_MODE_KEY, "file"); } catch {}
|
||||
refreshTabs();
|
||||
});
|
||||
refreshTabs();
|
||||
|
||||
// Optional file name section (only for file mode)
|
||||
this.fileNameSection = contentEl.createDiv({ cls: "quick-capture-minimal-filename" });
|
||||
|
||||
// Editor container
|
||||
const editorContainer = contentEl.createDiv({
|
||||
cls: "quick-capture-minimal-editor-container",
|
||||
|
|
@ -147,13 +191,68 @@ export class MinimalQuickCaptureModal extends Modal {
|
|||
this.createMainButtons(buttonsContainer);
|
||||
}
|
||||
|
||||
|
||||
private updateModeUI() {
|
||||
if (!this.fileNameSection) return;
|
||||
this.fileNameSection.empty();
|
||||
if (this.currentMode === "file") {
|
||||
this.fileNameSection.createDiv({ text: t("Capture as:"), cls: "quick-capture-section-title" });
|
||||
if (this.fileNameInput) {
|
||||
this.fileNameInput.destroy();
|
||||
this.fileNameInput = null;
|
||||
}
|
||||
this.fileNameInput = new FileNameInput(this.app, this.fileNameSection, {
|
||||
placeholder: t("Enter file name..."),
|
||||
defaultValue:
|
||||
this.plugin.settings.quickCapture.defaultFileNameTemplate ||
|
||||
"{{DATE:YYYY-MM-DD}} - ",
|
||||
currentFolder:
|
||||
this.plugin.settings.quickCapture.createFileMode?.defaultFolder,
|
||||
onChange: (value) => {
|
||||
this.taskMetadata.targetFile = undefined; // not used in file mode
|
||||
(this.taskMetadata as any).customFileName = value;
|
||||
},
|
||||
});
|
||||
} else {
|
||||
if (this.fileNameInput) {
|
||||
this.fileNameInput.destroy();
|
||||
this.fileNameInput = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Extract #tags from content for frontmatter */
|
||||
private extractTagsFromContentForFrontmatter(content: string): string[] {
|
||||
if (!content) return [];
|
||||
const tagRegex = /(^|\s)#([A-Za-z0-9_\/-]+)/g;
|
||||
const results = new Set<string>();
|
||||
let match: RegExpExecArray | null;
|
||||
while ((match = tagRegex.exec(content)) !== null) {
|
||||
const tag = match[2];
|
||||
if (tag) results.add(tag);
|
||||
}
|
||||
return Array.from(results);
|
||||
}
|
||||
|
||||
private sanitizeFilename(filename: string): string {
|
||||
return filename.replace(/[<>:"|*?\\]/g, "-").replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
private sanitizeFilePath(filePath: string): string {
|
||||
const parts = filePath.split("/");
|
||||
const sanitized = parts.map((part, idx) =>
|
||||
idx === parts.length - 1 ? this.sanitizeFilename(part) : part.replace(/[<>:"|*?\\]/g, "-").replace(/\s+/g, " ").trim()
|
||||
);
|
||||
return sanitized.join("/");
|
||||
}
|
||||
|
||||
private setupMarkdownEditor(container: HTMLElement) {
|
||||
setTimeout(() => {
|
||||
this.markdownEditor = createEmbeddableMarkdownEditor(
|
||||
this.app,
|
||||
container,
|
||||
{
|
||||
placeholder: t("Enter your task..."),
|
||||
placeholder: this.currentMode === "file" ? t("Enter file content...") : t("Enter your task..."),
|
||||
singleLine: true, // Single line mode
|
||||
|
||||
onEnter: (editor, mod, shift) => {
|
||||
|
|
@ -545,16 +644,69 @@ export class MinimalQuickCaptureModal extends Modal {
|
|||
}
|
||||
|
||||
try {
|
||||
// Process content
|
||||
let processedContent = this.processMinimalContent(content);
|
||||
processedContent = this.addMetadataToContent(processedContent);
|
||||
let processedContent = content;
|
||||
|
||||
if (this.currentMode === "checkbox") {
|
||||
// Task (checkbox) mode: wrap and add inline metadata
|
||||
processedContent = this.processMinimalContent(content);
|
||||
processedContent = this.addMetadataToContent(processedContent);
|
||||
|
||||
const captureOptions = {
|
||||
...this.plugin.settings.quickCapture,
|
||||
targetFile: this.taskMetadata.targetFile || this.getTargetFile(),
|
||||
targetType: (this.taskMetadata.location || "fixed") as "fixed" | "daily-note" | "custom-file",
|
||||
};
|
||||
await saveCapture(this.app, processedContent, captureOptions);
|
||||
new Notice(t("Captured successfully"));
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
// File mode: save as a new file (replace content) with frontmatter mirror logic
|
||||
const useTemplate = !!this.plugin.settings.quickCapture.createFileMode?.useTemplate;
|
||||
const hasFrontmatter = processedContent.trimStart().startsWith("---");
|
||||
if (useTemplate) {
|
||||
if (!hasFrontmatter) {
|
||||
processedContent = `---\nstatus: ${JSON.stringify("not-started")}\n---\n\n${processedContent}`;
|
||||
}
|
||||
} else {
|
||||
if (!hasFrontmatter) {
|
||||
const yamlLines: string[] = [];
|
||||
yamlLines.push(`status: ${JSON.stringify("not-started")}`);
|
||||
if (this.taskMetadata.dueDate) {
|
||||
yamlLines.push(`dueDate: ${JSON.stringify(this.formatDate(this.taskMetadata.dueDate))}`);
|
||||
}
|
||||
if (this.taskMetadata.priority != null) {
|
||||
yamlLines.push(`priority: ${JSON.stringify(String(this.taskMetadata.priority))}`);
|
||||
}
|
||||
const writeContentTags = !!this.plugin.settings.quickCapture.createFileMode?.writeContentTagsToFrontmatter;
|
||||
if (writeContentTags) {
|
||||
const tags = this.extractTagsFromContentForFrontmatter(content);
|
||||
if (tags.length > 0) {
|
||||
yamlLines.push(`tags: [${tags.map((t) => JSON.stringify(t)).join(", ")}]`);
|
||||
}
|
||||
}
|
||||
processedContent = `---\n${yamlLines.join("\n")}\n---\n\n${processedContent}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Build target file path
|
||||
let targetFilePath = (this.taskMetadata as any).customFileName as string | undefined;
|
||||
if (!targetFilePath || !targetFilePath.trim()) {
|
||||
targetFilePath = this.plugin.settings.quickCapture.defaultFileNameTemplate || "{{DATE:YYYY-MM-DD}} - ";
|
||||
}
|
||||
targetFilePath = processDateTemplates(targetFilePath);
|
||||
if (!targetFilePath.endsWith(".md")) targetFilePath += ".md";
|
||||
const defaultFolder = this.plugin.settings.quickCapture.createFileMode?.defaultFolder?.trim();
|
||||
if (defaultFolder && !targetFilePath.includes("/")) {
|
||||
targetFilePath = this.sanitizeFilePath(`${defaultFolder}/${targetFilePath}`);
|
||||
}
|
||||
|
||||
// Save options
|
||||
const captureOptions = {
|
||||
...this.plugin.settings.quickCapture,
|
||||
targetFile:
|
||||
this.taskMetadata.targetFile || this.getTargetFile(),
|
||||
targetType: this.taskMetadata.location || "fixed",
|
||||
targetFile: targetFilePath,
|
||||
targetType: "fixed" as const,
|
||||
appendToFile: "replace" as const,
|
||||
};
|
||||
|
||||
await saveCapture(this.app, processedContent, captureOptions);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ import {
|
|||
} from "./BaseQuickCaptureModal";
|
||||
import { FileNameInput } from "../components/FileNameInput";
|
||||
|
||||
const LAST_USED_MODE_KEY = "task-genius.lastUsedQuickCaptureMode";
|
||||
|
||||
/**
|
||||
* Enhanced Quick Capture Modal extending the base class
|
||||
*/
|
||||
|
|
@ -68,14 +70,12 @@ export class QuickCaptureModal extends BaseQuickCaptureModal {
|
|||
metadata?: TaskMetadata,
|
||||
useFullFeaturedMode: boolean = false
|
||||
) {
|
||||
// Determine initial mode - default to checkbox (task) mode
|
||||
// Determine initial mode from local storage, default to checkbox
|
||||
let initialMode: QuickCaptureMode = "checkbox";
|
||||
if (
|
||||
plugin.settings.quickCapture.rememberLastMode &&
|
||||
plugin.settings.quickCapture.lastUsedMode
|
||||
) {
|
||||
initialMode = plugin.settings.quickCapture.lastUsedMode;
|
||||
}
|
||||
try {
|
||||
const stored = app.loadLocalStorage(LAST_USED_MODE_KEY) as string | null;
|
||||
if (stored === "checkbox" || stored === "file") initialMode = stored;
|
||||
} catch {}
|
||||
|
||||
super(app, plugin, initialMode, metadata);
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue