chore: redirect to instance url

This commit is contained in:
Steven 2023-12-24 09:50:10 +08:00
parent 7c9798b6b1
commit 764d776524
8 changed files with 115 additions and 75 deletions

View File

@ -1,7 +1,7 @@
{ {
"name": "slash-extension", "name": "slash-extension",
"displayName": "Slash", "displayName": "Slash",
"version": "1.0.2", "version": "1.0.3",
"description": "An open source, self-hosted bookmarks and link sharing platform. Save and share your links very easily.", "description": "An open source, self-hosted bookmarks and link sharing platform. Save and share your links very easily.",
"scripts": { "scripts": {
"dev": "plasmo dev", "dev": "plasmo dev",

View File

@ -1,5 +1,4 @@
import { Storage } from "@plasmohq/storage"; import { Storage } from "@plasmohq/storage";
import type { Shortcut } from "@/types/proto/api/v2/shortcut_service";
const storage = new Storage(); const storage = new Storage();
const urlRegex = /https?:\/\/s\/(.+)/; const urlRegex = /https?:\/\/s\/(.+)/;
@ -13,12 +12,8 @@ chrome.webRequest.onBeforeRequest.addListener(
const shortcutName = getShortcutNameFromUrl(param.url); const shortcutName = getShortcutNameFromUrl(param.url);
if (shortcutName) { if (shortcutName) {
const shortcuts = (await storage.getItem<Shortcut[]>("shortcuts")) || []; const instanceUrl = (await storage.getItem<string>("domain")) || "";
const shortcut = shortcuts.find((shortcut) => shortcut.name === shortcutName); return chrome.tabs.update({ url: `${instanceUrl}/s/${shortcutName}` });
if (!shortcut) {
return;
}
return chrome.tabs.update({ url: shortcut.link });
} }
})(); })();
}, },

View File

@ -1,34 +1,22 @@
import { Button, IconButton, Input, Modal, ModalDialog } from "@mui/joy"; import { Button, IconButton, Input, Modal, ModalDialog } from "@mui/joy";
import { useStorage } from "@plasmohq/storage/hook"; import { useStorage } from "@plasmohq/storage/hook";
import axios from "axios";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import useShortcutStore from "@/store/shortcut";
import { Visibility } from "@/types/proto/api/v2/common"; import { Visibility } from "@/types/proto/api/v2/common";
import { CreateShortcutResponse, OpenGraphMetadata } from "@/types/proto/api/v2/shortcut_service"; import { Shortcut } from "@/types/proto/api/v2/shortcut_service";
import Icon from "./Icon"; import Icon from "./Icon";
const generateName = (length = 8) => {
let result = "";
const characters = "abcdefghijklmnopqrstuvwxyz0123456789";
const charactersLength = characters.length;
let counter = 0;
while (counter < length) {
result += characters.charAt(Math.floor(Math.random() * charactersLength));
counter += 1;
}
return result;
};
interface State { interface State {
name: string; name: string;
title: string; title: string;
link: string; link: string;
} }
const CreateShortcutsButton = () => { const CreateShortcutButton = () => {
const [domain] = useStorage("domain"); const [instanceUrl] = useStorage("domain");
const [accessToken] = useStorage("access_token"); const [accessToken] = useStorage("access_token");
const [shortcuts, setShortcuts] = useStorage("shortcuts"); const shortcutStore = useShortcutStore();
const [state, setState] = useState<State>({ const [state, setState] = useState<State>({
name: "", name: "",
title: "", title: "",
@ -54,7 +42,7 @@ const CreateShortcutsButton = () => {
const tab = tabs[0]; const tab = tabs[0];
setState((state) => ({ setState((state) => ({
...state, ...state,
name: generateName(), name: "",
title: tab.title || "", title: tab.title || "",
link: tab.url || "", link: tab.url || "",
})); }));
@ -94,25 +82,16 @@ const CreateShortcutsButton = () => {
setIsLoading(true); setIsLoading(true);
try { try {
const { await shortcutStore.createShortcut(
data: { shortcut }, instanceUrl,
} = await axios.post<CreateShortcutResponse>( accessToken,
`${domain}/api/v2/shortcuts`, Shortcut.fromPartial({
{
name: state.name, name: state.name,
title: state.title, title: state.title,
link: state.link, link: state.link,
visibility: Visibility.PRIVATE, visibility: Visibility.PUBLIC,
ogMetadata: OpenGraphMetadata.fromPartial({}), })
},
{
headers: {
Authorization: `Bearer ${accessToken}`,
},
}
); );
setShortcuts([shortcut, ...shortcuts]);
toast.success("Shortcut created successfully"); toast.success("Shortcut created successfully");
setShowModal(false); setShowModal(false);
} catch (error: any) { } catch (error: any) {
@ -171,4 +150,4 @@ const CreateShortcutsButton = () => {
); );
}; };
export default CreateShortcutsButton; export default CreateShortcutButton;

View File

@ -1,32 +1,24 @@
import { IconButton } from "@mui/joy"; import { IconButton } from "@mui/joy";
import { useStorage } from "@plasmohq/storage/hook"; import { useStorage } from "@plasmohq/storage/hook";
import axios from "axios";
import { useEffect } from "react"; import { useEffect } from "react";
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import { ListShortcutsResponse } from "@/types/proto/api/v2/shortcut_service"; import useShortcutStore from "@/store/shortcut";
import Icon from "./Icon"; import Icon from "./Icon";
const PullShortcutsButton = () => { const PullShortcutsButton = () => {
const [domain] = useStorage("domain"); const [instanceUrl] = useStorage("domain");
const [accessToken] = useStorage("access_token"); const [accessToken] = useStorage("access_token");
const [, setShortcuts] = useStorage("shortcuts"); const shortcutStore = useShortcutStore();
useEffect(() => { useEffect(() => {
if (domain && accessToken) { if (instanceUrl && accessToken) {
handlePullShortcuts(true); handlePullShortcuts(true);
} }
}, [domain, accessToken]); }, [instanceUrl, accessToken]);
const handlePullShortcuts = async (silence = false) => { const handlePullShortcuts = async (silence = false) => {
try { try {
const { await shortcutStore.fetchShortcutList(instanceUrl, accessToken);
data: { shortcuts },
} = await axios.get<ListShortcutsResponse>(`${domain}/api/v2/shortcuts`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
setShortcuts(shortcuts);
if (!silence) { if (!silence) {
toast.success("Shortcuts pulled"); toast.success("Shortcuts pulled");
} }

View File

@ -1,16 +1,24 @@
import { useStorage } from "@plasmohq/storage/hook";
import classNames from "classnames"; import classNames from "classnames";
import type { Shortcut } from "@/types/proto/api/v2/shortcut_service"; import useShortcutStore from "@/store/shortcut";
import Icon from "./Icon";
import ShortcutView from "./ShortcutView"; import ShortcutView from "./ShortcutView";
const ShortcutsContainer = () => { const ShortcutsContainer = () => {
const [shortcuts] = useStorage<Shortcut[]>("shortcuts", (v) => (v ? v : [])); const shortcuts = useShortcutStore().getShortcutList();
return ( return (
<div className={classNames("w-full grid grid-cols-2 gap-2")}> <div>
{shortcuts.map((shortcut) => { <div className="w-full flex flex-row justify-start items-center mb-4">
return <ShortcutView key={shortcut.id} shortcut={shortcut} />; <a className="bg-blue-100 dark:bg-blue-500 dark:opacity-70 py-2 px-3 rounded-full border dark:border-blue-600 flex flex-row justify-start items-center cursor-pointer shadow">
})} <Icon.AlertCircle className="w-4 h-auto" />
<span className="mx-1 text-sm">Please make sure you have signed in your instance.</span>
</a>
</div>
<div className={classNames("w-full grid grid-cols-2 gap-2")}>
{shortcuts.map((shortcut) => {
return <ShortcutView key={shortcut.id} shortcut={shortcut} />;
})}
</div>
</div> </div>
); );
}; };

View File

@ -2,12 +2,12 @@ import { Button, CssVarsProvider, Divider, Input, Select, Option } from "@mui/jo
import { useStorage } from "@plasmohq/storage/hook"; import { useStorage } from "@plasmohq/storage/hook";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Toaster, toast } from "react-hot-toast"; import { Toaster, toast } from "react-hot-toast";
import type { Shortcut } from "@/types/proto/api/v2/shortcut_service";
import Icon from "./components/Icon"; import Icon from "./components/Icon";
import Logo from "./components/Logo"; import Logo from "./components/Logo";
import PullShortcutsButton from "./components/PullShortcutsButton"; import PullShortcutsButton from "./components/PullShortcutsButton";
import ShortcutsContainer from "./components/ShortcutsContainer"; import ShortcutsContainer from "./components/ShortcutsContainer";
import useColorTheme from "./hooks/useColorTheme"; import useColorTheme from "./hooks/useColorTheme";
import useShortcutStore from "./store/shortcut";
import "./style.css"; import "./style.css";
interface SettingState { interface SettingState {
@ -38,7 +38,8 @@ const IndexOptions = () => {
domain, domain,
accessToken, accessToken,
}); });
const [shortcuts] = useStorage<Shortcut[]>("shortcuts", []); const shortcutStore = useShortcutStore();
const shortcuts = shortcutStore.getShortcutList();
const isInitialized = domain && accessToken; const isInitialized = domain && accessToken;
useEffect(() => { useEffect(() => {
@ -66,7 +67,7 @@ const IndexOptions = () => {
}; };
return ( return (
<div className="w-full"> <div className="w-full px-4">
<div className="w-full flex flex-row justify-center items-center"> <div className="w-full flex flex-row justify-center items-center">
<a <a
className="bg-yellow-100 dark:bg-yellow-500 dark:opacity-70 mt-12 py-2 px-3 rounded-full border dark:border-yellow-600 flex flex-row justify-start items-center cursor-pointer shadow hover:underline hover:text-blue-600" className="bg-yellow-100 dark:bg-yellow-500 dark:opacity-70 mt-12 py-2 px-3 rounded-full border dark:border-yellow-600 flex flex-row justify-start items-center cursor-pointer shadow hover:underline hover:text-blue-600"
@ -93,7 +94,7 @@ const IndexOptions = () => {
<span className="dark:text-gray-400">Instance URL</span> <span className="dark:text-gray-400">Instance URL</span>
{domain !== "" && ( {domain !== "" && (
<a <a
className="text-sm flex flex-row justify-start items-center dark:text-gray-400 hover:underline hover:text-blue-600" className="text-sm flex flex-row justify-start items-center underline text-blue-600 hover:opacity-80"
href={domain} href={domain}
target="_blank" target="_blank"
> >

View File

@ -1,21 +1,31 @@
import { Button, CssVarsProvider, Divider, IconButton } from "@mui/joy"; import { Button, CssVarsProvider, Divider, IconButton } from "@mui/joy";
import { useStorage } from "@plasmohq/storage/hook"; import { useStorage } from "@plasmohq/storage/hook";
import { useEffect } from "react";
import { Toaster } from "react-hot-toast"; import { Toaster } from "react-hot-toast";
import CreateShortcutsButton from "@/components/CreateShortcutsButton"; import CreateShortcutButton from "@/components/CreateShortcutButton";
import Icon from "@/components/Icon"; import Icon from "@/components/Icon";
import Logo from "@/components/Logo"; import Logo from "@/components/Logo";
import PullShortcutsButton from "@/components/PullShortcutsButton"; import PullShortcutsButton from "@/components/PullShortcutsButton";
import ShortcutsContainer from "@/components/ShortcutsContainer"; import ShortcutsContainer from "@/components/ShortcutsContainer";
import type { Shortcut } from "@/types/proto/api/v2/shortcut_service";
import useColorTheme from "./hooks/useColorTheme"; import useColorTheme from "./hooks/useColorTheme";
import useShortcutStore from "./store/shortcut";
import "./style.css"; import "./style.css";
const IndexPopup = () => { const IndexPopup = () => {
useColorTheme(); useColorTheme();
const [domain] = useStorage<string>("domain", ""); const [instanceUrl] = useStorage<string>("domain", "");
const [accessToken] = useStorage<string>("access_token", ""); const [accessToken] = useStorage<string>("access_token", "");
const [shortcuts] = useStorage<Shortcut[]>("shortcuts", []); const shortcutStore = useShortcutStore();
const isInitialized = domain && accessToken; const shortcuts = shortcutStore.getShortcutList();
const isInitialized = instanceUrl && accessToken;
useEffect(() => {
if (!isInitialized) {
return;
}
shortcutStore.fetchShortcutList(instanceUrl, accessToken);
}, [isInitialized]);
const handleSettingButtonClick = () => { const handleSettingButtonClick = () => {
chrome.runtime.openOptionsPage(); chrome.runtime.openOptionsPage();
@ -41,7 +51,7 @@ const IndexPopup = () => {
</> </>
)} )}
</div> </div>
<div>{isInitialized && <CreateShortcutsButton />}</div> <div>{isInitialized && <CreateShortcutButton />}</div>
</div> </div>
<div className="w-full mt-4"> <div className="w-full mt-4">
@ -75,8 +85,8 @@ const IndexPopup = () => {
</div> </div>
<div className="flex flex-row justify-end items-center"> <div className="flex flex-row justify-end items-center">
<a <a
className="text-sm flex flex-row justify-start items-center text-gray-500 dark:text-gray-400 hover:underline hover:text-blue-600" className="text-sm flex flex-row justify-start items-center underline text-blue-600 hover:opacity-80"
href={domain} href={instanceUrl}
target="_blank" target="_blank"
> >
<span className="mr-1">Go to my Slash</span> <span className="mr-1">Go to my Slash</span>

View File

@ -0,0 +1,55 @@
import axios from "axios";
import { create } from "zustand";
import { combine } from "zustand/middleware";
import { CreateShortcutResponse, ListShortcutsResponse, Shortcut } from "@/types/proto/api/v2/shortcut_service";
interface State {
shortcutMapById: Record<number, Shortcut>;
}
const getDefaultState = (): State => {
return {
shortcutMapById: {},
};
};
const useShortcutStore = create(
combine(getDefaultState(), (set, get) => ({
fetchShortcutList: async (instanceUrl: string, accessToken: string) => {
const {
data: { shortcuts },
} = await axios.get<ListShortcutsResponse>(`${instanceUrl}/api/v2/shortcuts`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
const shortcutMap = get().shortcutMapById;
shortcuts.forEach((shortcut) => {
shortcutMap[shortcut.id] = shortcut;
});
set({ shortcutMapById: shortcutMap });
return shortcuts;
},
getShortcutList: () => {
return Object.values(get().shortcutMapById);
},
createShortcut: async (instanceUrl: string, accessToken: string, create: Shortcut) => {
const {
data: { shortcut },
} = await axios.post<CreateShortcutResponse>(`${instanceUrl}/api/v2/shortcuts`, create, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (!shortcut) {
throw new Error(`Failed to create shortcut`);
}
const shortcutMap = get().shortcutMapById;
shortcutMap[shortcut.id] = shortcut;
set({ shortcutMapById: shortcutMap });
return shortcut;
},
}))
);
export default useShortcutStore;