mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
refactor(react): replace global app with useApp() hook in React layer (#2448)
First wave of eliminating global `app` references (per Obsidian's
`obsidianmd/no-restricted-globals` rule). Adds a `useApp()` hook that
reads from `AppContext` and throws if no provider is in scope, then
converts the React layer to use it. Modals that render React content
now wrap their tree with `<AppContext.Provider value={this.app}>` so
descendants pick up the modal-owned app rather than the global one,
which matters in popout windows.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7009925585
commit
8239443814
26 changed files with 123 additions and 52 deletions
|
|
@ -569,7 +569,7 @@ const ChatInternal: React.FC<ChatProps & { chatInput: ReturnType<typeof useChatI
|
|||
// and duplicate-id errors). reloadCurrentProject() handles its own error notices.
|
||||
const currentProject = getCurrentProject();
|
||||
if (currentProject?.id === project.id) {
|
||||
void reloadCurrentProject();
|
||||
void reloadCurrentProject(plugin.app);
|
||||
}
|
||||
},
|
||||
[plugin.app]
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { navigateToPlusPage, useIsPlusUser } from "@/plusUtils";
|
|||
import { updateSetting, useSettingsValue } from "@/settings/model";
|
||||
import { Docs4LLMParser } from "@/tools/FileParserManager";
|
||||
import { isRateLimitError } from "@/utils/rateLimitUtils";
|
||||
import { useApp } from "@/context";
|
||||
import { DropdownMenu, DropdownMenuTrigger } from "@radix-ui/react-dropdown-menu";
|
||||
import {
|
||||
AlertTriangle,
|
||||
|
|
@ -28,7 +29,7 @@ import {
|
|||
Sparkles,
|
||||
SquareArrowOutUpRight,
|
||||
} from "lucide-react";
|
||||
import { Notice } from "obsidian";
|
||||
import { App, Notice } from "obsidian";
|
||||
import React from "react";
|
||||
import {
|
||||
ChatHistoryItem,
|
||||
|
|
@ -89,7 +90,7 @@ export async function forceReindexVault() {
|
|||
}
|
||||
}
|
||||
|
||||
export async function reloadCurrentProject() {
|
||||
export async function reloadCurrentProject(app: App) {
|
||||
const currentProject = getCurrentProject();
|
||||
if (!currentProject) {
|
||||
new Notice("No project is currently selected to reload.");
|
||||
|
|
@ -131,7 +132,7 @@ export async function reloadCurrentProject() {
|
|||
}
|
||||
}
|
||||
|
||||
export async function forceRebuildCurrentProjectContext() {
|
||||
export async function forceRebuildCurrentProjectContext(app: App) {
|
||||
const currentProject = getCurrentProject();
|
||||
if (!currentProject) {
|
||||
new Notice("No project is currently selected to rebuild.");
|
||||
|
|
@ -213,6 +214,7 @@ export function ChatControls({
|
|||
onOpenSourceFile,
|
||||
latestTokenCount,
|
||||
}: ChatControlsProps) {
|
||||
const app = useApp();
|
||||
const settings = useSettingsValue();
|
||||
const [selectedChain, setSelectedChain] = useChainType();
|
||||
const isPlusUser = useIsPlusUser();
|
||||
|
|
@ -409,14 +411,14 @@ export function ChatControls({
|
|||
<>
|
||||
<DropdownMenuItem
|
||||
className="tw-flex tw-items-center tw-gap-2"
|
||||
onSelect={() => void reloadCurrentProject()}
|
||||
onSelect={() => void reloadCurrentProject(app)}
|
||||
>
|
||||
<RefreshCw className="tw-size-4" />
|
||||
Reload Current Project
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
className="tw-flex tw-items-center tw-gap-2"
|
||||
onSelect={() => void forceRebuildCurrentProjectContext()}
|
||||
onSelect={() => void forceRebuildCurrentProjectContext(app)}
|
||||
>
|
||||
<AlertTriangle className="tw-size-4" />
|
||||
Force Rebuild Context
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useApp } from "@/context";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
|
@ -33,6 +34,7 @@ const RESETTABLE_MODEL_PARAMS: (keyof CustomModel)[] = [
|
|||
];
|
||||
|
||||
export function ChatSettingsPopover() {
|
||||
const app = useApp();
|
||||
const settings = getSettings();
|
||||
const modelKey = getModelKey();
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import type CopilotPlugin from "@/main";
|
|||
import { AddProjectModal } from "@/components/modals/project/AddProjectModal";
|
||||
import { ConfirmModal } from "@/components/modals/ConfirmModal";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useApp } from "@/context";
|
||||
import { useChatInput } from "@/context/ChatInputContext";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
import {
|
||||
|
|
@ -74,6 +75,7 @@ function ProjectItem({
|
|||
onEdit: (project: ProjectConfig) => void;
|
||||
onDelete: (project: ProjectConfig) => void;
|
||||
}) {
|
||||
const app = useApp();
|
||||
return (
|
||||
<div
|
||||
className="tw-group tw-flex tw-cursor-pointer tw-items-center tw-justify-between tw-gap-2 tw-rounded-lg tw-border tw-border-solid tw-border-border tw-p-3 tw-transition-all tw-duration-200 tw-bg-secondary/40 hover:tw-border-interactive-accent hover:tw-text-accent hover:tw-shadow-[0_2px_12px_rgba(0,0,0,0.1)] active:tw-scale-[0.98]"
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Badge } from "@/components/ui/badge";
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { useApp } from "@/context";
|
||||
import { HelpTooltip } from "@/components/ui/help-tooltip";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { useChatInput } from "@/context/ChatInputContext";
|
||||
|
|
@ -106,6 +107,7 @@ function RelevantNote({
|
|||
onAddToChat: () => void;
|
||||
onNavigateToNote: (openInNewLeaf: boolean) => void;
|
||||
}) {
|
||||
const app = useApp();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [fileContent, setFileContent] = useState<string | null>(null);
|
||||
const handleDragStart = useNoteDrag();
|
||||
|
|
@ -128,7 +130,7 @@ function RelevantNote({
|
|||
// Take first 1000 characters as preview
|
||||
setFileContent(cleanContent.slice(0, 1000) + (cleanContent.length > 1000 ? "..." : ""));
|
||||
}
|
||||
}, [fileContent, note.note.path]);
|
||||
}, [app, fileContent, note.note.path]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
|
|
@ -272,6 +274,7 @@ function RelevantNotePopover({
|
|||
|
||||
export const RelevantNotes = memo(
|
||||
({ className, defaultOpen = false }: { className?: string; defaultOpen?: boolean }) => {
|
||||
const app = useApp();
|
||||
const [refresher, setRefresher] = useState(0);
|
||||
const [isOpen, setIsOpen] = useState(defaultOpen);
|
||||
const relevantNotes = useRelevantNotes(refresher);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { Platform } from "obsidian";
|
||||
|
||||
import { useApp } from "@/context";
|
||||
import { getWebViewerService } from "@/services/webViewerService/webViewerServiceSingleton";
|
||||
import type { ActiveWebTabStateSnapshot } from "@/services/webViewerService/webViewerServiceTypes";
|
||||
|
||||
|
|
@ -18,6 +19,7 @@ const EMPTY_ACTIVE_WEB_TAB_STATE: ActiveWebTabStateSnapshot = {
|
|||
* - activeOrLastWebTab: For pill display (active or last active tab)
|
||||
*/
|
||||
export function useActiveWebTabState(): ActiveWebTabStateSnapshot {
|
||||
const app = useApp();
|
||||
const [state, setState] = useState<ActiveWebTabStateSnapshot>(() => {
|
||||
if (!Platform.isDesktopApp) {
|
||||
return EMPTY_ACTIVE_WEB_TAB_STATE;
|
||||
|
|
@ -49,7 +51,7 @@ export function useActiveWebTabState(): ActiveWebTabStateSnapshot {
|
|||
return () => {
|
||||
unsubscribe?.();
|
||||
};
|
||||
}, []);
|
||||
}, [app]);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useEffect, useRef, useState } from "react";
|
||||
import { Platform } from "obsidian";
|
||||
import { App, Platform } from "obsidian";
|
||||
import { useApp } from "@/context";
|
||||
import { getWebViewerService } from "@/services/webViewerService/webViewerServiceSingleton";
|
||||
import type { WebTabContext } from "@/types/message";
|
||||
|
||||
|
|
@ -26,7 +27,7 @@ export interface UseOpenWebTabsOptions {
|
|||
* Sorting ensures stable output ordering for equality checks.
|
||||
* Includes tabs with title but no URL (not yet loaded) with isLoaded=false.
|
||||
*/
|
||||
function getOpenWebTabSnapshot(): WebTabContext[] {
|
||||
function getOpenWebTabSnapshot(app: App): WebTabContext[] {
|
||||
try {
|
||||
const service = getWebViewerService(app);
|
||||
const leaves = service.getLeaves();
|
||||
|
|
@ -115,6 +116,7 @@ function areWebTabSnapshotsEqual(a: WebTabContext[], b: WebTabContext[]): boolea
|
|||
* @param options.enabled - Whether to enable polling (default: true)
|
||||
*/
|
||||
export function useOpenWebTabs(options: UseOpenWebTabsOptions = {}): WebTabContext[] {
|
||||
const app = useApp();
|
||||
const { enabled = true } = options;
|
||||
const [tabs, setTabs] = useState<WebTabContext[]>([]);
|
||||
const rafIdRef = useRef<number | null>(null);
|
||||
|
|
@ -137,7 +139,7 @@ export function useOpenWebTabs(options: UseOpenWebTabsOptions = {}): WebTabConte
|
|||
/** Refresh state from the current Web Viewer tab snapshot. */
|
||||
const refresh = () => {
|
||||
if (disposed) return;
|
||||
const next = getOpenWebTabSnapshot();
|
||||
const next = getOpenWebTabSnapshot(app);
|
||||
setTabs((prev) => (areWebTabSnapshotsEqual(prev, next) ? prev : next));
|
||||
};
|
||||
|
||||
|
|
@ -176,7 +178,7 @@ export function useOpenWebTabs(options: UseOpenWebTabsOptions = {}): WebTabConte
|
|||
app.workspace.offref(activeLeafRef);
|
||||
unsubscribeWebviewLoad();
|
||||
};
|
||||
}, [enabled]);
|
||||
}, [app, enabled]);
|
||||
|
||||
return tabs;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { App, Modal } from "obsidian";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { AppContext, useApp } from "@/context";
|
||||
import React, { useState } from "react";
|
||||
import { createRoot, Root } from "react-dom/client";
|
||||
import {
|
||||
|
|
@ -62,6 +63,7 @@ function PatternMatchingModalContent({
|
|||
onUpdate: (value: string) => void;
|
||||
container: HTMLElement;
|
||||
}) {
|
||||
const app = useApp();
|
||||
const [value, setValue] = useState(initialValue);
|
||||
const patterns = getDecodedPatterns(value);
|
||||
const { tagPatterns, extensionPatterns, folderPatterns, notePatterns } =
|
||||
|
|
@ -280,11 +282,13 @@ export class PatternMatchingModal extends Modal {
|
|||
};
|
||||
|
||||
this.root.render(
|
||||
<PatternMatchingModalContent
|
||||
value={this.value}
|
||||
onUpdate={handleUpdate}
|
||||
container={this.contentEl}
|
||||
/>
|
||||
<AppContext.Provider value={this.app}>
|
||||
<PatternMatchingModalContent
|
||||
value={this.value}
|
||||
onUpdate={handleUpdate}
|
||||
container={this.contentEl}
|
||||
/>
|
||||
</AppContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import { File, FileText, Folder, Tag, Wrench, X } from "lucide-react";
|
|||
import { App, Modal, TFile } from "obsidian";
|
||||
import React, { useState } from "react";
|
||||
import { createRoot, Root } from "react-dom/client";
|
||||
import { AppContext, useApp } from "@/context";
|
||||
import { CustomPatternInputModal } from "./CustomPatternInputModal";
|
||||
import { ExtensionInputModal } from "./ExtensionInputModal";
|
||||
import { FolderSearchModal } from "./FolderSearchModal";
|
||||
|
|
@ -62,6 +63,7 @@ function ProjectPatternMatchingModalContent({
|
|||
onUpdate: (value: string) => void;
|
||||
container: HTMLElement;
|
||||
}) {
|
||||
const app = useApp();
|
||||
const [value, setValue] = useState(initialValue);
|
||||
const patterns = getDecodedPatterns(value);
|
||||
const { tagPatterns, extensionPatterns, folderPatterns, notePatterns } =
|
||||
|
|
@ -279,11 +281,13 @@ export class ProjectPatternMatchingModal extends Modal {
|
|||
};
|
||||
|
||||
this.root.render(
|
||||
<ProjectPatternMatchingModalContent
|
||||
value={this.value}
|
||||
onUpdate={handleUpdate}
|
||||
container={this.contentEl}
|
||||
/>
|
||||
<AppContext.Provider value={this.app}>
|
||||
<ProjectPatternMatchingModalContent
|
||||
value={this.value}
|
||||
onUpdate={handleUpdate}
|
||||
container={this.contentEl}
|
||||
/>
|
||||
</AppContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export class TagSearchModal extends FuzzySuggestModal<string> {
|
|||
|
||||
getItems(): string[] {
|
||||
// Get all Markdown files in the vault.
|
||||
const files = app.vault.getMarkdownFiles();
|
||||
const files = this.app.vault.getMarkdownFiles();
|
||||
const tagSet = new Set<string>();
|
||||
|
||||
// Loop through each file and extract tags.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { ProjectConfig } from "@/aiParams";
|
||||
import { AppContext, useApp } from "@/context";
|
||||
import { ContextManageModal } from "@/components/modals/project/context-manage-modal";
|
||||
import { openCachedItemPreview } from "@/utils/cacheFileOpener";
|
||||
import type { ProcessingItem } from "@/components/project/processingAdapter";
|
||||
|
|
@ -39,6 +40,7 @@ function AddProjectModalContent({
|
|||
onCancel,
|
||||
plugin,
|
||||
}: AddProjectModalContentProps) {
|
||||
const app = useApp();
|
||||
const settings = useSettingsValue();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [touched, setTouched] = useState({
|
||||
|
|
@ -480,12 +482,14 @@ export class AddProjectModal extends Modal {
|
|||
};
|
||||
|
||||
this.root.render(
|
||||
<AddProjectModalContent
|
||||
initialProject={this.initialProject}
|
||||
onSave={handleSave}
|
||||
onCancel={handleCancel}
|
||||
plugin={this.plugin}
|
||||
/>
|
||||
<AppContext.Provider value={this.app}>
|
||||
<AddProjectModalContent
|
||||
initialProject={this.initialProject}
|
||||
onSave={handleSave}
|
||||
onCancel={handleCancel}
|
||||
plugin={this.plugin}
|
||||
/>
|
||||
</AppContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import type { ProcessingItem } from "@/components/project/processingAdapter";
|
|||
import { ProjectFileManager } from "@/projects/ProjectFileManager";
|
||||
import { splitUrlsStringToArray } from "@/projects/projectUtils";
|
||||
import CopilotPlugin from "@/main";
|
||||
import { useApp } from "@/context";
|
||||
import { logError } from "@/logger";
|
||||
|
||||
interface ProgressCardProps {
|
||||
|
|
@ -32,6 +33,7 @@ interface ProgressCardProps {
|
|||
}
|
||||
|
||||
export default function ProgressCard({ plugin, setHiddenCard, onEditContext }: ProgressCardProps) {
|
||||
const app = useApp();
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const [contextLoadState] = useProjectContextLoad();
|
||||
const totalFiles = contextLoadState.total;
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import {
|
|||
buildProcessingItems,
|
||||
type ProcessingAdapterResult,
|
||||
} from "@/components/project/processingAdapter";
|
||||
import { useApp } from "@/context";
|
||||
import { getMatchingPatterns, shouldIndexFile } from "@/search/searchUtils";
|
||||
import { FileParserManager } from "@/tools/FileParserManager";
|
||||
import { TFile } from "obsidian";
|
||||
|
|
@ -50,6 +51,7 @@ export interface UseProjectProcessingDataResult {
|
|||
export function useProjectProcessingData(
|
||||
params: UseProjectProcessingDataParams
|
||||
): UseProjectProcessingDataResult {
|
||||
const app = useApp();
|
||||
const { cacheProject, contextSource } = params;
|
||||
|
||||
// Reason: Load the project's persistent context cache.
|
||||
|
|
@ -105,7 +107,7 @@ export function useProjectProcessingData(
|
|||
setProjectFiles(
|
||||
app.vault.getFiles().filter((file) => shouldIndexFile(file, inclusions, exclusions, true))
|
||||
);
|
||||
}, [cacheProject, effectiveInclusions, effectiveExclusions]);
|
||||
}, [app, cacheProject, effectiveInclusions, effectiveExclusions]);
|
||||
|
||||
// Reason: Static method — the set of supported extensions never changes at runtime.
|
||||
const supportedExtensions = useMemo(() => FileParserManager.getProjectSupportedExtensions(), []);
|
||||
|
|
|
|||
|
|
@ -6,3 +6,18 @@ export const AppContext = React.createContext<App | undefined>(undefined);
|
|||
|
||||
// Event target context
|
||||
export const EventTargetContext = React.createContext<EventTarget | undefined>(undefined);
|
||||
|
||||
/**
|
||||
* Returns the Obsidian {@link App} provided by the nearest {@link AppContext}.
|
||||
*
|
||||
* Use this inside React components and hooks instead of touching the global
|
||||
* `app` object. Throws if no provider is in scope so callers fail loud rather
|
||||
* than silently picking up the wrong window's app in popouts.
|
||||
*/
|
||||
export function useApp(): App {
|
||||
const app = React.useContext(AppContext);
|
||||
if (!app) {
|
||||
throw new Error("useApp() called outside of an <AppContext.Provider>");
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { EVENT_NAMES } from "@/constants";
|
||||
import { EventTargetContext } from "@/context";
|
||||
import { EventTargetContext, useApp } from "@/context";
|
||||
import { TFile } from "obsidian";
|
||||
import { useContext, useEffect, useState } from "react";
|
||||
|
||||
export function useActiveFile() {
|
||||
const app = useApp();
|
||||
const [activeFile, setActiveFile] = useState<TFile | null>(null);
|
||||
const eventTarget = useContext(EventTargetContext);
|
||||
|
||||
|
|
@ -16,7 +17,7 @@ export function useActiveFile() {
|
|||
return () => {
|
||||
eventTarget?.removeEventListener(EVENT_NAMES.ACTIVE_LEAF_CHANGE, handleActiveLeafChange);
|
||||
};
|
||||
}, [eventTarget]);
|
||||
}, [app, eventTarget]);
|
||||
|
||||
return activeFile;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useCallback } from "react";
|
||||
import { TFile } from "obsidian";
|
||||
import { useApp } from "@/context";
|
||||
|
||||
/**
|
||||
* Returns a drag-start handler that integrates with Obsidian's native dragManager API.
|
||||
|
|
@ -16,24 +17,28 @@ import { TFile } from "obsidian";
|
|||
* ```
|
||||
*/
|
||||
export function useNoteDrag() {
|
||||
const handleDragStart = useCallback((e: React.DragEvent, file: TFile): void => {
|
||||
const dragManager = (
|
||||
app as unknown as {
|
||||
dragManager?: {
|
||||
dragLink: (event: DragEvent, linkText: string) => unknown;
|
||||
onDragStart: (event: DragEvent, data: unknown) => void;
|
||||
};
|
||||
}
|
||||
).dragManager;
|
||||
if (!dragManager) return;
|
||||
const app = useApp();
|
||||
const handleDragStart = useCallback(
|
||||
(e: React.DragEvent, file: TFile): void => {
|
||||
const dragManager = (
|
||||
app as unknown as {
|
||||
dragManager?: {
|
||||
dragLink: (event: DragEvent, linkText: string) => unknown;
|
||||
onDragStart: (event: DragEvent, data: unknown) => void;
|
||||
};
|
||||
}
|
||||
).dragManager;
|
||||
if (!dragManager) return;
|
||||
|
||||
// Mark this drag as internal so the chat drop zone overlay doesn't appear
|
||||
e.dataTransfer.setData("copilot/internal-drag", "true");
|
||||
// Mark this drag as internal so the chat drop zone overlay doesn't appear
|
||||
e.dataTransfer.setData("copilot/internal-drag", "true");
|
||||
|
||||
const linkText = app.metadataCache.fileToLinktext(file, "");
|
||||
const dragData = dragManager.dragLink(e.nativeEvent, linkText);
|
||||
dragManager.onDragStart(e.nativeEvent, dragData);
|
||||
}, []);
|
||||
const linkText = app.metadataCache.fileToLinktext(file, "");
|
||||
const dragData = dragManager.dragLink(e.nativeEvent, linkText);
|
||||
dragManager.onDragStart(e.nativeEvent, dragData);
|
||||
},
|
||||
[app]
|
||||
);
|
||||
|
||||
return handleDragStart;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import React from "react";
|
|||
import { createRoot } from "react-dom/client";
|
||||
import SettingsMainV2 from "@/settings/v2/SettingsMainV2";
|
||||
import { ContainerContext } from "@/settings/v2/components/ContainerContext";
|
||||
import { AppContext } from "@/context";
|
||||
|
||||
export class CopilotSettingTab extends PluginSettingTab {
|
||||
plugin: CopilotPlugin;
|
||||
|
|
@ -69,9 +70,11 @@ export class CopilotSettingTab extends PluginSettingTab {
|
|||
const sections = createRoot(div);
|
||||
|
||||
sections.render(
|
||||
<ContainerContext.Provider value={containerEl}>
|
||||
<SettingsMainV2 plugin={this.plugin} />
|
||||
</ContainerContext.Provider>
|
||||
<AppContext.Provider value={this.app}>
|
||||
<ContainerContext.Provider value={containerEl}>
|
||||
<SettingsMainV2 plugin={this.plugin} />
|
||||
</ContainerContext.Provider>
|
||||
</AppContext.Provider>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@ const SettingsMainV2: React.FC<SettingsMainV2Props> = ({ plugin }) => {
|
|||
const { latestVersion, hasUpdate } = useLatestVersion(plugin.manifest.version);
|
||||
|
||||
const handleReset = () => {
|
||||
const modal = new ResetSettingsConfirmModal(app, () => {
|
||||
const modal = new ResetSettingsConfirmModal(plugin.app, () => {
|
||||
resetSettings();
|
||||
// Increment the key to force re-render of all components
|
||||
setResetKey((prev) => prev + 1);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { Button } from "@/components/ui/button";
|
||||
import { SettingItem } from "@/components/ui/setting-item";
|
||||
import { ObsidianNativeSelect } from "@/components/ui/obsidian-native-select";
|
||||
import { useApp } from "@/context";
|
||||
import { logFileManager } from "@/logFileManager";
|
||||
import { flushRecordedPromptPayloadToLog } from "@/LLMProviders/chainRunner/utils/promptPayloadRecorder";
|
||||
import { updateSetting, useSettingsValue } from "@/settings/model";
|
||||
|
|
@ -10,6 +11,7 @@ import { getPromptFilePath, SystemPromptAddModal } from "@/system-prompts";
|
|||
import { useSystemPrompts } from "@/system-prompts/state";
|
||||
|
||||
export const AdvancedSettings: React.FC = () => {
|
||||
const app = useApp();
|
||||
const settings = useSettingsValue();
|
||||
const prompts = useSystemPrompts();
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { Input } from "@/components/ui/input";
|
|||
import { getModelDisplayWithIcons } from "@/components/ui/model-display";
|
||||
import { SettingItem } from "@/components/ui/setting-item";
|
||||
import { DEFAULT_OPEN_AREA, PLUS_UTM_MEDIUMS, SEND_SHORTCUT } from "@/constants";
|
||||
import { useApp } from "@/context";
|
||||
import { useTab } from "@/contexts/TabContext";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { createPlusPageUrl } from "@/plusUtils";
|
||||
|
|
@ -25,6 +26,7 @@ const ChainType2Label: Record<ChainType, string> = {
|
|||
};
|
||||
|
||||
export const BasicSettings: React.FC = () => {
|
||||
const app = useApp();
|
||||
const settings = useSettingsValue();
|
||||
const { setSelectedTab } = useTab();
|
||||
const [isChecking, setIsChecking] = useState(false);
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import {
|
|||
import { generateDefaultCommands } from "@/commands/migrator";
|
||||
import { CustomCommand } from "@/commands/type";
|
||||
import { ConfirmModal } from "@/components/modals/ConfirmModal";
|
||||
import { useApp } from "@/context";
|
||||
import { SettingItem } from "@/components/ui/setting-item";
|
||||
import { Notice } from "obsidian";
|
||||
|
||||
|
|
@ -57,6 +58,7 @@ const MobileCommandCard: React.FC<{
|
|||
onCopy: (command: CustomCommand) => void | Promise<void>;
|
||||
containerRef: React.RefObject<HTMLDivElement>;
|
||||
}> = ({ command, commands, onUpdate, onRemove, onCopy, containerRef }) => {
|
||||
const app = useApp();
|
||||
const handleEdit = (cmd: CustomCommand) => {
|
||||
const modal = new CustomCommandSettingsModal(app, commands, cmd, async (updatedCommand) => {
|
||||
await onUpdate(updatedCommand, cmd.title);
|
||||
|
|
@ -172,6 +174,7 @@ const SortableTableRow: React.FC<{
|
|||
onRemove: (command: CustomCommand) => void | Promise<void>;
|
||||
onCopy: (command: CustomCommand) => void | Promise<void>;
|
||||
}> = ({ command, commands, onUpdate, onRemove, onCopy }) => {
|
||||
const app = useApp();
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
|
||||
id: command.title,
|
||||
});
|
||||
|
|
@ -285,6 +288,7 @@ const SortableTableRow: React.FC<{
|
|||
};
|
||||
|
||||
export const CommandSettings: React.FC = () => {
|
||||
const app = useApp();
|
||||
const rawCommands = useCustomCommands();
|
||||
const commands = useMemo(() => {
|
||||
return sortCommandsByOrder([...rawCommands]);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { ConfirmModal } from "@/components/modals/ConfirmModal";
|
||||
import { useApp } from "@/context";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { HelpTooltip } from "@/components/ui/help-tooltip";
|
||||
import { SettingItem } from "@/components/ui/setting-item";
|
||||
|
|
@ -12,6 +13,7 @@ import React, { useState } from "react";
|
|||
import { ToolSettingsSection } from "./ToolSettingsSection";
|
||||
|
||||
export const CopilotPlusSettings: React.FC = () => {
|
||||
const app = useApp();
|
||||
const settings = useSettingsValue();
|
||||
const [isValidatingSelfHost, setIsValidatingSelfHost] = useState(false);
|
||||
const isSelfHostEligible = useIsSelfHostEligible();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { CustomModel } from "@/aiParams";
|
||||
import { SettingItem } from "@/components/ui/setting-item";
|
||||
import { useApp } from "@/context";
|
||||
import { BUILTIN_CHAT_MODELS, BUILTIN_EMBEDDING_MODELS } from "@/constants";
|
||||
import EmbeddingManager from "@/LLMProviders/embeddingManager";
|
||||
import ProjectManager from "@/LLMProviders/projectManager";
|
||||
|
|
@ -13,6 +14,7 @@ import { Notice } from "obsidian";
|
|||
import React, { useState } from "react";
|
||||
|
||||
export const ModelSettings: React.FC = () => {
|
||||
const app = useApp();
|
||||
const settings = useSettingsValue();
|
||||
const [showAddDialog, setShowAddDialog] = useState(false);
|
||||
const [showAddEmbeddingDialog, setShowAddEmbeddingDialog] = useState(false);
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { useApp } from "@/context";
|
||||
import { cn } from "@/lib/utils";
|
||||
import {
|
||||
categorizePatterns,
|
||||
|
|
@ -50,6 +51,7 @@ export const PatternListEditor: React.FC<PatternListEditorProps> = ({
|
|||
maxCollapsedHeight = 84,
|
||||
maxExpandedHeight = 200,
|
||||
}) => {
|
||||
const app = useApp();
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
const [isOverflowing, setIsOverflowing] = useState(false);
|
||||
const [contentHeight, setContentHeight] = useState<number>(0);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { CopilotPlusWelcomeModal } from "@/components/modals/CopilotPlusWelcomeModal";
|
||||
import { useApp } from "@/context";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { PasswordInput } from "@/components/ui/password-input";
|
||||
|
|
@ -9,6 +10,7 @@ import { ExternalLink, Loader2 } from "lucide-react";
|
|||
import React, { useEffect, useState } from "react";
|
||||
|
||||
export function PlusSettings() {
|
||||
const app = useApp();
|
||||
const settings = useSettingsValue();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isChecking, setIsChecking] = useState(false);
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { Notice } from "obsidian";
|
|||
|
||||
import { RebuildIndexConfirmModal } from "@/components/modals/RebuildIndexConfirmModal";
|
||||
import { SemanticSearchToggleModal } from "@/components/modals/SemanticSearchToggleModal";
|
||||
import { useApp } from "@/context";
|
||||
import { HelpTooltip } from "@/components/ui/help-tooltip";
|
||||
import { getModelDisplayWithIcons } from "@/components/ui/model-display";
|
||||
import { SettingItem } from "@/components/ui/setting-item";
|
||||
|
|
@ -12,6 +13,7 @@ import { getModelKeyFromModel, updateSetting, useSettingsValue } from "@/setting
|
|||
import { PatternListEditor } from "@/settings/v2/components/PatternListEditor";
|
||||
|
||||
export const QASettings: React.FC = () => {
|
||||
const app = useApp();
|
||||
const settings = useSettingsValue();
|
||||
const isMiyoSearchActive = settings.enableMiyo;
|
||||
const visibleEmbeddingModels = settings.activeEmbeddingModels;
|
||||
|
|
|
|||
Loading…
Reference in a new issue