mirror of
https://github.com/logancyang/obsidian-copilot.git
synced 2026-07-22 07:50:24 +00:00
* chore(react): centralize React root creation via createPluginRoot helper Every standalone React root in the plugin must wrap its tree in AppContext.Provider so descendants can rely on useApp() — PR #2466 fixed one missing wrap (QuickAskOverlay) but the contract was implicit and any new createRoot callsite could silently re-introduce the same crash. Introduce a createPluginRoot(container, app) helper that injects the provider automatically, migrate all 17 createRoot callsites to use it, and add a Jest guardrail that fails if any non-helper file imports createRoot from react-dom/client. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(lint): replace createRoot Jest guardrail with ESLint rule Switch the createPluginRoot enforcement from a Jest test (walks src/ and greps imports) to an ESLint no-restricted-syntax rule. Same invariant, faster feedback (in-editor instead of on test run), and one fewer test file to maintain. The helper file is exempted via the config block's `ignores`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
95 lines
2.4 KiB
TypeScript
95 lines
2.4 KiB
TypeScript
import { App, Modal } from "obsidian";
|
|
import React, { useState } from "react";
|
|
import { Root } from "react-dom/client";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { createPluginRoot } from "@/utils/react/createPluginRoot";
|
|
|
|
function ExtensionInputModalContent({
|
|
onConfirm,
|
|
onCancel,
|
|
}: {
|
|
onConfirm: (extension: string) => void;
|
|
onCancel: () => void;
|
|
}) {
|
|
const [extension, setExtension] = useState("");
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const validateAndConfirm = (value: string) => {
|
|
if (value.includes(" ")) {
|
|
setError("Extension cannot contain spaces");
|
|
return;
|
|
}
|
|
setError(null);
|
|
onConfirm(value);
|
|
};
|
|
|
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
|
if (e.key === "Enter") {
|
|
e.preventDefault();
|
|
e.stopPropagation();
|
|
validateAndConfirm(extension);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="tw-flex tw-flex-col tw-gap-4">
|
|
<div className="tw-flex tw-flex-col tw-gap-2">
|
|
<Input
|
|
placeholder="Enter the extension (e.g. txt, excalidraw.md)"
|
|
value={extension}
|
|
onChange={(e) => {
|
|
setExtension(e.target.value);
|
|
setError(null);
|
|
}}
|
|
onKeyDown={handleKeyDown}
|
|
/>
|
|
{error && <p className="tw-text-sm tw-text-error">{error}</p>}
|
|
</div>
|
|
<div className="tw-flex tw-justify-end tw-gap-2">
|
|
<Button variant="secondary" onClick={onCancel}>
|
|
Cancel
|
|
</Button>
|
|
<Button variant="default" onClick={() => validateAndConfirm(extension)}>
|
|
Confirm
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export class ExtensionInputModal extends Modal {
|
|
private root: Root;
|
|
|
|
constructor(
|
|
app: App,
|
|
private onConfirm: (extension: string) => void
|
|
) {
|
|
super(app);
|
|
// https://docs.obsidian.md/Reference/TypeScript+API/Modal/setTitle
|
|
// @ts-ignore
|
|
this.setTitle("Add Extension");
|
|
}
|
|
|
|
onOpen() {
|
|
const { contentEl } = this;
|
|
this.root = createPluginRoot(contentEl, this.app);
|
|
|
|
const handleConfirm = (extension: string) => {
|
|
this.onConfirm(extension);
|
|
this.close();
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
this.close();
|
|
};
|
|
|
|
this.root.render(
|
|
<ExtensionInputModalContent onConfirm={handleConfirm} onCancel={handleCancel} />
|
|
);
|
|
}
|
|
|
|
onClose() {
|
|
this.root.unmount();
|
|
}
|
|
}
|