chore: upgrade version 0.13.1 (#1754)

This commit is contained in:
boojack 2023-05-27 09:09:41 +08:00 committed by GitHub
parent 93d608f050
commit 2e34ce90a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 48 additions and 303 deletions

View file

@ -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)

View file

@ -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))

View file

@ -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" {

View file

@ -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

View file

@ -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,

View file

@ -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");
});
});

View file

@ -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();
});
});

View file

@ -1,2 +0,0 @@
const baseHost = "http://localhost:5230";
export { baseHost };

View file

@ -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 };

View file

@ -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",

View file

@ -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"] },
},
],
});

View file

@ -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'}

View file

@ -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>

View file

@ -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>

View file

@ -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"

View file

@ -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;
}