mirror of
https://github.com/epistemic-technology/co-intelligence.git
synced 2026-07-22 06:45:17 +00:00
Address Obsidian plugin review feedback
Fix issues raised in obsidianmd/obsidian-releases#6690: - Update copyright year to 2025-2026 - Namespace generic CSS classes (sr-only, model-selector, etc.) with coi- prefix - Add FolderSuggest (AbstractInputSuggest) for folder setting inputs - Fix MarkdownRenderChild memory leak by tracking and unloading instances Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
638d33dc7f
commit
4da7fee481
8 changed files with 91 additions and 23 deletions
2
LICENSE
2
LICENSE
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2025 Epistemic Technology
|
||||
Copyright (c) 2025-2026 Epistemic Technology
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
31
src/components/FolderSuggest.ts
Normal file
31
src/components/FolderSuggest.ts
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import { AbstractInputSuggest, App, TFolder } from "obsidian";
|
||||
|
||||
export class FolderSuggest extends AbstractInputSuggest<TFolder> {
|
||||
constructor(app: App, inputEl: HTMLInputElement) {
|
||||
super(app, inputEl);
|
||||
}
|
||||
|
||||
getSuggestions(query: string): TFolder[] {
|
||||
const lowerQuery = query.toLowerCase();
|
||||
const folders: TFolder[] = [];
|
||||
for (const abstractFile of this.app.vault.getAllLoadedFiles()) {
|
||||
if (
|
||||
abstractFile instanceof TFolder &&
|
||||
abstractFile.path.toLowerCase().contains(lowerQuery)
|
||||
) {
|
||||
folders.push(abstractFile);
|
||||
}
|
||||
}
|
||||
return folders;
|
||||
}
|
||||
|
||||
renderSuggestion(folder: TFolder, el: HTMLElement): void {
|
||||
el.setText(folder.path);
|
||||
}
|
||||
|
||||
selectSuggestion(folder: TFolder): void {
|
||||
(this as any).inputEl.value = folder.path;
|
||||
(this as any).inputEl.trigger("input");
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
import { MarkdownRenderer, MarkdownRenderChild, Notice } from "obsidian";
|
||||
import { createEffect, onMount, useContext } from "solid-js";
|
||||
import { createEffect, onCleanup, onMount, useContext } from "solid-js";
|
||||
import { AppContext, PluginContext } from "@/CoiChatApp";
|
||||
|
||||
interface MarkdownViewProps {
|
||||
|
|
@ -12,6 +12,7 @@ export const MarkdownView = ({
|
|||
sourcePath = "",
|
||||
}: MarkdownViewProps) => {
|
||||
let containerRef: HTMLDivElement | undefined;
|
||||
let currentRenderChild: MarkdownRenderChild | undefined;
|
||||
const app = useContext(AppContext);
|
||||
const plugin = useContext(PluginContext);
|
||||
if (!app) {
|
||||
|
|
@ -31,18 +32,22 @@ export const MarkdownView = ({
|
|||
|
||||
const renderMarkdown = async () => {
|
||||
if (containerRef && app && plugin) {
|
||||
if (currentRenderChild) {
|
||||
currentRenderChild.unload();
|
||||
}
|
||||
|
||||
while (containerRef.firstChild) {
|
||||
containerRef.removeChild(containerRef.firstChild);
|
||||
}
|
||||
|
||||
try {
|
||||
const renderChild = new MarkdownRenderChild(containerRef);
|
||||
currentRenderChild = new MarkdownRenderChild(containerRef);
|
||||
await MarkdownRenderer.render(
|
||||
app,
|
||||
markdown,
|
||||
containerRef,
|
||||
sourcePath,
|
||||
renderChild,
|
||||
currentRenderChild,
|
||||
);
|
||||
} catch (error) {
|
||||
new Notice("Error: failed to render markdown");
|
||||
|
|
@ -62,5 +67,11 @@ export const MarkdownView = ({
|
|||
renderMarkdown();
|
||||
});
|
||||
|
||||
onCleanup(() => {
|
||||
if (currentRenderChild) {
|
||||
currentRenderChild.unload();
|
||||
}
|
||||
});
|
||||
|
||||
return <div class="markdown-rendered" ref={(el) => (containerRef = el)} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -24,16 +24,16 @@ export const ModelSelector = ({
|
|||
const hasModels = registry.availableModels.length > 0;
|
||||
|
||||
return (
|
||||
<div class="model-selector">
|
||||
<div class="coi-model-selector">
|
||||
{showLabel ? (
|
||||
<label for={id} class="model-selector-label">
|
||||
<label for={id} class="coi-model-selector-label">
|
||||
<LucideIcon name="bot-message-square" aria-hidden="true" />
|
||||
{label}
|
||||
</label>
|
||||
) : (
|
||||
<>
|
||||
<LucideIcon name="bot-message-square" aria-hidden="true" />
|
||||
<label for={id} class="sr-only">
|
||||
<label for={id} class="coi-sr-only">
|
||||
{label}
|
||||
</label>
|
||||
</>
|
||||
|
|
@ -62,7 +62,7 @@ export const ModelSelector = ({
|
|||
)}
|
||||
</select>
|
||||
{!hasModels && (
|
||||
<div id={`${id}-description`} class="sr-only">
|
||||
<div id={`${id}-description`} class="coi-sr-only">
|
||||
No AI models are currently available. Please configure model providers
|
||||
in settings.
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -94,16 +94,16 @@ export const SystemPromptSelector: Component<SystemPromptSelectorProps> = ({
|
|||
};
|
||||
|
||||
return (
|
||||
<div class="system-prompt-selector">
|
||||
<div class="coi-system-prompt-selector">
|
||||
{showLabel ? (
|
||||
<label for={id} class="system-prompt-selector-label">
|
||||
<label for={id} class="coi-system-prompt-selector-label">
|
||||
<LucideIcon name="message-circle-code" aria-hidden="true" />
|
||||
{label}
|
||||
</label>
|
||||
) : (
|
||||
<>
|
||||
<LucideIcon name="message-circle-code" aria-hidden="true" />
|
||||
<label for={id} class="sr-only">
|
||||
<label for={id} class="coi-sr-only">
|
||||
{label}
|
||||
</label>
|
||||
</>
|
||||
|
|
@ -140,7 +140,7 @@ export const SystemPromptSelector: Component<SystemPromptSelectorProps> = ({
|
|||
</For>
|
||||
</select>
|
||||
{prompts().length === 0 && (
|
||||
<div id={`${id}-description`} class="sr-only">
|
||||
<div id={`${id}-description`} class="coi-sr-only">
|
||||
No system prompts are available. Configure a system prompts folder in
|
||||
settings to add custom prompts.
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type CoIntelligencePlugin from "@/CoIntelligencePlugin";
|
||||
import { App, normalizePath, PluginSettingTab, Setting } from "obsidian";
|
||||
import { ModelId } from "@/types";
|
||||
import { FolderSuggest } from "@/components/FolderSuggest";
|
||||
|
||||
import kofiLogo from "@assets/images/kofi.png";
|
||||
|
||||
|
|
@ -201,12 +202,13 @@ export class CoIntelligenceSettingsTab extends PluginSettingTab {
|
|||
new Setting(containerEl)
|
||||
.setName("Default folder")
|
||||
.setDesc("Enter the default folder for CoIntelligence")
|
||||
.addText((text) =>
|
||||
.addText((text) => {
|
||||
text
|
||||
.setPlaceholder("Enter the default folder for CoIntelligence")
|
||||
.setValue(this.plugin.settings.defaultFolder)
|
||||
.onChange(this.createDebouncedChangeHandler("defaultFolder")),
|
||||
);
|
||||
.onChange(this.createDebouncedChangeHandler("defaultFolder"));
|
||||
new FolderSuggest(this.app, text.inputEl);
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Default model")
|
||||
|
|
@ -256,6 +258,7 @@ export class CoIntelligenceSettingsTab extends PluginSettingTab {
|
|||
textArea.onChange(
|
||||
this.createDebouncedChangeHandler("systemPromptFolder", false, true),
|
||||
);
|
||||
new FolderSuggest(this.app, textArea.inputEl);
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
|
|
@ -293,7 +296,7 @@ export class CoIntelligenceSettingsTab extends PluginSettingTab {
|
|||
});
|
||||
const srOnlySpan = kofiLink.createEl("span", {
|
||||
text: "Support me on Ko-fi",
|
||||
cls: "sr-only",
|
||||
cls: "coi-sr-only",
|
||||
});
|
||||
kofiLink.createEl("img", {
|
||||
attr: {
|
||||
|
|
|
|||
|
|
@ -434,13 +434,13 @@ li button.coi-add-context-menu-option:focus-visible {
|
|||
}
|
||||
|
||||
/* ModelSelector accessibility styles */
|
||||
.model-selector {
|
||||
.coi-model-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.model-selector-label {
|
||||
.coi-model-selector-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
|
|
@ -450,13 +450,13 @@ li button.coi-add-context-menu-option:focus-visible {
|
|||
}
|
||||
|
||||
/* SystemPromptSelector accessibility styles */
|
||||
.system-prompt-selector {
|
||||
.coi-system-prompt-selector {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.system-prompt-selector-label {
|
||||
.coi-system-prompt-selector-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
|
|
@ -466,7 +466,7 @@ li button.coi-add-context-menu-option:focus-visible {
|
|||
}
|
||||
|
||||
/* Screen reader only text */
|
||||
.sr-only {
|
||||
.coi-sr-only {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
|
|
@ -481,8 +481,8 @@ li button.coi-add-context-menu-option:focus-visible {
|
|||
|
||||
/* Compact view for narrow containers */
|
||||
@container (max-width: 545px) {
|
||||
.model-selector .coi-lucide-icon,
|
||||
.system-prompt-selector .coi-lucide-icon {
|
||||
.coi-model-selector .coi-lucide-icon,
|
||||
.coi-system-prompt-selector .coi-lucide-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ export class Vault {
|
|||
rename = vi.fn().mockResolvedValue(undefined);
|
||||
getAbstractFileByPath = vi.fn().mockReturnValue(null);
|
||||
getMarkdownFiles = vi.fn().mockReturnValue([]);
|
||||
getAllLoadedFiles = vi.fn().mockReturnValue([]);
|
||||
createFolder = vi.fn().mockImplementation(async () => new TFolder());
|
||||
}
|
||||
|
||||
|
|
@ -347,6 +348,28 @@ export class MarkdownRenderChild {
|
|||
constructor(containerEl: HTMLElement) {
|
||||
this.containerEl = containerEl;
|
||||
}
|
||||
load(): void {}
|
||||
unload(): void {}
|
||||
onload(): void {}
|
||||
onunload(): void {}
|
||||
}
|
||||
|
||||
export class AbstractInputSuggest<T> {
|
||||
app: App;
|
||||
inputEl: HTMLInputElement;
|
||||
|
||||
constructor(app: App, inputEl: HTMLInputElement) {
|
||||
this.app = app;
|
||||
this.inputEl = inputEl;
|
||||
}
|
||||
|
||||
getSuggestions(_query: string): T[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
renderSuggestion(_item: T, _el: HTMLElement): void {}
|
||||
|
||||
selectSuggestion(_item: T): void {}
|
||||
|
||||
close(): void {}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue