Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3743b23613 | |||
| e6e243c14d |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/mail-webui",
|
"name": "@govoplan/mail-webui",
|
||||||
"version": "0.1.1",
|
"version": "0.1.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "webui/src/index.ts",
|
"main": "webui/src/index.ts",
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.1",
|
"@govoplan/core-webui": "^0.1.4",
|
||||||
"lucide-react": "^0.555.0",
|
"lucide-react": "^0.555.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
|||||||
@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "govoplan-mail"
|
name = "govoplan-mail"
|
||||||
version = "0.1.2"
|
version = "0.1.4"
|
||||||
description = "GovOPlaN mail module with backend and WebUI integration."
|
description = "GovOPlaN mail module with backend and WebUI integration."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
license = { file = "LICENSE" }
|
license = { file = "LICENSE" }
|
||||||
authors = [{ name = "GovOPlaN" }]
|
authors = [{ name = "GovOPlaN" }]
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"govoplan-core>=0.1.2",
|
"govoplan-core>=0.1.4",
|
||||||
"pydantic>=2,<3",
|
"pydantic>=2,<3",
|
||||||
"redis>=5,<6",
|
"redis>=5,<6",
|
||||||
"SQLAlchemy>=2,<3",
|
"SQLAlchemy>=2,<3",
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ def _mail_router(context: ModuleContext):
|
|||||||
manifest = ModuleManifest(
|
manifest = ModuleManifest(
|
||||||
id="mail",
|
id="mail",
|
||||||
name="Mail",
|
name="Mail",
|
||||||
version="1.0.0",
|
version="0.1.4",
|
||||||
dependencies=("access",),
|
dependencies=("access",),
|
||||||
optional_dependencies=(),
|
optional_dependencies=(),
|
||||||
permissions=PERMISSIONS,
|
permissions=PERMISSIONS,
|
||||||
@@ -90,4 +90,3 @@ manifest = ModuleManifest(
|
|||||||
|
|
||||||
def get_manifest() -> ModuleManifest:
|
def get_manifest() -> ModuleManifest:
|
||||||
return manifest
|
return manifest
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@govoplan/mail-webui",
|
"name": "@govoplan/mail-webui",
|
||||||
"version": "0.1.1",
|
"version": "0.1.4",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "src/index.ts",
|
"main": "src/index.ts",
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"./styles/mail-profiles.css": "./src/styles/mail-profiles.css"
|
"./styles/mail-profiles.css": "./src/styles/mail-profiles.css"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.1",
|
"@govoplan/core-webui": "^0.1.4",
|
||||||
"lucide-react": "^0.555.0",
|
"lucide-react": "^0.555.0",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ export default function MailboxPage({ settings }: { settings: ApiSettings }) {
|
|||||||
const [messages, setMessages] = useState<MailMailboxMessageSummary[]>([]);
|
const [messages, setMessages] = useState<MailMailboxMessageSummary[]>([]);
|
||||||
const [messageTotalCount, setMessageTotalCount] = useState<number | null>(null);
|
const [messageTotalCount, setMessageTotalCount] = useState<number | null>(null);
|
||||||
const [messagePage, setMessagePage] = useState(1);
|
const [messagePage, setMessagePage] = useState(1);
|
||||||
const [messagePageSize, setMessagePageSize] = useState(50);
|
const [messagePageSize, setMessagePageSize] = useState(10);
|
||||||
const [messageQuery, setMessageQuery] = useState("");
|
const [messageQuery, setMessageQuery] = useState("");
|
||||||
const [selectedMessage, setSelectedMessage] = useState<MailMailboxMessageDetail | null>(null);
|
const [selectedMessage, setSelectedMessage] = useState<MailMailboxMessageDetail | null>(null);
|
||||||
const [selectedMessageKeyState, setSelectedMessageKeyState] = useState("");
|
const [selectedMessageKeyState, setSelectedMessageKeyState] = useState("");
|
||||||
@@ -71,6 +71,37 @@ export default function MailboxPage({ settings }: { settings: ApiSettings }) {
|
|||||||
useEffect(() => { if (selectedProfileId) void loadFolders(selectedProfileId); }, [selectedProfileId]);
|
useEffect(() => { if (selectedProfileId) void loadFolders(selectedProfileId); }, [selectedProfileId]);
|
||||||
useEffect(() => { if (selectedProfileId && selectedFolder && foldersReady) void loadMessages(selectedProfileId, selectedFolder, messagePage, messagePageSize); }, [foldersReady, messagePage, messagePageSize, selectedProfileId, selectedFolder]);
|
useEffect(() => { if (selectedProfileId && selectedFolder && foldersReady) void loadMessages(selectedProfileId, selectedFolder, messagePage, messagePageSize); }, [foldersReady, messagePage, messagePageSize, selectedProfileId, selectedFolder]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handlePreviewShortcut = (event: KeyboardEvent) => {
|
||||||
|
if (isEditableTarget(event.target)) return;
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
if (selectedMessage || selectedMessageKey || pendingMessageKey || detailError) {
|
||||||
|
event.preventDefault();
|
||||||
|
setSelectedMessage(null);
|
||||||
|
setSelectedMessageKeyState("");
|
||||||
|
selectedMessageKeyRef.current = "";
|
||||||
|
setPendingMessageKey("");
|
||||||
|
setDetailError("");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (shellBusy || loadingMessage || filteredMessages.length === 0) return;
|
||||||
|
const selectedIndex = filteredMessages.findIndex((message) => mailboxMessageKey(message.folder || selectedFolder, message.uid) === selectedMessageKey);
|
||||||
|
let nextIndex: number | null = null;
|
||||||
|
if (event.key === "ArrowLeft") nextIndex = selectedIndex > 0 ? selectedIndex - 1 : 0;
|
||||||
|
else if (event.key === "ArrowRight") nextIndex = selectedIndex >= 0 ? Math.min(filteredMessages.length - 1, selectedIndex + 1) : 0;
|
||||||
|
else if (event.key === "Home") nextIndex = 0;
|
||||||
|
else if (event.key === "End") nextIndex = filteredMessages.length - 1;
|
||||||
|
if (nextIndex === null) return;
|
||||||
|
event.preventDefault();
|
||||||
|
const nextMessage = filteredMessages[nextIndex];
|
||||||
|
if (nextMessage) void openMessage(nextMessage);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("keydown", handlePreviewShortcut);
|
||||||
|
return () => window.removeEventListener("keydown", handlePreviewShortcut);
|
||||||
|
}, [detailError, filteredMessages, loadingMessage, pendingMessageKey, selectedFolder, selectedMessage, selectedMessageKey, shellBusy]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const ancestorIds = folderAncestorIds(folderTree, selectedFolder);
|
const ancestorIds = folderAncestorIds(folderTree, selectedFolder);
|
||||||
if (ancestorIds.length === 0) return;
|
if (ancestorIds.length === 0) return;
|
||||||
@@ -458,7 +489,7 @@ function MailboxPagination({ page, pageSize, totalRows, disabled, onPageChange,
|
|||||||
const pageCount = Math.max(1, Math.ceil(totalRows / pageSize));
|
const pageCount = Math.max(1, Math.ceil(totalRows / pageSize));
|
||||||
const first = totalRows === 0 ? 0 : (page - 1) * pageSize + 1;
|
const first = totalRows === 0 ? 0 : (page - 1) * pageSize + 1;
|
||||||
const last = Math.min(totalRows, page * pageSize);
|
const last = Math.min(totalRows, page * pageSize);
|
||||||
const options = [25, 50, 100];
|
const options = [10, 25, 50, 100];
|
||||||
return (
|
return (
|
||||||
<div className="data-grid-pagination mailbox-pagination" aria-label="Mailbox message pagination">
|
<div className="data-grid-pagination mailbox-pagination" aria-label="Mailbox message pagination">
|
||||||
<div className="data-grid-pagination-summary">{first}-{last} of {totalRows}</div>
|
<div className="data-grid-pagination-summary">{first}-{last} of {totalRows}</div>
|
||||||
@@ -549,3 +580,8 @@ function formatBytes(value?: number | null): string {
|
|||||||
function errorText(error: unknown): string {
|
function errorText(error: unknown): string {
|
||||||
return error instanceof Error ? error.message : String(error);
|
return error instanceof Error ? error.message : String(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isEditableTarget(target: EventTarget | null): boolean {
|
||||||
|
if (!(target instanceof HTMLElement)) return false;
|
||||||
|
return target.isContentEditable || ["INPUT", "SELECT", "TEXTAREA"].includes(target.tagName);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user