From f03ddbce5805444492b001346cf8480875f66a4e Mon Sep 17 00:00:00 2001 From: callumalpass Date: Thu, 27 Nov 2025 22:52:49 +1100 Subject: [PATCH] refactor: consolidate file selector modals into FileSelectorModal Create a new unified FileSelectorModal that replaces AttachmentSelectModal and ICSNoteLinkModal. The new modal provides: - Fuzzy search for existing files - Shift+Enter to create new files with typed name - Configurable filter (markdown only, all files, or custom) - Simple footer showing create option with filename preview Updated usages: - TimeblockCreationModal: attachments (filter: all) - TimeblockInfoModal: attachments (filter: all) - ICSEventContextMenu: note linking (filter: markdown) - ICSEventInfoModal: note linking (filter: markdown) Also improved task selector create preview spacing. Removed: - src/modals/AttachmentSelectModal.ts - src/modals/ICSNoteLinkModal.ts --- build-css.mjs | 1 + src/components/ICSEventContextMenu.ts | 54 ++-- src/modals/AttachmentSelectModal.ts | 88 ----- src/modals/FileSelectorModal.ts | 359 +++++++++++++++++++++ src/modals/ICSEventInfoModal.ts | 11 +- src/modals/ICSNoteLinkModal.ts | 99 ------ src/modals/TimeblockCreationModal.ts | 10 +- src/modals/TimeblockInfoModal.ts | 10 +- styles/file-selector-modal.css | 92 ++++++ styles/task-selector-with-create-modal.css | 11 +- 10 files changed, 504 insertions(+), 231 deletions(-) delete mode 100644 src/modals/AttachmentSelectModal.ts create mode 100644 src/modals/FileSelectorModal.ts delete mode 100644 src/modals/ICSNoteLinkModal.ts create mode 100644 styles/file-selector-modal.css diff --git a/build-css.mjs b/build-css.mjs index dfe4c0c8..a02b7154 100644 --- a/build-css.mjs +++ b/build-css.mjs @@ -19,6 +19,7 @@ const CSS_FILES = [ 'styles/reminder-modal.css', // Reminder modal component with proper BEM scoping 'styles/date-picker.css', // Enhanced date/time picker styling 'styles/task-selector-with-create-modal.css', // TaskSelectorWithCreateModal component with proper BEM scoping + 'styles/file-selector-modal.css', // FileSelectorModal component with proper BEM scoping 'styles/unscheduled-tasks-selector-modal.css', // UnscheduledTasksSelectorModal component with proper BEM scoping 'styles/task-action-palette-modal.css', // TaskActionPaletteModal component with proper BEM scoping 'styles/time-entry-editor-modal.css', // TimeEntryEditorModal component with proper BEM scoping diff --git a/src/components/ICSEventContextMenu.ts b/src/components/ICSEventContextMenu.ts index 0dc431d7..8636098a 100644 --- a/src/components/ICSEventContextMenu.ts +++ b/src/components/ICSEventContextMenu.ts @@ -3,7 +3,7 @@ import TaskNotesPlugin from "../main"; import { ICSEvent } from "../types"; import { ICSEventInfoModal } from "../modals/ICSEventInfoModal"; import { ICSNoteCreationModal } from "../modals/ICSNoteCreationModal"; -import { ICSNoteLinkModal } from "../modals/ICSNoteLinkModal"; +import { openFileSelector } from "../modals/FileSelectorModal"; import { SafeAsync } from "../utils/safeAsync"; import { ContextMenu } from "./ContextMenu"; @@ -205,34 +205,34 @@ export class ICSEventContextMenu { private async linkExistingNote(): Promise { await SafeAsync.execute( async () => { - const modal = new ICSNoteLinkModal( - this.options.plugin.app, - this.options.plugin, - async (file) => { - await SafeAsync.execute( - async () => { - await this.options.plugin.icsNoteService.linkNoteToICS( - file.path, - this.options.icsEvent - ); - new Notice( - this.t("contextMenus.ics.notices.linkSuccess", { - name: file.name, - }) - ); + openFileSelector(this.options.plugin, async (file) => { + if (!file) return; - // Trigger update callback if provided - if (this.options.onUpdate) { - this.options.onUpdate(); - } - }, - { - errorMessage: this.t("contextMenus.ics.notices.linkFailure"), + await SafeAsync.execute( + async () => { + await this.options.plugin.icsNoteService.linkNoteToICS( + file.path, + this.options.icsEvent + ); + new Notice( + this.t("contextMenus.ics.notices.linkSuccess", { + name: file.name, + }) + ); + + // Trigger update callback if provided + if (this.options.onUpdate) { + this.options.onUpdate(); } - ); - } - ); - modal.open(); + }, + { + errorMessage: this.t("contextMenus.ics.notices.linkFailure"), + } + ); + }, { + placeholder: "Search notes to link...", + filter: "markdown", + }); }, { errorMessage: this.t("contextMenus.ics.notices.linkSelectionFailure"), diff --git a/src/modals/AttachmentSelectModal.ts b/src/modals/AttachmentSelectModal.ts deleted file mode 100644 index 487afd06..00000000 --- a/src/modals/AttachmentSelectModal.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { - App, - FuzzySuggestModal, - TAbstractFile, - TFile, - SearchResult, - parseFrontMatterAliases, -} from "obsidian"; -import type TaskNotesPlugin from "../main"; - -export class AttachmentSelectModal extends FuzzySuggestModal { - private onChoose: (file: TAbstractFile) => void; - private plugin: TaskNotesPlugin; - - constructor(app: App, plugin: TaskNotesPlugin, onChoose: (file: TAbstractFile) => void) { - super(app); - this.plugin = plugin; - this.onChoose = onChoose; - this.setPlaceholder("Type to search for files and notes..."); - this.setInstructions([ - { command: "↑↓", purpose: "to navigate" }, - { command: "↵", purpose: "to select" }, - { command: "esc", purpose: "to cancel" }, - ]); - } - - getItems(): TAbstractFile[] { - return this.app.vault.getAllLoadedFiles(); - } - - getItemText(file: TAbstractFile): string { - let text = `${file.name} ${file.path}`; - - // Add aliases to searchable text - if (file instanceof TFile) { - const cache = this.app.metadataCache.getFileCache(file); - if (cache?.frontmatter) { - const aliases = parseFrontMatterAliases(cache.frontmatter); - if (aliases && aliases.length > 0) { - text += ` ${aliases.join(" ")}`; - } - } - } - - return text; - } - - renderSuggestion(value: { item: TAbstractFile; match: SearchResult }, el: HTMLElement) { - const file = value.item; - el.empty(); - - const container = el.createDiv({ cls: "attachment-suggestion" }); - - // File name (main line) - const nameEl = container.createSpan({ cls: "attachment-name" }); - nameEl.textContent = file.name; - - // Title or aliases (second line) - if (file instanceof TFile) { - const cache = this.app.metadataCache.getFileCache(file); - if (cache?.frontmatter) { - const titleField = this.plugin.fieldMapper.toUserField("title"); - const title = cache.frontmatter[titleField]; - - if (title) { - const titleEl = container.createDiv({ cls: "attachment-title" }); - titleEl.textContent = title; - } else { - const aliases = parseFrontMatterAliases(cache.frontmatter); - if (aliases && aliases.length > 0) { - const aliasEl = container.createDiv({ cls: "attachment-aliases" }); - aliasEl.textContent = aliases.join(", "); - } - } - } - } - - // File path (if different from name) - if (file.path !== file.name) { - const pathEl = container.createDiv({ cls: "attachment-path" }); - pathEl.textContent = file.path; - } - } - - onChooseItem(file: TAbstractFile, evt: MouseEvent | KeyboardEvent) { - this.onChoose(file); - } -} diff --git a/src/modals/FileSelectorModal.ts b/src/modals/FileSelectorModal.ts new file mode 100644 index 00000000..6843a52b --- /dev/null +++ b/src/modals/FileSelectorModal.ts @@ -0,0 +1,359 @@ +import { + App, + SuggestModal, + TAbstractFile, + TFile, + TFolder, + parseFrontMatterAliases, + Notice, +} from "obsidian"; +import type TaskNotesPlugin from "../main"; + +export type FileSelectorResult = + | { type: "selected"; file: TAbstractFile } + | { type: "created"; file: TFile } + | { type: "cancelled" }; + +export interface FileSelectorOptions { + /** Callback when a file is selected or created */ + onResult: (result: FileSelectorResult) => void; + /** Optional placeholder text */ + placeholder?: string; + /** Optional title */ + title?: string; + /** Filter to apply to files (default: markdown files only) */ + filter?: "markdown" | "all" | ((file: TAbstractFile) => boolean); + /** Folder to create new files in (default: vault root) */ + newFileFolder?: string; +} + +/** + * A generic file selector modal that allows users to: + * 1. Select an existing file from a fuzzy search + * 2. Create a new file by pressing Shift+Enter + */ +export class FileSelectorModal extends SuggestModal { + private plugin: TaskNotesPlugin; + private options: FileSelectorOptions; + private currentQuery: string = ""; + private resultHandled: boolean = false; + private createFooterEl: HTMLElement | null = null; + + constructor( + app: App, + plugin: TaskNotesPlugin, + options: FileSelectorOptions + ) { + super(app); + this.plugin = plugin; + this.options = options; + + // Set placeholder + this.setPlaceholder( + options.placeholder || "Search files or type to create new..." + ); + + // Set instructions + this.setInstructions([ + { command: "↑↓", purpose: "to navigate" }, + { command: "↵", purpose: "to select" }, + { command: "⇧↵", purpose: "to create new" }, + { command: "esc", purpose: "to cancel" }, + ]); + + // Set modal title + if (options.title) { + this.titleEl.setText(options.title); + } + + // Add classes for styling + this.containerEl.addClass("file-selector-modal"); + this.containerEl.addClass("tasknotes-plugin"); + } + + onOpen(): void { + super.onOpen(); + + // Register Shift+Enter for creating new file + this.scope.register(["Shift"], "Enter", (e: KeyboardEvent) => { + e.preventDefault(); + e.stopPropagation(); + this.createNewFile(); + return false; + }); + + // Track input changes for the footer preview + this.inputEl.addEventListener("input", this.handleInputChange); + + // Create footer after DOM is ready + setTimeout(() => this.createFooter(), 0); + } + + private handleInputChange = (): void => { + this.currentQuery = this.inputEl.value.trim(); + this.updateCreateFooter(); + }; + + private createFooter(): void { + const promptEl = this.modalEl.querySelector(".prompt"); + if (!promptEl) return; + + this.createFooterEl = promptEl.parentElement?.createDiv({ + cls: "file-selector-create-footer", + }) || null; + + if (this.createFooterEl) { + this.createFooterEl.style.display = "none"; + } + } + + private updateCreateFooter(): void { + if (!this.createFooterEl) return; + + if (!this.currentQuery) { + this.createFooterEl.style.display = "none"; + return; + } + + this.createFooterEl.empty(); + this.createFooterEl.style.display = "flex"; + + // Content + const contentDiv = this.createFooterEl.createDiv({ + cls: "file-selector-create-footer__content", + }); + + // Title line + const titleLine = contentDiv.createDiv({ + cls: "file-selector-create-footer__title-line", + }); + + const shortcut = titleLine.createSpan({ + cls: "file-selector-create-footer__shortcut", + text: "⇧↵", + }); + + titleLine.createSpan({ + cls: "file-selector-create-footer__hint-text", + text: " to create: ", + }); + + titleLine.createSpan({ + cls: "file-selector-create-footer__filename", + text: this.getNewFileName(), + }); + } + + private getNewFileName(): string { + let name = this.currentQuery; + // Remove .md extension if user typed it + if (name.toLowerCase().endsWith(".md")) { + name = name.slice(0, -3); + } + return name + ".md"; + } + + private async createNewFile(): Promise { + if (!this.currentQuery) { + new Notice("Please enter a file name"); + return; + } + + try { + let fileName = this.currentQuery; + // Remove .md extension if user typed it (we'll add it back) + if (fileName.toLowerCase().endsWith(".md")) { + fileName = fileName.slice(0, -3); + } + + // Determine the folder + const folderPath = this.options.newFileFolder || ""; + const filePath = folderPath + ? `${folderPath}/${fileName}.md` + : `${fileName}.md`; + + // Check if file already exists + const existingFile = this.app.vault.getAbstractFileByPath(filePath); + if (existingFile) { + new Notice(`File "${filePath}" already exists`); + return; + } + + // Ensure folder exists + if (folderPath) { + const folder = this.app.vault.getAbstractFileByPath(folderPath); + if (!folder) { + await this.app.vault.createFolder(folderPath); + } + } + + // Create the file + const newFile = await this.app.vault.create(filePath, ""); + + this.resultHandled = true; + this.close(); + this.options.onResult({ type: "created", file: newFile }); + } catch (error) { + console.error("Error creating file:", error); + new Notice("Failed to create file"); + } + } + + getSuggestions(query: string): TAbstractFile[] { + this.currentQuery = query.trim(); + this.updateCreateFooter(); + + const allFiles = this.app.vault.getAllLoadedFiles(); + const lowerQuery = query.toLowerCase(); + + // Apply filter + let filtered: TAbstractFile[]; + const filter = this.options.filter || "markdown"; + + if (typeof filter === "function") { + filtered = allFiles.filter(filter); + } else if (filter === "markdown") { + filtered = allFiles.filter( + (file) => + file instanceof TFile && + file.extension === "md" && + !file.path.includes(".trash") + ); + } else { + filtered = allFiles.filter( + (file) => + file instanceof TFile && !file.path.includes(".trash") + ); + } + + // Filter by query + if (!query) { + return filtered.slice(0, 50); // Limit initial results + } + + return filtered + .filter((file) => { + const searchText = this.getSearchText(file).toLowerCase(); + return searchText.includes(lowerQuery); + }) + .slice(0, 50); + } + + private getSearchText(file: TAbstractFile): string { + let text = `${file.name} ${file.path}`; + + if (file instanceof TFile) { + const cache = this.app.metadataCache.getFileCache(file); + if (cache?.frontmatter) { + // Add title + const titleField = this.plugin.fieldMapper.toUserField("title"); + const title = cache.frontmatter[titleField]; + if (title) { + text += ` ${title}`; + } + + // Add aliases + const aliases = parseFrontMatterAliases(cache.frontmatter); + if (aliases && aliases.length > 0) { + text += ` ${aliases.join(" ")}`; + } + } + } + + return text; + } + + renderSuggestion(file: TAbstractFile, el: HTMLElement): void { + const container = el.createDiv({ cls: "file-selector-suggestion" }); + + // File name + container.createDiv({ + cls: "file-selector-suggestion__name", + text: file.name, + }); + + // Title or aliases (for markdown files) + if (file instanceof TFile) { + const cache = this.app.metadataCache.getFileCache(file); + if (cache?.frontmatter) { + const titleField = this.plugin.fieldMapper.toUserField("title"); + const title = cache.frontmatter[titleField]; + + if (title) { + container.createDiv({ + cls: "file-selector-suggestion__title", + text: title, + }); + } else { + const aliases = parseFrontMatterAliases(cache.frontmatter); + if (aliases && aliases.length > 0) { + container.createDiv({ + cls: "file-selector-suggestion__aliases", + text: aliases.join(", "), + }); + } + } + } + } + + // Path (if not in root) + if (file.parent && file.parent.path !== "/") { + container.createDiv({ + cls: "file-selector-suggestion__path", + text: file.parent.path, + }); + } + } + + onChooseSuggestion(file: TAbstractFile, evt: MouseEvent | KeyboardEvent): void { + this.resultHandled = true; + this.options.onResult({ type: "selected", file }); + } + + onClose(): void { + this.inputEl.removeEventListener("input", this.handleInputChange); + + if (this.createFooterEl) { + this.createFooterEl.remove(); + this.createFooterEl = null; + } + + // Defer cancelled check (same pattern as TaskSelectorWithCreateModal) + setTimeout(() => { + if (!this.resultHandled) { + this.options.onResult({ type: "cancelled" }); + } + }, 0); + + super.onClose(); + } +} + +/** + * Helper function to open a file selector modal + */ +export function openFileSelector( + plugin: TaskNotesPlugin, + onChoose: (file: TAbstractFile | null) => void, + options?: { + placeholder?: string; + title?: string; + filter?: "markdown" | "all" | ((file: TAbstractFile) => boolean); + newFileFolder?: string; + } +): void { + const modal = new FileSelectorModal(plugin.app, plugin, { + placeholder: options?.placeholder, + title: options?.title, + filter: options?.filter, + newFileFolder: options?.newFileFolder, + onResult: (result) => { + if (result.type === "selected" || result.type === "created") { + onChoose(result.file); + } else { + onChoose(null); + } + }, + }); + modal.open(); +} diff --git a/src/modals/ICSEventInfoModal.ts b/src/modals/ICSEventInfoModal.ts index 19c0575f..d81d83d4 100644 --- a/src/modals/ICSEventInfoModal.ts +++ b/src/modals/ICSEventInfoModal.ts @@ -3,7 +3,7 @@ import { App, Modal, Setting, Notice, TFile } from "obsidian"; import TaskNotesPlugin from "../main"; import { ICSEvent, TaskInfo, NoteInfo } from "../types"; import { ICSNoteCreationModal } from "./ICSNoteCreationModal"; -import { ICSNoteLinkModal } from "./ICSNoteLinkModal"; +import { openFileSelector } from "./FileSelectorModal"; import { SafeAsync } from "../utils/safeAsync"; import { TranslationKey } from "../i18n"; @@ -185,10 +185,11 @@ export class ICSEventInfoModal extends Modal { } private async linkExistingNote(): Promise { - console.log("Link existing note button clicked"); await SafeAsync.execute( async () => { - const modal = new ICSNoteLinkModal(this.app, this.plugin, async (file) => { + openFileSelector(this.plugin, async (file) => { + if (!file) return; + await SafeAsync.execute( async () => { await this.plugin.icsNoteService.linkNoteToICS( @@ -202,8 +203,10 @@ export class ICSEventInfoModal extends Modal { errorMessage: "Failed to link note", } ); + }, { + placeholder: "Search notes to link...", + filter: "markdown", }); - modal.open(); }, { errorMessage: "Failed to open note selection", diff --git a/src/modals/ICSNoteLinkModal.ts b/src/modals/ICSNoteLinkModal.ts deleted file mode 100644 index 3557ad4b..00000000 --- a/src/modals/ICSNoteLinkModal.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { - App, - FuzzySuggestModal, - TAbstractFile, - TFile, - SearchResult, - parseFrontMatterAliases, -} from "obsidian"; -import type TaskNotesPlugin from "../main"; - -/** - * Modal for selecting notes to link to ICS events using fuzzy search - * Based on the existing ProjectSelectModal pattern - */ -export class ICSNoteLinkModal extends FuzzySuggestModal { - private onChoose: (file: TAbstractFile) => void; - private plugin: TaskNotesPlugin; - - constructor(app: App, plugin: TaskNotesPlugin, onChoose: (file: TAbstractFile) => void) { - super(app); - this.plugin = plugin; - this.onChoose = onChoose; - this.setPlaceholder("Type to search for notes to link..."); - this.setInstructions([ - { command: "↑↓", purpose: "to navigate" }, - { command: "↵", purpose: "to select" }, - { command: "esc", purpose: "to cancel" }, - ]); - } - - getItems(): TAbstractFile[] { - return this.app.vault - .getAllLoadedFiles() - .filter( - (file) => - file instanceof TFile && - file.extension === "md" && - !file.path.includes(".trash") - ); - } - - getItemText(file: TAbstractFile): string { - let text = `${file.name} ${file.path}`; - - // Add aliases to searchable text - if (file instanceof TFile) { - const cache = this.app.metadataCache.getFileCache(file); - if (cache?.frontmatter) { - const aliases = parseFrontMatterAliases(cache.frontmatter); - if (aliases && aliases.length > 0) { - text += ` ${aliases.join(" ")}`; - } - } - } - - return text; - } - - renderSuggestion(value: { item: TAbstractFile; match: SearchResult }, el: HTMLElement) { - const file = value.item; - el.empty(); - - const container = el.createDiv({ cls: "ics-note-link-suggestion" }); - - // File name (main line) - const nameEl = container.createSpan({ cls: "note-link-name" }); - nameEl.textContent = file.name; - - // Title or aliases (second line) - if (file instanceof TFile) { - const cache = this.app.metadataCache.getFileCache(file); - if (cache?.frontmatter) { - const titleField = this.plugin.fieldMapper.toUserField("title"); - const title = cache.frontmatter[titleField]; - - if (title) { - const titleEl = container.createDiv({ cls: "note-link-title" }); - titleEl.textContent = title; - } else { - const aliases = parseFrontMatterAliases(cache.frontmatter); - if (aliases && aliases.length > 0) { - const aliasEl = container.createDiv({ cls: "note-link-aliases" }); - aliasEl.textContent = aliases.join(", "); - } - } - } - } - - // File path (if different from name) - if (file.path !== file.name) { - const pathEl = container.createDiv({ cls: "note-link-path" }); - pathEl.textContent = file.path; - } - } - - onChooseItem(file: TAbstractFile, evt: MouseEvent | KeyboardEvent) { - this.onChoose(file); - } -} diff --git a/src/modals/TimeblockCreationModal.ts b/src/modals/TimeblockCreationModal.ts index 74d19bf2..920cda7e 100644 --- a/src/modals/TimeblockCreationModal.ts +++ b/src/modals/TimeblockCreationModal.ts @@ -11,7 +11,7 @@ import { import TaskNotesPlugin from "../main"; import { TimeBlock, DailyNoteFrontmatter } from "../types"; import { generateTimeblockId } from "../utils/helpers"; -import { AttachmentSelectModal } from "./AttachmentSelectModal"; +import { openFileSelector } from "./FileSelectorModal"; import { parseDateAsLocal } from "../utils/dateUtils"; import { createDailyNote, @@ -148,10 +148,12 @@ export class TimeblockCreationModal extends Modal { .setButtonText(this.translate("modals.timeblockCreation.addAttachmentButton")) .setTooltip(this.translate("modals.timeblockCreation.addAttachmentTooltip")) .onClick(() => { - const modal = new AttachmentSelectModal(this.app, this.plugin, (file) => { - this.addAttachment(file); + openFileSelector(this.plugin, (file) => { + if (file) this.addAttachment(file); + }, { + placeholder: "Search files or type to create new...", + filter: "all", }); - modal.open(); }); }); diff --git a/src/modals/TimeblockInfoModal.ts b/src/modals/TimeblockInfoModal.ts index f0fd09ce..eae46e66 100644 --- a/src/modals/TimeblockInfoModal.ts +++ b/src/modals/TimeblockInfoModal.ts @@ -10,7 +10,7 @@ import { setTooltip, } from "obsidian"; import TaskNotesPlugin from "../main"; -import { AttachmentSelectModal } from "./AttachmentSelectModal"; +import { openFileSelector } from "./FileSelectorModal"; import { getDailyNote, getAllDailyNotes, @@ -130,10 +130,12 @@ export class TimeblockInfoModal extends Modal { .setButtonText(this.translate("modals.timeblockInfo.addAttachmentButton")) .setTooltip(this.translate("modals.timeblockInfo.addAttachmentTooltip")) .onClick(() => { - const modal = new AttachmentSelectModal(this.app, this.plugin, (file) => { - this.addAttachment(file); + openFileSelector(this.plugin, (file) => { + if (file) this.addAttachment(file); + }, { + placeholder: "Search files or type to create new...", + filter: "all", }); - modal.open(); }); }); diff --git a/styles/file-selector-modal.css b/styles/file-selector-modal.css new file mode 100644 index 00000000..186977df --- /dev/null +++ b/styles/file-selector-modal.css @@ -0,0 +1,92 @@ +/* ================================================ + FILE SELECTOR MODAL + ================================================ */ + +/* ================================================ + SUGGESTION STYLING + ================================================ */ + +.file-selector-suggestion { + display: flex; + flex-direction: column; + gap: 2px; + padding: var(--tn-spacing-xs) 0; +} + +.file-selector-suggestion__name { + font-weight: 500; + font-size: var(--tn-font-size-md); + color: var(--text-normal); +} + +.file-selector-suggestion__title, +.file-selector-suggestion__aliases { + font-size: var(--tn-font-size-sm); + color: var(--text-muted); +} + +.file-selector-suggestion__path { + font-size: var(--tn-font-size-xs); + color: var(--text-faint); +} + +/* ================================================ + CREATE FOOTER + ================================================ */ + +.file-selector-create-footer { + display: flex; + align-items: center; + gap: var(--tn-spacing-md); + padding: var(--tn-spacing-md) var(--tn-spacing-lg); + background: var(--background-secondary); + border-top: 1px solid var(--background-modifier-border); + margin-top: auto; +} + +.file-selector-create-footer__content { + flex: 1; + min-width: 0; +} + +.file-selector-create-footer__title-line { + display: flex; + align-items: center; + gap: var(--tn-spacing-xs); +} + +.file-selector-create-footer__shortcut { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 2px 6px; + border-radius: 3px; + background-color: var(--background-modifier-border); + color: var(--text-faint); + font-size: 10px; + font-weight: 500; + font-family: var(--font-monospace); +} + +.file-selector-create-footer__hint-text { + font-size: var(--tn-font-size-sm); + color: var(--text-faint); +} + +.file-selector-create-footer__filename { + font-size: var(--tn-font-size-sm); + color: var(--text-normal); + font-weight: 500; +} + +/* ================================================ + DARK THEME ADJUSTMENTS + ================================================ */ + +.theme-dark .file-selector-create-footer { + background: var(--background-secondary-alt); +} + +.theme-dark .file-selector-create-footer__shortcut { + background-color: var(--background-modifier-form-field); +} diff --git a/styles/task-selector-with-create-modal.css b/styles/task-selector-with-create-modal.css index aee3b562..16675929 100644 --- a/styles/task-selector-with-create-modal.css +++ b/styles/task-selector-with-create-modal.css @@ -42,11 +42,11 @@ display: flex; align-items: flex-start; gap: var(--tn-spacing-md); - padding: var(--tn-spacing-sm) var(--tn-spacing-md); + padding: var(--tn-spacing-md) var(--tn-spacing-lg); background: var(--background-secondary); border-top: 1px solid var(--background-modifier-border); margin-top: auto; - min-height: 50px; + min-height: 60px; } .task-selector-create-footer__icon { @@ -71,7 +71,7 @@ min-width: 0; display: flex; flex-direction: column; - gap: var(--tn-spacing-xs); + gap: var(--tn-spacing-sm); } /* ================================================ @@ -100,15 +100,16 @@ .task-selector-create-footer__meta { display: flex; align-items: center; - gap: 6px; + gap: 8px; flex-wrap: wrap; + margin-top: var(--tn-spacing-xs); } .task-selector-create-footer__chip { display: inline-flex; align-items: center; gap: 4px; - padding: 2px 8px; + padding: 3px 10px; border-radius: 12px; font-size: var(--tn-font-size-xs); font-weight: 500;