renove mails
This commit is contained in:
parent
960f889e92
commit
05f961da62
|
|
@ -1 +1 @@
|
|||
VITE_API_KEY = ''
|
||||
VITE_API_KEY = '@yQveRWre@b(!_9HmL'
|
||||
31
README.md
31
README.md
|
|
@ -1,30 +1,3 @@
|
|||
# React + TypeScript + Vite
|
||||
DELETE https://zulip.ipsupply.com.au/apac-custom/api/truncate-notes?key={key}
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||
|
||||
- Configure the top-level `parserOptions` property like this:
|
||||
|
||||
```js
|
||||
export default {
|
||||
// other rules...
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
project: ['./tsconfig.json', './tsconfig.node.json'],
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
|
||||
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
|
||||
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
|
||||
GET https://zulip.ipsupply.com.au/apac-custom/api/getAllNotes?key={key}
|
||||
|
|
|
|||
|
|
@ -119,7 +119,9 @@ function createWindow() {
|
|||
}
|
||||
|
||||
function createTray() {
|
||||
tray = new Tray(path.join(process.env.VITE_PUBLIC, "assets", "icon16x16.png"));
|
||||
tray = new Tray(
|
||||
path.join(process.env.VITE_PUBLIC, "assets", "icon16x16.png")
|
||||
);
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
|
|
@ -228,5 +230,13 @@ ipcMain.handle("show-notification", async (_, { title, body }) => {
|
|||
body,
|
||||
silent: false,
|
||||
});
|
||||
|
||||
notification.on("click", () => {
|
||||
if (win && !isQuiting) {
|
||||
win.show();
|
||||
win.focus();
|
||||
}
|
||||
});
|
||||
|
||||
notification.show();
|
||||
});
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 168 KiB |
|
|
@ -1,12 +1,12 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { Route, HashRouter as Router, Routes } from "react-router-dom";
|
||||
import { MailsPage, MainPage } from "./pages";
|
||||
import { MainPage } from "./pages";
|
||||
function App() {
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/" element={<MainPage />} />
|
||||
<Route path="/mails" element={<MailsPage />} />
|
||||
{/* <Route path="/mails" element={<MailsPage />} /> */}
|
||||
</Routes>
|
||||
</Router>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,67 +1,100 @@
|
|||
import { ActionIcon, Box, Paper, Textarea, Tooltip } from '@mantine/core';
|
||||
import { useDisclosure } from '@mantine/hooks';
|
||||
import { IconEye, IconEyeOff, IconTrash } from '@tabler/icons-react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { ActionIcon, Box, Paper, Textarea, Tooltip } from "@mantine/core";
|
||||
import { useDisclosure } from "@mantine/hooks";
|
||||
import { IconEye, IconEyeOff, IconTrash } from "@tabler/icons-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
import dayjs from 'dayjs'
|
||||
import dayjs from "dayjs";
|
||||
|
||||
export interface IMessageProps {
|
||||
data: IMessage;
|
||||
onDelete: () => void
|
||||
data: IMessage;
|
||||
onDelete: () => void;
|
||||
}
|
||||
|
||||
export default function Message({ data, onDelete }: IMessageProps) {
|
||||
const [show, { toggle }] = useDisclosure(true);
|
||||
const [show, { toggle }] = useDisclosure(true);
|
||||
|
||||
const [animation, setAnimation] = useState(false);
|
||||
const [animation, setAnimation] = useState(false);
|
||||
|
||||
const messageContent = useMemo(() => {
|
||||
return show ? data.message : data.message.replace(/./g, '•');
|
||||
}, [data.message, show]);
|
||||
const messageContent = useMemo(() => {
|
||||
return show ? data.message : data.message.replace(/./g, "•");
|
||||
}, [data.message, show]);
|
||||
|
||||
// Tắt hiệu ứng nhấp nháy sau 1 giây
|
||||
useEffect(() => {
|
||||
if (!animation) return;
|
||||
// Tắt hiệu ứng nhấp nháy sau 1 giây
|
||||
useEffect(() => {
|
||||
if (!animation) return;
|
||||
|
||||
const timer = setTimeout(() => {
|
||||
setAnimation(false);
|
||||
}, 5000);
|
||||
return () => clearTimeout(timer);
|
||||
}, [animation]);
|
||||
const timer = setTimeout(() => {
|
||||
setAnimation(false);
|
||||
}, 5000);
|
||||
return () => clearTimeout(timer);
|
||||
}, [animation]);
|
||||
|
||||
useEffect(() => {
|
||||
const showAnimation = new Date().getTime() - new Date(data.time).getTime() <= 110000;
|
||||
useEffect(() => {
|
||||
const showAnimation =
|
||||
new Date().getTime() - new Date(data.time).getTime() <= 110000;
|
||||
|
||||
setAnimation(showAnimation);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
setAnimation(showAnimation);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Paper
|
||||
key={data.message}
|
||||
shadow="xs"
|
||||
radius="md"
|
||||
p="xs"
|
||||
withBorder
|
||||
className={`flex items-center relative gap-3 w-full transition-all duration-300 ${animation ? 'animate-pulse !bg-blue-100' : ''}`}
|
||||
>
|
||||
<div className="flex items-center gap-2 w-full">
|
||||
<Textarea defaultValue={messageContent} value={messageContent} className="flex-1" variant="unstyled" readOnly />
|
||||
<Box className="flex gap-1">
|
||||
<Tooltip label="View">
|
||||
<ActionIcon onClick={toggle} variant="light" size="sm">
|
||||
{show ? <IconEyeOff size={16} /> : <IconEye size={16} />}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Delete">
|
||||
<ActionIcon onClick={onDelete} variant="light" size="sm" color="red">
|
||||
<IconTrash size={16} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
return (
|
||||
<Tooltip
|
||||
position="top-start"
|
||||
style={{
|
||||
fontSize: "11px",
|
||||
}}
|
||||
label={`Sender: ${data.sender}`}
|
||||
>
|
||||
<Paper
|
||||
key={data.message}
|
||||
shadow="xs"
|
||||
radius="md"
|
||||
p="xs"
|
||||
withBorder
|
||||
className={`flex items-center relative gap-3 w-full transition-all duration-300 ${
|
||||
animation ? "animate-pulse !bg-blue-100" : ""
|
||||
}`}
|
||||
>
|
||||
<div className="flex items-center w-full flex-col">
|
||||
<Box className="flex items-center justify-between w-full">
|
||||
<div className="text-xs">
|
||||
{data.recipient.length > 0 && <span>{data.recipient}</span>}
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-xs">
|
||||
{dayjs(data.time).format("HH:mm D/M/YYYY")}
|
||||
</span>
|
||||
</div>
|
||||
</Box>
|
||||
|
||||
<span className='absolute top-1 right-2 text-xs'>{dayjs(data.time).format('HH:ss:mm DD/MM/YYYY')}</span>
|
||||
</Paper>
|
||||
);
|
||||
<Box className="w-full flex items-center">
|
||||
<Textarea
|
||||
defaultValue={messageContent}
|
||||
value={messageContent}
|
||||
className="flex-1"
|
||||
variant="unstyled"
|
||||
readOnly
|
||||
/>
|
||||
<Box className="flex gap-2">
|
||||
<Tooltip label="View">
|
||||
<ActionIcon onClick={toggle} variant="light" size="sm">
|
||||
{show ? <IconEyeOff size={16} /> : <IconEye size={16} />}
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
<Tooltip label="Delete">
|
||||
<ActionIcon
|
||||
onClick={onDelete}
|
||||
variant="light"
|
||||
size="sm"
|
||||
color="red"
|
||||
>
|
||||
<IconTrash size={16} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Box>
|
||||
</Box>
|
||||
</div>
|
||||
</Paper>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { Menu } from "@mantine/core";
|
||||
import { IconCode, IconRefresh } from "@tabler/icons-react";
|
||||
import { Menu, useMantineColorScheme } from "@mantine/core";
|
||||
import { IconCode, IconMoon, IconRefresh, IconSun } from "@tabler/icons-react";
|
||||
import { ReactNode } from "react";
|
||||
export interface ISettingsProps {
|
||||
children: ReactNode;
|
||||
funtions?: {
|
||||
refresh?: () => void
|
||||
}
|
||||
refresh?: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
export default function Settings({ children, funtions }: ISettingsProps) {
|
||||
|
|
@ -13,18 +13,34 @@ export default function Settings({ children, funtions }: ISettingsProps) {
|
|||
window.ipcRenderer?.openDevTools();
|
||||
};
|
||||
|
||||
const {toggleColorScheme, colorScheme}= useMantineColorScheme()
|
||||
return (
|
||||
<Menu shadow="md" width={200}>
|
||||
<Menu.Target>{children}</Menu.Target>
|
||||
|
||||
<Menu.Dropdown className="text-xs">
|
||||
<Menu.Label className="text-xs">Application</Menu.Label>
|
||||
<Menu.Item onClick={openDevtools} leftSection={<IconCode size={12} />} className="text-xs">
|
||||
<Menu.Item
|
||||
onClick={openDevtools}
|
||||
leftSection={<IconCode size={12} />}
|
||||
className="text-xs"
|
||||
>
|
||||
Devtool
|
||||
</Menu.Item>
|
||||
<Menu.Item onClick={funtions?.refresh} leftSection={<IconRefresh size={12} />} className="text-xs">
|
||||
<Menu.Item
|
||||
onClick={funtions?.refresh}
|
||||
leftSection={<IconRefresh size={12} />}
|
||||
className="text-xs"
|
||||
>
|
||||
Refresh
|
||||
</Menu.Item>
|
||||
<Menu.Item
|
||||
onClick={toggleColorScheme}
|
||||
leftSection={colorScheme === 'dark'?<IconSun size={12} />: <IconMoon size={12}/>}
|
||||
className="text-xs flex items-center capitalize"
|
||||
>
|
||||
{colorScheme === 'dark' ? 'Light' : 'Dark'}
|
||||
</Menu.Item>
|
||||
</Menu.Dropdown>
|
||||
</Menu>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import '@mantine/core/styles.css';
|
|||
import "./index.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<MantineProvider >
|
||||
<MantineProvider defaultColorScheme="dark">
|
||||
<App />
|
||||
</MantineProvider>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import {
|
||||
ActionIcon,
|
||||
Avatar,
|
||||
Box,
|
||||
LoadingOverlay,
|
||||
ScrollAreaAutosize,
|
||||
Tooltip,
|
||||
useMantineColorScheme
|
||||
} from "@mantine/core";
|
||||
import { useDisclosure, useViewportSize } from "@mantine/hooks"; // 🔥 thêm cái này
|
||||
import { IconDotsVertical, IconMail } from "@tabler/icons-react";
|
||||
import { IconDotsVertical } from "@tabler/icons-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { ConfirmModal, Message, Settings } from "../components";
|
||||
import { showNotification } from "../ultils/fn";
|
||||
|
|
@ -19,6 +21,9 @@ function MainPage() {
|
|||
const [opened, { open, close }] = useDisclosure(false);
|
||||
const { height } = useViewportSize(); // lấy kích thước cửa sổ
|
||||
const [clickMessage, setClickMessage] = useState<IMessage | null>(null);
|
||||
|
||||
const {colorScheme} = useMantineColorScheme();
|
||||
|
||||
const scrollToBottom = () => {
|
||||
const viewport = viewportRef.current;
|
||||
if (viewport) {
|
||||
|
|
@ -50,7 +55,7 @@ function MainPage() {
|
|||
close();
|
||||
|
||||
showNotification("Thông báo", "Đã xóa note: " + clickMessage?.id);
|
||||
setClickMessage(null)
|
||||
setClickMessage(null);
|
||||
} catch (error) {
|
||||
console.log("%csrc/pages/mails.tsx:88 error", "color: #007acc;", error);
|
||||
} finally {
|
||||
|
|
@ -81,9 +86,9 @@ function MainPage() {
|
|||
});
|
||||
}, []);
|
||||
|
||||
const openMailsWindow = () => {
|
||||
window.ipcRenderer.invoke("open-new-window");
|
||||
};
|
||||
// const openMailsWindow = () => {
|
||||
// window.ipcRenderer.invoke("open-new-window");
|
||||
// };
|
||||
|
||||
// 🧠 Tính chiều cao dynamic (ví dụ trừ header 80px + padding 30px)
|
||||
const scrollAreaHeight = height - 70; // Bạn chỉnh số này nếu muốn
|
||||
|
|
@ -91,19 +96,24 @@ function MainPage() {
|
|||
return (
|
||||
<Box className="flex flex-col !overflow-hidden h-full">
|
||||
{/* Header */}
|
||||
<header className="p-4 flex items-center justify-between sticky top-0 border-b border-gray-100 pb-2 bg-white z-10">
|
||||
<Tooltip label="Mail">
|
||||
<header className={`px-4 py-2 flex items-center justify-between sticky top-0 border-b z-10 ${colorScheme === 'dark' ? '': 'border-gray-100 pb-2 bg-white'}`}>
|
||||
{/* <Tooltip label="Mail">
|
||||
<ActionIcon
|
||||
onClick={openMailsWindow}
|
||||
onClick={() => {
|
||||
|
||||
showNotification('test', 'test', scrollToBottom)
|
||||
}}
|
||||
variant="light"
|
||||
radius="md"
|
||||
size="lg"
|
||||
>
|
||||
<IconMail size={18} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
</Tooltip> */}
|
||||
|
||||
<div className="text-xl font-semibold">Zulip Notes</div>
|
||||
<Avatar radius="sm" src={"./assets/icon-1.png"} />
|
||||
{/*
|
||||
<div className="text-xl font-semibold">Zulip Notes</div> */}
|
||||
|
||||
<Settings
|
||||
funtions={{
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
export async function showNotification(title: string, body: string) {
|
||||
export async function showNotification(
|
||||
title: string,
|
||||
body: string,
|
||||
callback?: () => void
|
||||
) {
|
||||
await window.ipcRenderer.invoke("show-notification", {
|
||||
title,
|
||||
body,
|
||||
});
|
||||
callback?.();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ interface IMessage {
|
|||
sender: string;
|
||||
time: number;
|
||||
message: string;
|
||||
recipient:string
|
||||
}
|
||||
|
||||
interface IEmail {
|
||||
|
|
|
|||
Loading…
Reference in New Issue