mirror of
https://github.com/aykhans/slash-e.git
synced 2025-04-20 14:01:24 +00:00
chore: redirect to instance url
This commit is contained in:
parent
7c9798b6b1
commit
764d776524
@ -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",
|
||||||
|
@ -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 });
|
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
},
|
},
|
||||||
|
@ -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;
|
@ -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");
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -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"
|
||||||
>
|
>
|
||||||
|
@ -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>
|
||||||
|
55
frontend/extension/src/store/shortcut.ts
Normal file
55
frontend/extension/src/store/shortcut.ts
Normal 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;
|
Loading…
x
Reference in New Issue
Block a user