-
-
-
-
-

-
-
Memos
-
Capture first. Keep it yours.
-
-
-
- {PRODUCT_LINKS.map((link) => (
- } variant="outline" size="lg">
- {link.label}
-
-
- ))}
-
+
+

+
+
+
{instanceTitle}
+ {profile.demo && Demo}
-
-
-
-
- {PRODUCT_POINTS.map((item) => (
-
- {item}
-
- ))}
+
{instanceTagline}
+
+ {profile.version && {versionLabel}}
+ {shortCommit && {shortCommit}}
+ {isCustomBranded && Powered by Memos}
-
+
+
-
-
- {TILE_SPRITES.map((sprite) => (
-
- ))}
-
-
-
+
+
+
Free and open source under the MIT license.
diff --git a/web/src/pages/Setting.tsx b/web/src/pages/Setting.tsx
index 622f3ea3..4e8d3328 100644
--- a/web/src/pages/Setting.tsx
+++ b/web/src/pages/Setting.tsx
@@ -16,19 +16,14 @@ import useMediaQuery from "@/hooks/useMediaQuery";
import { User_Role } from "@/types/proto/api/v1/user_service_pb";
import { useTranslate } from "@/utils/i18n";
-const GITHUB_COMMIT_URL_PREFIX = "https://github.com/usememos/memos/commit/";
-
-const isCommitSha = (commit: string) => /^[0-9a-f]{7,40}$/i.test(commit);
-
const Setting = () => {
const t = useTranslate();
const sm = useMediaQuery("sm");
const location = useLocation();
const user = useCurrentUser();
- const { profile, fetchSettings } = useInstance();
+ const { fetchSettings } = useInstance();
const [selectedSection, setSelectedSection] = useState(DEFAULT_SETTING_SECTION);
const isHost = user?.role === User_Role.ADMIN;
- const commitUrl = isCommitSha(profile.commit) ? `${GITHUB_COMMIT_URL_PREFIX}${profile.commit}` : "";
const sectionGroups = useMemo(() => {
const visibleSections = SETTINGS_SECTIONS.filter((section) => section.scope === "basic" || isHost);
@@ -93,24 +88,7 @@ const Setting = () => {
{isHost && (
<>
{t("common.admin")}
-
- {renderSectionMenuItems(sectionGroups.admin)}
-
- {t("setting.version")}: {profile.version}
- {profile.commit && (
-
- Commit:{" "}
- {commitUrl ? (
-
- {profile.commit}
-
- ) : (
- profile.commit
- )}
-
- )}
-
-
+ {renderSectionMenuItems(sectionGroups.admin)}
>
)}
diff --git a/web/tests/about-page.test.tsx b/web/tests/about-page.test.tsx
index fb79c600..51e3cbe9 100644
--- a/web/tests/about-page.test.tsx
+++ b/web/tests/about-page.test.tsx
@@ -1,26 +1,103 @@
-import { render, screen, within } from "@testing-library/react";
-import { afterEach, describe, expect, it } from "vitest";
-import { TILE_SPRITES } from "@/components/Placeholder/tileSprites";
+import { render, screen } from "@testing-library/react";
+import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import About from "@/pages/About";
+const mockInstance = {
+ profile: {
+ version: "0.25.0",
+ commit: "0123456789abcdef0123456789abcdef01234567",
+ instanceUrl: "",
+ demo: false,
+ admin: undefined as { username: string; displayName: string } | undefined,
+ },
+ generalSetting: {} as { customProfile?: { title: string; description: string; logoUrl: string } },
+};
+
+vi.mock("@/contexts/InstanceContext", () => ({
+ useInstance: () => mockInstance,
+}));
+
+vi.mock("@/utils/i18n", () => ({
+ useTranslate: () => (key: string) => (key === "common.version" ? "Version" : key),
+}));
+
describe("
", () => {
+ beforeEach(() => {
+ mockInstance.profile = {
+ version: "0.25.0",
+ commit: "0123456789abcdef0123456789abcdef01234567",
+ instanceUrl: "https://notes.example.com",
+ demo: false,
+ admin: { username: "steven", displayName: "Steven" },
+ };
+ mockInstance.generalSetting = {};
+ });
+
afterEach(() => {
document.documentElement.removeAttribute("data-theme");
});
- it("renders the product story and current bird sprites", () => {
+ it("renders the identity hero with linked version and commit chips", () => {
render();
expect(screen.getByRole("heading", { name: "Memos" })).toBeInTheDocument();
expect(screen.getByText(/Capture first/i)).toBeInTheDocument();
- expect(screen.getByText(/quick capture/i)).toBeInTheDocument();
+ expect(screen.getByRole("link", { name: "v0.25.0" })).toHaveAttribute("href", "https://github.com/usememos/memos/releases/tag/v0.25.0");
+ expect(screen.getByRole("link", { name: "0123456" })).toHaveAttribute(
+ "href",
+ "https://github.com/usememos/memos/commit/0123456789abcdef0123456789abcdef01234567",
+ );
+ expect(screen.getByText(/MIT license/i)).toBeInTheDocument();
+ });
- const birds = screen.getByRole("region", { name: "Birds" });
- expect(within(birds).getAllByTestId("about-bird-sprite")).toHaveLength(TILE_SPRITES.length);
+ it("links to the usememos.com homepage, docs, API docs, and GitHub repo", () => {
+ render();
- for (const sprite of TILE_SPRITES) {
- expect(within(birds).getByText(sprite.name)).toBeInTheDocument();
- }
+ expect(screen.getByRole("link", { name: /about\.official-website/ })).toHaveAttribute("href", "https://usememos.com/");
+ expect(screen.getByRole("link", { name: /about\.documents/ })).toHaveAttribute("href", "https://usememos.com/docs");
+ expect(screen.getByRole("link", { name: /API Docs/ })).toHaveAttribute("href", "https://usememos.com/docs/api");
+ expect(screen.getByRole("link", { name: /about\.github-repository/ })).toHaveAttribute("href", "https://github.com/usememos/memos");
+ });
+
+ it("does not surface the instance URL, administrator, or birds", () => {
+ render();
+
+ expect(screen.queryByText("https://notes.example.com")).not.toBeInTheDocument();
+ expect(screen.queryByText("Administrator")).not.toBeInTheDocument();
+ expect(screen.queryByText("Steven")).not.toBeInTheDocument();
+ expect(screen.queryByText("Birds")).not.toBeInTheDocument();
+ expect(screen.queryByTestId("about-bird-sprite")).not.toBeInTheDocument();
+ });
+
+ it("shows a plain version chip and no commit chip on dev builds", () => {
+ mockInstance.profile.version = "dev";
+ mockInstance.profile.commit = "unknown";
+
+ render();
+
+ expect(screen.getByText("dev")).toBeInTheDocument();
+ expect(screen.queryByText("vdev")).not.toBeInTheDocument();
+ expect(screen.queryByText(/unknown/)).not.toBeInTheDocument();
+ });
+
+ it("shows the demo badge on demo instances", () => {
+ mockInstance.profile.demo = true;
+
+ render();
+
+ expect(screen.getByText("Demo")).toBeInTheDocument();
+ });
+
+ it("uses custom branding for the identity hero and credits Memos", () => {
+ mockInstance.generalSetting = {
+ customProfile: { title: "Team Notes", description: "Our shared scratchpad.", logoUrl: "/custom-logo.png" },
+ };
+
+ render();
+
+ expect(screen.getByRole("heading", { name: "Team Notes" })).toBeInTheDocument();
+ expect(screen.getByText("Our shared scratchpad.")).toBeInTheDocument();
+ expect(screen.getByText("Powered by Memos")).toBeInTheDocument();
});
it("does not add nested horizontal page padding on mobile", () => {