mirror of
https://github.com/khoj-ai/khoj.git
synced 2025-02-17 08:04:21 +00:00
Create Nav bar for Obsidian pane. Use abstract View class for reuse
- Jump to chat, show similar actions from nav menu of Khoj side pane - Add chat, search icons from web, desktop app - Use lucide icon for find similar (for now) - Match proportions of find similar icon to khoj other icons via css, js - Use KhojPaneView abstract class to allow reuse of common functionality like - Creating the nav bar header in side pane views - Loading geo-location data for chat context This should make creating new views easier
This commit is contained in:
parent
0a1a6cd041
commit
57f1c53214
5 changed files with 268 additions and 34 deletions
|
@ -1,7 +1,7 @@
|
|||
import { ItemView, MarkdownRenderer, WorkspaceLeaf, request, requestUrl, setIcon } from 'obsidian';
|
||||
import { MarkdownRenderer, WorkspaceLeaf, request, requestUrl, setIcon } from 'obsidian';
|
||||
import { KhojSetting } from 'src/settings';
|
||||
|
||||
export const KHOJ_CHAT_VIEW = "khoj-chat-view";
|
||||
import { KhojPaneView } from 'src/pane_view';
|
||||
import { KhojView } from 'src/utils';
|
||||
|
||||
export interface ChatJsonResult {
|
||||
image?: string;
|
||||
|
@ -11,7 +11,7 @@ export interface ChatJsonResult {
|
|||
}
|
||||
|
||||
|
||||
export class KhojChatView extends ItemView {
|
||||
export class KhojChatView extends KhojPaneView {
|
||||
result: string;
|
||||
setting: KhojSetting;
|
||||
region: string;
|
||||
|
@ -20,33 +20,15 @@ export class KhojChatView extends ItemView {
|
|||
timezone: string;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, setting: KhojSetting) {
|
||||
super(leaf);
|
||||
|
||||
this.setting = setting;
|
||||
|
||||
// Register Modal Keybindings to send user message
|
||||
// this.scope.register([], 'Enter', async () => { await this.chat() });
|
||||
|
||||
fetch("https://ipapi.co/json")
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.region = data.region;
|
||||
this.city = data.city;
|
||||
this.countryName = data.country_name;
|
||||
this.timezone = data.timezone;
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
return;
|
||||
});
|
||||
super(leaf, setting);
|
||||
}
|
||||
|
||||
getViewType(): string {
|
||||
return KHOJ_CHAT_VIEW;
|
||||
return KhojView.CHAT;
|
||||
}
|
||||
|
||||
getDisplayText(): string {
|
||||
return "Khoj";
|
||||
return "Khoj Chat";
|
||||
}
|
||||
|
||||
getIcon(): string {
|
||||
|
@ -70,8 +52,7 @@ export class KhojChatView extends ItemView {
|
|||
let { contentEl } = this;
|
||||
contentEl.addClass("khoj-chat");
|
||||
|
||||
// Add title to the Khoj Chat modal
|
||||
contentEl.createEl("h1", ({ attr: { id: "khoj-chat-title" }, text: "Khoj Chat" }));
|
||||
super.onOpen();
|
||||
|
||||
// Create area for chat logs
|
||||
let chatBodyEl = contentEl.createDiv({ attr: { id: "khoj-chat-body", class: "khoj-chat-body" } });
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { Plugin, WorkspaceLeaf } from 'obsidian';
|
||||
import { KhojSetting, KhojSettingTab, DEFAULT_SETTINGS } from 'src/settings'
|
||||
import { KhojSearchModal } from 'src/search_modal'
|
||||
import { KhojChatView, KHOJ_CHAT_VIEW } from 'src/chat_view'
|
||||
import { updateContentIndex, canConnectToBackend } from './utils';
|
||||
import { KhojChatView } from 'src/chat_view'
|
||||
import { updateContentIndex, canConnectToBackend, KhojView } from './utils';
|
||||
|
||||
|
||||
export default class Khoj extends Plugin {
|
||||
|
@ -30,14 +30,14 @@ export default class Khoj extends Plugin {
|
|||
this.addCommand({
|
||||
id: 'chat',
|
||||
name: 'Chat',
|
||||
callback: () => { this.activateView(KHOJ_CHAT_VIEW); }
|
||||
callback: () => { this.activateView(KhojView.CHAT); }
|
||||
});
|
||||
|
||||
this.registerView(KHOJ_CHAT_VIEW, (leaf) => new KhojChatView(leaf, this.settings));
|
||||
this.registerView(KhojView.CHAT, (leaf) => new KhojChatView(leaf, this.settings));
|
||||
|
||||
// Create an icon in the left ribbon.
|
||||
this.addRibbonIcon('message-circle', 'Khoj', (_: MouseEvent) => {
|
||||
this.activateView(KHOJ_CHAT_VIEW);
|
||||
this.activateView(KhojView.CHAT);
|
||||
});
|
||||
|
||||
// Add a settings tab so the user can configure khoj
|
||||
|
@ -72,7 +72,7 @@ export default class Khoj extends Plugin {
|
|||
this.unload();
|
||||
}
|
||||
|
||||
async activateView(viewType: string) {
|
||||
async activateView(viewType: KhojView) {
|
||||
const { workspace } = this.app;
|
||||
|
||||
let leaf: WorkspaceLeaf | null = null;
|
||||
|
|
71
src/interface/obsidian/src/pane_view.ts
Normal file
71
src/interface/obsidian/src/pane_view.ts
Normal file
|
@ -0,0 +1,71 @@
|
|||
import { ItemView, WorkspaceLeaf } from 'obsidian';
|
||||
import { KhojSetting } from 'src/settings';
|
||||
import { KhojSearchModal } from 'src/search_modal';
|
||||
import { KhojView, populateHeaderPane } from './utils';
|
||||
|
||||
export abstract class KhojPaneView extends ItemView {
|
||||
result: string;
|
||||
setting: KhojSetting;
|
||||
region: string;
|
||||
city: string;
|
||||
countryName: string;
|
||||
timezone: string;
|
||||
|
||||
constructor(leaf: WorkspaceLeaf, setting: KhojSetting) {
|
||||
super(leaf);
|
||||
|
||||
this.setting = setting;
|
||||
|
||||
// Register Modal Keybindings to send user message
|
||||
// this.scope.register([], 'Enter', async () => { await this.chat() });
|
||||
|
||||
fetch("https://ipapi.co/json")
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
this.region = data.region;
|
||||
this.city = data.city;
|
||||
this.countryName = data.country_name;
|
||||
this.timezone = data.timezone;
|
||||
})
|
||||
.catch(err => {
|
||||
console.log(err);
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
async onOpen() {
|
||||
let { contentEl } = this;
|
||||
|
||||
// Add title to the Khoj Chat modal
|
||||
let headerEl = contentEl.createDiv(({ attr: { id: "khoj-header", class: "khoj-header" } }));
|
||||
// Setup the header pane
|
||||
await populateHeaderPane(headerEl, this.setting);
|
||||
// Set the active nav pane
|
||||
headerEl.getElementsByClassName("chat-nav")[0]?.classList.add("khoj-nav-selected");
|
||||
headerEl.getElementsByClassName("chat-nav")[0]?.addEventListener("click", (_) => { this.activateView(KhojView.CHAT); });
|
||||
headerEl.getElementsByClassName("search-nav")[0]?.addEventListener("click", (_) => { new KhojSearchModal(this.app, this.setting).open(); });
|
||||
headerEl.getElementsByClassName("similar-nav")[0]?.addEventListener("click", (_) => { new KhojSearchModal(this.app, this.setting, true).open(); });
|
||||
let similarNavSvgEl = headerEl.getElementsByClassName("khoj-nav-icon-similar")[0]?.firstElementChild;
|
||||
if (!!similarNavSvgEl) similarNavSvgEl.id = "similar-nav-icon-svg";
|
||||
}
|
||||
|
||||
async activateView(viewType: string) {
|
||||
const { workspace } = this.app;
|
||||
|
||||
let leaf: WorkspaceLeaf | null = null;
|
||||
const leaves = workspace.getLeavesOfType(viewType);
|
||||
|
||||
if (leaves.length > 0) {
|
||||
// A leaf with our view already exists, use that
|
||||
leaf = leaves[0];
|
||||
} else {
|
||||
// Our view could not be found in the workspace, create a new leaf
|
||||
// in the right sidebar for it
|
||||
leaf = workspace.getRightLeaf(false);
|
||||
await leaf.setViewState({ type: viewType, active: true });
|
||||
}
|
||||
|
||||
// "Reveal" the leaf in case it is in a collapsed sidebar
|
||||
workspace.revealLeaf(leaf);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { FileSystemAdapter, Notice, Vault, Modal, TFile, request } from 'obsidian';
|
||||
import { FileSystemAdapter, Notice, Vault, Modal, TFile, request, setIcon } from 'obsidian';
|
||||
import { KhojSetting, UserInfo } from 'src/settings'
|
||||
|
||||
export function getVaultAbsolutePath(vault: Vault): string {
|
||||
|
@ -214,3 +214,93 @@ export function getBackendStatusMessage(
|
|||
else
|
||||
return `✅ Signed in to Khoj as ${userEmail}`;
|
||||
}
|
||||
|
||||
export async function populateHeaderPane(headerEl: Element, setting: KhojSetting): Promise<void> {
|
||||
let userInfo: UserInfo | null = null;
|
||||
try {
|
||||
const { userInfo: extractedUserInfo } = await canConnectToBackend(setting.khojUrl, setting.khojApiKey, false);
|
||||
userInfo = extractedUserInfo;
|
||||
} catch (error) {
|
||||
console.error("❗️Could not connect to Khoj");
|
||||
}
|
||||
|
||||
// Add Khoj title to header element
|
||||
const titleEl = headerEl.createDiv();
|
||||
titleEl.className = 'khoj-logo';
|
||||
titleEl.textContent = "KHOJ"
|
||||
|
||||
// Populate the header element with the navigation pane
|
||||
// Create the nav element
|
||||
const nav = headerEl.createEl('nav');
|
||||
nav.className = 'khoj-nav';
|
||||
|
||||
// Create the chat link
|
||||
const chatLink = nav.createEl('a');
|
||||
chatLink.id = 'chat-nav';
|
||||
chatLink.className = 'khoj-nav chat-nav';
|
||||
|
||||
// Create the chat icon
|
||||
const chatIcon = chatLink.createEl('span');
|
||||
chatIcon.className = 'khoj-nav-icon khoj-nav-icon-chat';
|
||||
setIcon(chatIcon, 'khoj-chat');
|
||||
|
||||
// Create the chat text
|
||||
const chatText = chatLink.createEl('span');
|
||||
chatText.className = 'khoj-nav-item-text';
|
||||
chatText.textContent = 'Chat';
|
||||
|
||||
// Append the chat icon and text to the chat link
|
||||
chatLink.appendChild(chatIcon);
|
||||
chatLink.appendChild(chatText);
|
||||
|
||||
// Create the search link
|
||||
const searchLink = nav.createEl('a');
|
||||
searchLink.id = 'search-nav';
|
||||
searchLink.className = 'khoj-nav search-nav';
|
||||
|
||||
// Create the search icon
|
||||
const searchIcon = searchLink.createEl('span');
|
||||
searchIcon.className = 'khoj-nav-icon khoj-nav-icon-search';
|
||||
|
||||
// Create the search text
|
||||
const searchText = searchLink.createEl('span');
|
||||
searchText.className = 'khoj-nav-item-text';
|
||||
searchText.textContent = 'Search';
|
||||
|
||||
// Append the search icon and text to the search link
|
||||
searchLink.appendChild(searchIcon);
|
||||
searchLink.appendChild(searchText);
|
||||
|
||||
// Create the search link
|
||||
const similarLink = nav.createEl('a');
|
||||
similarLink.id = 'similar-nav';
|
||||
similarLink.className = 'khoj-nav similar-nav';
|
||||
|
||||
// Create the search icon
|
||||
const similarIcon = searchLink.createEl('span');
|
||||
similarIcon.id = 'similar-nav-icon';
|
||||
similarIcon.className = 'khoj-nav-icon khoj-nav-icon-similar';
|
||||
setIcon(similarIcon, 'webhook');
|
||||
|
||||
// Create the search text
|
||||
const similarText = searchLink.createEl('span');
|
||||
similarText.className = 'khoj-nav-item-text';
|
||||
similarText.textContent = 'Similar';
|
||||
|
||||
// Append the search icon and text to the search link
|
||||
similarLink.appendChild(similarIcon);
|
||||
similarLink.appendChild(similarText);
|
||||
|
||||
// Append the nav items to the nav element
|
||||
nav.appendChild(chatLink);
|
||||
nav.appendChild(searchLink);
|
||||
nav.appendChild(similarLink);
|
||||
|
||||
// Append the title, nav items to the header element
|
||||
headerEl.appendChild(titleEl);
|
||||
headerEl.appendChild(nav);
|
||||
}
|
||||
|
||||
export enum KhojView {
|
||||
CHAT = "khoj-chat-view",
|
||||
}
|
||||
|
|
|
@ -11,6 +11,8 @@ If your plugin does not need CSS, delete this file.
|
|||
--khoj-winter-sun: #f9f5de;
|
||||
--khoj-sun: #fee285;
|
||||
--khoj-storm-grey: #475569;
|
||||
--chat-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24px' height='24px' viewBox='0 0 24 24' fill='currentColor' stroke-linecap='round' stroke-linejoin='round' class='svg-icon' version='1.1'%3E%3Cpath d='m 14.024348,9.8497703 0.04627,1.9750167' stroke='%231c274c' stroke-width='2' stroke-linecap='round' /%3E%3Cpath d='m 9.6453624,9.7953624 0.046275,1.9750166' stroke='%231c274c' stroke-width='2' stroke-linecap='round' /%3E%3Cpath d='m 11.90538,2.3619994 c -5.4939109,0 -9.6890976,4.0608185 -9.6890976,9.8578926 0,1.477202 0.2658016,2.542848 0.6989332,3.331408 0.433559,0.789293 1.0740097,1.372483 1.9230615,1.798517 1.7362861,0.87132 4.1946007,1.018626 7.0671029,1.018626 0.317997,0 0.593711,0.167879 0.784844,0.458501 0.166463,0.253124 0.238617,0.552748 0.275566,0.787233 0.07263,0.460801 0.05871,1.030165 0.04785,1.474824 v 4.8e-5 l -2.26e-4,0.0091 c -0.0085,0.348246 -0.01538,0.634247 -0.0085,0.861186 0.105589,-0.07971 0.227925,-0.185287 0.36735,-0.31735 0.348613,-0.330307 0.743513,-0.767362 1.176607,-1.246635 l 0.07837,-0.08673 c 0.452675,-0.500762 0.941688,-1.037938 1.41216,-1.473209 0.453774,-0.419787 0.969948,-0.822472 1.476003,-0.953853 1.323661,-0.343655 2.330132,-0.904027 3.005749,-1.76381 0.658957,-0.838568 1.073167,-2.051868 1.073167,-3.898667 0,-5.7970748 -4.195186,-9.8578946 -9.689097,-9.8578946 z M 0.92440678,12.219892 c 0,-7.0067939 5.05909412,-11.47090892 10.98097322,-11.47090892 5.921878,0 10.980972,4.46411502 10.980972,11.47090892 0,2.172259 -0.497596,3.825405 -1.442862,5.028357 -0.928601,1.181693 -2.218843,1.837914 -3.664937,2.213334 -0.211641,0.05502 -0.53529,0.268579 -0.969874,0.670658 -0.417861,0.386604 -0.865628,0.876836 -1.324566,1.384504 l -0.09131,0.101202 c -0.419252,0.464136 -0.849637,0.94059 -1.239338,1.309807 -0.210187,0.199169 -0.425281,0.383422 -0.635348,0.523424 -0.200911,0.133819 -0.449635,0.263369 -0.716376,0.281474 -0.327812,0.02226 -0.61539,-0.149209 -0.804998,-0.457293 -0.157614,-0.255993 -0.217622,-0.557143 -0.246564,-0.778198 -0.0542,-0.414027 -0.04101,-0.933065 -0.03027,-1.355183 l 0.0024,-0.0922 c 0.01099,-0.463865 0.01489,-0.820507 -0.01611,-1.06842 C 8.9434608,19.975238 6.3139711,19.828758 4.356743,18.84659 3.3355029,18.334136 2.4624526,17.578678 1.8500164,16.463713 1.2372016,15.348029 0.92459928,13.943803 0.92459928,12.219967 Z' clip-rule='evenodd' stroke-width='2' fill='currentColor' fill-rule='evenodd' fill-opacity='1' /%3E%3C/svg%3E%0A");
|
||||
--search-icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24px' height='24px' viewBox='0 0 24 24' fill='currentColor' stroke-linecap='round' stroke-linejoin='round' class='svg-icon' version='1.1'%3E%3Cpath d='m 18.562765,17.147843 c 1.380497,-1.679442 2.307667,-4.013099 2.307667,-6.330999 C 20.870432,5.3951476 16.353958,1 10.782674,1 5.2113555,1 0.69491525,5.3951476 0.69491525,10.816844 c 0,5.421663 4.51644025,9.816844 10.08775875,9.816844 2.381867,0 4.570922,-0.803307 6.296712,-2.14673 0.508475,-0.508475 4.514633,4.192839 4.514633,4.192839 1.036377,1.008544 2.113087,-0.02559 1.07671,-1.034139 z m -7.780091,1.925408 c -4.3394583,0 -8.6708434,-4.033489 -8.6708434,-8.256407 0,-4.2229187 4.3313851,-8.2564401 8.6708434,-8.2564401 4.339458,0 8.670809,4.2369112 8.670809,8.4598301 0,4.222918 -4.331351,8.053017 -8.670809,8.053017 z' fill='currentColor' fill-rule='evenodd' clip-rule='evenodd' fill-opacity='1' stroke-width='1.10519' stroke-dasharray='none' /%3E%3Cpath d='m 13.337351,9.3402647 0.05184,2.1532893' stroke='%231c274c' stroke-width='2' stroke-linecap='round' /%3E%3Cpath d='M 8.431347,9.2809457 8.483191,11.434235' stroke='%231c274c' stroke-width='2' stroke-linecap='round' /%3E%3C/svg%3E%0A");
|
||||
}
|
||||
|
||||
.khoj-chat p {
|
||||
|
@ -344,3 +346,93 @@ img {
|
|||
.khoj-result-entry p br {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Khoj Header, Navigation Pane */
|
||||
div.khoj-header {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
gap: 20px;
|
||||
padding: 0 0 10px 0;
|
||||
margin: 0;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
/* Keeps the navigation menu clickable */
|
||||
a.khoj-nav {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
div.khoj-nav {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
nav.khoj-nav {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-gap: 32px;
|
||||
justify-self: right;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
a.khoj-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
div.khoj-logo {
|
||||
justify-self: left;
|
||||
}
|
||||
|
||||
.khoj-nav a {
|
||||
color: var(--main-text-color);
|
||||
text-decoration: none;
|
||||
font-size: small;
|
||||
font-weight: normal;
|
||||
padding: 0 4px;
|
||||
border-radius: 4px;
|
||||
justify-self: center;
|
||||
margin: 0;
|
||||
}
|
||||
.khoj-nav a:hover {
|
||||
background-color: var(--khoj-sun);
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
a.khoj-nav-selected {
|
||||
background-color: var(--khoj-winter-sun);
|
||||
}
|
||||
#similar-nav-icon-svg,
|
||||
.khoj-nav-icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
.khoj-nav-icon-chat {
|
||||
background-image: var(--chat-icon);
|
||||
}
|
||||
.khoj-nav-icon-search {
|
||||
background-image: var(--search-icon);
|
||||
}
|
||||
span.khoj-nav-item-text {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
div.khoj-header {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
gap: 20px;
|
||||
padding: 24px 10px 10px 10px;
|
||||
margin: 0 0 16px 0;
|
||||
}
|
||||
|
||||
nav.khoj-nav {
|
||||
grid-gap: 0px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
a.khoj-nav {
|
||||
padding: 0 16px;
|
||||
}
|
||||
span.khoj-nav-item-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue