diff --git a/build/icon.icns b/build/icon.icns new file mode 100644 index 0000000..7668333 Binary files /dev/null and b/build/icon.icns differ diff --git a/build/icon.ico b/build/icon.ico new file mode 100644 index 0000000..21c66c2 Binary files /dev/null and b/build/icon.ico differ diff --git a/build/icon.png b/build/icon.png new file mode 100644 index 0000000..dde0232 Binary files /dev/null and b/build/icon.png differ diff --git a/electron-builder.json5 b/electron-builder.json5 index 4ebb93b..0429429 100644 --- a/electron-builder.json5 +++ b/electron-builder.json5 @@ -1,34 +1,46 @@ -// @see - https://www.electron.build/configuration/configuration { - $schema: 'https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json', - appId: 'b1730d2e-6f3c-4f92-9f4a-9f8e918b40d2', - asar: true, - productName: 'Zulip messages', - directories: { - output: 'release/${version}', - }, - files: ['dist', 'dist-electron'], - mac: { - target: ['dmg'], - artifactName: '${productName}-Mac-${version}-Installer.${ext}', - }, - win: { - target: [ - { - target: 'nsis', - arch: ['x64'], - }, - ], - artifactName: '${productName}-Windows-${version}-Setup.${ext}', - }, - nsis: { - oneClick: false, - perMachine: false, - allowToChangeInstallationDirectory: true, - deleteAppDataOnUninstall: false, - }, - linux: { - target: ['AppImage'], - artifactName: '${productName}-Linux-${version}.${ext}', - }, + "$schema": "https://raw.githubusercontent.com/electron-userland/electron-builder/master/packages/app-builder-lib/scheme.json", + "appId": "b1730d2e-6f3c-4f92-9f4a-9f8e918b40d2", + "productName": "Zulip messages", + "asar": { + "smartUnpack": true + }, + "compression": "maximum", + "directories": { + "output": "release/${version}" + }, + "files": [ + "dist/**", + "dist-electron/**", + "!**/*.map", + "!**/*.ts", + "!**/*.md", + "!**/__tests__/**" + ], + "mac": { + "target": ["dmg"], + "artifactName": "${productName}-Mac-${version}-Installer.${ext}", + "icon": "build/icons/icon.icns" + }, + "win": { + "target": [ + { + "target": "nsis", + "arch": ["x64"] + } + ], + "artifactName": "${productName}-Windows-${version}-Setup.${ext}", + "icon": "build/icons/icon.ico" + }, + "linux": { + "target": ["AppImage"], + "artifactName": "${productName}-Linux-${version}.${ext}", + "icon": "build/icons/icon.png" + }, + "nsis": { + "oneClick": false, + "perMachine": false, + "allowToChangeInstallationDirectory": true, + "deleteAppDataOnUninstall": false + } } diff --git a/electron/main.ts b/electron/main.ts index 953b77f..f7ba552 100644 --- a/electron/main.ts +++ b/electron/main.ts @@ -1,11 +1,24 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { app, BrowserWindow, ipcMain, Menu, Notification, screen } from 'electron'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; -import { io } from 'socket.io-client'; -import { addEmail, deleteEmail, deleteMessage, fetchEmails, fetchMessages } from '../src/apis'; -import { createMailWindow } from './windows/mails.window'; +import { + app, + BrowserWindow, + ipcMain, + Menu, + Notification, + screen, +} from "electron"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { io } from "socket.io-client"; +import { + addEmail, + deleteEmail, + deleteMessage, + fetchEmails, + fetchMessages, +} from "../src/apis"; +import { createMailWindow } from "./windows/mails.window"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // The built directory structure @@ -17,146 +30,156 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url)); // │ │ ├── main.js // │ │ └── preload.mjs // │ -process.env.APP_ROOT = path.join(__dirname, '..'); +process.env.APP_ROOT = path.join(__dirname, ".."); // 🚧 Use ['ENV_NAME'] avoid vite:define plugin - Vite@2.x -export const VITE_DEV_SERVER_URL = process.env['VITE_DEV_SERVER_URL']; -export const MAIN_DIST = path.join(process.env.APP_ROOT, 'dist-electron'); -export const RENDERER_DIST = path.join(process.env.APP_ROOT, 'dist'); +export const VITE_DEV_SERVER_URL = process.env["VITE_DEV_SERVER_URL"]; +export const MAIN_DIST = path.join(process.env.APP_ROOT, "dist-electron"); +export const RENDERER_DIST = path.join(process.env.APP_ROOT, "dist"); -process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL ? path.join(process.env.APP_ROOT, 'public') : RENDERER_DIST; +process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL + ? path.join(process.env.APP_ROOT, "public") + : RENDERER_DIST; let win: BrowserWindow | null; Menu.setApplicationMenu(null); + + function createWindow() { - // Lấy thông tin tất cả các màn hình - const displays = screen.getAllDisplays(); + // Lấy thông tin tất cả các màn hình + const displays = screen.getAllDisplays(); - // Lấy vị trí con trỏ chuột - const cursorPoint = screen.getCursorScreenPoint(); + // Lấy vị trí con trỏ chuột + const cursorPoint = screen.getCursorScreenPoint(); - // Tìm màn hình có chứa con trỏ chuột - let display = displays.find((display) => { - const { x, y, width, height } = display.bounds; - return cursorPoint.x >= x && cursorPoint.x <= x + width && cursorPoint.y >= y && cursorPoint.y <= y + height; - }); + // Tìm màn hình có chứa con trỏ chuột + let display = displays.find((display) => { + const { x, y, width, height } = display.bounds; + return ( + cursorPoint.x >= x && + cursorPoint.x <= x + width && + cursorPoint.y >= y && + cursorPoint.y <= y + height + ); + }); - // Nếu không tìm thấy màn hình chứa con trỏ, sử dụng màn hình chính - if (!display) { - display = screen.getPrimaryDisplay(); - } + // Nếu không tìm thấy màn hình chứa con trỏ, sử dụng màn hình chính + if (!display) { + display = screen.getPrimaryDisplay(); + } - const { width, height } = display.workAreaSize; + const { width, height } = display.workAreaSize; - // Vị trí cửa sổ ở góc phải dưới của màn hình đã chọn + // Vị trí cửa sổ ở góc phải dưới của màn hình đã chọn - win = new BrowserWindow({ - width: 400, - height: 400, - x: 0, // Đặt cửa sổ ở góc phải - y: 0, // Đặt cửa sổ ở góc dưới - alwaysOnTop: true, // Cửa sổ luôn nằm trên các cửa sổ khác - resizable: true, // Không cho phép thay đổi kích thước - icon: path.join(process.env.VITE_PUBLIC, 'electron-vite.svg'), - webPreferences: { - preload: path.join(__dirname, 'preload.mjs'), - }, - }); + win = new BrowserWindow({ + width: 400, + height: 400, + x: 0, // Đặt cửa sổ ở góc phải + y: 0, // Đặt cửa sổ ở góc dưới + alwaysOnTop: true, // Cửa sổ luôn nằm trên các cửa sổ khác + resizable: true, // Không cho phép thay đổi kích thước + icon: path.join(app.isPackaged ? process.resourcesPath : ".", 'build/icons/icon.png'), + // icon: path.join(process.env.VITE_PUBLIC ,'assets', 'icon.png'), + webPreferences: { + preload: path.join(__dirname, "preload.mjs"), + }, + }); - // Test active push message to Renderer-process. - win.webContents.on('did-finish-load', () => { - win?.webContents.send('main-process-message', new Date().toLocaleString()); - }); + // Test active push message to Renderer-process. + win.webContents.on("did-finish-load", () => { + win?.webContents.send("main-process-message", new Date().toLocaleString()); + }); - win.setPosition(width - 400, height - 400); + win.setPosition(width - 400, height - 400); - if (VITE_DEV_SERVER_URL) { - win.loadURL(VITE_DEV_SERVER_URL); - } else { - // win.loadFile('dist/index.html') - win.loadFile(path.join(RENDERER_DIST, 'index.html')); - } + if (VITE_DEV_SERVER_URL) { + win.loadURL(VITE_DEV_SERVER_URL); + } else { + // win.loadFile('dist/index.html') + win.loadFile(path.join(RENDERER_DIST, "index.html")); + } } // Quit when all windows are closed, except on macOS. There, it's common // for applications and their menu bar to stay active until the user quits // explicitly with Cmd + Q. -app.on('window-all-closed', () => { - if (process.platform !== 'darwin') { - app.quit(); - win = null; - } +app.on("window-all-closed", () => { + if (process.platform !== "darwin") { + app.quit(); + win = null; + } }); -app.on('activate', () => { - // On OS X it's common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (BrowserWindow.getAllWindows().length === 0) { - createWindow(); - } +app.on("activate", () => { + // On OS X it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (BrowserWindow.getAllWindows().length === 0) { + createWindow(); + } }); app.whenReady().then(createWindow); // IPC Main Events -ipcMain.on('open-devtools', (event) => { - const webContents = event.sender; - webContents.openDevTools({ mode: 'detach' }); +ipcMain.on("open-devtools", (event) => { + const webContents = event.sender; + webContents.openDevTools({ mode: "detach" }); }); // Xử lý connect socket -ipcMain.handle('connect-socket', async (_) => { - const socket = io('https://zulip.ipsupply.com.au', { - path: '/apac-custom/socket.io', - secure: true, - query: { token: import.meta.env.VITE_API_KEY }, - rejectUnauthorized: false, - }); +ipcMain.handle("connect-socket", async (_) => { + const socket = io("https://zulip.ipsupply.com.au", { + path: "/apac-custom/socket.io", + secure: true, + query: { token: import.meta.env.VITE_API_KEY }, + rejectUnauthorized: false, + }); - if (!socket.connected) { - socket.connect(); - } + if (!socket.connected) { + socket.connect(); + } - socket.on('connect', () => { - console.log(socket.connected); // true - }); + socket.on("connect", () => { + console.log(socket.connected); // true + }); - socket.on('newNote', (data) => { - win?.webContents.send('newNote', data); - }); + socket.on("newNote", (data) => { + win?.webContents.send("newNote", data); + }); }); -ipcMain.handle('fetchMessages', async () => { - return await fetchMessages(); +ipcMain.handle("fetchMessages", async () => { + return await fetchMessages(); }); -ipcMain.handle('fetchEmails', async () => { - return await fetchEmails(); +ipcMain.handle("fetchEmails", async () => { + return await fetchEmails(); }); -ipcMain.handle('open-new-window', async () => { - createMailWindow({ RENDERER_DIST }); +ipcMain.handle("open-new-window", async () => { + createMailWindow({ RENDERER_DIST }); }); -ipcMain.handle('add-email', async (_, email: string) => { - return addEmail(email); +ipcMain.handle("add-email", async (_, email: string) => { + return addEmail(email); }); -ipcMain.handle('del-email', async (_, id: number) => { - return deleteEmail(id); +ipcMain.handle("del-email", async (_, id: number) => { + return deleteEmail(id); }); -ipcMain.handle('del-message', async (_, id: number) => { - return deleteMessage(id); +ipcMain.handle("del-message", async (_, id: number) => { + return deleteMessage(id); }); -ipcMain.handle('show-notification', async (_, { title, body }) => { - const notification = new Notification({ - title, - body, - silent: false, - }); - notification.show(); +ipcMain.handle("show-notification", async (_, { title, body }) => { + const notification = new Notification({ + title, + body, + silent: false, + }); + notification.show(); }); diff --git a/index.html b/index.html index e726060..721880d 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - Notes + Zulip Notes
diff --git a/package-lock.json b/package-lock.json index 2f73f97..1749133 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,9 +13,9 @@ "@mantine/hooks": "^7.17.5", "@tabler/icons-react": "^3.31.0", "@tailwindcss/vite": "^4.1.4", - "@types/ws": "^8.18.1", "axios": "^1.9.0", "bufferutil": "^4.0.9", + "dayjs": "^1.11.13", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^7.5.3", @@ -2292,6 +2292,7 @@ "version": "20.17.32", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.32.tgz", "integrity": "sha512-zeMXFn8zQ+UkjK4ws0RiOC9EWByyW1CcVmLe+2rQocXRsGEDxUCwPEIVgpsGcLHS/P8JkT0oa3839BRABS0oPw==", + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~6.19.2" @@ -2355,15 +2356,6 @@ "license": "MIT", "optional": true }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/yauzl": { "version": "2.10.3", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", @@ -3636,6 +3628,12 @@ "devOptional": true, "license": "MIT" }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -7879,6 +7877,7 @@ "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "devOptional": true, "license": "MIT" }, "node_modules/universalify": { diff --git a/package.json b/package.json index 02cdd57..ad6adca 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,9 @@ "@mantine/hooks": "^7.17.5", "@tabler/icons-react": "^3.31.0", "@tailwindcss/vite": "^4.1.4", - "@types/ws": "^8.18.1", "axios": "^1.9.0", "bufferutil": "^4.0.9", + "dayjs": "^1.11.13", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^7.5.3", diff --git a/public/assets/icon.png b/public/assets/icon.png new file mode 100644 index 0000000..f2959fd Binary files /dev/null and b/public/assets/icon.png differ diff --git a/public/electron-vite.animate.svg b/public/electron-vite.animate.svg deleted file mode 100644 index ea3e777..0000000 --- a/public/electron-vite.animate.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/electron-vite.svg b/public/electron-vite.svg deleted file mode 100644 index 8a6aefe..0000000 --- a/public/electron-vite.svg +++ /dev/null @@ -1,26 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/public/vite.svg b/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/message.tsx b/src/components/message.tsx index 0f38771..9df3085 100644 --- a/src/components/message.tsx +++ b/src/components/message.tsx @@ -3,13 +3,15 @@ import { useDisclosure } from '@mantine/hooks'; import { IconEye, IconEyeOff, IconTrash } from '@tabler/icons-react'; import { useEffect, useMemo, useState } from 'react'; +import dayjs from 'dayjs' + export interface IMessageProps { data: IMessage; onDelete: () => void } export default function Message({ data, onDelete }: IMessageProps) { - const [show, { toggle }] = useDisclosure(false); + const [show, { toggle }] = useDisclosure(true); const [animation, setAnimation] = useState(false); @@ -41,7 +43,7 @@ export default function Message({ data, onDelete }: IMessageProps) { radius="md" p="xs" withBorder - className={`flex items-center gap-3 w-full transition-all duration-300 ${animation ? 'animate-pulse !bg-blue-100' : ''}`} + className={`flex items-center relative gap-3 w-full transition-all duration-300 ${animation ? 'animate-pulse !bg-blue-100' : ''}`} >