mirror of
https://github.com/aykhans/slash-e.git
synced 2025-04-16 12:23:12 +00:00
feat: add create shortcut button
This commit is contained in:
parent
ae56f6df8c
commit
0efd495f56
@ -12,6 +12,7 @@
|
||||
"lint-fix": "eslint --ext .js,.ts,.tsx, src --fix"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bufbuild/protobuf": "^1.3.0",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@mui/joy": "5.0.0-beta.0",
|
||||
|
7
extension/pnpm-lock.yaml
generated
7
extension/pnpm-lock.yaml
generated
@ -5,6 +5,9 @@ settings:
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
dependencies:
|
||||
'@bufbuild/protobuf':
|
||||
specifier: ^1.3.0
|
||||
version: 1.3.0
|
||||
'@emotion/react':
|
||||
specifier: ^11.11.1
|
||||
version: 11.11.1(@types/react@18.2.15)(react@18.2.0)
|
||||
@ -339,6 +342,10 @@ packages:
|
||||
'@babel/helper-validator-identifier': 7.22.5
|
||||
to-fast-properties: 2.0.0
|
||||
|
||||
/@bufbuild/protobuf@1.3.0:
|
||||
resolution: {integrity: sha512-G372ods0pLt46yxVRsnP/e2btVPuuzArcMPFpIDeIwiGPuuglEs9y75iG0HMvZgncsj5TvbYRWqbVyOe3PLCWQ==}
|
||||
dev: false
|
||||
|
||||
/@emotion/babel-plugin@11.11.0:
|
||||
resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==}
|
||||
dependencies:
|
||||
|
166
extension/src/components/CreateShortcutsButton.tsx
Normal file
166
extension/src/components/CreateShortcutsButton.tsx
Normal file
@ -0,0 +1,166 @@
|
||||
import { Button, Input, Modal, ModalDialog } from "@mui/joy";
|
||||
import { useStorage } from "@plasmohq/storage/hook";
|
||||
import axios from "axios";
|
||||
import { useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { CreateShortcutResponse, OpenGraphMetadata, Visibility } from "@/types/proto/api/v2/shortcut_service_pb";
|
||||
import "../style.css";
|
||||
import Icon from "./Icon";
|
||||
|
||||
const generateTempName = (length = 6) => {
|
||||
let result = "";
|
||||
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const charactersLength = characters.length;
|
||||
let counter = 0;
|
||||
while (counter < length) {
|
||||
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||
counter += 1;
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
interface State {
|
||||
name: string;
|
||||
title: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
const CreateShortcutsButton = () => {
|
||||
const [domain] = useStorage("domain");
|
||||
const [accessToken] = useStorage("access_token");
|
||||
const [shortcuts, setShortcuts] = useStorage("shortcuts");
|
||||
const [state, setState] = useState<State>({
|
||||
name: "",
|
||||
title: "",
|
||||
link: "",
|
||||
});
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
|
||||
const handleCreateShortcutButtonClick = async () => {
|
||||
chrome.tabs.query({ active: true, currentWindow: true }, async (tabs) => {
|
||||
if (tabs.length === 0) {
|
||||
toast.error("No active tab found");
|
||||
return;
|
||||
}
|
||||
const tab = tabs[0];
|
||||
setState((state) => ({
|
||||
...state,
|
||||
name: generateTempName().toLowerCase() + "-temp",
|
||||
title: tab.title || "",
|
||||
link: tab.url || "",
|
||||
}));
|
||||
setShowModal(true);
|
||||
});
|
||||
};
|
||||
|
||||
const handleNameInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setState((state) => ({
|
||||
...state,
|
||||
name: e.target.value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleTitleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setState((state) => ({
|
||||
...state,
|
||||
title: e.target.value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleLinkInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setState((state) => ({
|
||||
...state,
|
||||
link: e.target.value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSaveBtnClick = async () => {
|
||||
if (isLoading) {
|
||||
return;
|
||||
}
|
||||
if (!state.name) {
|
||||
toast.error("Name is required");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const {
|
||||
data: { shortcut },
|
||||
} = await axios.post<CreateShortcutResponse>(
|
||||
`${domain}/api/v2/shortcuts`,
|
||||
{
|
||||
name: state.name,
|
||||
title: state.title,
|
||||
link: state.link,
|
||||
visibility: Visibility.PRIVATE,
|
||||
ogMetadata: OpenGraphMetadata.fromJsonString("{}"),
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
setShortcuts([shortcut, ...shortcuts]);
|
||||
toast.success("Shortcut created successfully");
|
||||
setShowModal(false);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
toast.error(error.response.data.message);
|
||||
}
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button size="sm" onClick={() => handleCreateShortcutButtonClick()}>
|
||||
<Icon.Plus className="w-5 h-auto" />
|
||||
</Button>
|
||||
|
||||
<Modal open={showModal}>
|
||||
<ModalDialog layout="fullscreen">
|
||||
<div className="w-full flex flex-row justify-between items-center mb-2">
|
||||
<span className="text-base font-medium">Create Shortcut</span>
|
||||
<Button size="sm" variant="plain" onClick={() => setShowModal(false)}>
|
||||
<Icon.X className="w-5 h-auto text-gray-600" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="overflow-y-auto overflow-x-hidden px-2 w-full h-full flex flex-col justify-center items-center">
|
||||
<div className="w-full flex flex-row justify-start items-center mb-2">
|
||||
<span className="block w-16 shrink-0">Name</span>
|
||||
<Input className="grow" type="text" placeholder="Unique shortcut name" value={state.name} onChange={handleNameInputChange} />
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-start items-center mb-2">
|
||||
<span className="block w-16 shrink-0">Title</span>
|
||||
<Input className="grow" type="text" placeholder="Shortcut title" value={state.title} onChange={handleTitleInputChange} />
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-start items-center mb-2">
|
||||
<span className="block w-16 shrink-0">Link</span>
|
||||
<Input
|
||||
className="grow"
|
||||
type="text"
|
||||
placeholder="https://github.com/boojack/slash"
|
||||
value={state.link}
|
||||
onChange={handleLinkInputChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="w-full flex flex-row justify-end items-center mt-2 space-x-2">
|
||||
<Button color="neutral" variant="plain" onClick={() => setShowModal(false)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button color="primary" disabled={isLoading} loading={isLoading} onClick={handleSaveBtnClick}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</ModalDialog>
|
||||
</Modal>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateShortcutsButton;
|
@ -1,8 +1,11 @@
|
||||
import { Button, Input } from "@mui/joy";
|
||||
import type { Shortcut } from "./types/proto/api/v2/shortcut_service_pb";
|
||||
import { Button, Divider, Input } from "@mui/joy";
|
||||
import { useStorage } from "@plasmohq/storage/hook";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Toaster, toast } from "react-hot-toast";
|
||||
import Icon from "./components/Icon";
|
||||
import PullShortcutsButton from "./components/PullShortcutsButton";
|
||||
import ShortcutsContainer from "./components/ShortcutsContainer";
|
||||
import "./style.css";
|
||||
|
||||
interface SettingState {
|
||||
@ -17,6 +20,7 @@ const IndexOptions = () => {
|
||||
domain,
|
||||
accessToken,
|
||||
});
|
||||
const [shortcuts] = useStorage<Shortcut[]>("shortcuts", []);
|
||||
|
||||
useEffect(() => {
|
||||
setSettingState({
|
||||
@ -41,6 +45,18 @@ const IndexOptions = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="w-full">
|
||||
<div className="w-full flex flex-row justify-center items-center">
|
||||
<a
|
||||
className="bg-yellow-100 mt-12 py-2 px-3 rounded-full border flex flex-row justify-start items-center cursor-pointer shadow hover:underline hover:text-blue-600"
|
||||
href="https://github.com/boojack/slash#browser-extension"
|
||||
target="_blank"
|
||||
>
|
||||
<Icon.HelpCircle className="w-4 h-auto" />
|
||||
<span className="mx-1 text-sm">Need help? Check out the docs</span>
|
||||
<Icon.ExternalLink className="w-4 h-auto" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="w-full max-w-lg mx-auto flex flex-col justify-start items-start mt-12">
|
||||
<h2 className="flex flex-row justify-start items-center mb-6 font-mono">
|
||||
<Icon.CircleSlash className="w-8 h-auto mr-2 text-gray-500" />
|
||||
@ -49,35 +65,62 @@ const IndexOptions = () => {
|
||||
<span className="text-lg">Setting</span>
|
||||
</h2>
|
||||
|
||||
<div className="w-full flex flex-col justify-start items-start mb-4">
|
||||
<span className="mb-2 text-base">Domain</span>
|
||||
<div className="relative w-full">
|
||||
<Input
|
||||
className="w-full"
|
||||
type="text"
|
||||
placeholder="The domain of your Slash instance"
|
||||
value={settingState.domain}
|
||||
onChange={(e) => setPartialSettingState({ domain: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full flex flex-col justify-start items-start">
|
||||
<span className="mb-2 text-base">Access Token</span>
|
||||
<div className="relative w-full">
|
||||
<Input
|
||||
className="w-full"
|
||||
type="text"
|
||||
placeholder="The access token of your Slash instance"
|
||||
value={settingState.accessToken}
|
||||
onChange={(e) => setPartialSettingState({ accessToken: e.target.value })}
|
||||
/>
|
||||
<div className="w-full flex flex-col justify-start items-start mb-4">
|
||||
<div className="mb-2 text-base w-full flex flex-row justify-between items-center">
|
||||
<span>Domain</span>
|
||||
{domain !== "" && (
|
||||
<a
|
||||
className="text-sm flex flex-row justify-start items-center hover:underline hover:text-blue-600"
|
||||
href={domain}
|
||||
target="_blank"
|
||||
>
|
||||
<span className="mr-1">Go to my Slash</span>
|
||||
<Icon.ExternalLink className="w-4 h-auto" />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
<div className="relative w-full">
|
||||
<Input
|
||||
className="w-full"
|
||||
type="text"
|
||||
placeholder="The domain of your Slash instance"
|
||||
value={settingState.domain}
|
||||
onChange={(e) => setPartialSettingState({ domain: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full flex flex-col justify-start items-start">
|
||||
<span className="mb-2 text-base">Access Token</span>
|
||||
<div className="relative w-full">
|
||||
<Input
|
||||
className="w-full"
|
||||
type="text"
|
||||
placeholder="The access token of your Slash instance"
|
||||
value={settingState.accessToken}
|
||||
onChange={(e) => setPartialSettingState({ accessToken: e.target.value })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full mt-6">
|
||||
<Button onClick={handleSaveSetting}>Save</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full mt-6">
|
||||
<Button onClick={handleSaveSetting}>Save</Button>
|
||||
</div>
|
||||
{shortcuts.length > 0 && (
|
||||
<>
|
||||
<Divider className="!my-6" />
|
||||
|
||||
<h2 className="flex flex-row justify-start items-center mb-4 font-mono">
|
||||
<span className="text-lg">Shortcuts</span>
|
||||
<span className="text-gray-500 mr-1">({shortcuts.length})</span>
|
||||
<PullShortcutsButton />
|
||||
</h2>
|
||||
<ShortcutsContainer />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { Button } from "@mui/joy";
|
||||
import { useStorage } from "@plasmohq/storage/hook";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
import { Shortcut } from "@/types/proto/api/v2/shortcut_service_pb";
|
||||
import CreateShortcutsButton from "./components/CreateShortcutsButton";
|
||||
import Icon from "./components/Icon";
|
||||
import PullShortcutsButton from "./components/PullShortcutsButton";
|
||||
import ShortcutsContainer from "./components/ShortcutsContainer";
|
||||
@ -23,36 +24,48 @@ const IndexPopup = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full min-w-[480px] p-4">
|
||||
<div className="w-full flex flex-row justify-between items-center text-sm">
|
||||
<div className="flex flex-row justify-start items-center font-mono">
|
||||
<Icon.CircleSlash className="w-5 h-auto mr-1 text-gray-500 -mt-0.5" />
|
||||
<span className="font-mono">Slash</span>
|
||||
<div className="w-full min-w-[512px] p-6">
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<div className="flex flex-row justify-start items-center">
|
||||
<Icon.CircleSlash className="w-5 h-auto mr-1 text-gray-500" />
|
||||
<span className="">Slash</span>
|
||||
{isInitialized && (
|
||||
<>
|
||||
<span className="mx-1 text-gray-400">/</span>
|
||||
<span>Shortcuts</span>
|
||||
<span className="mr-1 text-gray-500">({shortcuts.length})</span>
|
||||
<span className="text-gray-500 mr-0.5">({shortcuts.length})</span>
|
||||
<PullShortcutsButton />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div>
|
||||
<Button size="sm" variant="plain" color="neutral" onClick={handleSettingButtonClick}>
|
||||
<Icon.Settings className="w-5 h-auto" />
|
||||
</Button>
|
||||
</div>
|
||||
<div>{isInitialized && <CreateShortcutsButton />}</div>
|
||||
</div>
|
||||
|
||||
<div className="w-full mt-4">
|
||||
{isInitialized ? (
|
||||
shortcuts.length !== 0 ? (
|
||||
<ShortcutsContainer />
|
||||
) : (
|
||||
<div className="w-full flex flex-col justify-center items-center">
|
||||
<p>No shortcut found.</p>
|
||||
<>
|
||||
{shortcuts.length !== 0 ? (
|
||||
<ShortcutsContainer />
|
||||
) : (
|
||||
<div className="w-full flex flex-col justify-center items-center">
|
||||
<p>No shortcut found.</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="mt-4 w-full flex flex-row justify-start items-center">
|
||||
<Button size="sm" variant="plain" color="neutral" onClick={handleSettingButtonClick}>
|
||||
<Icon.Settings className="w-5 h-auto text-gray-500" />
|
||||
</Button>
|
||||
<a
|
||||
className="flex flex-row justify-start items-center text-sm rounded-md py-1 px-2 text-gray-500 cursor-pointer hover:text-blue-600 hover:underline"
|
||||
href="https://github.com/boojack/slash"
|
||||
target="_blank"
|
||||
>
|
||||
<Icon.Github className="w-5 h-auto mr-1" />
|
||||
<span>GitHub</span>
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
</>
|
||||
) : (
|
||||
<div className="w-full flex flex-col justify-start items-center">
|
||||
<p>No domain and access token found.</p>
|
||||
|
Loading…
x
Reference in New Issue
Block a user