From 0efd495f565c7f84d5606c6382d851c29d8caac6 Mon Sep 17 00:00:00 2001 From: Steven Date: Thu, 10 Aug 2023 22:23:22 +0800 Subject: [PATCH] feat: add create shortcut button --- extension/package.json | 1 + extension/pnpm-lock.yaml | 7 + .../src/components/CreateShortcutsButton.tsx | 166 ++++++++++++++++++ extension/src/options.tsx | 95 +++++++--- extension/src/popup.tsx | 47 +++-- 5 files changed, 273 insertions(+), 43 deletions(-) create mode 100644 extension/src/components/CreateShortcutsButton.tsx diff --git a/extension/package.json b/extension/package.json index f96443c..abcdfcf 100644 --- a/extension/package.json +++ b/extension/package.json @@ -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", diff --git a/extension/pnpm-lock.yaml b/extension/pnpm-lock.yaml index 8f57757..ec627b3 100644 --- a/extension/pnpm-lock.yaml +++ b/extension/pnpm-lock.yaml @@ -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: diff --git a/extension/src/components/CreateShortcutsButton.tsx b/extension/src/components/CreateShortcutsButton.tsx new file mode 100644 index 0000000..76043b2 --- /dev/null +++ b/extension/src/components/CreateShortcutsButton.tsx @@ -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({ + 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) => { + setState((state) => ({ + ...state, + name: e.target.value, + })); + }; + + const handleTitleInputChange = (e: React.ChangeEvent) => { + setState((state) => ({ + ...state, + title: e.target.value, + })); + }; + + const handleLinkInputChange = (e: React.ChangeEvent) => { + 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( + `${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 ( + <> + + + + +
+ Create Shortcut + +
+
+
+ Name + +
+
+ Title + +
+
+ Link + +
+ +
+ + +
+
+
+
+ + ); +}; + +export default CreateShortcutsButton; diff --git a/extension/src/options.tsx b/extension/src/options.tsx index 7fef656..f80f448 100644 --- a/extension/src/options.tsx +++ b/extension/src/options.tsx @@ -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("shortcuts", []); useEffect(() => { setSettingState({ @@ -41,6 +45,18 @@ const IndexOptions = () => { return ( <>
+ +

@@ -49,35 +65,62 @@ const IndexOptions = () => { Setting

-
- Domain -
- setPartialSettingState({ domain: e.target.value })} - /> -
-
-
- Access Token -
- setPartialSettingState({ accessToken: e.target.value })} - /> +
+
+ Domain + {domain !== "" && ( + + Go to my Slash + + + )} +
+
+ setPartialSettingState({ domain: e.target.value })} + /> +
+
+ +
+ Access Token +
+ setPartialSettingState({ accessToken: e.target.value })} + /> +
+
+ +
+
-
- -
+ {shortcuts.length > 0 && ( + <> + + +

+ Shortcuts + ({shortcuts.length}) + +

+ + + )}
diff --git a/extension/src/popup.tsx b/extension/src/popup.tsx index 0716cf1..559523f 100644 --- a/extension/src/popup.tsx +++ b/extension/src/popup.tsx @@ -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 ( <> -
-
-
- - Slash +
+
+
+ + Slash {isInitialized && ( <> / Shortcuts - ({shortcuts.length}) + ({shortcuts.length}) )}
-
- -
+
{isInitialized && }
{isInitialized ? ( - shortcuts.length !== 0 ? ( - - ) : ( -
-

No shortcut found.

+ <> + {shortcuts.length !== 0 ? ( + + ) : ( +
+

No shortcut found.

+
+ )} + + - ) + ) : (

No domain and access token found.