mirror of
https://github.com/usememos/memos.git
synced 2025-01-31 17:48:12 +08:00
chore: upgrade version 0.13.1
(#1754)
This commit is contained in:
parent
93d608f050
commit
2e34ce90a1
16 changed files with 48 additions and 303 deletions
66
.github/workflows/e2e-test.yml
vendored
66
.github/workflows/e2e-test.yml
vendored
|
@ -1,66 +0,0 @@
|
|||
name: "E2E Test"
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
jobs:
|
||||
build:
|
||||
name: Build and Run Memos With E2E Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Build Docker image
|
||||
id: docker_build
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: ./
|
||||
file: ./Dockerfile
|
||||
platforms: linux/amd64
|
||||
push: false
|
||||
tags: neosmemo/memos:e2e
|
||||
labels: neosmemo/memos:e2e
|
||||
|
||||
- name: Run Docker container
|
||||
run: docker run -d -p 5230:5230 neosmemo/memos:e2e
|
||||
|
||||
- uses: pnpm/action-setup@v2.2.4
|
||||
with:
|
||||
version: 8.0.0
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "pnpm"
|
||||
cache-dependency-path: ./web/pnpm-lock.yaml
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: web
|
||||
run: pnpm install
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
working-directory: web
|
||||
run: npx playwright install --with-deps
|
||||
|
||||
- name: Run Playwright tests
|
||||
working-directory: web
|
||||
run: npx playwright test
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: web/playwright-report/
|
||||
retention-days: 30
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-screenshot
|
||||
path: web/playwright-screenshot/
|
||||
retention-days: 30
|
||||
|
||||
- name: Stop Docker container
|
||||
run: docker stop $(docker ps -q)
|
|
@ -123,6 +123,7 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||
return echo.NewHTTPError(http.StatusInternalServerError, "Failed to unmarshal storage service id").SetInternal(err)
|
||||
}
|
||||
}
|
||||
|
||||
publicID := common.GenUUID()
|
||||
if storageServiceID == api.DatabaseStorage {
|
||||
fileBytes, err := io.ReadAll(sourceFile)
|
||||
|
@ -326,11 +327,12 @@ func (s *Server) registerResourceRoutes(g *echo.Group) {
|
|||
if err := os.Remove(resource.InternalPath); err != nil {
|
||||
log.Warn(fmt.Sprintf("failed to delete local file with path %s", resource.InternalPath), zap.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
thumbnailPath := path.Join(s.Profile.Data, thumbnailImagePath, resource.PublicID)
|
||||
if err := os.Remove(thumbnailPath); err != nil {
|
||||
log.Warn(fmt.Sprintf("failed to delete local thumbnail with path %s", thumbnailPath), zap.Error(err))
|
||||
}
|
||||
ext := filepath.Ext(resource.Filename)
|
||||
thumbnailPath := path.Join(s.Profile.Data, thumbnailImagePath, fmt.Sprintf("%d-%s%s", resource.ID, resource.PublicID, ext))
|
||||
if err := os.Remove(thumbnailPath); err != nil {
|
||||
log.Warn(fmt.Sprintf("failed to delete local thumbnail with path %s", thumbnailPath), zap.Error(err))
|
||||
}
|
||||
|
||||
resourceDelete := &api.ResourceDelete{
|
||||
|
@ -434,7 +436,7 @@ func (s *Server) registerResourcePublicRoutes(g *echo.Group) {
|
|||
|
||||
if c.QueryParam("thumbnail") == "1" && common.HasPrefixes(resource.Type, "image/png", "image/jpeg") {
|
||||
ext := filepath.Ext(filename)
|
||||
thumbnailPath := path.Join(s.Profile.Data, thumbnailImagePath, resource.PublicID+ext)
|
||||
thumbnailPath := path.Join(s.Profile.Data, thumbnailImagePath, fmt.Sprintf("%d-%s%s", resource.ID, resource.PublicID, ext))
|
||||
thumbnailBlob, err := getOrGenerateThumbnailImage(blob, thumbnailPath)
|
||||
if err != nil {
|
||||
log.Warn(fmt.Sprintf("failed to get or generate local thumbnail with path %s", thumbnailPath), zap.Error(err))
|
||||
|
|
|
@ -9,10 +9,10 @@ import (
|
|||
|
||||
// Version is the service current released version.
|
||||
// Semantic versioning: https://semver.org/
|
||||
var Version = "0.13.0"
|
||||
var Version = "0.13.1"
|
||||
|
||||
// DevVersion is the service current development version.
|
||||
var DevVersion = "0.13.0"
|
||||
var DevVersion = "0.13.1"
|
||||
|
||||
func GetCurrentVersion(mode string) string {
|
||||
if mode == "dev" || mode == "demo" {
|
||||
|
|
|
@ -3,7 +3,6 @@ ALTER TABLE
|
|||
ADD
|
||||
COLUMN public_id TEXT NOT NULL DEFAULT '';
|
||||
|
||||
-- TODO(steven): remove this in next release.
|
||||
CREATE UNIQUE INDEX resource_id_public_id_unique_index ON resource (id, public_id);
|
||||
|
||||
UPDATE
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/usememos/memos/common"
|
||||
)
|
||||
|
@ -90,6 +91,10 @@ func (s *Store) CreateMemo(ctx context.Context, create *MemoMessage) (*MemoMessa
|
|||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
if create.CreatedTs == 0 {
|
||||
create.CreatedTs = time.Now().Unix()
|
||||
}
|
||||
|
||||
query := `
|
||||
INSERT INTO memo (
|
||||
creator_id,
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
import { test } from "@playwright/test";
|
||||
import { signUp } from "./utils";
|
||||
|
||||
test.use({
|
||||
locale: "en-US",
|
||||
timezoneId: "Europe/Berlin",
|
||||
});
|
||||
|
||||
test.describe("Sign up a host account", async () => {
|
||||
test("Sign Up", async ({ page }) => {
|
||||
await signUp(page, "admin", "admin");
|
||||
});
|
||||
});
|
|
@ -1,37 +0,0 @@
|
|||
import { test, expect } from "@playwright/test";
|
||||
import { review, login, writeMemo } from "./utils";
|
||||
import randomstring from "randomstring";
|
||||
|
||||
test.use({
|
||||
locale: "en-US",
|
||||
timezoneId: "Europe/Berlin",
|
||||
});
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await login(page, "admin", "admin");
|
||||
});
|
||||
|
||||
test.describe("Write some memos", async () => {
|
||||
test("Write memos", async ({ page }) => {
|
||||
const content = `${randomstring.generate()} from Write memos`;
|
||||
await writeMemo(page, content);
|
||||
await expect(page.getByText(content)).toBeVisible();
|
||||
});
|
||||
|
||||
test("Write memos with Tag", async ({ page }) => {
|
||||
const tag = randomstring.generate(5);
|
||||
const content = `#${tag} ${randomstring.generate()} from Write memos with Tag`;
|
||||
await writeMemo(page, content);
|
||||
// 1.memo contentg 2.tags list of memos editor 3.tags list
|
||||
await expect(page.getByText(tag)).toHaveCount(3);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe("Daily Review", async () => {
|
||||
test("Daily Review", async ({ page }) => {
|
||||
const content = randomstring.generate();
|
||||
await writeMemo(page, content);
|
||||
await review(page);
|
||||
await expect(page.getByText(content)).toBeVisible();
|
||||
});
|
||||
});
|
|
@ -1,2 +0,0 @@
|
|||
const baseHost = "http://localhost:5230";
|
||||
export { baseHost };
|
|
@ -1,52 +0,0 @@
|
|||
import { expect, Page } from "@playwright/test";
|
||||
import locale from "../src/locales/en.json";
|
||||
import { baseHost } from "./fixtures";
|
||||
|
||||
async function screenshot(page: Page, name: string) {
|
||||
await page.screenshot({ path: `playwright-screenshot/${name}.png`, fullPage: true });
|
||||
}
|
||||
|
||||
async function writeMemo(page: Page, content: string) {
|
||||
await expect(page.getByRole("button", { name: locale.editor.save })).toBeDisabled();
|
||||
await page.getByPlaceholder("Any thoughts...").fill(content);
|
||||
await expect(page.getByRole("button", { name: locale.editor.save })).toBeEnabled();
|
||||
await page.getByRole("button", { name: locale.editor.save }).click();
|
||||
}
|
||||
|
||||
async function login(page: Page, username: string, password: string) {
|
||||
page.goto(`${baseHost}/`);
|
||||
await screenshot(page, "explore-page");
|
||||
await page.waitForURL("**/explore");
|
||||
await screenshot(page, "explore-page-after-wait");
|
||||
await page.getByRole("link", { name: locale.common["sign-in"] }).click();
|
||||
await screenshot(page, "auth-page");
|
||||
await page.waitForURL("**/auth");
|
||||
await page.locator('input[type="text"]').click();
|
||||
await page.locator('input[type="text"]').fill(username);
|
||||
await page.locator('input[type="password"]').click();
|
||||
await page.locator('input[type="password"]').fill(password);
|
||||
await page.getByRole("button", { name: locale.common["sign-in"] }).click();
|
||||
await page.waitForTimeout(1000);
|
||||
await screenshot(page, "home-page-login-success");
|
||||
}
|
||||
|
||||
async function signUp(page: Page, username: string, password: string) {
|
||||
await page.goto(`${baseHost}/`);
|
||||
await page.waitForURL("**/auth");
|
||||
await screenshot(page, "sign-up-page");
|
||||
await page.locator('input[type="text"]').click();
|
||||
await page.locator('input[type="text"]').fill(username);
|
||||
await page.locator('input[type="password"]').click();
|
||||
await page.locator('input[type="password"]').fill(password);
|
||||
await page.getByRole("button", { name: locale.auth["signup-as-host"] }).click();
|
||||
await page.waitForTimeout(1000);
|
||||
await screenshot(page, "home-page-sign-up-success");
|
||||
}
|
||||
|
||||
async function review(page: Page) {
|
||||
await page.goto(`${baseHost}/`);
|
||||
await page.getByRole("link", { name: locale["daily-review"]["title"] }).click();
|
||||
await screenshot(page, "review");
|
||||
}
|
||||
|
||||
export { writeMemo, login, signUp, review };
|
|
@ -35,7 +35,6 @@
|
|||
"zustand": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.32.2",
|
||||
"@types/lodash-es": "^4.17.5",
|
||||
"@types/node": "^18.0.3",
|
||||
"@types/qs": "^6.9.7",
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
export default defineConfig({
|
||||
testDir: "./e2e-tests",
|
||||
fullyParallel: true,
|
||||
forbidOnly: !!process.env.CI,
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
workers: 1,
|
||||
reporter: [["html", { outputFolder: "playwright-report", open: "never" }]],
|
||||
use: {
|
||||
trace: "on-first-retry",
|
||||
},
|
||||
projects: [
|
||||
{
|
||||
name: "chromium",
|
||||
use: { ...devices["Desktop Chrome"] },
|
||||
},
|
||||
],
|
||||
});
|
|
@ -78,9 +78,6 @@ dependencies:
|
|||
version: 4.3.6(react@18.2.0)
|
||||
|
||||
devDependencies:
|
||||
'@playwright/test':
|
||||
specifier: ^1.32.2
|
||||
version: 1.32.2
|
||||
'@types/lodash-es':
|
||||
specifier: ^4.17.5
|
||||
version: 4.17.5
|
||||
|
@ -784,17 +781,6 @@ packages:
|
|||
'@nodelib/fs.scandir': 2.1.5
|
||||
fastq: 1.15.0
|
||||
|
||||
/@playwright/test@1.32.2:
|
||||
resolution: {integrity: sha512-nhaTSDpEdTTttdkDE8Z6K3icuG1DVRxrl98Qq0Lfc63SS9a2sjc9+x8ezysh7MzCKz6Y+nArml3/mmt+gqRmQQ==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
'@types/node': 18.0.3
|
||||
playwright-core: 1.32.2
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
||||
/@popperjs/core@2.11.7:
|
||||
resolution: {integrity: sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==}
|
||||
dev: false
|
||||
|
@ -2944,12 +2930,6 @@ packages:
|
|||
dev: true
|
||||
optional: true
|
||||
|
||||
/playwright-core@1.32.2:
|
||||
resolution: {integrity: sha512-zD7aonO+07kOTthsrCR3YCVnDcqSHIJpdFUtZEMOb6//1Rc7/6mZDRdw+nlzcQiQltOOsiqI3rrSyn/SlyjnJQ==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
dev: true
|
||||
|
||||
/postcss-import@14.1.0(postcss@8.4.21):
|
||||
resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
|
|
@ -1,17 +1,4 @@
|
|||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Input,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Modal,
|
||||
ModalClose,
|
||||
ModalDialog,
|
||||
Stack,
|
||||
Textarea,
|
||||
Typography,
|
||||
} from "@mui/joy";
|
||||
import { Button, FormControl, Input, Modal, ModalClose, ModalDialog, Stack, Textarea, Typography } from "@mui/joy";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { toast } from "react-hot-toast";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
@ -19,11 +6,11 @@ import * as api from "@/helpers/api";
|
|||
import useLoading from "@/hooks/useLoading";
|
||||
import { marked } from "@/labs/marked";
|
||||
import { useMessageStore } from "@/store/zustand/message";
|
||||
import { defaultMessageGroup, MessageGroup, useMessageGroupStore } from "@/store/zustand/message-group";
|
||||
import Icon from "./Icon";
|
||||
import { generateDialog } from "./Dialog";
|
||||
import showSettingDialog from "./SettingDialog";
|
||||
import { defaultMessageGroup, MessageGroup, useMessageGroupStore } from "@/store/zustand/message-group";
|
||||
import { PlusIcon, Trash2Icon } from "lucide-react";
|
||||
import Selector from "./kit/Selector";
|
||||
|
||||
type Props = DialogProps;
|
||||
|
||||
|
@ -92,26 +79,21 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
|
|||
});
|
||||
};
|
||||
|
||||
const [anchorEl, setAnchorEl] = useState<null | (EventTarget & Element)>(null);
|
||||
const handleMenuOpen = (event: React.SyntheticEvent) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
};
|
||||
const handleMenuClose = () => {
|
||||
setAnchorEl(null);
|
||||
};
|
||||
const handleOptionSelect = (option: MessageGroup) => {
|
||||
setMessageGroup(option);
|
||||
setAnchorEl(null);
|
||||
const handleMessageGroupSelect = (value: string) => {
|
||||
const messageGroup = messageGroupList.find((group) => group.messageStorageId === value);
|
||||
if (messageGroup) {
|
||||
setMessageGroup(messageGroup);
|
||||
}
|
||||
};
|
||||
|
||||
const [isAddMessageGroupDlgOpen, setIsAddMessageGroupDlgOpen] = useState<boolean>(false);
|
||||
const [isAddMessageGroupDialogOpen, setIsAddMessageGroupDialogOpen] = useState<boolean>(false);
|
||||
const [groupName, setGroupName] = useState<string>("");
|
||||
|
||||
const messageGroupStore = useMessageGroupStore();
|
||||
const messageGroupList = messageGroupStore.groupList;
|
||||
|
||||
const handleOpenDialog = () => {
|
||||
setIsAddMessageGroupDlgOpen(true);
|
||||
setIsAddMessageGroupDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleRemoveDialog = () => {
|
||||
|
@ -119,7 +101,7 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
|
|||
};
|
||||
|
||||
const handleCloseDialog = () => {
|
||||
setIsAddMessageGroupDlgOpen(false);
|
||||
setIsAddMessageGroupDialogOpen(false);
|
||||
setGroupName("");
|
||||
};
|
||||
|
||||
|
@ -142,29 +124,24 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
|
|||
<div className="dialog-header-container">
|
||||
<p className="title-text flex flex-row items-center">
|
||||
<Icon.Bot className="mr-1 w-5 h-auto opacity-80" />
|
||||
{t("ask-ai.title")}
|
||||
<span className="button-group" style={{ marginLeft: "10px" }}>
|
||||
<Button color={"primary"} onClick={handleMenuOpen}>
|
||||
<div className="button-len-max-150">{messageGroup.name}</div>
|
||||
</Button>
|
||||
<Button color={"success"} onClick={handleOpenDialog}>
|
||||
<PlusIcon size={"13px"} />
|
||||
</Button>
|
||||
<Button color={"danger"} onClick={handleRemoveDialog}>
|
||||
<Trash2Icon size={"13px"} />
|
||||
</Button>
|
||||
<span className="mr-4">{t("ask-ai.title")}</span>
|
||||
<span className="flex flex-row justify-start items-center">
|
||||
<Selector
|
||||
className="w-32"
|
||||
dataSource={messageGroupList.map((item) => ({ text: item.name, value: item.messageStorageId }))}
|
||||
value={messageGroup.messageStorageId}
|
||||
handleValueChanged={handleMessageGroupSelect}
|
||||
/>
|
||||
<button className="btn-text px-1 ml-1" onClick={handleOpenDialog}>
|
||||
<Icon.Plus className="w-4 h-auto" />
|
||||
</button>
|
||||
<button className="btn-text px-1" onClick={handleRemoveDialog}>
|
||||
<Icon.Trash2 className="w-4 h-auto" />
|
||||
</button>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
||||
<MenuItem onClick={() => handleOptionSelect(defaultMessageGroup)}>{defaultMessageGroup.name}</MenuItem>
|
||||
{messageGroupList.map((messageGroup, index) => (
|
||||
<MenuItem key={index} onClick={() => handleOptionSelect(messageGroup)}>
|
||||
{messageGroup.name}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Menu>
|
||||
<Modal open={isAddMessageGroupDlgOpen} onClose={handleCloseDialog}>
|
||||
<Modal open={isAddMessageGroupDialogOpen} onClose={handleCloseDialog}>
|
||||
<ModalDialog aria-labelledby="basic-modal-dialog-title" sx={{ maxWidth: 500 }}>
|
||||
<ModalClose />
|
||||
<Typography id="basic-modal-dialog-title" component="h2">
|
||||
|
@ -172,19 +149,18 @@ const AskAIDialog: React.FC<Props> = (props: Props) => {
|
|||
</Typography>
|
||||
<Stack spacing={2}>
|
||||
<FormControl>
|
||||
<FormLabel>{t("ask-ai.label-message-group-name-title")}</FormLabel>
|
||||
<Input
|
||||
value={groupName}
|
||||
onChange={(e) => setGroupName(e.target.value)}
|
||||
placeholder={t("ask-ai.label-message-group-name-title")}
|
||||
/>
|
||||
</FormControl>
|
||||
<Typography>
|
||||
<Button onClick={handleCancel} style={{ marginRight: "10px" }}>
|
||||
<div className="w-full flex justify-end gap-x-2">
|
||||
<Button variant="plain" onClick={handleCancel}>
|
||||
{t("common.cancel")}
|
||||
</Button>
|
||||
<Button onClick={handleAddMessageGroupDlgConfirm}>{t("common.confirm")}</Button>
|
||||
</Typography>
|
||||
</div>
|
||||
</Stack>
|
||||
</ModalDialog>
|
||||
</Modal>
|
||||
|
|
|
@ -282,7 +282,7 @@ const Memo: React.FC<Props> = (props: Props) => {
|
|||
|
||||
{showRelatedMemos && relatedMemoList.length > 0 && (
|
||||
<>
|
||||
<p className="text-sm mt-4 mb-1 pl-4 opacity-50 flex flex-row items-center">
|
||||
<p className="text-sm dark:text-gray-300 mt-4 mb-1 pl-4 opacity-50 flex flex-row items-center">
|
||||
<Icon.Link className="w-4 h-auto mr-1" />
|
||||
<span>Related memos</span>
|
||||
</p>
|
||||
|
|
|
@ -28,7 +28,7 @@ const MobileHeader = (props: Props) => {
|
|||
}, [filter, shortcuts]);
|
||||
|
||||
return (
|
||||
<div className="sticky top-0 pt-4 pb-1 mb-1 backdrop-blur-sm flex md:hidden flex-row justify-between items-center w-full h-auto flex-nowrap shrink-0 z-2">
|
||||
<div className="sticky top-0 pt-4 pb-1 mb-1 backdrop-blur bg-zinc-100 dark:bg-zinc-800 bg-opacity-70 flex md:hidden flex-row justify-between items-center w-full h-auto flex-nowrap shrink-0 z-2">
|
||||
<div className="flex flex-row justify-start items-center mr-2 shrink-0 overflow-hidden">
|
||||
<div
|
||||
className="flex sm:hidden flex-row justify-center items-center w-6 h-6 mr-1 shrink-0 bg-transparent"
|
||||
|
|
|
@ -1,38 +1,11 @@
|
|||
html,
|
||||
body {
|
||||
@apply text-base w-full h-full dark:bg-zinc-800;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Noto Sans", "Noto Sans CJK SC",
|
||||
"Microsoft YaHei UI", "Microsoft YaHei", "WenQuanYi Micro Hei", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji",
|
||||
sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "PingFang SC", "Noto Sans",
|
||||
"Noto Sans CJK SC", "Microsoft YaHei UI", "Microsoft YaHei", "WenQuanYi Micro Hei", "Apple Color Emoji", "Segoe UI Emoji",
|
||||
"Segoe UI Symbol", "Noto Color Emoji", sans-serif;
|
||||
}
|
||||
|
||||
#root {
|
||||
@apply w-full h-full;
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
gap: 0; /* 按钮之间的间距 */
|
||||
}
|
||||
|
||||
.button-group>button:not(:first-child):not(:last-child) {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.button-group>button:first-child {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.button-group>button:last-child {
|
||||
border-bottom-left-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
|
||||
|
||||
.button-len-max-150 {
|
||||
max-width: 150px; /* 按钮的最大宽度 */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue