Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions assets/github.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@
"test": "jest --passWithNoTests"
},
"dependencies": {
"@octokit/rest": "^22.0.0",
"acorn": "^8.14.0",
"acorn-walk": "^8.3.4",
"d3-array": "^3.2.4",
"escodegen": "^2.1.0",
"framer-motion": "^12.23.12",
"plasmo": "0.89.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"tailwindcss": "3.4.1"
"tailwindcss": "3.4.1",
"uuid": "^11.1.0"
},
"devDependencies": {
"@babel/preset-env": "^7.26.9",
Expand All @@ -35,6 +38,7 @@
"@types/node": "20.11.5",
"@types/react": "18.2.48",
"@types/react-dom": "18.2.18",
"@types/uuid": "^10.0.0",
"autoprefixer": "^10.4.20",
"babel-jest": "^29.7.0",
"husky": "^9.1.7",
Expand Down
4 changes: 4 additions & 0 deletions plasmo.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ export default {
"storage",
"cookies",
"webRequest"
],
host_permissions: [
"https://api.github.com/*",
"https://github.com/*"
]
}
}
306 changes: 306 additions & 0 deletions src/components/DialogPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
import React, { useCallback, useEffect, useRef, useState } from "react";
import { motion } from "framer-motion";
import faviconIco from "data-base64:../../assets/icon.png";

// Close button component
const CloseButton = ({ close }: { close: () => void }) => {
return (
<motion.button
onClick={close}
className="absolute top-4 right-4 z-20 text-gray-400 hover:text-gray-700 text-2xl font-bold transition-colors duration-200"
whileHover={{ scale: 1.2, rotate: 90 }}
whileTap={{ scale: 0.9 }}
>
&times;
</motion.button>
);
};

// Arrow head component for resize handles
const ArrowHead = ({ direction }: { direction: 'top' | 'right' | 'bottom' | 'left' }) => {
const getArrowStyle = () => {
switch (direction) {
case 'top':
return { transform: 'rotate(0deg)' };
case 'right':
return { transform: 'rotate(90deg)' };
case 'bottom':
return { transform: 'rotate(180deg)' };
case 'left':
return { transform: 'rotate(270deg)' };
}
};

return (
<div className="absolute inset-0 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity">
<div className="w-2 h-2 border-t-2 border-l-2 border-gray-400" style={getArrowStyle()} />
</div>
);
};

// Dialog panel component with drag and resize functionality
export const DialogPanel = ({
children,
overlay,
close
}: {
children: React.ReactNode,
overlay?: React.ReactNode,
close?: () => void
}) => {
const [panelSize, setPanelSize] = useState<{ width: number; height: number }>({ width: 550, height: 330 });
const resizingRef = useRef<{
startX: number;
startY: number;
startW: number;
startH: number;
startLeft: number;
startTop: number;
viewportW: number;
viewportH: number;
edge: 'top'|'right'|'bottom'|'left';
} | null>(null);

const [pos, setPos] = useState<{ top: number; left: number }>(() => {
const minMargin = 4;
const bottom = 130;
const right = 80;
const top = Math.max(minMargin, window.innerHeight - bottom - 365);
const left = Math.max(minMargin, window.innerWidth - right - 550);
return { top, left };
});

const draggingRef = useRef<{ startX: number; startY: number; startTop: number; startLeft: number } | null>(null);

const onMouseMove = useCallback((e: MouseEvent) => {
if (!resizingRef.current) return;
const dx = e.clientX - resizingRef.current.startX;
const dy = e.clientY - resizingRef.current.startY;

const minW = 320;
const minH = 200;
const maxW = Math.min(window.innerWidth * 0.92, 900);
const maxH = Math.min(window.innerHeight * 0.7, 800);

let newW = resizingRef.current.startW;
let newH = resizingRef.current.startH;
let newLeft = pos.left;
let newTop = pos.top;

switch (resizingRef.current.edge) {
case 'right':
newW = resizingRef.current.startW + dx;
break;
case 'left':
newW = resizingRef.current.startW - dx;
newLeft = resizingRef.current.startLeft + dx;
break;
case 'bottom':
newH = resizingRef.current.startH + dy;
break;
case 'top':
newH = resizingRef.current.startH - dy;
newTop = resizingRef.current.startTop + dy;
break;
}

// Apply constraints
newW = Math.max(minW, Math.min(maxW, newW));
newH = Math.max(minH, Math.min(maxH, newH));
newLeft = Math.max(0, Math.min(window.innerWidth - newW, newLeft));
newTop = Math.max(0, Math.min(window.innerHeight - newH, newTop));

setPanelSize({ width: newW, height: newH });
if (newLeft !== pos.left || newTop !== pos.top) {
setPos({ left: newLeft, top: newTop });
}
}, [panelSize.width, panelSize.height, pos.left, pos.top]);

const onMouseMoveDrag = useCallback((e: MouseEvent) => {
if (!draggingRef.current) return;
const dx = e.clientX - draggingRef.current.startX;
const dy = e.clientY - draggingRef.current.startY;

const newLeft = draggingRef.current.startLeft + dx;
const newTop = draggingRef.current.startTop + dy;

const minMargin = 4;
const maxLeft = window.innerWidth - panelSize.width - minMargin;
const maxTop = window.innerHeight - panelSize.height - minMargin;

setPos({
left: Math.max(minMargin, Math.min(maxLeft, newLeft)),
top: Math.max(minMargin, Math.min(maxTop, newTop))
});
}, [panelSize.width, panelSize.height]);

const endResize = useCallback(() => {
resizingRef.current = null;
document.body.style.cursor = '';
document.body.style.userSelect = '';

// Remove event listeners
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', endResize);
}, [onMouseMove]);

const endDrag = useCallback(() => {
draggingRef.current = null;
document.body.style.cursor = '';
document.body.style.userSelect = '';

// Remove event listeners
document.removeEventListener('mousemove', onMouseMoveDrag);
document.removeEventListener('mouseup', endDrag);
}, [onMouseMoveDrag]);

const startResize = useCallback((e: React.MouseEvent, edge: 'top'|'right'|'bottom'|'left') => {
e.preventDefault();
resizingRef.current = {
startX: e.clientX,
startY: e.clientY,
startW: panelSize.width,
startH: panelSize.height,
startLeft: pos.left,
startTop: pos.top,
viewportW: window.innerWidth,
viewportH: window.innerHeight,
edge
};
document.body.style.cursor = edge === 'left' || edge === 'right' ? 'ew-resize' : 'ns-resize';
document.body.style.userSelect = 'none';

// Add event listeners
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', endResize);
}, [panelSize.width, panelSize.height, pos.left, pos.top, onMouseMove, endResize]);

const startDrag = useCallback((e: React.MouseEvent) => {
e.preventDefault();
draggingRef.current = {
startX: e.clientX,
startY: e.clientY,
startTop: pos.top,
startLeft: pos.left
};
document.body.style.cursor = 'grabbing';
document.body.style.userSelect = 'none';

// Add event listeners
document.addEventListener('mousemove', onMouseMoveDrag);
document.addEventListener('mouseup', endDrag);
}, [pos.top, pos.left, onMouseMoveDrag, endDrag]);

useEffect(() => {
const handleResize = () => {
const minMargin = 4;
const bottom = 130;
const right = 80;
const newTop = Math.max(minMargin, window.innerHeight - bottom - 365);
const newLeft = Math.max(minMargin, window.innerWidth - right - 550);

if (newLeft !== pos.left || newTop !== pos.top) {
setPos({ left: newLeft, top: newTop });
}
};

window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, [panelSize.width, panelSize.height, pos.left, pos.top]);

useEffect(() => () => endDrag(), [endDrag]);

return (
<motion.div
className="fixed bg-white/95 backdrop-blur-md p-6 rounded-2xl shadow-xl ring-1 ring-gray-200 relative z-[9999]"
style={{ position: 'fixed', top: `${pos.top}px`, left: `${pos.left}px`, bottom: 'auto', right: 'auto' }}
initial={{ opacity: 0, scale: 0.98, y: 16, x: 8 }}
animate={{ opacity: 1, scale: 1, y: 0, x: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 20 }}
transition={{
type: "spring",
damping: 20,
stiffness: 300
}}
>
{close && <CloseButton close={close} />}
<div
className="min-h-[200px] max-h-[70vh] overflow-y-auto -mr-6 pr-6"
style={{ width: `${panelSize.width}px`, height: `${panelSize.height}px`, maxWidth: '92vw' }}
>
<div className="flex items-center justify-center mb-6 cursor-grab select-none"
onMouseDown={startDrag}
title="Drag">
<motion.div
initial={{ scale: 0, rotate: -180 }}
animate={{ scale: 1, rotate: 0 }}
transition={{
type: "spring",
stiffness: 260,
damping: 20,
delay: 0.1
}}
className="mr-3"
>
<img
src={faviconIco}
alt="Mantis"
className="w-10 h-10"
/>
</motion.div>
<motion.h2
className="text-3xl font-bold bg-black bg-clip-text text-transparent"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }}
>
Mantis
</motion.h2>
</div>
<div className="relative">
{children}
</div>
</div>

{/* Resize handles */}
<div
onMouseDown={(e) => startResize(e, 'top')}
className="absolute top-0 left-0 right-0 h-2 cursor-n-resize group"
style={{ transform: 'translateY(-1px)' }}
title="Resize"
>
<ArrowHead direction="top" />
</div>
<div
onMouseDown={(e) => startResize(e, 'bottom')}
className="absolute bottom-0 left-0 right-0 h-2 cursor-s-resize group"
style={{ transform: 'translateY(1px)' }}
title="Resize"
>
<ArrowHead direction="bottom" />
</div>
<div
onMouseDown={(e) => startResize(e, 'left')}
className="absolute left-0 top-0 bottom-0 w-2 cursor-w-resize group"
style={{ transform: 'translateX(-1px)' }}
title="Resize"
>
<ArrowHead direction="left" />
</div>
<div
onMouseDown={(e) => startResize(e, 'right')}
className="absolute right-0 top-0 bottom-0 w-2 cursor-e-resize group"
style={{ transform: 'translateX(1px)' }}
title="Resize"
>
<ArrowHead direction="right" />
</div>

{overlay && (
<div className="absolute inset-0 z-10 pointer-events-none">
{overlay}
</div>
)}
</motion.div>
);
};
3 changes: 2 additions & 1 deletion src/connection_manager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ import { GoogleScholarConnection } from "./connections/googleScholar/connection"
import { WikipediaSegmentConnection } from "./connections/wikipediaSegment/connection";
import { GmailConnection } from "./connections/Gmail/connection";
import { LinkedInConnection } from "./connections/Linkedin/connection";
import { GitBlameConnection } from "./connections/gitblame/connection";


export const CONNECTIONS = [GmailConnection, WikipediaSegmentConnection, WikipediaReferencesConnection, GoogleConnection, PubmedConnection, GoogleDocsConnection, GoogleScholarConnection,LinkedInConnection];
export const CONNECTIONS = [GmailConnection, WikipediaSegmentConnection, WikipediaReferencesConnection, GoogleConnection, PubmedConnection, GoogleDocsConnection, GoogleScholarConnection, LinkedInConnection, GitBlameConnection];

export const searchConnections = (url: string, ) => {
const connections = CONNECTIONS.filter(connection => connection.trigger(url));
Expand Down
Loading