mirror of
https://github.com/usememos/memos.git
synced 2024-12-25 04:13:07 +03:00
parent
c7a57191bd
commit
1ea65c0b60
18
README.md
18
README.md
@ -1,16 +1,18 @@
|
||||
<p align="center"><a href="https://usememos.com"><img height="64px" src="https://usememos.com/logo-full.png" alt="✍️ memos" /></a></p>
|
||||
# memos
|
||||
|
||||
<p align="center">
|
||||
<img height="72px" src="https://usememos.com/logo.webp" alt="✍️ memos" align="right" />
|
||||
|
||||
A lightweight, self-hosted memo hub. Open Source and Free forever.
|
||||
|
||||
<a href="https://demo.usememos.com/">Live Demo</a> •
|
||||
Discuss in <a href="https://t.me/+-_tNF1k70UU4ZTc9">Telegram</a> / <a href="https://discord.gg/tfPJa4UmAv">Discord</a>
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/usememos/memos/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/usememos/memos" /></a>
|
||||
<a href="https://hub.docker.com/r/neosmemo/memos"><img alt="Docker pull" src="https://img.shields.io/docker/pulls/neosmemo/memos.svg" /></a>
|
||||
<a href="https://discord.gg/tfPJa4UmAv"><img alt="Discord" src="https://img.shields.io/badge/discord-chat-5865f2?logo=discord&logoColor=f5f5f5" /></a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<a href="https://demo.usememos.com/">Live Demo</a> •
|
||||
Discuss in <a href="https://t.me/+-_tNF1k70UU4ZTc9">Telegram</a> / <a href="https://discord.gg/tfPJa4UmAv">Discord</a>
|
||||
</p>
|
||||
|
||||
![demo](https://usememos.com/demo.webp)
|
||||
|
||||
## Key points
|
||||
@ -39,7 +41,7 @@ Contributions are what make the open-source community such an amazing place to l
|
||||
<img src="https://contrib.rocks/image?repo=usememos/memos" />
|
||||
</a>
|
||||
|
||||
Here are some products made by our community:
|
||||
---
|
||||
|
||||
- [Moe Memos](https://memos.moe/) - Third party client for iOS and Android
|
||||
- [lmm214/memos-bber](https://github.com/lmm214/memos-bber) - Chrome extension
|
||||
|
@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" href="/logo.png" type="image/*" />
|
||||
<link rel="icon" href="/logo.webp" type="image/*" />
|
||||
<meta name="theme-color" media="(prefers-color-scheme: light)" content="#f4f4f5" />
|
||||
<meta name="theme-color" media="(prefers-color-scheme: dark)" content="#27272a" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" />
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 110 KiB |
BIN
web/public/logo.webp
Normal file
BIN
web/public/logo.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 56 KiB |
@ -4,8 +4,8 @@
|
||||
"description": "usememos/memos",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/logo.png",
|
||||
"type": "image/png",
|
||||
"src": "/logo.webp",
|
||||
"type": "image/webp",
|
||||
"sizes": "520x520"
|
||||
}
|
||||
],
|
||||
|
@ -53,7 +53,7 @@ const App = () => {
|
||||
// dynamic update metadata with customized profile.
|
||||
document.title = systemStatus.customizedProfile.name;
|
||||
const link = document.querySelector("link[rel~='icon']") as HTMLLinkElement;
|
||||
link.href = systemStatus.customizedProfile.logoUrl || "/logo.png";
|
||||
link.href = systemStatus.customizedProfile.logoUrl || "/logo.webp";
|
||||
}, [systemStatus]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -31,8 +31,8 @@ const AboutSiteDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
<div className="mt-4 w-full flex flex-row text-sm justify-start items-center">
|
||||
<div className="flex flex-row justify-start items-center mr-2">
|
||||
Powered by
|
||||
<a href="https://usememos.com" target="_blank" className="flex flex-row justify-start items-center mr-1 hover:underline">
|
||||
<img className="w-6 h-auto" src="/logo.png" alt="" />
|
||||
<a href="https://usememos.com" target="_blank" className="flex flex-row justify-start items-center mx-1 hover:underline">
|
||||
<img className="w-6 h-auto rounded-full mr-1" src="/logo.webp" alt="" />
|
||||
memos
|
||||
</a>
|
||||
<span>v{profile.version}</span>
|
||||
|
@ -474,7 +474,7 @@ const MemoEditor = () => {
|
||||
disabled={!(allowSave || editorState.resourceList.length > 0) || state.isUploadingResource || state.isRequesting}
|
||||
onClick={handleSaveBtnClick}
|
||||
>
|
||||
<img className="w-5 -ml-0.5 mr-0.5 h-auto" src="/logo.png" />
|
||||
<img className="w-5 -ml-0.5 mr-0.5 h-auto" src="/logo.webp" />
|
||||
{t("editor.save")}
|
||||
</button>
|
||||
</div>
|
||||
|
@ -131,7 +131,7 @@ const ShareMemoDialog: React.FC<Props> = (props: Props) => {
|
||||
</div>
|
||||
<div className="watermark-container">
|
||||
<div className="logo-container">
|
||||
<img className="logo-img" src={`${systemStatus.customizedProfile.logoUrl || "/logo.png"}`} alt="" />
|
||||
<img className="logo-img" src={`${systemStatus.customizedProfile.logoUrl || "/logo.webp"}`} alt="" />
|
||||
</div>
|
||||
<div className="userinfo-container">
|
||||
<span className="name-text">{user.nickname || user.username}</span>
|
||||
|
@ -67,7 +67,7 @@ const UpdateCustomizedProfileDialog: React.FC<Props> = ({ destroy }: Props) => {
|
||||
const handleRestoreButtonClick = () => {
|
||||
setState({
|
||||
name: "memos",
|
||||
logoUrl: "/logo.png",
|
||||
logoUrl: "/logo.webp",
|
||||
description: "",
|
||||
locale: "en",
|
||||
appearance: "system",
|
||||
|
@ -6,8 +6,8 @@ interface Props {
|
||||
const UserAvatar = (props: Props) => {
|
||||
const { avatarUrl, className } = props;
|
||||
return (
|
||||
<div className={`${className ?? ""} w-8 h-8 overflow-clip bg-gray-100 dark:bg-zinc-800`}>
|
||||
<img className="w-full h-auto rounded-full min-w-full min-h-full object-cover" src={avatarUrl || "/logo.png"} alt="" />
|
||||
<div className={`${className ?? ""} w-8 h-8 overflow-clip`}>
|
||||
<img className="w-full h-auto rounded-full min-w-full min-h-full object-cover" src={avatarUrl || "/logo.webp"} alt="" />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -113,7 +113,7 @@ const Auth = () => {
|
||||
<div className="auth-form-wrapper">
|
||||
<div className="page-header-container">
|
||||
<div className="title-container">
|
||||
<img className="logo-img" src={systemStatus.customizedProfile.logoUrl} alt="" />
|
||||
<img className="h-12 w-auto rounded-lg mr-1" src={systemStatus.customizedProfile.logoUrl} alt="" />
|
||||
<p className="logo-text">{systemStatus.customizedProfile.name}</p>
|
||||
</div>
|
||||
<p className="slogan-text">{systemStatus.customizedProfile.description || t("slogan")}</p>
|
||||
|
@ -3,6 +3,7 @@ import copy from "copy-to-clipboard";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
|
||||
import useLoading from "@/hooks/useLoading";
|
||||
import { useResourceStore } from "@/store/module";
|
||||
import { getResourceUrl } from "@/utils/resource";
|
||||
@ -16,7 +17,6 @@ import { showCommonDialog } from "@/components/Dialog/CommonDialog";
|
||||
import showChangeResourceFilenameDialog from "@/components/ChangeResourceFilenameDialog";
|
||||
import showPreviewImageDialog from "@/components/PreviewImageDialog";
|
||||
import showCreateResourceDialog from "@/components/CreateResourceDialog";
|
||||
import { DEFAULT_MEMO_LIMIT } from "@/helpers/consts";
|
||||
|
||||
const ResourcesDashboard = () => {
|
||||
const { t } = useTranslation();
|
||||
@ -52,53 +52,6 @@ const ResourcesDashboard = () => {
|
||||
setSelectedList(selectedList.filter((resId) => resId !== resourceId));
|
||||
};
|
||||
|
||||
const handleDeleteUnusedResourcesBtnClick = async () => {
|
||||
let warningText = t("resources.warning-text-unused");
|
||||
await loadAllResources((allResources: Resource[]) => {
|
||||
const unusedResources = allResources.filter((resource) => {
|
||||
if (resource.linkedMemoAmount === 0) {
|
||||
warningText = warningText + `\n- ${resource.filename}`;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (unusedResources.length === 0) {
|
||||
toast.success(t("resources.no-unused-resources"));
|
||||
return;
|
||||
}
|
||||
showCommonDialog({
|
||||
title: t("resources.delete-resource"),
|
||||
content: warningText,
|
||||
style: "warning",
|
||||
dialogName: "delete-unused-resources",
|
||||
onConfirm: async () => {
|
||||
for (const resource of unusedResources) {
|
||||
await resourceStore.deleteResourceById(resource.id);
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteSelectedBtnClick = () => {
|
||||
if (selectedList.length == 0) {
|
||||
toast.error(t("resources.no-files-selected"));
|
||||
} else {
|
||||
const warningText = t("resources.warning-text");
|
||||
showCommonDialog({
|
||||
title: t("resources.delete-resource"),
|
||||
content: warningText,
|
||||
style: "warning",
|
||||
dialogName: "delete-resource-dialog",
|
||||
onConfirm: async () => {
|
||||
selectedList.map(async (resourceId: ResourceId) => {
|
||||
await resourceStore.deleteResourceById(resourceId);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleStyleChangeBtnClick = (listStyle: "GRID" | "TABLE") => {
|
||||
setListStyle(listStyle);
|
||||
setSelectedList([]);
|
||||
@ -125,6 +78,53 @@ const ResourcesDashboard = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteUnusedResourcesBtnClick = async () => {
|
||||
let warningText = t("resources.warning-text-unused");
|
||||
const allResources = await fetchAllResources();
|
||||
const unusedResources = allResources.filter((resource) => {
|
||||
if (resource.linkedMemoAmount === 0) {
|
||||
warningText = warningText + `\n- ${resource.filename}`;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (unusedResources.length === 0) {
|
||||
toast.success(t("resources.no-unused-resources"));
|
||||
return;
|
||||
}
|
||||
|
||||
showCommonDialog({
|
||||
title: t("resources.delete-resource"),
|
||||
content: warningText,
|
||||
style: "warning",
|
||||
dialogName: "delete-unused-resources",
|
||||
onConfirm: async () => {
|
||||
for (const resource of unusedResources) {
|
||||
await resourceStore.deleteResourceById(resource.id);
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleDeleteSelectedBtnClick = () => {
|
||||
if (selectedList.length == 0) {
|
||||
toast.error(t("resources.no-files-selected"));
|
||||
} else {
|
||||
const warningText = t("resources.warning-text");
|
||||
showCommonDialog({
|
||||
title: t("resources.delete-resource"),
|
||||
content: warningText,
|
||||
style: "warning",
|
||||
dialogName: "delete-resource-dialog",
|
||||
onConfirm: async () => {
|
||||
selectedList.map(async (resourceId: ResourceId) => {
|
||||
await resourceStore.deleteResourceById(resourceId);
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handlePreviewBtnClick = (resource: Resource) => {
|
||||
const resourceUrl = getResourceUrl(resource);
|
||||
if (resource.type.startsWith("image")) {
|
||||
@ -157,30 +157,30 @@ const ResourcesDashboard = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const loadAllResources = async (resolve: (allResources: Resource[]) => void) => {
|
||||
if (!isComplete) {
|
||||
loadingState.setLoading();
|
||||
try {
|
||||
const allResources = await resourceStore.fetchResourceList();
|
||||
loadingState.setFinish();
|
||||
setIsComplete(true);
|
||||
resolve(allResources);
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
toast.error(error.response.data.message);
|
||||
}
|
||||
} else {
|
||||
resolve(resources);
|
||||
const fetchAllResources = async () => {
|
||||
if (isComplete) {
|
||||
return resources;
|
||||
}
|
||||
|
||||
loadingState.setLoading();
|
||||
try {
|
||||
const allResources = await resourceStore.fetchResourceList();
|
||||
loadingState.setFinish();
|
||||
setIsComplete(true);
|
||||
return allResources;
|
||||
} catch (error: any) {
|
||||
console.error(error);
|
||||
toast.error(error.response.data.message);
|
||||
return resources;
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearchResourceInputChange = async (query: string) => {
|
||||
// to prevent first tiger when page is loaded
|
||||
if (query === queryText) return;
|
||||
await loadAllResources(() => {
|
||||
setQueryText(query);
|
||||
setSelectedList([]);
|
||||
});
|
||||
await fetchAllResources();
|
||||
setQueryText(query);
|
||||
setSelectedList([]);
|
||||
};
|
||||
|
||||
const resourceList = useMemo(
|
||||
|
@ -16,7 +16,7 @@ export const initialGlobalState = async () => {
|
||||
additionalScript: "",
|
||||
customizedProfile: {
|
||||
name: "memos",
|
||||
logoUrl: "/logo.png",
|
||||
logoUrl: "/logo.webp",
|
||||
description: "",
|
||||
locale: "en",
|
||||
appearance: "system",
|
||||
@ -41,7 +41,7 @@ export const initialGlobalState = async () => {
|
||||
...data,
|
||||
customizedProfile: {
|
||||
name: customizedProfile.name || "memos",
|
||||
logoUrl: customizedProfile.logoUrl || "/logo.png",
|
||||
logoUrl: customizedProfile.logoUrl || "/logo.webp",
|
||||
description: customizedProfile.description,
|
||||
locale: customizedProfile.locale || "en",
|
||||
appearance: customizedProfile.appearance || "system",
|
||||
|
@ -24,7 +24,7 @@ const globalSlice = createSlice({
|
||||
additionalScript: "",
|
||||
customizedProfile: {
|
||||
name: "memos",
|
||||
logoUrl: "/logo.png",
|
||||
logoUrl: "/logo.webp",
|
||||
description: "",
|
||||
locale: "en",
|
||||
appearance: "system",
|
||||
|
Loading…
Reference in New Issue
Block a user