mirror of
https://github.com/aykhans/slash-e.git
synced 2025-04-20 22:07:15 +00:00
chore: use shortcut v2 api
This commit is contained in:
parent
c449669793
commit
61b167ef66
@ -13,11 +13,11 @@
|
|||||||
"@emotion/styled": "^11.11.0",
|
"@emotion/styled": "^11.11.0",
|
||||||
"@mui/joy": "5.0.0-beta.14",
|
"@mui/joy": "5.0.0-beta.14",
|
||||||
"@reduxjs/toolkit": "^1.9.7",
|
"@reduxjs/toolkit": "^1.9.7",
|
||||||
"axios": "^1.6.0",
|
"axios": "^1.6.2",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"copy-to-clipboard": "^3.3.3",
|
"copy-to-clipboard": "^3.3.3",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"i18next": "^23.6.0",
|
"i18next": "^23.7.6",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"lucide-react": "^0.292.0",
|
"lucide-react": "^0.292.0",
|
||||||
"nice-grpc-web": "^3.3.2",
|
"nice-grpc-web": "^3.3.2",
|
||||||
@ -25,24 +25,23 @@
|
|||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-hot-toast": "^2.4.1",
|
"react-hot-toast": "^2.4.1",
|
||||||
"react-i18next": "^13.3.1",
|
"react-i18next": "^13.5.0",
|
||||||
"react-redux": "^8.1.3",
|
"react-router-dom": "^6.19.0",
|
||||||
"react-router-dom": "^6.18.0",
|
|
||||||
"react-use": "^17.4.0",
|
"react-use": "^17.4.0",
|
||||||
"tailwindcss": "^3.3.5",
|
"tailwindcss": "^3.3.5",
|
||||||
"zustand": "^4.4.6"
|
"zustand": "^4.4.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@bufbuild/buf": "^1.27.2",
|
"@bufbuild/buf": "^1.28.1",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^4.2.1",
|
"@trivago/prettier-plugin-sort-imports": "^4.3.0",
|
||||||
"@types/lodash-es": "^4.17.11",
|
"@types/lodash-es": "^4.17.11",
|
||||||
"@types/react": "^18.2.37",
|
"@types/react": "^18.2.38",
|
||||||
"@types/react-dom": "^18.2.15",
|
"@types/react-dom": "^18.2.16",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.10.0",
|
"@typescript-eslint/eslint-plugin": "^6.12.0",
|
||||||
"@typescript-eslint/parser": "^6.10.0",
|
"@typescript-eslint/parser": "^6.12.0",
|
||||||
"@vitejs/plugin-react-swc": "^3.4.1",
|
"@vitejs/plugin-react-swc": "^3.5.0",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.16",
|
||||||
"eslint": "^8.53.0",
|
"eslint": "^8.54.0",
|
||||||
"eslint-config-prettier": "^8.10.0",
|
"eslint-config-prettier": "^8.10.0",
|
||||||
"eslint-plugin-prettier": "^4.2.1",
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
"eslint-plugin-react": "^7.33.2",
|
"eslint-plugin-react": "^7.33.2",
|
||||||
@ -50,7 +49,7 @@
|
|||||||
"postcss": "^8.4.31",
|
"postcss": "^8.4.31",
|
||||||
"prettier": "2.6.2",
|
"prettier": "2.6.2",
|
||||||
"protobufjs": "^7.2.5",
|
"protobufjs": "^7.2.5",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "^5.3.2",
|
||||||
"vite": "^4.5.0"
|
"vite": "^4.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
704
frontend/web/pnpm-lock.yaml
generated
704
frontend/web/pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,7 @@ import * as api from "../helpers/api";
|
|||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
shortcutId: ShortcutId;
|
shortcutId: number;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,10 +7,11 @@ import { Link } from "react-router-dom";
|
|||||||
import { absolutifyLink } from "@/helpers/utils";
|
import { absolutifyLink } from "@/helpers/utils";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
import useResponsiveWidth from "@/hooks/useResponsiveWidth";
|
||||||
import { useAppSelector } from "@/stores";
|
|
||||||
import useCollectionStore from "@/stores/v1/collection";
|
import useCollectionStore from "@/stores/v1/collection";
|
||||||
|
import useShortcutStore from "@/stores/v1/shortcut";
|
||||||
import useUserStore from "@/stores/v1/user";
|
import useUserStore from "@/stores/v1/user";
|
||||||
import { Collection } from "@/types/proto/api/v2/collection_service";
|
import { Collection } from "@/types/proto/api/v2/collection_service";
|
||||||
|
import { Shortcut } from "@/types/proto/api/v2/shortcut_service";
|
||||||
import { showCommonDialog } from "./Alert";
|
import { showCommonDialog } from "./Alert";
|
||||||
import CreateCollectionDialog from "./CreateCollectionDrawer";
|
import CreateCollectionDialog from "./CreateCollectionDrawer";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
@ -28,7 +29,7 @@ const CollectionView = (props: Props) => {
|
|||||||
const navigateTo = useNavigateTo();
|
const navigateTo = useNavigateTo();
|
||||||
const currentUser = useUserStore().getCurrentUser();
|
const currentUser = useUserStore().getCurrentUser();
|
||||||
const collectionStore = useCollectionStore();
|
const collectionStore = useCollectionStore();
|
||||||
const { shortcutList } = useAppSelector((state) => state.shortcut);
|
const shortcutList = useShortcutStore().getShortcutList();
|
||||||
const [showEditDialog, setShowEditDialog] = useState<boolean>(false);
|
const [showEditDialog, setShowEditDialog] = useState<boolean>(false);
|
||||||
const shortcuts = collection.shortcutIds
|
const shortcuts = collection.shortcutIds
|
||||||
.map((shortcutId) => shortcutList.find((shortcut) => shortcut?.id === shortcutId))
|
.map((shortcutId) => shortcutList.find((shortcut) => shortcut?.id === shortcutId))
|
||||||
|
@ -3,10 +3,11 @@ import { isUndefined } from "lodash-es";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppSelector } from "@/stores";
|
|
||||||
import useCollectionStore from "@/stores/v1/collection";
|
import useCollectionStore from "@/stores/v1/collection";
|
||||||
|
import useShortcutStore from "@/stores/v1/shortcut";
|
||||||
import { Collection } from "@/types/proto/api/v2/collection_service";
|
import { Collection } from "@/types/proto/api/v2/collection_service";
|
||||||
import { Visibility } from "@/types/proto/api/v2/common";
|
import { Visibility } from "@/types/proto/api/v2/common";
|
||||||
|
import { Shortcut } from "@/types/proto/api/v2/shortcut_service";
|
||||||
import { convertVisibilityFromPb } from "@/utils/visibility";
|
import { convertVisibilityFromPb } from "@/utils/visibility";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
@ -27,7 +28,7 @@ const CreateCollectionDrawer: React.FC<Props> = (props: Props) => {
|
|||||||
const { onClose, onConfirm, collectionId } = props;
|
const { onClose, onConfirm, collectionId } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const collectionStore = useCollectionStore();
|
const collectionStore = useCollectionStore();
|
||||||
const { shortcutList } = useAppSelector((state) => state.shortcut);
|
const shortcutList = useShortcutStore().getShortcutList();
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
collectionCreate: Collection.fromPartial({
|
collectionCreate: Collection.fromPartial({
|
||||||
visibility: Visibility.PRIVATE,
|
visibility: Visibility.PRIVATE,
|
||||||
@ -40,9 +41,9 @@ const CreateCollectionDrawer: React.FC<Props> = (props: Props) => {
|
|||||||
const unselectedShortcuts = shortcutList
|
const unselectedShortcuts = shortcutList
|
||||||
.filter((shortcut) => {
|
.filter((shortcut) => {
|
||||||
if (state.collectionCreate.visibility === Visibility.PUBLIC) {
|
if (state.collectionCreate.visibility === Visibility.PUBLIC) {
|
||||||
return shortcut.visibility === "PUBLIC";
|
return shortcut.visibility === Visibility.PUBLIC;
|
||||||
} else if (state.collectionCreate.visibility === Visibility.WORKSPACE) {
|
} else if (state.collectionCreate.visibility === Visibility.WORKSPACE) {
|
||||||
return shortcut.visibility === "PUBLIC" || shortcut.visibility === "WORKSPACE";
|
return shortcut.visibility === Visibility.PUBLIC || shortcut.visibility === Visibility.WORKSPACE;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -16,46 +16,42 @@ import { isUndefined, uniq } from "lodash-es";
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppSelector } from "@/stores";
|
import useShortcutStore from "@/stores/v1/shortcut";
|
||||||
|
import { Visibility } from "@/types/proto/api/v2/common";
|
||||||
|
import { Shortcut } from "@/types/proto/api/v2/shortcut_service";
|
||||||
|
import { convertVisibilityFromPb } from "@/utils/visibility";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import { shortcutService } from "../services";
|
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import ResourceNameInput from "./ResourceNameInput";
|
import ResourceNameInput from "./ResourceNameInput";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
shortcutId?: ShortcutId;
|
shortcutId?: number;
|
||||||
initialShortcut?: Partial<Shortcut>;
|
initialShortcut?: Partial<Shortcut>;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onConfirm?: () => void;
|
onConfirm?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
shortcutCreate: ShortcutCreate;
|
shortcutCreate: Shortcut;
|
||||||
}
|
}
|
||||||
|
|
||||||
const visibilities: Visibility[] = ["PRIVATE", "WORKSPACE", "PUBLIC"];
|
|
||||||
|
|
||||||
const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
||||||
const { onClose, onConfirm, shortcutId, initialShortcut } = props;
|
const { onClose, onConfirm, shortcutId, initialShortcut } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { shortcutList } = useAppSelector((state) => state.shortcut);
|
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
shortcutCreate: {
|
shortcutCreate: Shortcut.fromPartial({
|
||||||
name: "",
|
visibility: Visibility.PRIVATE,
|
||||||
link: "",
|
ogMetadata: {
|
||||||
title: "",
|
|
||||||
description: "",
|
|
||||||
visibility: "PRIVATE",
|
|
||||||
tags: [],
|
|
||||||
openGraphMetadata: {
|
|
||||||
title: "",
|
title: "",
|
||||||
description: "",
|
description: "",
|
||||||
image: "",
|
image: "",
|
||||||
},
|
},
|
||||||
...initialShortcut,
|
...initialShortcut,
|
||||||
},
|
}),
|
||||||
});
|
});
|
||||||
|
const shortcutStore = useShortcutStore();
|
||||||
const [showOpenGraphMetadata, setShowOpenGraphMetadata] = useState<boolean>(false);
|
const [showOpenGraphMetadata, setShowOpenGraphMetadata] = useState<boolean>(false);
|
||||||
|
const shortcutList = shortcutStore.getShortcutList();
|
||||||
const [tag, setTag] = useState<string>("");
|
const [tag, setTag] = useState<string>("");
|
||||||
const tagSuggestions = uniq(shortcutList.map((shortcut) => shortcut.tags).flat());
|
const tagSuggestions = uniq(shortcutList.map((shortcut) => shortcut.tags).flat());
|
||||||
const isCreating = isUndefined(shortcutId);
|
const isCreating = isUndefined(shortcutId);
|
||||||
@ -64,7 +60,7 @@ const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (shortcutId) {
|
if (shortcutId) {
|
||||||
const shortcut = shortcutService.getShortcutById(shortcutId);
|
const shortcut = shortcutStore.getShortcutById(shortcutId);
|
||||||
if (shortcut) {
|
if (shortcut) {
|
||||||
setState({
|
setState({
|
||||||
...state,
|
...state,
|
||||||
@ -74,7 +70,7 @@ const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
|||||||
title: shortcut.title,
|
title: shortcut.title,
|
||||||
description: shortcut.description,
|
description: shortcut.description,
|
||||||
visibility: shortcut.visibility,
|
visibility: shortcut.visibility,
|
||||||
openGraphMetadata: shortcut.openGraphMetadata,
|
ogMetadata: shortcut.ogMetadata,
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
setTag(shortcut.tags.join(" "));
|
setTag(shortcut.tags.join(" "));
|
||||||
@ -121,7 +117,7 @@ const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
|||||||
const handleVisibilityInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleVisibilityInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setPartialState({
|
setPartialState({
|
||||||
shortcutCreate: Object.assign(state.shortcutCreate, {
|
shortcutCreate: Object.assign(state.shortcutCreate, {
|
||||||
visibility: e.target.value,
|
visibility: Number(e.target.value),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@ -142,8 +138,8 @@ const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
|||||||
const handleOpenGraphMetadataImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleOpenGraphMetadataImageChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setPartialState({
|
setPartialState({
|
||||||
shortcutCreate: Object.assign(state.shortcutCreate, {
|
shortcutCreate: Object.assign(state.shortcutCreate, {
|
||||||
openGraphMetadata: {
|
ogMetadata: {
|
||||||
...state.shortcutCreate.openGraphMetadata,
|
...state.shortcutCreate.ogMetadata,
|
||||||
image: e.target.value,
|
image: e.target.value,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -153,8 +149,8 @@ const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
|||||||
const handleOpenGraphMetadataTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleOpenGraphMetadataTitleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setPartialState({
|
setPartialState({
|
||||||
shortcutCreate: Object.assign(state.shortcutCreate, {
|
shortcutCreate: Object.assign(state.shortcutCreate, {
|
||||||
openGraphMetadata: {
|
ogMetadata: {
|
||||||
...state.shortcutCreate.openGraphMetadata,
|
...state.shortcutCreate.ogMetadata,
|
||||||
title: e.target.value,
|
title: e.target.value,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -164,8 +160,8 @@ const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
|||||||
const handleOpenGraphMetadataDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
const handleOpenGraphMetadataDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||||
setPartialState({
|
setPartialState({
|
||||||
shortcutCreate: Object.assign(state.shortcutCreate, {
|
shortcutCreate: Object.assign(state.shortcutCreate, {
|
||||||
openGraphMetadata: {
|
ogMetadata: {
|
||||||
...state.shortcutCreate.openGraphMetadata,
|
...state.shortcutCreate.ogMetadata,
|
||||||
description: e.target.value,
|
description: e.target.value,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
@ -188,18 +184,13 @@ const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (shortcutId) {
|
if (shortcutId) {
|
||||||
await shortcutService.patchShortcut({
|
await shortcutStore.updateShortcut({
|
||||||
|
...state.shortcutCreate,
|
||||||
id: shortcutId,
|
id: shortcutId,
|
||||||
name: state.shortcutCreate.name,
|
|
||||||
link: state.shortcutCreate.link,
|
|
||||||
title: state.shortcutCreate.title,
|
|
||||||
description: state.shortcutCreate.description,
|
|
||||||
visibility: state.shortcutCreate.visibility,
|
|
||||||
tags: tag.split(" ").filter(Boolean),
|
tags: tag.split(" ").filter(Boolean),
|
||||||
openGraphMetadata: state.shortcutCreate.openGraphMetadata,
|
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await shortcutService.createShortcut({
|
await shortcutStore.createShortcut({
|
||||||
...state.shortcutCreate,
|
...state.shortcutCreate,
|
||||||
tags: tag.split(" ").filter(Boolean),
|
tags: tag.split(" ").filter(Boolean),
|
||||||
});
|
});
|
||||||
@ -281,13 +272,13 @@ const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
|||||||
<span className="mb-2">Visibility</span>
|
<span className="mb-2">Visibility</span>
|
||||||
<div className="w-full flex flex-row justify-start items-center text-base">
|
<div className="w-full flex flex-row justify-start items-center text-base">
|
||||||
<RadioGroup orientation="horizontal" value={state.shortcutCreate.visibility} onChange={handleVisibilityInputChange}>
|
<RadioGroup orientation="horizontal" value={state.shortcutCreate.visibility} onChange={handleVisibilityInputChange}>
|
||||||
{visibilities.map((visibility) => (
|
<Radio value={Visibility.PRIVATE} label={t(`shortcut.visibility.private.self`)} />
|
||||||
<Radio key={visibility} value={visibility} label={t(`shortcut.visibility.${visibility.toLowerCase()}.self`)} />
|
<Radio value={Visibility.WORKSPACE} label={t(`shortcut.visibility.workspace.self`)} />
|
||||||
))}
|
<Radio value={Visibility.PUBLIC} label={t(`shortcut.visibility.public.self`)} />
|
||||||
</RadioGroup>
|
</RadioGroup>
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-3 text-sm text-gray-500 w-full bg-gray-100 border border-gray-200 dark:bg-zinc-800 dark:border-zinc-700 dark:text-gray-400 px-2 py-1 rounded-md">
|
<p className="mt-3 text-sm text-gray-500 w-full bg-gray-100 border border-gray-200 dark:bg-zinc-800 dark:border-zinc-700 dark:text-gray-400 px-2 py-1 rounded-md">
|
||||||
{t(`shortcut.visibility.${state.shortcutCreate.visibility.toLowerCase()}.description`)}
|
{t(`shortcut.visibility.${convertVisibilityFromPb(state.shortcutCreate.visibility).toLowerCase()}.description`)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<Divider className="text-gray-500">More</Divider>
|
<Divider className="text-gray-500">More</Divider>
|
||||||
@ -316,7 +307,7 @@ const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="https://the.link.to/the/image.png"
|
placeholder="https://the.link.to/the/image.png"
|
||||||
size="sm"
|
size="sm"
|
||||||
value={state.shortcutCreate.openGraphMetadata.image}
|
value={state.shortcutCreate.ogMetadata?.image}
|
||||||
onChange={handleOpenGraphMetadataImageChange}
|
onChange={handleOpenGraphMetadataImageChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -327,7 +318,7 @@ const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
|||||||
type="text"
|
type="text"
|
||||||
placeholder="Slash - An open source, self-hosted bookmarks and link sharing platform"
|
placeholder="Slash - An open source, self-hosted bookmarks and link sharing platform"
|
||||||
size="sm"
|
size="sm"
|
||||||
value={state.shortcutCreate.openGraphMetadata.title}
|
value={state.shortcutCreate.ogMetadata?.title}
|
||||||
onChange={handleOpenGraphMetadataTitleChange}
|
onChange={handleOpenGraphMetadataTitleChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -338,7 +329,7 @@ const CreateShortcutDrawer: React.FC<Props> = (props: Props) => {
|
|||||||
placeholder="An open source, self-hosted bookmarks and link sharing platform."
|
placeholder="An open source, self-hosted bookmarks and link sharing platform."
|
||||||
size="sm"
|
size="sm"
|
||||||
maxRows={3}
|
maxRows={3}
|
||||||
value={state.shortcutCreate.openGraphMetadata.description}
|
value={state.shortcutCreate.ogMetadata?.description}
|
||||||
onChange={handleOpenGraphMetadataDescriptionChange}
|
onChange={handleOpenGraphMetadataDescriptionChange}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { convertVisibilityFromPb } from "@/utils/visibility";
|
||||||
import useViewStore from "../stores/v1/view";
|
import useViewStore from "../stores/v1/view";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import VisibilityIcon from "./VisibilityIcon";
|
import VisibilityIcon from "./VisibilityIcon";
|
||||||
@ -32,7 +33,7 @@ const FilterView = () => {
|
|||||||
onClick={() => viewStore.setFilter({ visibility: undefined })}
|
onClick={() => viewStore.setFilter({ visibility: undefined })}
|
||||||
>
|
>
|
||||||
<VisibilityIcon className="w-4 h-auto mr-1" visibility={filter.visibility} />
|
<VisibilityIcon className="w-4 h-auto mr-1" visibility={filter.visibility} />
|
||||||
{t(`shortcut.visibility.${filter.visibility.toLowerCase()}.self`)}
|
{t(`shortcut.visibility.${convertVisibilityFromPb(filter.visibility).toLowerCase()}.self`)}
|
||||||
<Icon.X className="w-4 h-auto ml-1" />
|
<Icon.X className="w-4 h-auto ml-1" />
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
@ -3,6 +3,7 @@ import { QRCodeCanvas } from "qrcode.react";
|
|||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { toast } from "react-hot-toast";
|
import { toast } from "react-hot-toast";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { Shortcut } from "@/types/proto/api/v2/shortcut_service";
|
||||||
import { absolutifyLink } from "../helpers/utils";
|
import { absolutifyLink } from "../helpers/utils";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
|
import useShortcutStore from "@/stores/v1/shortcut";
|
||||||
|
import { Shortcut } from "@/types/proto/api/v2/shortcut_service";
|
||||||
import { Role } from "@/types/proto/api/v2/user_service";
|
import { Role } from "@/types/proto/api/v2/user_service";
|
||||||
import { shortcutService } from "../services";
|
|
||||||
import useUserStore from "../stores/v1/user";
|
import useUserStore from "../stores/v1/user";
|
||||||
import { showCommonDialog } from "./Alert";
|
import { showCommonDialog } from "./Alert";
|
||||||
import CreateShortcutDrawer from "./CreateShortcutDrawer";
|
import CreateShortcutDrawer from "./CreateShortcutDrawer";
|
||||||
@ -18,6 +19,7 @@ const ShortcutActionsDropdown = (props: Props) => {
|
|||||||
const { shortcut } = props;
|
const { shortcut } = props;
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigateTo = useNavigateTo();
|
const navigateTo = useNavigateTo();
|
||||||
|
const shortcutStore = useShortcutStore();
|
||||||
const currentUser = useUserStore().getCurrentUser();
|
const currentUser = useUserStore().getCurrentUser();
|
||||||
const [showEditDrawer, setShowEditDrawer] = useState<boolean>(false);
|
const [showEditDrawer, setShowEditDrawer] = useState<boolean>(false);
|
||||||
const [showQRCodeDialog, setShowQRCodeDialog] = useState<boolean>(false);
|
const [showQRCodeDialog, setShowQRCodeDialog] = useState<boolean>(false);
|
||||||
@ -29,7 +31,7 @@ const ShortcutActionsDropdown = (props: Props) => {
|
|||||||
content: `Are you sure to delete shortcut \`${shortcut.name}\`? You cannot undo this action.`,
|
content: `Are you sure to delete shortcut \`${shortcut.name}\`? You cannot undo this action.`,
|
||||||
style: "danger",
|
style: "danger",
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
await shortcutService.deleteShortcutById(shortcut.id);
|
await shortcutStore.deleteShortcut(shortcut.id);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -4,6 +4,8 @@ import copy from "copy-to-clipboard";
|
|||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { Shortcut } from "@/types/proto/api/v2/shortcut_service";
|
||||||
|
import { convertVisibilityFromPb } from "@/utils/visibility";
|
||||||
import { absolutifyLink, getFaviconWithGoogleS2 } from "../helpers/utils";
|
import { absolutifyLink, getFaviconWithGoogleS2 } from "../helpers/utils";
|
||||||
import useViewStore from "../stores/v1/view";
|
import useViewStore from "../stores/v1/view";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
@ -102,13 +104,18 @@ const ShortcutCard = (props: Props) => {
|
|||||||
{shortcut.tags.length === 0 && <span className="text-gray-400 text-sm leading-4 italic">No tags</span>}
|
{shortcut.tags.length === 0 && <span className="text-gray-400 text-sm leading-4 italic">No tags</span>}
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex mt-2 gap-2 overflow-x-auto">
|
<div className="w-full flex mt-2 gap-2 overflow-x-auto">
|
||||||
<Tooltip title={t(`shortcut.visibility.${shortcut.visibility.toLowerCase()}.description`)} variant="solid" placement="top" arrow>
|
<Tooltip
|
||||||
|
title={t(`shortcut.visibility.${convertVisibilityFromPb(shortcut.visibility).toLowerCase()}.description`)}
|
||||||
|
variant="solid"
|
||||||
|
placement="top"
|
||||||
|
arrow
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
className="w-auto px-2 leading-6 flex flex-row justify-start items-center flex-nowrap whitespace-nowrap border rounded-full cursor-pointer text-gray-500 dark:text-gray-400 text-sm dark:border-zinc-700"
|
className="w-auto px-2 leading-6 flex flex-row justify-start items-center flex-nowrap whitespace-nowrap border rounded-full cursor-pointer text-gray-500 dark:text-gray-400 text-sm dark:border-zinc-700"
|
||||||
onClick={() => viewStore.setFilter({ visibility: shortcut.visibility })}
|
onClick={() => viewStore.setFilter({ visibility: shortcut.visibility })}
|
||||||
>
|
>
|
||||||
<VisibilityIcon className="w-4 h-auto mr-1 opacity-60" visibility={shortcut.visibility} />
|
<VisibilityIcon className="w-4 h-auto mr-1 opacity-60" visibility={shortcut.visibility} />
|
||||||
{t(`shortcut.visibility.${shortcut.visibility.toLowerCase()}.self`)}
|
{t(`shortcut.visibility.${convertVisibilityFromPb(shortcut.visibility).toLowerCase()}.self`)}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="View count" variant="solid" placement="top" arrow>
|
<Tooltip title="View count" variant="solid" placement="top" arrow>
|
||||||
@ -117,7 +124,7 @@ const ShortcutCard = (props: Props) => {
|
|||||||
className="w-auto px-2 leading-6 flex flex-row justify-start items-center flex-nowrap whitespace-nowrap border rounded-full cursor-pointer text-gray-500 dark:text-gray-400 text-sm dark:border-zinc-700"
|
className="w-auto px-2 leading-6 flex flex-row justify-start items-center flex-nowrap whitespace-nowrap border rounded-full cursor-pointer text-gray-500 dark:text-gray-400 text-sm dark:border-zinc-700"
|
||||||
>
|
>
|
||||||
<Icon.BarChart2 className="w-4 h-auto mr-1 opacity-80" />
|
<Icon.BarChart2 className="w-4 h-auto mr-1 opacity-80" />
|
||||||
{t("shortcut.visits", { count: shortcut.view })}
|
{t("shortcut.visits", { count: shortcut.viewCount })}
|
||||||
</Link>
|
</Link>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
|
import { Shortcut } from "@/types/proto/api/v2/shortcut_service";
|
||||||
import { getFaviconWithGoogleS2 } from "../helpers/utils";
|
import { getFaviconWithGoogleS2 } from "../helpers/utils";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
import ShortcutActionsDropdown from "./ShortcutActionsDropdown";
|
import ShortcutActionsDropdown from "./ShortcutActionsDropdown";
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
|
import { Shortcut } from "@/types/proto/api/v2/shortcut_service";
|
||||||
import useViewStore from "../stores/v1/view";
|
import useViewStore from "../stores/v1/view";
|
||||||
import ShortcutCard from "./ShortcutCard";
|
import ShortcutCard from "./ShortcutCard";
|
||||||
import ShortcutView from "./ShortcutView";
|
import ShortcutView from "./ShortcutView";
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppSelector } from "../stores";
|
import useShortcutStore from "@/stores/v1/shortcut";
|
||||||
import useViewStore from "../stores/v1/view";
|
import useViewStore from "../stores/v1/view";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
|
|
||||||
const ShortcutsNavigator = () => {
|
const ShortcutsNavigator = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const viewStore = useViewStore();
|
const viewStore = useViewStore();
|
||||||
const { shortcutList } = useAppSelector((state) => state.shortcut);
|
const shortcutList = useShortcutStore().getShortcutList();
|
||||||
const tags = shortcutList.map((shortcut) => shortcut.tags).flat();
|
const tags = shortcutList.map((shortcut) => shortcut.tags).flat();
|
||||||
const currentTab = viewStore.filter.tab || `tab:all`;
|
const currentTab = viewStore.filter.tab || `tab:all`;
|
||||||
const sortedTagMap = sortTags(tags);
|
const sortedTagMap = sortTags(tags);
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import { Visibility } from "@/types/proto/api/v2/common";
|
||||||
import Icon from "./Icon";
|
import Icon from "./Icon";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
@ -7,11 +8,11 @@ interface Props {
|
|||||||
|
|
||||||
const VisibilityIcon = (props: Props) => {
|
const VisibilityIcon = (props: Props) => {
|
||||||
const { visibility, className } = props;
|
const { visibility, className } = props;
|
||||||
if (visibility === "PRIVATE") {
|
if (visibility === Visibility.PRIVATE) {
|
||||||
return <Icon.Lock className={className || ""} />;
|
return <Icon.Lock className={className || ""} />;
|
||||||
} else if (visibility === "WORKSPACE") {
|
} else if (visibility === Visibility.WORKSPACE) {
|
||||||
return <Icon.Building2 className={className || ""} />;
|
return <Icon.Building2 className={className || ""} />;
|
||||||
} else if (visibility === "PUBLIC") {
|
} else if (visibility === Visibility.PUBLIC) {
|
||||||
return <Icon.Globe2 className={className || ""} />;
|
return <Icon.Globe2 className={className || ""} />;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -19,30 +19,6 @@ export function signout() {
|
|||||||
return axios.post("/api/v1/auth/logout");
|
return axios.post("/api/v1/auth/logout");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getShortcutList(shortcutFind?: ShortcutFind) {
|
export function getShortcutAnalytics(shortcutId: number) {
|
||||||
const queryList = [];
|
|
||||||
if (shortcutFind?.tag) {
|
|
||||||
queryList.push(`tag=${shortcutFind.tag}`);
|
|
||||||
}
|
|
||||||
return axios.get<Shortcut[]>(`/api/v1/shortcut?${queryList.join("&")}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getShortcutById(id: number) {
|
|
||||||
return axios.get<Shortcut>(`/api/v1/shortcut/${id}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function createShortcut(shortcutCreate: ShortcutCreate) {
|
|
||||||
return axios.post<Shortcut>("/api/v1/shortcut", shortcutCreate);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getShortcutAnalytics(shortcutId: ShortcutId) {
|
|
||||||
return axios.get<AnalysisData>(`/api/v1/shortcut/${shortcutId}/analytics`);
|
return axios.get<AnalysisData>(`/api/v1/shortcut/${shortcutId}/analytics`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function patchShortcut(shortcutPatch: ShortcutPatch) {
|
|
||||||
return axios.patch<Shortcut>(`/api/v1/shortcut/${shortcutPatch.id}`, shortcutPatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteShortcutById(shortcutId: ShortcutId) {
|
|
||||||
return axios.delete(`/api/v1/shortcut/${shortcutId}`);
|
|
||||||
}
|
|
||||||
|
@ -1,22 +1,18 @@
|
|||||||
import { CssVarsProvider } from "@mui/joy";
|
import { CssVarsProvider } from "@mui/joy";
|
||||||
import { createRoot } from "react-dom/client";
|
import { createRoot } from "react-dom/client";
|
||||||
import { Toaster } from "react-hot-toast";
|
import { Toaster } from "react-hot-toast";
|
||||||
import { Provider } from "react-redux";
|
|
||||||
import { RouterProvider } from "react-router-dom";
|
import { RouterProvider } from "react-router-dom";
|
||||||
import "./css/index.css";
|
import "./css/index.css";
|
||||||
import "./css/joy-ui.css";
|
import "./css/joy-ui.css";
|
||||||
import "./i18n";
|
import "./i18n";
|
||||||
import router from "./routers";
|
import router from "./routers";
|
||||||
import store from "./stores";
|
|
||||||
|
|
||||||
const container = document.getElementById("root");
|
const container = document.getElementById("root");
|
||||||
const root = createRoot(container as HTMLElement);
|
const root = createRoot(container as HTMLElement);
|
||||||
|
|
||||||
root.render(
|
root.render(
|
||||||
<Provider store={store}>
|
<CssVarsProvider>
|
||||||
<CssVarsProvider>
|
<RouterProvider router={router} />
|
||||||
<RouterProvider router={router} />
|
<Toaster position="top-center" />
|
||||||
<Toaster position="top-center" />
|
</CssVarsProvider>
|
||||||
</CssVarsProvider>
|
|
||||||
</Provider>
|
|
||||||
);
|
);
|
||||||
|
@ -3,8 +3,8 @@ import { useEffect, useState } from "react";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import CollectionView from "@/components/CollectionView";
|
import CollectionView from "@/components/CollectionView";
|
||||||
import CreateCollectionDrawer from "@/components/CreateCollectionDrawer";
|
import CreateCollectionDrawer from "@/components/CreateCollectionDrawer";
|
||||||
import { shortcutService } from "@/services";
|
|
||||||
import useCollectionStore from "@/stores/v1/collection";
|
import useCollectionStore from "@/stores/v1/collection";
|
||||||
|
import useShortcutStore from "@/stores/v1/shortcut";
|
||||||
import FilterView from "../components/FilterView";
|
import FilterView from "../components/FilterView";
|
||||||
import Icon from "../components/Icon";
|
import Icon from "../components/Icon";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
@ -16,6 +16,7 @@ interface State {
|
|||||||
const CollectionDashboard: React.FC = () => {
|
const CollectionDashboard: React.FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const loadingState = useLoading();
|
const loadingState = useLoading();
|
||||||
|
const shortcutStore = useShortcutStore();
|
||||||
const collectionStore = useCollectionStore();
|
const collectionStore = useCollectionStore();
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
showCreateCollectionDrawer: false,
|
showCreateCollectionDrawer: false,
|
||||||
@ -30,7 +31,7 @@ const CollectionDashboard: React.FC = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Promise.all([shortcutService.getMyAllShortcuts(), collectionStore.fetchCollectionList()]).finally(() => {
|
Promise.all([shortcutStore.fetchShortcutList(), collectionStore.fetchCollectionList()]).finally(() => {
|
||||||
loadingState.setFinish();
|
loadingState.setFinish();
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -12,7 +12,6 @@ import useShortcutStore from "@/stores/v1/shortcut";
|
|||||||
import useUserStore from "@/stores/v1/user";
|
import useUserStore from "@/stores/v1/user";
|
||||||
import { Collection } from "@/types/proto/api/v2/collection_service";
|
import { Collection } from "@/types/proto/api/v2/collection_service";
|
||||||
import { Shortcut } from "@/types/proto/api/v2/shortcut_service";
|
import { Shortcut } from "@/types/proto/api/v2/shortcut_service";
|
||||||
import { convertShortcutFromPb } from "@/utils/shortcut";
|
|
||||||
|
|
||||||
const CollectionSpace = () => {
|
const CollectionSpace = () => {
|
||||||
const { collectionName } = useParams();
|
const { collectionName } = useParams();
|
||||||
@ -90,7 +89,7 @@ const CollectionSpace = () => {
|
|||||||
: "sm:border-transparent dark:sm:border-transparent"
|
: "sm:border-transparent dark:sm:border-transparent"
|
||||||
)}
|
)}
|
||||||
key={shortcut.name}
|
key={shortcut.name}
|
||||||
shortcut={convertShortcutFromPb(shortcut)}
|
shortcut={shortcut}
|
||||||
alwaysShowLink={!sm}
|
alwaysShowLink={!sm}
|
||||||
onClick={() => handleShortcutClick(shortcut)}
|
onClick={() => handleShortcutClick(shortcut)}
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Button, Input } from "@mui/joy";
|
import { Button, Input } from "@mui/joy";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
|
import useShortcutStore from "@/stores/v1/shortcut";
|
||||||
import CreateShortcutDrawer from "../components/CreateShortcutDrawer";
|
import CreateShortcutDrawer from "../components/CreateShortcutDrawer";
|
||||||
import FilterView from "../components/FilterView";
|
import FilterView from "../components/FilterView";
|
||||||
import Icon from "../components/Icon";
|
import Icon from "../components/Icon";
|
||||||
@ -8,8 +9,6 @@ import ShortcutsContainer from "../components/ShortcutsContainer";
|
|||||||
import ShortcutsNavigator from "../components/ShortcutsNavigator";
|
import ShortcutsNavigator from "../components/ShortcutsNavigator";
|
||||||
import ViewSetting from "../components/ViewSetting";
|
import ViewSetting from "../components/ViewSetting";
|
||||||
import useLoading from "../hooks/useLoading";
|
import useLoading from "../hooks/useLoading";
|
||||||
import { shortcutService } from "../services";
|
|
||||||
import { useAppSelector } from "../stores";
|
|
||||||
import useUserStore from "../stores/v1/user";
|
import useUserStore from "../stores/v1/user";
|
||||||
import useViewStore, { getFilteredShortcutList, getOrderedShortcutList } from "../stores/v1/view";
|
import useViewStore, { getFilteredShortcutList, getOrderedShortcutList } from "../stores/v1/view";
|
||||||
|
|
||||||
@ -21,8 +20,9 @@ const Home: React.FC = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const loadingState = useLoading();
|
const loadingState = useLoading();
|
||||||
const currentUser = useUserStore().getCurrentUser();
|
const currentUser = useUserStore().getCurrentUser();
|
||||||
|
const shortcutStore = useShortcutStore();
|
||||||
const viewStore = useViewStore();
|
const viewStore = useViewStore();
|
||||||
const { shortcutList } = useAppSelector((state) => state.shortcut);
|
const shortcutList = shortcutStore.getShortcutList();
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
showCreateShortcutDrawer: false,
|
showCreateShortcutDrawer: false,
|
||||||
});
|
});
|
||||||
@ -31,7 +31,7 @@ const Home: React.FC = () => {
|
|||||||
const orderedShortcutList = getOrderedShortcutList(filteredShortcutList, viewStore.order);
|
const orderedShortcutList = getOrderedShortcutList(filteredShortcutList, viewStore.order);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
Promise.all([shortcutService.getMyAllShortcuts()]).finally(() => {
|
Promise.all([shortcutStore.fetchShortcutList()]).finally(() => {
|
||||||
loadingState.setFinish();
|
loadingState.setFinish();
|
||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import { Tooltip } from "@mui/joy";
|
import { Tooltip } from "@mui/joy";
|
||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import toast from "react-hot-toast";
|
import toast from "react-hot-toast";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useLoaderData } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
|
import useLoading from "@/hooks/useLoading";
|
||||||
import useNavigateTo from "@/hooks/useNavigateTo";
|
import useNavigateTo from "@/hooks/useNavigateTo";
|
||||||
|
import useShortcutStore from "@/stores/v1/shortcut";
|
||||||
|
import { Shortcut } from "@/types/proto/api/v2/shortcut_service";
|
||||||
import { Role } from "@/types/proto/api/v2/user_service";
|
import { Role } from "@/types/proto/api/v2/user_service";
|
||||||
|
import { convertVisibilityFromPb } from "@/utils/visibility";
|
||||||
import { showCommonDialog } from "../components/Alert";
|
import { showCommonDialog } from "../components/Alert";
|
||||||
import AnalyticsView from "../components/AnalyticsView";
|
import AnalyticsView from "../components/AnalyticsView";
|
||||||
import CreateShortcutDrawer from "../components/CreateShortcutDrawer";
|
import CreateShortcutDrawer from "../components/CreateShortcutDrawer";
|
||||||
@ -15,7 +19,6 @@ import Icon from "../components/Icon";
|
|||||||
import VisibilityIcon from "../components/VisibilityIcon";
|
import VisibilityIcon from "../components/VisibilityIcon";
|
||||||
import Dropdown from "../components/common/Dropdown";
|
import Dropdown from "../components/common/Dropdown";
|
||||||
import { absolutifyLink, getFaviconWithGoogleS2 } from "../helpers/utils";
|
import { absolutifyLink, getFaviconWithGoogleS2 } from "../helpers/utils";
|
||||||
import { shortcutService } from "../services";
|
|
||||||
import useUserStore from "../stores/v1/user";
|
import useUserStore from "../stores/v1/user";
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
@ -24,18 +27,35 @@ interface State {
|
|||||||
|
|
||||||
const ShortcutDetail = () => {
|
const ShortcutDetail = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const params = useParams();
|
||||||
const navigateTo = useNavigateTo();
|
const navigateTo = useNavigateTo();
|
||||||
const shortcutId = (useLoaderData() as Shortcut).id;
|
const shortcutId = Number(params.shortcutId);
|
||||||
const shortcut = shortcutService.getShortcutById(shortcutId) as Shortcut;
|
const shortcutStore = useShortcutStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const shortcut = shortcutStore.getShortcutById(shortcutId);
|
||||||
const currentUser = useUserStore().getCurrentUser();
|
const currentUser = useUserStore().getCurrentUser();
|
||||||
const [state, setState] = useState<State>({
|
const [state, setState] = useState<State>({
|
||||||
showEditDrawer: false,
|
showEditDrawer: false,
|
||||||
});
|
});
|
||||||
const [showQRCodeDialog, setShowQRCodeDialog] = useState<boolean>(false);
|
const [showQRCodeDialog, setShowQRCodeDialog] = useState<boolean>(false);
|
||||||
|
const loadingState = useLoading(true);
|
||||||
|
const creator = userStore.getUserById(shortcut.creatorId);
|
||||||
const havePermission = currentUser.role === Role.ADMIN || shortcut.creatorId === currentUser.id;
|
const havePermission = currentUser.role === Role.ADMIN || shortcut.creatorId === currentUser.id;
|
||||||
const shortcutLink = absolutifyLink(`/s/${shortcut.name}`);
|
const shortcutLink = absolutifyLink(`/s/${shortcut.name}`);
|
||||||
const favicon = getFaviconWithGoogleS2(shortcut.link);
|
const favicon = getFaviconWithGoogleS2(shortcut.link);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
(async () => {
|
||||||
|
const shortcut = await shortcutStore.getOrFetchShortcutById(shortcutId);
|
||||||
|
await userStore.getOrFetchUserById(shortcut.creatorId);
|
||||||
|
loadingState.setFinish();
|
||||||
|
})();
|
||||||
|
}, [shortcutId]);
|
||||||
|
|
||||||
|
if (loadingState.isLoading) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const handleCopyButtonClick = () => {
|
const handleCopyButtonClick = () => {
|
||||||
copy(shortcutLink);
|
copy(shortcutLink);
|
||||||
toast.success("Shortcut link copied to clipboard.");
|
toast.success("Shortcut link copied to clipboard.");
|
||||||
@ -47,7 +67,7 @@ const ShortcutDetail = () => {
|
|||||||
content: `Are you sure to delete shortcut \`${shortcut.name}\`? You cannot undo this action.`,
|
content: `Are you sure to delete shortcut \`${shortcut.name}\`? You cannot undo this action.`,
|
||||||
style: "danger",
|
style: "danger",
|
||||||
onConfirm: async () => {
|
onConfirm: async () => {
|
||||||
await shortcutService.deleteShortcutById(shortcut.id);
|
await shortcutStore.deleteShortcut(shortcut.id);
|
||||||
navigateTo("/", {
|
navigateTo("/", {
|
||||||
replace: true,
|
replace: true,
|
||||||
});
|
});
|
||||||
@ -151,19 +171,24 @@ const ShortcutDetail = () => {
|
|||||||
<Tooltip title="Creator" variant="solid" placement="top" arrow>
|
<Tooltip title="Creator" variant="solid" placement="top" arrow>
|
||||||
<div className="w-auto px-2 leading-6 flex flex-row justify-start items-center border rounded-full text-gray-500 text-sm dark:border-zinc-800">
|
<div className="w-auto px-2 leading-6 flex flex-row justify-start items-center border rounded-full text-gray-500 text-sm dark:border-zinc-800">
|
||||||
<Icon.User className="w-4 h-auto mr-1" />
|
<Icon.User className="w-4 h-auto mr-1" />
|
||||||
<span className="max-w-[4rem] sm:max-w-[6rem] truncate">{shortcut.creator.nickname}</span>
|
<span className="max-w-[4rem] sm:max-w-[6rem] truncate">{creator.nickname}</span>
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title={t(`shortcut.visibility.${shortcut.visibility.toLowerCase()}.description`)} variant="solid" placement="top" arrow>
|
<Tooltip
|
||||||
|
title={t(`shortcut.visibility.${convertVisibilityFromPb(shortcut.visibility).toLowerCase()}.description`)}
|
||||||
|
variant="solid"
|
||||||
|
placement="top"
|
||||||
|
arrow
|
||||||
|
>
|
||||||
<div className="w-auto px-2 leading-6 flex flex-row justify-start items-center border rounded-full text-gray-500 text-sm dark:border-zinc-800">
|
<div className="w-auto px-2 leading-6 flex flex-row justify-start items-center border rounded-full text-gray-500 text-sm dark:border-zinc-800">
|
||||||
<VisibilityIcon className="w-4 h-auto mr-1" visibility={shortcut.visibility} />
|
<VisibilityIcon className="w-4 h-auto mr-1" visibility={shortcut.visibility} />
|
||||||
{t(`shortcut.visibility.${shortcut.visibility.toLowerCase()}.self`)}
|
{t(`shortcut.visibility.${convertVisibilityFromPb(shortcut.visibility).toLowerCase()}.self`)}
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title="View count" variant="solid" placement="top" arrow>
|
<Tooltip title="View count" variant="solid" placement="top" arrow>
|
||||||
<div className="w-auto px-2 leading-6 flex flex-row justify-start items-center border rounded-full text-gray-500 text-sm dark:border-zinc-800">
|
<div className="w-auto px-2 leading-6 flex flex-row justify-start items-center border rounded-full text-gray-500 text-sm dark:border-zinc-800">
|
||||||
<Icon.BarChart2 className="w-4 h-auto mr-1" />
|
<Icon.BarChart2 className="w-4 h-auto mr-1" />
|
||||||
{shortcut.view} visits
|
{shortcut.viewCount} visits
|
||||||
</div>
|
</div>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,7 +11,6 @@ import App from "../App";
|
|||||||
import Root from "../layouts/Root";
|
import Root from "../layouts/Root";
|
||||||
import Home from "../pages/Home";
|
import Home from "../pages/Home";
|
||||||
import ShortcutDetail from "../pages/ShortcutDetail";
|
import ShortcutDetail from "../pages/ShortcutDetail";
|
||||||
import { shortcutService } from "../services";
|
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
@ -41,10 +40,6 @@ const router = createBrowserRouter([
|
|||||||
{
|
{
|
||||||
path: "/shortcut/:shortcutId",
|
path: "/shortcut/:shortcutId",
|
||||||
element: <ShortcutDetail />,
|
element: <ShortcutDetail />,
|
||||||
loader: async ({ params }) => {
|
|
||||||
const shortcut = await shortcutService.getOrFetchShortcutById(Number(params.shortcutId));
|
|
||||||
return shortcut;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/setting/general",
|
path: "/setting/general",
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
import shortcutService from "./shortcutService";
|
|
||||||
|
|
||||||
export { shortcutService };
|
|
@ -1,64 +0,0 @@
|
|||||||
import * as api from "../helpers/api";
|
|
||||||
import store from "../stores";
|
|
||||||
import { createShortcut, deleteShortcut, patchShortcut, setShortcuts } from "../stores/modules/shortcut";
|
|
||||||
|
|
||||||
const convertResponseModelShortcut = (shortcut: Shortcut): Shortcut => {
|
|
||||||
return {
|
|
||||||
...shortcut,
|
|
||||||
createdTs: shortcut.createdTs * 1000,
|
|
||||||
updatedTs: shortcut.updatedTs * 1000,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const shortcutService = {
|
|
||||||
getState: () => {
|
|
||||||
return store.getState().shortcut;
|
|
||||||
},
|
|
||||||
|
|
||||||
getMyAllShortcuts: async () => {
|
|
||||||
const data = (await api.getShortcutList()).data;
|
|
||||||
const shortcuts = data.map((s) => convertResponseModelShortcut(s));
|
|
||||||
store.dispatch(setShortcuts(shortcuts));
|
|
||||||
},
|
|
||||||
|
|
||||||
getShortcutById: (id: ShortcutId) => {
|
|
||||||
for (const shortcut of shortcutService.getState().shortcutList) {
|
|
||||||
if (shortcut.id === id) {
|
|
||||||
return shortcut;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
|
|
||||||
getOrFetchShortcutById: async (id: ShortcutId) => {
|
|
||||||
for (const shortcut of shortcutService.getState().shortcutList) {
|
|
||||||
if (shortcut.id === id) {
|
|
||||||
return shortcut;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = (await api.getShortcutById(id)).data;
|
|
||||||
const shortcut = convertResponseModelShortcut(data);
|
|
||||||
store.dispatch(createShortcut(shortcut));
|
|
||||||
return shortcut;
|
|
||||||
},
|
|
||||||
|
|
||||||
createShortcut: async (shortcutCreate: ShortcutCreate) => {
|
|
||||||
const data = (await api.createShortcut(shortcutCreate)).data;
|
|
||||||
const shortcut = convertResponseModelShortcut(data);
|
|
||||||
store.dispatch(createShortcut(shortcut));
|
|
||||||
},
|
|
||||||
|
|
||||||
patchShortcut: async (shortcutPatch: ShortcutPatch) => {
|
|
||||||
const data = (await api.patchShortcut(shortcutPatch)).data;
|
|
||||||
const shortcut = convertResponseModelShortcut(data);
|
|
||||||
store.dispatch(patchShortcut(shortcut));
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteShortcutById: async (shortcutId: ShortcutId) => {
|
|
||||||
await api.deleteShortcutById(shortcutId);
|
|
||||||
store.dispatch(deleteShortcut(shortcutId));
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default shortcutService;
|
|
@ -1,15 +0,0 @@
|
|||||||
import { configureStore } from "@reduxjs/toolkit";
|
|
||||||
import { TypedUseSelectorHook, useSelector } from "react-redux";
|
|
||||||
import shortcutReducer from "./modules/shortcut";
|
|
||||||
|
|
||||||
const store = configureStore({
|
|
||||||
reducer: {
|
|
||||||
shortcut: shortcutReducer,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
type AppState = ReturnType<typeof store.getState>;
|
|
||||||
|
|
||||||
export const useAppSelector: TypedUseSelectorHook<AppState> = useSelector;
|
|
||||||
|
|
||||||
export default store;
|
|
@ -1,51 +0,0 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
|
||||||
|
|
||||||
interface State {
|
|
||||||
shortcutList: Shortcut[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const shortcutSlice = createSlice({
|
|
||||||
name: "shortcut",
|
|
||||||
initialState: {
|
|
||||||
shortcutList: [],
|
|
||||||
} as State,
|
|
||||||
reducers: {
|
|
||||||
setShortcuts: (state, action: PayloadAction<Shortcut[]>) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
shortcutList: action.payload,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
createShortcut: (state, action: PayloadAction<Shortcut>) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
shortcutList: state.shortcutList.concat(action.payload).sort((a, b) => b.createdTs - a.createdTs),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
patchShortcut: (state, action: PayloadAction<Partial<Shortcut>>) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
shortcutList: state.shortcutList.map((s) => {
|
|
||||||
if (s.id === action.payload.id) {
|
|
||||||
return {
|
|
||||||
...s,
|
|
||||||
...action.payload,
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
deleteShortcut: (state, action: PayloadAction<ShortcutId>) => {
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
shortcutList: [...state.shortcutList].filter((shortcut) => shortcut.id !== action.payload),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const { setShortcuts, createShortcut, patchShortcut, deleteShortcut } = shortcutSlice.actions;
|
|
||||||
|
|
||||||
export default shortcutSlice.reducer;
|
|
@ -84,8 +84,6 @@ const useCollectionStore = create<CollectionState>()((set, get) => ({
|
|||||||
throw new Error("Collection not found");
|
throw new Error("Collection not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("updatedCollection", updatedCollection);
|
|
||||||
|
|
||||||
const collectionMap = get().collectionMapById;
|
const collectionMap = get().collectionMapById;
|
||||||
collectionMap[updatedCollection.id] = updatedCollection;
|
collectionMap[updatedCollection.id] = updatedCollection;
|
||||||
set(collectionMap);
|
set(collectionMap);
|
||||||
|
@ -3,11 +3,14 @@ import { shortcutServiceClient } from "@/grpcweb";
|
|||||||
import { Shortcut } from "@/types/proto/api/v2/shortcut_service";
|
import { Shortcut } from "@/types/proto/api/v2/shortcut_service";
|
||||||
|
|
||||||
interface ShortcutState {
|
interface ShortcutState {
|
||||||
shortcutMapById: Record<ShortcutId, Shortcut>;
|
shortcutMapById: Record<number, Shortcut>;
|
||||||
fetchShortcutList: () => Promise<Shortcut[]>;
|
fetchShortcutList: () => Promise<Shortcut[]>;
|
||||||
getOrFetchShortcutById: (id: ShortcutId) => Promise<Shortcut>;
|
getOrFetchShortcutById: (id: number) => Promise<Shortcut>;
|
||||||
getShortcutById: (id: ShortcutId) => Shortcut;
|
getShortcutById: (id: number) => Shortcut;
|
||||||
getShortcutList: () => Shortcut[];
|
getShortcutList: () => Shortcut[];
|
||||||
|
createShortcut: (shortcut: Shortcut) => Promise<Shortcut>;
|
||||||
|
updateShortcut: (shortcut: Partial<Shortcut>) => Promise<Shortcut>;
|
||||||
|
deleteShortcut: (id: number) => Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const useShortcutStore = create<ShortcutState>()((set, get) => ({
|
const useShortcutStore = create<ShortcutState>()((set, get) => ({
|
||||||
@ -21,7 +24,7 @@ const useShortcutStore = create<ShortcutState>()((set, get) => ({
|
|||||||
set(shortcutMap);
|
set(shortcutMap);
|
||||||
return shortcuts;
|
return shortcuts;
|
||||||
},
|
},
|
||||||
getOrFetchShortcutById: async (id: ShortcutId) => {
|
getOrFetchShortcutById: async (id: number) => {
|
||||||
const shortcutMap = get().shortcutMapById;
|
const shortcutMap = get().shortcutMapById;
|
||||||
if (shortcutMap[id]) {
|
if (shortcutMap[id]) {
|
||||||
return shortcutMap[id] as Shortcut;
|
return shortcutMap[id] as Shortcut;
|
||||||
@ -38,13 +41,50 @@ const useShortcutStore = create<ShortcutState>()((set, get) => ({
|
|||||||
set(shortcutMap);
|
set(shortcutMap);
|
||||||
return shortcut;
|
return shortcut;
|
||||||
},
|
},
|
||||||
getShortcutById: (id: ShortcutId) => {
|
getShortcutById: (id: number) => {
|
||||||
const shortcutMap = get().shortcutMapById;
|
const shortcutMap = get().shortcutMapById;
|
||||||
return shortcutMap[id] as Shortcut;
|
return shortcutMap[id] || unknownShortcut;
|
||||||
},
|
},
|
||||||
getShortcutList: () => {
|
getShortcutList: () => {
|
||||||
return Object.values(get().shortcutMapById);
|
return Object.values(get().shortcutMapById);
|
||||||
},
|
},
|
||||||
|
createShortcut: async (shortcut: Shortcut) => {
|
||||||
|
const { shortcut: createdShortcut } = await shortcutServiceClient.createShortcut({
|
||||||
|
shortcut: shortcut,
|
||||||
|
});
|
||||||
|
if (!createdShortcut) {
|
||||||
|
throw new Error(`Failed to create shortcut`);
|
||||||
|
}
|
||||||
|
const shortcutMap = get().shortcutMapById;
|
||||||
|
shortcutMap[createdShortcut.id] = createdShortcut;
|
||||||
|
set(shortcutMap);
|
||||||
|
return createdShortcut;
|
||||||
|
},
|
||||||
|
updateShortcut: async (shortcut: Partial<Shortcut>) => {
|
||||||
|
const { shortcut: updatedShortcut } = await shortcutServiceClient.updateShortcut({
|
||||||
|
shortcut: shortcut,
|
||||||
|
});
|
||||||
|
if (!updatedShortcut) {
|
||||||
|
throw new Error(`Failed to update shortcut`);
|
||||||
|
}
|
||||||
|
const shortcutMap = get().shortcutMapById;
|
||||||
|
shortcutMap[updatedShortcut.id] = updatedShortcut;
|
||||||
|
set(shortcutMap);
|
||||||
|
return updatedShortcut;
|
||||||
|
},
|
||||||
|
deleteShortcut: async (id: number) => {
|
||||||
|
await shortcutServiceClient.deleteShortcut({
|
||||||
|
id: id,
|
||||||
|
});
|
||||||
|
const shortcutMap = get().shortcutMapById;
|
||||||
|
delete shortcutMap[id];
|
||||||
|
set(shortcutMap);
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const unknownShortcut: Shortcut = Shortcut.fromPartial({
|
||||||
|
id: -1,
|
||||||
|
name: "Unknown",
|
||||||
|
});
|
||||||
|
|
||||||
export default useShortcutStore;
|
export default useShortcutStore;
|
||||||
|
@ -103,7 +103,7 @@ const useUserStore = create<UserState>()((set, get) => ({
|
|||||||
},
|
},
|
||||||
getUserById: (id: number) => {
|
getUserById: (id: number) => {
|
||||||
const userMap = get().userMapById;
|
const userMap = get().userMapById;
|
||||||
return userMap[id] as User;
|
return userMap[id] || unknownUser;
|
||||||
},
|
},
|
||||||
getCurrentUser: () => {
|
getCurrentUser: () => {
|
||||||
const userMap = get().userMapById;
|
const userMap = get().userMapById;
|
||||||
@ -148,4 +148,10 @@ const useUserStore = create<UserState>()((set, get) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const unknownUser: User = User.fromPartial({
|
||||||
|
id: -1,
|
||||||
|
email: "Unknown",
|
||||||
|
nickname: "Unknown",
|
||||||
|
});
|
||||||
|
|
||||||
export default useUserStore;
|
export default useUserStore;
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
|
import { Visibility } from "@/types/proto/api/v2/common";
|
||||||
|
import { Shortcut } from "@/types/proto/api/v2/shortcut_service";
|
||||||
import { User } from "@/types/proto/api/v2/user_service";
|
import { User } from "@/types/proto/api/v2/user_service";
|
||||||
|
|
||||||
export interface Filter {
|
export interface Filter {
|
||||||
@ -102,11 +104,15 @@ export const getOrderedShortcutList = (shortcutList: Shortcut[], order: Order) =
|
|||||||
if (field === "name") {
|
if (field === "name") {
|
||||||
return direction === "asc" ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
|
return direction === "asc" ? a.name.localeCompare(b.name) : b.name.localeCompare(a.name);
|
||||||
} else if (field === "createdTs") {
|
} else if (field === "createdTs") {
|
||||||
return direction === "asc" ? a.createdTs - b.createdTs : b.createdTs - a.createdTs;
|
return direction === "asc"
|
||||||
|
? getDateTimestamp(a.createdTime) - getDateTimestamp(b.createdTime)
|
||||||
|
: getDateTimestamp(b.createdTime) - getDateTimestamp(a.createdTime);
|
||||||
} else if (field === "updatedTs") {
|
} else if (field === "updatedTs") {
|
||||||
return direction === "asc" ? a.updatedTs - b.updatedTs : b.updatedTs - a.updatedTs;
|
return direction === "asc"
|
||||||
|
? getDateTimestamp(a.updatedTime) - getDateTimestamp(b.updatedTime)
|
||||||
|
: getDateTimestamp(b.updatedTime) - getDateTimestamp(a.updatedTime);
|
||||||
} else if (field === "view") {
|
} else if (field === "view") {
|
||||||
return direction === "asc" ? a.view - b.view : b.view - a.view;
|
return direction === "asc" ? a.viewCount - b.viewCount : b.viewCount - a.viewCount;
|
||||||
} else {
|
} else {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -114,4 +120,8 @@ export const getOrderedShortcutList = (shortcutList: Shortcut[], order: Order) =
|
|||||||
return orderedShortcutList;
|
return orderedShortcutList;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getDateTimestamp = (date: Date = new Date()) => {
|
||||||
|
return new Date(date).getTime();
|
||||||
|
};
|
||||||
|
|
||||||
export default useViewStore;
|
export default useViewStore;
|
||||||
|
1
frontend/web/src/types/modules/common.d.ts
vendored
1
frontend/web/src/types/modules/common.d.ts
vendored
@ -1 +0,0 @@
|
|||||||
type RowStatus = "NORMAL" | "ARCHIVED";
|
|
54
frontend/web/src/types/modules/shortcut.d.ts
vendored
54
frontend/web/src/types/modules/shortcut.d.ts
vendored
@ -1,54 +0,0 @@
|
|||||||
type ShortcutId = number;
|
|
||||||
|
|
||||||
type Visibility = "PRIVATE" | "WORKSPACE" | "PUBLIC";
|
|
||||||
|
|
||||||
interface OpenGraphMetadata {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
image: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Shortcut {
|
|
||||||
id: ShortcutId;
|
|
||||||
|
|
||||||
creatorId: number;
|
|
||||||
creator: User;
|
|
||||||
createdTs: TimeStamp;
|
|
||||||
updatedTs: TimeStamp;
|
|
||||||
rowStatus: RowStatus;
|
|
||||||
|
|
||||||
name: string;
|
|
||||||
link: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
visibility: Visibility;
|
|
||||||
tags: string[];
|
|
||||||
openGraphMetadata: OpenGraphMetadata;
|
|
||||||
view: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ShortcutCreate {
|
|
||||||
name: string;
|
|
||||||
link: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
visibility: Visibility;
|
|
||||||
tags: string[];
|
|
||||||
openGraphMetadata: OpenGraphMetadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ShortcutPatch {
|
|
||||||
id: ShortcutId;
|
|
||||||
|
|
||||||
name?: string;
|
|
||||||
link?: string;
|
|
||||||
title?: string;
|
|
||||||
description?: string;
|
|
||||||
visibility?: Visibility;
|
|
||||||
tags?: string[];
|
|
||||||
openGraphMetadata?: OpenGraphMetadata;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ShortcutFind {
|
|
||||||
tag?: string;
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
import { Shortcut as ShortcutPb } from "@/types/proto/api/v2/shortcut_service";
|
|
||||||
|
|
||||||
export const convertShortcutFromPb = (shortcutMessage: ShortcutPb): Shortcut => {
|
|
||||||
return {
|
|
||||||
id: shortcutMessage.id,
|
|
||||||
creatorId: shortcutMessage.creatorId,
|
|
||||||
createdTs: shortcutMessage.createdTime?.getTime() || 0,
|
|
||||||
updatedTs: shortcutMessage.updatedTime?.getTime() || 0,
|
|
||||||
name: shortcutMessage.name,
|
|
||||||
link: shortcutMessage.link,
|
|
||||||
title: shortcutMessage.title,
|
|
||||||
description: shortcutMessage.description,
|
|
||||||
tags: shortcutMessage.tags,
|
|
||||||
} as Shortcut;
|
|
||||||
};
|
|
Loading…
x
Reference in New Issue
Block a user