Add tooltip widget functionality

This commit is contained in:
FBarrca 2024-10-27 19:33:35 +01:00
parent d67c4ad70b
commit 4d725b1c2b
5 changed files with 318 additions and 1 deletions

8
src/api.ts Normal file
View file

@ -0,0 +1,8 @@
// api.ts
export async function callApi(content: string): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve("API response for content: " + content);
}, 2000);
});
}

View file

@ -0,0 +1,96 @@
// TooltipWidget.ts
import { EditorView, placeholder, keymap } from "@codemirror/view";
import { EditorState } from "@codemirror/state";
import { ButtonComponent, setIcon, Notice } from "obsidian";
import { callApi } from "../api";
export class TooltipWidget {
dom: HTMLDivElement;
editorView: EditorView;
submitButton: ButtonComponent;
loader: HTMLDivElement;
selectedText: string; // Add property to store selected text
constructor(selectedText: string) {
// Accept selectedText as a parameter
this.selectedText = selectedText;
this.dom = document.createElement("div");
this.dom.className = "cm-tooltip";
setIcon(this.dom, "pencil");
const editorDom = document.createElement("div");
editorDom.className = "cm-tooltip-editor";
this.dom.appendChild(editorDom);
this.editorView = new EditorView({
state: EditorState.create({
doc: "",
extensions: [
placeholder("Ask copilot"),
keymap.of([
{
key: "Enter",
run: () => {
this.submitAction();
return true;
},
preventDefault: true,
},
]),
],
}),
parent: editorDom,
dispatch: (tr) => this.editorView.update([tr]),
});
this.submitButton = new ButtonComponent(this.dom);
this.submitButton.buttonEl.classList.add("submit-button");
setIcon(this.submitButton.buttonEl, "send-horizontal");
this.submitButton.onClick(() => this.submitAction());
this.loader = document.createElement("div");
this.loader.className = "loader";
this.loader.style.display = "none";
this.dom.appendChild(this.loader);
this.editorView.requestMeasure({
read: () => {
this.editorView.dom.style.height = "100%";
this.editorView.dom.style.width = "100%";
},
});
}
async submitAction() {
const userInput = this.editorView.state.doc.toString();
// Log the selected text from the main editor
console.log("Selected Text:", this.selectedText);
this.submitButton.setDisabled(true);
this.submitButton.buttonEl.style.display = "none";
this.loader.style.display = "block";
try {
const response = await callApi(userInput);
new Notice(response);
} catch (error) {
new Notice("Error: " + (error as Error).message);
} finally {
this.submitButton.setDisabled(false);
this.submitButton.buttonEl.style.display = "block";
this.loader.style.display = "none";
}
}
destroy() {
this.editorView.destroy();
}
mount() {
setTimeout(() => {
this.editorView.focus();
}, 0);
}
}

64
src/main.ts Normal file
View file

@ -0,0 +1,64 @@
import { Plugin, MarkdownView } from "obsidian";
import {
cursorTooltipExtension,
showTooltipEffect,
} from "./modules/tooltipExtension";
import { EditorView } from "@codemirror/view";
interface MyPluginSettings {
mySetting: string;
}
const DEFAULT_SETTINGS: MyPluginSettings = {
mySetting: "default",
};
export default class MyPlugin extends Plugin {
settings: MyPluginSettings = DEFAULT_SETTINGS;
async onload() {
await this.loadSettings();
// Register the cursor tooltip extension
this.registerEditorExtension(cursorTooltipExtension());
// Add command to show tooltip
this.addCommand({
id: "show-cursor-tooltip",
name: "Show Cursor Tooltip",
callback: () => {
const markdownView =
this.app.workspace.getActiveViewOfType(MarkdownView);
if (markdownView) {
// Dispatch the effect to show the tooltip
// eslint-disable-next-line @typescript-eslint/no-explicit-any
((markdownView.editor as any).cm as EditorView).dispatch({
effects: showTooltipEffect.of(null),
});
}
},
hotkeys: [
{
modifiers: ["Mod"], // "Mod" stands for "Ctrl" on Windows/Linux and "Cmd" on macOS
key: "a", // You can change this key to your preference
},
],
});
}
onunload() {
// Cleanup if necessary
}
async loadSettings() {
this.settings = Object.assign(
{},
DEFAULT_SETTINGS,
await this.loadData()
);
}
async saveSettings() {
await this.saveData(this.settings);
}
}

View file

@ -0,0 +1,81 @@
// extensions.ts
import { StateField, EditorState } from "@codemirror/state";
import { showTooltip, type Tooltip } from "@codemirror/view";
import { TooltipWidget } from "../components/tooltipWidget";
import { StateEffect } from "@codemirror/state";
export const showTooltipEffect = StateEffect.define<null>();
export function cursorTooltipExtension() {
return StateField.define<Tooltip | null>({
create() {
return null;
},
update(tooltip, tr) {
if (tr.effects.some((e) => e.is(showTooltipEffect))) {
return getCursorTooltip(tr.state);
}
if (tr.docChanged || tr.selection) {
return null;
}
return tooltip;
},
provide: (field) => showTooltip.from(field),
});
}
function getCursorTooltip(state: EditorState): Tooltip | null {
const { selection } = state;
const mainSelection = selection.main;
const { from, to, head, anchor } = mainSelection;
const currentLine = state.doc.lineAt(from);
const isSelectionActive = !mainSelection.empty;
let posAt = currentLine.from;
let above = true;
let selectedText = "";
if (isSelectionActive) {
selectedText = state.doc.sliceString(from, to);
if (head > anchor) {
console.log("Putting tooltip below");
const endLine = state.doc.lineAt(to);
if (endLine.number < state.doc.lines) {
// Get the next line's start position
const nextLine = state.doc.line(endLine.number + 1);
posAt = nextLine.from;
} else {
// If at the end of the document, place tooltip at the document's end
posAt = state.doc.length;
}
above = false; // Tooltip is below
} else if (head < anchor) {
console.log("Putting tooltip above");
const startLine = state.doc.lineAt(from);
posAt = startLine.from;
above = true; // Tooltip is above
} else {
// Fallback for any other cases
posAt = currentLine.from;
above = true;
}
} else {
// When there's no active selection, place tooltip at the start of the current line
posAt = currentLine.from;
above = true;
}
const tooltipWidget = new TooltipWidget(selectedText); // Pass selectedText
return {
pos: posAt,
above,
create: () => ({
dom: tooltipWidget.dom,
destroy: () => tooltipWidget.destroy(),
mount: () => tooltipWidget.mount(),
}),
};
}

View file

@ -4,5 +4,73 @@ This CSS file will be included with your plugin, and
available in the app when your plugin is enabled.
If your plugin does not need CSS, delete this file.
// Styling for the tooltip
dom.style.padding = "4px 8px";
dom.style.background = "--background-secondary";
dom.style.zIndex = "--layer-tooltip";
dom.style.color = "#fff";
dom.style.borderRadius = "4px";
dom.style.fontSize = "--font-interface-theme";
*/
.cm-tooltip {
padding: 4px 8px;
background: var(--background-secondary) !important;
z-index: var(--layer-tooltip);
color: var(--text-normal);
border-radius: 4px;
font-size: var(--font-interface-theme);
width: 80%;
max-width: var(--file-line-width);
max-height: 200px;
border-color: var(--background-modifier-border) !important;
display: flex;
flex-direction: row;
align-items: center; /* Center children vertically */
}
.cm-tooltip > :first-child {
margin-right: 8px; /* Add some space to the right of the first child */
}
/* .cm-tooltip > :last-child {
margin-left: 8px;
} */
.cm-tooltip-editor {
display: flex;
padding: 1px;
padding-left: 10px;
height: var(--line-height);
width: 100%;
max-height: 100%; /* Ensure it can't be taller than the parent container */
align-items: center; /* Center children vertically */
background-color: var(--background-primary);
border: 1px solid var(--background-modifier-border-focus );
border-radius: 4px;
overflow: hidden; /* Hide any overflow content */
}
/* HTML: <div class="loader"></div> */
.loader {
width: 5px; /* Adjusted width to ensure total width is 42px with box-shadow */
aspect-ratio: 1;
border-radius: 50%;
color: var(--text-normal);
background: var(--background-primary);
animation: l5 1s infinite linear alternate;
margin-left: 26px;
margin-right: 20px;
}
@keyframes l5 {
0% {box-shadow: 10px 0 var(--text-accent), -10px 0 var(--text-faint); background: var(--text-accent) }
33% {box-shadow: 10px 0 var(--text-accent), -10px 0 var(--text-faint); background: var(--text-faint)}
66% {box-shadow: 10px 0 var(--text-faint), -10px 0 var(--text-accent); background: var(--text-faint)}
100%{box-shadow: 10px 0 var(--text-faint), -10px 0 var(--text-accent); background: var(--text-accent) }
}
.submit-button {
height: 100%;
margin-left: 8px;
}