mirror of
https://github.com/usememos/memos.git
synced 2025-02-06 09:45:29 +00:00
chore: update resource dialog style (#982)
This commit is contained in:
parent
0aaf153717
commit
c5368fe8d3
@ -1,2 +1 @@
|
||||
web/node_modules
|
||||
web/yarn.lock
|
||||
|
@ -9,10 +9,11 @@ type Resource struct {
|
||||
UpdatedTs int64 `json:"updatedTs"`
|
||||
|
||||
// Domain specific fields
|
||||
Filename string `json:"filename"`
|
||||
Blob []byte `json:"-"`
|
||||
Type string `json:"type"`
|
||||
Size int64 `json:"size"`
|
||||
Filename string `json:"filename"`
|
||||
Blob []byte `json:"-"`
|
||||
ExternalLink string `json:"externalLink"`
|
||||
Type string `json:"type"`
|
||||
Size int64 `json:"size"`
|
||||
|
||||
// Related fields
|
||||
LinkedMemoAmount int `json:"linkedMemoAmount"`
|
||||
@ -23,10 +24,11 @@ type ResourceCreate struct {
|
||||
CreatorID int `json:"-"`
|
||||
|
||||
// Domain specific fields
|
||||
Filename string `json:"filename"`
|
||||
Blob []byte `json:"blob"`
|
||||
Type string `json:"type"`
|
||||
Size int64 `json:"size"`
|
||||
Filename string `json:"filename"`
|
||||
Blob []byte `json:"-"`
|
||||
ExternalLink string `json:"externalLink"`
|
||||
Type string `json:"-"`
|
||||
Size int64 `json:"-"`
|
||||
}
|
||||
|
||||
type ResourceFind struct {
|
||||
|
@ -21,7 +21,7 @@ import (
|
||||
|
||||
const (
|
||||
// The max file size is 32MB.
|
||||
maxFileSize = (32 * 8) << 20
|
||||
maxFileSize = 32 << 20
|
||||
)
|
||||
|
||||
func (s *Server) registerResourceRoutes(g *echo.Group) {
|
||||
@ -32,6 +32,34 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
||||
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
|
||||
}
|
||||
|
||||
resourceCreate := &api.ResourceCreate{}
|
||||
if err := json.NewDecoder(c.Request().Body).Decode(resourceCreate); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Malformatted post resource request").SetInternal(err)
|
||||
}
|
||||
|
||||
resourceCreate.CreatorID = userID
|
||||
resource, err := s.Store.CreateResource(ctx, resourceCreate)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create resource").SetInternal(err)
|
||||
}
|
||||
if err := s.createResourceCreateActivity(c, resource); err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to create activity").SetInternal(err)
|
||||
}
|
||||
|
||||
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSONCharsetUTF8)
|
||||
if err := json.NewEncoder(c.Response().Writer).Encode(composeResponse(resource)); err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to encode resource response").SetInternal(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
g.POST("/resource/blob", func(c echo.Context) error {
|
||||
ctx := c.Request().Context()
|
||||
userID, ok := c.Get(getUserIDContextKey()).(int)
|
||||
if !ok {
|
||||
return echo.NewHTTPError(http.StatusUnauthorized, "Missing user in session")
|
||||
}
|
||||
|
||||
if err := c.Request().ParseMultipartForm(maxFileSize); err != nil {
|
||||
return echo.NewHTTPError(http.StatusBadRequest, "Upload file overload max size").SetInternal(err)
|
||||
}
|
||||
|
@ -22,10 +22,11 @@ type resourceRaw struct {
|
||||
UpdatedTs int64
|
||||
|
||||
// Domain specific fields
|
||||
Filename string
|
||||
Blob []byte
|
||||
Type string
|
||||
Size int64
|
||||
Filename string
|
||||
Blob []byte
|
||||
ExternalLink string
|
||||
Type string
|
||||
Size int64
|
||||
}
|
||||
|
||||
func (raw *resourceRaw) toResource() *api.Resource {
|
||||
@ -38,10 +39,11 @@ func (raw *resourceRaw) toResource() *api.Resource {
|
||||
UpdatedTs: raw.UpdatedTs,
|
||||
|
||||
// Domain specific fields
|
||||
Filename: raw.Filename,
|
||||
Blob: raw.Blob,
|
||||
Type: raw.Type,
|
||||
Size: raw.Size,
|
||||
Filename: raw.Filename,
|
||||
Blob: raw.Blob,
|
||||
ExternalLink: raw.ExternalLink,
|
||||
Type: raw.Type,
|
||||
Size: raw.Size,
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,18 +217,20 @@ func createResource(ctx context.Context, tx *sql.Tx, create *api.ResourceCreate)
|
||||
INSERT INTO resource (
|
||||
filename,
|
||||
blob,
|
||||
external_link,
|
||||
type,
|
||||
size,
|
||||
creator_id
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
RETURNING id, filename, blob, type, size, creator_id, created_ts, updated_ts
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
RETURNING id, filename, blob, external_link, type, size, creator_id, created_ts, updated_ts
|
||||
`
|
||||
var resourceRaw resourceRaw
|
||||
if err := tx.QueryRowContext(ctx, query, create.Filename, create.Blob, create.Type, create.Size, create.CreatorID).Scan(
|
||||
if err := tx.QueryRowContext(ctx, query, create.Filename, create.Blob, create.ExternalLink, create.Type, create.Size, create.CreatorID).Scan(
|
||||
&resourceRaw.ID,
|
||||
&resourceRaw.Filename,
|
||||
&resourceRaw.Blob,
|
||||
&resourceRaw.ExternalLink,
|
||||
&resourceRaw.Type,
|
||||
&resourceRaw.Size,
|
||||
&resourceRaw.CreatorID,
|
||||
@ -255,13 +259,14 @@ func patchResource(ctx context.Context, tx *sql.Tx, patch *api.ResourcePatch) (*
|
||||
UPDATE resource
|
||||
SET ` + strings.Join(set, ", ") + `
|
||||
WHERE id = ?
|
||||
RETURNING id, filename, blob, type, size, creator_id, created_ts, updated_ts
|
||||
RETURNING id, filename, blob, external_link, type, size, creator_id, created_ts, updated_ts
|
||||
`
|
||||
var resourceRaw resourceRaw
|
||||
if err := tx.QueryRowContext(ctx, query, args...).Scan(
|
||||
&resourceRaw.ID,
|
||||
&resourceRaw.Filename,
|
||||
&resourceRaw.Blob,
|
||||
&resourceRaw.ExternalLink,
|
||||
&resourceRaw.Type,
|
||||
&resourceRaw.Size,
|
||||
&resourceRaw.CreatorID,
|
||||
@ -295,6 +300,7 @@ func findResourceList(ctx context.Context, tx *sql.Tx, find *api.ResourceFind) (
|
||||
id,
|
||||
filename,
|
||||
blob,
|
||||
external_link,
|
||||
type,
|
||||
size,
|
||||
creator_id,
|
||||
@ -317,6 +323,7 @@ func findResourceList(ctx context.Context, tx *sql.Tx, find *api.ResourceFind) (
|
||||
&resourceRaw.ID,
|
||||
&resourceRaw.Filename,
|
||||
&resourceRaw.Blob,
|
||||
&resourceRaw.ExternalLink,
|
||||
&resourceRaw.Type,
|
||||
&resourceRaw.Size,
|
||||
&resourceRaw.CreatorID,
|
||||
|
@ -90,6 +90,7 @@ const CreateTagDialog: React.FC<Props> = (props: Props) => {
|
||||
<div className="dialog-content-container !w-80">
|
||||
<Input
|
||||
className="mb-2"
|
||||
size="md"
|
||||
placeholder="TAG_NAME"
|
||||
value={tagName}
|
||||
onChange={handleTagNameChanged}
|
||||
|
@ -9,8 +9,8 @@ import theme from "../../theme";
|
||||
import "../../less/base-dialog.less";
|
||||
|
||||
interface DialogConfig {
|
||||
className: string;
|
||||
dialogName: string;
|
||||
className?: string;
|
||||
clickSpaceDestroy?: boolean;
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ const BaseDialog: React.FC<Props> = (props: Props) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`dialog-wrapper ${className}`} onMouseDown={handleSpaceClicked}>
|
||||
<div className={`dialog-wrapper ${className ?? ""}`} onMouseDown={handleSpaceClicked}>
|
||||
<div ref={dialogContainerRef} className="dialog-container" onMouseDown={(e) => e.stopPropagation()}>
|
||||
{children}
|
||||
</div>
|
||||
|
@ -37,6 +37,7 @@ const setEditingMemoVisibilityCache = (visibility: Visibility) => {
|
||||
interface State {
|
||||
fullscreen: boolean;
|
||||
isUploadingResource: boolean;
|
||||
isRequesting: boolean;
|
||||
}
|
||||
|
||||
const MemoEditor = () => {
|
||||
@ -51,6 +52,7 @@ const MemoEditor = () => {
|
||||
const [state, setState] = useState<State>({
|
||||
isUploadingResource: false,
|
||||
fullscreen: false,
|
||||
isRequesting: false,
|
||||
});
|
||||
const [allowSave, setAllowSave] = useState<boolean>(false);
|
||||
const editorState = editorStore.state;
|
||||
@ -280,7 +282,7 @@ const MemoEditor = () => {
|
||||
let resource = undefined;
|
||||
|
||||
try {
|
||||
resource = await resourceStore.upload(file);
|
||||
resource = await resourceStore.createResourceWithBlob(file);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
toastHelper.error(error.response.data.message);
|
||||
@ -305,6 +307,16 @@ const MemoEditor = () => {
|
||||
}, [editorState.editMemoId]);
|
||||
|
||||
const handleSaveBtnClick = async () => {
|
||||
if (state.isRequesting) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState((state) => {
|
||||
return {
|
||||
...state,
|
||||
isRequesting: true,
|
||||
};
|
||||
});
|
||||
const content = editorRef.current?.getContent() ?? "";
|
||||
try {
|
||||
const { editMemoId } = editorStore.getState();
|
||||
@ -332,6 +344,12 @@ const MemoEditor = () => {
|
||||
console.error(error);
|
||||
toastHelper.error(error.response.data.message);
|
||||
}
|
||||
setState((state) => {
|
||||
return {
|
||||
...state,
|
||||
isRequesting: false,
|
||||
};
|
||||
});
|
||||
|
||||
// Upsert tag with the content.
|
||||
const matchedNodes = getMatchedNodes(content);
|
||||
@ -561,7 +579,7 @@ const MemoEditor = () => {
|
||||
</button>
|
||||
<button
|
||||
className="action-btn confirm-btn"
|
||||
disabled={!(allowSave || editorState.resourceList.length > 0) || state.isUploadingResource}
|
||||
disabled={!(allowSave || editorState.resourceList.length > 0) || state.isUploadingResource || state.isRequesting}
|
||||
onClick={handleSaveBtnClick}
|
||||
>
|
||||
{t("editor.save")}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { Tooltip } from "@mui/joy";
|
||||
import { Button } from "@mui/joy";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import useLoading from "../hooks/useLoading";
|
||||
import { useResourceStore } from "../store/module";
|
||||
@ -16,19 +16,12 @@ import "../less/resources-dialog.less";
|
||||
|
||||
type Props = DialogProps;
|
||||
|
||||
interface State {
|
||||
isUploadingResource: boolean;
|
||||
}
|
||||
|
||||
const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
||||
const { destroy } = props;
|
||||
const { t } = useTranslation();
|
||||
const loadingState = useLoading();
|
||||
const resourceStore = useResourceStore();
|
||||
const resources = resourceStore.state.resources;
|
||||
const [state, setState] = useState<State>({
|
||||
isUploadingResource: false,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
resourceStore
|
||||
@ -43,10 +36,6 @@ const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
||||
}, []);
|
||||
|
||||
const handleUploadFileBtnClick = async () => {
|
||||
if (state.isUploadingResource) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inputEl = document.createElement("input");
|
||||
inputEl.style.position = "fixed";
|
||||
inputEl.style.top = "-100vh";
|
||||
@ -60,22 +49,12 @@ const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
||||
return;
|
||||
}
|
||||
|
||||
setState({
|
||||
...state,
|
||||
isUploadingResource: true,
|
||||
});
|
||||
|
||||
for (const file of inputEl.files) {
|
||||
try {
|
||||
await resourceStore.upload(file);
|
||||
await resourceStore.createResourceWithBlob(file);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
toastHelper.error(error.response.data.message);
|
||||
} finally {
|
||||
setState({
|
||||
...state,
|
||||
isUploadingResource: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,18 +137,16 @@ const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
||||
</button>
|
||||
</div>
|
||||
<div className="dialog-content-container">
|
||||
<div className="action-buttons-container">
|
||||
<div className="buttons-wrapper">
|
||||
<div className="upload-resource-btn" onClick={() => handleUploadFileBtnClick()}>
|
||||
<Icon.File className="icon-img" />
|
||||
<span>{t("resources.upload")}</span>
|
||||
</div>
|
||||
<div className="w-full flex flex-row justify-between items-center">
|
||||
<div className="flex flex-row justify-start items-center space-x-2">
|
||||
<Button onClick={() => handleUploadFileBtnClick()} startDecorator={<Icon.Plus className="w-5 h-auto" />}>
|
||||
{t("common.create")}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="buttons-wrapper">
|
||||
<div className="delete-unused-resource-btn" onClick={handleDeleteUnusedResourcesBtnClick}>
|
||||
<Icon.Trash2 className="icon-img" />
|
||||
<span>{t("resources.clear-unused-resources")}</span>
|
||||
</div>
|
||||
<div className="flex flex-row justify-end items-center">
|
||||
<Button color="danger" onClick={handleDeleteUnusedResourcesBtnClick} startDecorator={<Icon.Trash2 className="w-4 h-auto" />}>
|
||||
<span>{t("resources.clear")}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{loadingState.isLoading ? (
|
||||
@ -189,9 +166,9 @@ const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
||||
resources.map((resource) => (
|
||||
<div key={resource.id} className="resource-container">
|
||||
<span className="field-text id-text">{resource.id}</span>
|
||||
<Tooltip title={resource.filename}>
|
||||
<span className="field-text name-text">{resource.filename}</span>
|
||||
</Tooltip>
|
||||
<span className="field-text name-text" onClick={() => handleRenameBtnClick(resource)}>
|
||||
{resource.filename}
|
||||
</span>
|
||||
<div className="buttons-container">
|
||||
<Dropdown
|
||||
actionsClassName="!w-28"
|
||||
@ -203,12 +180,6 @@ const ResourcesDialog: React.FC<Props> = (props: Props) => {
|
||||
>
|
||||
{t("resources.preview")}
|
||||
</button>
|
||||
<button
|
||||
className="w-full text-left text-sm leading-6 py-1 px-3 cursor-pointer rounded hover:bg-gray-100 dark:hover:bg-zinc-600"
|
||||
onClick={() => handleRenameBtnClick(resource)}
|
||||
>
|
||||
{t("resources.rename")}
|
||||
</button>
|
||||
<button
|
||||
className="w-full text-left text-sm leading-6 py-1 px-3 cursor-pointer rounded hover:bg-gray-100 dark:hover:bg-zinc-600"
|
||||
onClick={() => handleCopyResourceLinkBtnClick(resource)}
|
||||
|
@ -4,6 +4,7 @@ import { Link } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useLocationStore, useUserStore } from "../store/module";
|
||||
import showDailyReviewDialog from "./DailyReviewDialog";
|
||||
import showResourcesDialog from "./ResourcesDialog";
|
||||
import showSettingDialog from "./SettingDialog";
|
||||
import UserBanner from "./UserBanner";
|
||||
import UsageHeatMap from "./UsageHeatMap";
|
||||
@ -38,6 +39,9 @@ const Sidebar = () => {
|
||||
<Link to="/explore" className="btn action-btn">
|
||||
<span className="icon">🏂</span> {t("common.explore")}
|
||||
</Link>
|
||||
<button className="btn action-btn" onClick={() => showResourcesDialog()}>
|
||||
<span className="icon">🗂️</span> {t("sidebar.resources")}
|
||||
</button>
|
||||
{!userStore.isVisitorMode() && (
|
||||
<>
|
||||
<button className="btn action-btn" onClick={handleSettingBtnClick}>
|
||||
|
@ -5,7 +5,6 @@ import { getMemoStats } from "../helpers/api";
|
||||
import * as utils from "../helpers/utils";
|
||||
import Icon from "./Icon";
|
||||
import Dropdown from "./common/Dropdown";
|
||||
import showResourcesDialog from "./ResourcesDialog";
|
||||
import showArchivedMemoDialog from "./ArchivedMemoDialog";
|
||||
import showAboutSiteDialog from "./AboutSiteDialog";
|
||||
import "../less/user-banner.less";
|
||||
@ -51,10 +50,6 @@ const UserBanner = () => {
|
||||
locationStore.clearQuery();
|
||||
}, []);
|
||||
|
||||
const handleResourcesBtnClick = () => {
|
||||
showResourcesDialog();
|
||||
};
|
||||
|
||||
const handleArchivedBtnClick = () => {
|
||||
showArchivedMemoDialog();
|
||||
};
|
||||
@ -82,17 +77,11 @@ const UserBanner = () => {
|
||||
<>
|
||||
{!userStore.isVisitorMode() && (
|
||||
<>
|
||||
<button
|
||||
className="w-full px-3 whitespace-nowrap text-left leading-10 cursor-pointer rounded dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-zinc-800"
|
||||
onClick={handleResourcesBtnClick}
|
||||
>
|
||||
<span className="mr-1">🌄</span> {t("sidebar.resources")}
|
||||
</button>
|
||||
<button
|
||||
className="w-full px-3 whitespace-nowrap text-left leading-10 cursor-pointer rounded dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-zinc-800"
|
||||
onClick={handleArchivedBtnClick}
|
||||
>
|
||||
<span className="mr-1">🗂</span> {t("sidebar.archived")}
|
||||
<span className="mr-1">🗃️</span> {t("sidebar.archived")}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
@ -153,8 +153,12 @@ export function getResourceList() {
|
||||
return axios.get<ResponseObject<Resource[]>>("/api/resource");
|
||||
}
|
||||
|
||||
export function uploadFile(formData: FormData) {
|
||||
return axios.post<ResponseObject<Resource>>("/api/resource", formData);
|
||||
export function createResource(resourceCreate: ResourceCreate) {
|
||||
return axios.post<ResponseObject<Resource>>("/api/resource", resourceCreate);
|
||||
}
|
||||
|
||||
export function createResourceWithBlob(formData: FormData) {
|
||||
return axios.post<ResponseObject<Resource>>("/api/resource/blob", formData);
|
||||
}
|
||||
|
||||
export function deleteResourceById(id: ResourceId) {
|
||||
|
@ -9,7 +9,7 @@
|
||||
@apply relative w-full h-auto mx-auto flex flex-row justify-start sm:justify-center items-start;
|
||||
|
||||
> .sidebar-wrapper {
|
||||
@apply flex-shrink-0 ml-calc;
|
||||
@apply flex-shrink-0 h-full ml-calc;
|
||||
}
|
||||
|
||||
> .memos-wrapper {
|
||||
|
@ -62,11 +62,11 @@
|
||||
@apply w-full truncate text-base pr-2 last:pr-0;
|
||||
|
||||
&.id-text {
|
||||
@apply col-span-2;
|
||||
@apply col-span-1;
|
||||
}
|
||||
|
||||
&.name-text {
|
||||
@apply col-span-4;
|
||||
@apply col-span-5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,7 @@
|
||||
"warning-text": "Are you sure to delete this resource? THIS ACTION IS IRREVERSIABLE❗",
|
||||
"linked-amount": "Linked memo amount",
|
||||
"rename": "Rename",
|
||||
"clear-unused-resources": "Clear unused resources",
|
||||
"clear": "Clear",
|
||||
"warning-text-unused": "Are you sure to delete these unused resource? THIS ACTION IS IRREVERSIABLE❗",
|
||||
"no-unused-resources": "No unused resources",
|
||||
"name": "Name"
|
||||
|
@ -75,7 +75,7 @@
|
||||
"warning-text": "确定删除这个资源么?此操作不可逆❗",
|
||||
"linked-amount": "链接的 Memo 数量",
|
||||
"rename": "重命名",
|
||||
"clear-unused-resources": "清除无用资源",
|
||||
"clear": "清除",
|
||||
"warning-text-unused": "确定删除这些无用资源么?此操作不可逆❗",
|
||||
"no-unused-resources": "无可删除的资源",
|
||||
"name": "资源名称"
|
||||
|
@ -2,6 +2,8 @@ import store, { useAppSelector } from "../";
|
||||
import { patchResource, setResources, deleteResource } from "../reducer/resource";
|
||||
import * as api from "../../helpers/api";
|
||||
|
||||
const MAX_FILE_SIZE = 32 << 20;
|
||||
|
||||
const convertResponseModelResource = (resource: Resource): Resource => {
|
||||
return {
|
||||
...resource,
|
||||
@ -24,16 +26,22 @@ export const useResourceStore = () => {
|
||||
store.dispatch(setResources(resourceList));
|
||||
return resourceList;
|
||||
},
|
||||
async upload(file: File): Promise<Resource> {
|
||||
async createResource(resourceCreate: ResourceCreate): Promise<Resource> {
|
||||
const { data } = (await api.createResource(resourceCreate)).data;
|
||||
const resource = convertResponseModelResource(data);
|
||||
const resourceList = state.resources;
|
||||
store.dispatch(setResources([resource, ...resourceList]));
|
||||
return resource;
|
||||
},
|
||||
async createResourceWithBlob(file: File): Promise<Resource> {
|
||||
const { name: filename, size } = file;
|
||||
|
||||
if (size > 64 << 20) {
|
||||
return Promise.reject("overload max size: 8MB");
|
||||
if (size > MAX_FILE_SIZE) {
|
||||
return Promise.reject("overload max size: 32MB");
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("file", file, filename);
|
||||
const { data } = (await api.uploadFile(formData)).data;
|
||||
const { data } = (await api.createResourceWithBlob(formData)).data;
|
||||
const resource = convertResponseModelResource(data);
|
||||
const resourceList = state.resources;
|
||||
store.dispatch(setResources([resource, ...resourceList]));
|
||||
|
@ -7,6 +7,11 @@ const theme = extendTheme({
|
||||
size: "sm",
|
||||
},
|
||||
},
|
||||
JoyInput: {
|
||||
defaultProps: {
|
||||
size: "sm",
|
||||
},
|
||||
},
|
||||
JoySelect: {
|
||||
defaultProps: {
|
||||
size: "sm",
|
||||
|
6
web/src/types/modules/resource.d.ts
vendored
6
web/src/types/modules/resource.d.ts
vendored
@ -7,12 +7,18 @@ interface Resource {
|
||||
updatedTs: TimeStamp;
|
||||
|
||||
filename: string;
|
||||
externalLink: string;
|
||||
type: string;
|
||||
size: string;
|
||||
|
||||
linkedMemoAmount: number;
|
||||
}
|
||||
|
||||
interface ResourceCreate {
|
||||
filename: string;
|
||||
externalLink: string;
|
||||
}
|
||||
|
||||
interface ResourcePatch {
|
||||
id: ResourceId;
|
||||
filename?: string;
|
||||
|
@ -1,3 +1,6 @@
|
||||
export const getResourceUrl = (resource: Resource, withOrigin = true) => {
|
||||
if (resource.externalLink) {
|
||||
return resource.externalLink;
|
||||
}
|
||||
return `${withOrigin ? window.location.origin : ""}/o/r/${resource.id}/${encodeURI(resource.filename)}`;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user