From 303b2b1074654a37abd47003a302ec622f6dbe11 Mon Sep 17 00:00:00 2001 From: R3D347HR4Y Date: Thu, 11 Jun 2026 01:22:40 +0200 Subject: [PATCH] wow --- .env.example | 2 + app/api/demo/richtext-save/route.ts | 11 + app/chat/page.tsx | 50 + app/demo/docs/page.tsx | 18 + app/demo/mail/page.tsx | 18 + app/drive/draw/[fileId]/edit/page.tsx | 20 + app/drive/s/[token]/[[...path]]/page.tsx | 65 +- app/drive/s/[token]/edit/[[...path]]/page.tsx | 27 +- app/globals.css | 14 + app/login/page.tsx | 20 +- app/mail/mail-app-shell.tsx | 2 + app/page.tsx | 13 +- .../settings/admin-settings-section-view.tsx | 2 + .../sections/ai-assistant-section.tsx | 99 + components/ai/ai-chat-iframe.tsx | 55 + components/ai/ai-chat-panel.tsx | 46 + components/ai/docs-ai-panel.tsx | 223 ++ components/auth/auth-provider.tsx | 3 +- components/demo/demo-docs-editor.tsx | 51 + components/demo/demo-mail-app.tsx | 496 +++++ components/demo/demo-mail-data.ts | 151 ++ components/drive/drive-app-shell.tsx | 2 + components/drive/drive-new-sheet.tsx | 6 + components/drive/new-menu.tsx | 8 + components/drive/public-richtext-editor.tsx | 30 +- components/drive/public-share-view.tsx | 31 +- components/drive/public-ultidraw-editor.tsx | 172 ++ components/drive/richtext-document.tsx | 355 ++- components/drive/richtext-editor.tsx | 57 +- components/drive/richtext-legacy-redirect.tsx | 8 +- .../richtext/collab-presence-avatars.tsx | 20 +- components/drive/richtext/docs-chrome.tsx | 28 +- .../docs-drive-draw-picker-dialog.tsx | 160 ++ .../docs-drive-image-picker-dialog.tsx | 175 ++ .../richtext/docs-editor-loading-shell.tsx | 63 + .../drive/richtext/docs-editor-workspace.tsx | 103 +- .../drive/richtext/docs-excalidraw-editor.tsx | 159 ++ .../richtext/docs-exclusive-menu-sub.tsx | 32 +- .../richtext/docs-format-menu-presets.tsx | 203 ++ .../drive/richtext/docs-format-menu.tsx | 1081 ++++++++++ .../richtext/docs-graphic-context-menu.tsx | 14 +- .../richtext/docs-graphic-crop-overlay.tsx | 177 +- .../richtext/docs-graphic-draw-modal.tsx | 117 + .../docs-graphic-floating-toolbar.tsx | 488 +++++ .../richtext/docs-graphic-layout-previews.tsx | 183 ++ .../drive/richtext/docs-graphic-node-view.tsx | 794 ++++++- .../richtext/docs-graphic-options-panel.tsx | 90 +- .../richtext/docs-graphic-options-sidebar.tsx | 1008 +++++++++ .../richtext/docs-graphic-snap-guides.tsx | 60 + .../richtext/docs-graphic-toolbar-menu.tsx | 123 +- .../drive/richtext/docs-horizontal-ruler.tsx | 4 +- .../drive/richtext/docs-insert-menu.tsx | 583 +++++ .../richtext/docs-insert-table-picker.tsx | 57 + .../richtext/docs-line-spacing-dialog.tsx | 171 ++ .../drive/richtext/docs-link-popover.tsx | 307 +++ .../drive/richtext/docs-list-start-dialog.tsx | 85 + .../drive/richtext/docs-loading-splash.tsx | 64 + components/drive/richtext/docs-menubar.tsx | 58 +- components/drive/richtext/docs-page-view.tsx | 57 +- .../richtext/docs-paragraph-style-ui.tsx | 179 ++ .../drive/richtext/docs-rulers-chrome.tsx | 5 +- .../richtext/docs-table-context-menu.tsx | 246 +++ .../richtext/docs-table-floating-toolbar.tsx | 317 +++ components/drive/richtext/docs-toolbar.tsx | 231 +- .../drive/richtext/docs-vertical-ruler.tsx | 4 +- components/drive/ultidraw-chrome.tsx | 122 ++ components/drive/ultidraw-document.tsx | 312 +++ components/drive/ultidraw-editor.tsx | 175 ++ components/first-launch-splash.tsx | 2 + .../contacts-page/contacts-app-shell.tsx | 2 + components/gmail/right-panel.tsx | 17 +- components/landing/landing-data.ts | 162 ++ components/landing/landing-demo.tsx | 175 ++ components/landing/landing-header.tsx | 120 ++ components/landing/landing-hero.tsx | 210 ++ components/landing/landing-page.tsx | 48 + components/landing/landing-reveal.tsx | 54 + components/landing/landing-sections.tsx | 358 +++ lib/admin-settings/map-api-org-settings.ts | 11 + lib/admin-settings/org-settings-store.ts | 27 +- lib/admin-settings/org-settings-types.ts | 12 + lib/admin-settings/settings-nav.ts | 8 + lib/admin/deploy-runtime.ts | 1 + lib/ai/chat-context.ts | 79 + lib/ai/docs-apply.ts | 72 + lib/ai/docs-context.ts | 92 + lib/ai/tiptap-syntax-guide.ts | 20 + lib/ai/use-ai-panel.ts | 32 + lib/ai/use-docs-ai-panel.ts | 31 + lib/api/admin-org-types.ts | 17 + lib/api/hooks/use-ai-queries.ts | 67 + lib/api/hooks/use-drive-queries.ts | 2 +- lib/drive/docs-block-targets.ts | 32 + lib/drive/docs-docx-export.ts | 33 + lib/drive/docs-export-download.ts | 23 + lib/drive/docs-export-prepare.ts | 255 +++ lib/drive/docs-export-snapshot.ts | 43 + lib/drive/docs-export.test.ts | 105 + lib/drive/docs-file-menu-export.ts | 49 +- lib/drive/docs-format-actions.ts | 6 + lib/drive/docs-graphic-crop-bridge.ts | 21 + lib/drive/docs-graphic-draw-bridge.ts | 44 + lib/drive/docs-graphic-draw-import.ts | 85 + lib/drive/docs-graphic-flow-move.test.ts | 86 + lib/drive/docs-graphic-flow-move.ts | 122 ++ lib/drive/docs-graphic-image-size.test.ts | 31 + lib/drive/docs-graphic-image-size.ts | 70 + lib/drive/docs-graphic-import.ts | 6 +- lib/drive/docs-graphic-keyboard.test.ts | 89 + lib/drive/docs-graphic-keyboard.ts | 294 +++ lib/drive/docs-graphic-layout.ts | 126 +- lib/drive/docs-graphic-position.test.ts | 66 + lib/drive/docs-graphic-position.ts | 100 + lib/drive/docs-graphic-snap-bridge.ts | 29 + lib/drive/docs-graphic-snap.test.ts | 55 + lib/drive/docs-graphic-snap.ts | 283 +++ lib/drive/docs-graphic-types.ts | 631 +++++- lib/drive/docs-graphic-ui.ts | 98 + lib/drive/docs-graphic.test.ts | 285 ++- lib/drive/docs-indent-actions.ts | 90 + lib/drive/docs-indent.test.ts | 25 + lib/drive/docs-indent.ts | 21 + lib/drive/docs-line-spacing-actions.ts | 227 ++ lib/drive/docs-line-spacing.test.ts | 37 + lib/drive/docs-line-spacing.ts | 81 + lib/drive/docs-link-bridge.ts | 5 + lib/drive/docs-link-href.ts | 37 + lib/drive/docs-list-actions.ts | 251 +++ lib/drive/docs-list-styles.test.ts | 24 + lib/drive/docs-list-styles.ts | 48 + lib/drive/docs-loading-phase.ts | 45 + lib/drive/docs-page-elements-bridge.ts | 14 + lib/drive/docs-paragraph-style-apply.ts | 169 ++ lib/drive/docs-paragraph-style-bridge.ts | 26 + lib/drive/docs-paragraph-styles-context.tsx | 34 + lib/drive/docs-paragraph-styles-css.ts | 39 + lib/drive/docs-paragraph-styles.ts | 267 +++ lib/drive/docs-pdf-export.ts | 78 + lib/drive/docs-print.ts | 84 + lib/drive/docs-styles-types.ts | 18 + lib/drive/docs-table-actions.ts | 82 + lib/drive/docs-table-export.ts | 187 ++ lib/drive/docs-table-import.ts | 222 ++ lib/drive/docs-table-styles.ts | 78 + lib/drive/docs-table-types.ts | 36 + lib/drive/docs-table.test.ts | 168 ++ lib/drive/docx-header-footer-export.ts | 254 +++ lib/drive/docx-page-setup-export.ts | 71 + lib/drive/docx-position-export.ts | 153 ++ lib/drive/docx-position-import.ts | 45 +- lib/drive/drive-dialog-styles.ts | 5 + lib/drive/drive-open-item.ts | 9 + lib/drive/drive-preview.ts | 12 + lib/drive/drive-url.ts | 5 + .../extensions/docs-graphic-anchor-sync.ts | 100 + lib/drive/extensions/docs-graphic-keyboard.ts | 31 + .../extensions/docs-graphic-paste-drop.ts | 22 +- lib/drive/extensions/docs-graphic.ts | 400 +++- lib/drive/extensions/docs-list-styles.ts | 122 ++ lib/drive/extensions/docs-paragraph-indent.ts | 72 + .../extensions/docs-paragraph-spacing.ts | 152 ++ lib/drive/extensions/docs-paragraph-style.ts | 68 + lib/drive/extensions/docs-table.ts | 195 ++ lib/drive/open-public-share-item.ts | 13 + lib/drive/open-ultidraw-document.ts | 23 + lib/drive/public-share-url.ts | 5 +- lib/drive/richtext-content.test.ts | 15 +- lib/drive/richtext-content.ts | 15 + lib/drive/richtext-extensions.ts | 44 +- lib/drive/richtext-import.test.ts | 20 +- lib/drive/richtext-import.ts | 19 +- lib/drive/richtext-types.ts | 2 + lib/drive/share-path-looks-like-editor.ts | 16 + lib/drive/ultidraw-formats.ts | 31 + lib/drive/ultidraw-seed.ts | 33 + lib/drive/ultidraw-types.ts | 12 + lib/drive/use-docs-file-menu.tsx | 44 +- lib/drive/use-docs-fonts.ts | 34 + lib/drive/use-docs-format-menu.tsx | 238 ++ lib/drive/use-docs-insert-menu.tsx | 199 ++ lib/drive/use-docs-paragraph-styles.ts | 210 ++ lib/drive/use-docs-toolbar-state.ts | 37 +- lib/hooks/use-drive-new-menu.ts | 36 +- lib/mail-automation/api-token-permissions.ts | 8 + lib/suite/favorite-apps.ts | 7 +- lib/suite/page-metadata.ts | 26 +- package.json | 5 + pnpm-lock.yaml | 1911 ++++++++++++++++- public/admin-mark.svg | 20 +- public/agenda-mark.svg | 24 +- public/compte-mark.svg | 17 +- public/contacts-mark.svg | 45 +- public/demo/ultidoc-sample.json | 171 ++ public/drive/mistral-mark.svg | 15 - public/drive/ultiai-mark.svg | 15 + public/mistral-mark.svg | 15 - public/photos-mark.svg | 21 +- public/ultiai-mark.svg | 15 + public/ultimeet-mark.svg | 18 +- public/ultisuite-180.png | Bin 0 -> 3205 bytes public/ultisuite-32.png | Bin 0 -> 684 bytes public/ultisuite-mark.svg | 7 + scripts/emit-authentik-brand.mjs | 177 +- styles/docs-print.css | 90 + styles/landing.css | 340 +++ styles/richtext-editor.css | 734 ++++++- tsconfig.json | 1 + tsconfig.tsbuildinfo | 2 +- 208 files changed, 22553 insertions(+), 1082 deletions(-) create mode 100644 app/api/demo/richtext-save/route.ts create mode 100644 app/chat/page.tsx create mode 100644 app/demo/docs/page.tsx create mode 100644 app/demo/mail/page.tsx create mode 100644 app/drive/draw/[fileId]/edit/page.tsx create mode 100644 components/admin/settings/sections/ai-assistant-section.tsx create mode 100644 components/ai/ai-chat-iframe.tsx create mode 100644 components/ai/ai-chat-panel.tsx create mode 100644 components/ai/docs-ai-panel.tsx create mode 100644 components/demo/demo-docs-editor.tsx create mode 100644 components/demo/demo-mail-app.tsx create mode 100644 components/demo/demo-mail-data.ts create mode 100644 components/drive/public-ultidraw-editor.tsx create mode 100644 components/drive/richtext/docs-drive-draw-picker-dialog.tsx create mode 100644 components/drive/richtext/docs-drive-image-picker-dialog.tsx create mode 100644 components/drive/richtext/docs-editor-loading-shell.tsx create mode 100644 components/drive/richtext/docs-excalidraw-editor.tsx create mode 100644 components/drive/richtext/docs-format-menu-presets.tsx create mode 100644 components/drive/richtext/docs-format-menu.tsx create mode 100644 components/drive/richtext/docs-graphic-draw-modal.tsx create mode 100644 components/drive/richtext/docs-graphic-floating-toolbar.tsx create mode 100644 components/drive/richtext/docs-graphic-layout-previews.tsx create mode 100644 components/drive/richtext/docs-graphic-options-sidebar.tsx create mode 100644 components/drive/richtext/docs-graphic-snap-guides.tsx create mode 100644 components/drive/richtext/docs-insert-menu.tsx create mode 100644 components/drive/richtext/docs-insert-table-picker.tsx create mode 100644 components/drive/richtext/docs-line-spacing-dialog.tsx create mode 100644 components/drive/richtext/docs-link-popover.tsx create mode 100644 components/drive/richtext/docs-list-start-dialog.tsx create mode 100644 components/drive/richtext/docs-loading-splash.tsx create mode 100644 components/drive/richtext/docs-paragraph-style-ui.tsx create mode 100644 components/drive/richtext/docs-table-context-menu.tsx create mode 100644 components/drive/richtext/docs-table-floating-toolbar.tsx create mode 100644 components/drive/ultidraw-chrome.tsx create mode 100644 components/drive/ultidraw-document.tsx create mode 100644 components/drive/ultidraw-editor.tsx create mode 100644 components/landing/landing-data.ts create mode 100644 components/landing/landing-demo.tsx create mode 100644 components/landing/landing-header.tsx create mode 100644 components/landing/landing-hero.tsx create mode 100644 components/landing/landing-page.tsx create mode 100644 components/landing/landing-reveal.tsx create mode 100644 components/landing/landing-sections.tsx create mode 100644 lib/ai/chat-context.ts create mode 100644 lib/ai/docs-apply.ts create mode 100644 lib/ai/docs-context.ts create mode 100644 lib/ai/tiptap-syntax-guide.ts create mode 100644 lib/ai/use-ai-panel.ts create mode 100644 lib/ai/use-docs-ai-panel.ts create mode 100644 lib/api/hooks/use-ai-queries.ts create mode 100644 lib/drive/docs-block-targets.ts create mode 100644 lib/drive/docs-docx-export.ts create mode 100644 lib/drive/docs-export-download.ts create mode 100644 lib/drive/docs-export-prepare.ts create mode 100644 lib/drive/docs-export-snapshot.ts create mode 100644 lib/drive/docs-export.test.ts create mode 100644 lib/drive/docs-format-actions.ts create mode 100644 lib/drive/docs-graphic-crop-bridge.ts create mode 100644 lib/drive/docs-graphic-draw-bridge.ts create mode 100644 lib/drive/docs-graphic-draw-import.ts create mode 100644 lib/drive/docs-graphic-flow-move.test.ts create mode 100644 lib/drive/docs-graphic-flow-move.ts create mode 100644 lib/drive/docs-graphic-image-size.test.ts create mode 100644 lib/drive/docs-graphic-image-size.ts create mode 100644 lib/drive/docs-graphic-keyboard.test.ts create mode 100644 lib/drive/docs-graphic-keyboard.ts create mode 100644 lib/drive/docs-graphic-position.test.ts create mode 100644 lib/drive/docs-graphic-position.ts create mode 100644 lib/drive/docs-graphic-snap-bridge.ts create mode 100644 lib/drive/docs-graphic-snap.test.ts create mode 100644 lib/drive/docs-graphic-snap.ts create mode 100644 lib/drive/docs-graphic-ui.ts create mode 100644 lib/drive/docs-indent-actions.ts create mode 100644 lib/drive/docs-indent.test.ts create mode 100644 lib/drive/docs-indent.ts create mode 100644 lib/drive/docs-line-spacing-actions.ts create mode 100644 lib/drive/docs-line-spacing.test.ts create mode 100644 lib/drive/docs-line-spacing.ts create mode 100644 lib/drive/docs-link-bridge.ts create mode 100644 lib/drive/docs-link-href.ts create mode 100644 lib/drive/docs-list-actions.ts create mode 100644 lib/drive/docs-list-styles.test.ts create mode 100644 lib/drive/docs-list-styles.ts create mode 100644 lib/drive/docs-loading-phase.ts create mode 100644 lib/drive/docs-page-elements-bridge.ts create mode 100644 lib/drive/docs-paragraph-style-apply.ts create mode 100644 lib/drive/docs-paragraph-style-bridge.ts create mode 100644 lib/drive/docs-paragraph-styles-context.tsx create mode 100644 lib/drive/docs-paragraph-styles-css.ts create mode 100644 lib/drive/docs-paragraph-styles.ts create mode 100644 lib/drive/docs-pdf-export.ts create mode 100644 lib/drive/docs-print.ts create mode 100644 lib/drive/docs-styles-types.ts create mode 100644 lib/drive/docs-table-actions.ts create mode 100644 lib/drive/docs-table-export.ts create mode 100644 lib/drive/docs-table-import.ts create mode 100644 lib/drive/docs-table-styles.ts create mode 100644 lib/drive/docs-table-types.ts create mode 100644 lib/drive/docs-table.test.ts create mode 100644 lib/drive/docx-header-footer-export.ts create mode 100644 lib/drive/docx-page-setup-export.ts create mode 100644 lib/drive/docx-position-export.ts create mode 100644 lib/drive/extensions/docs-graphic-anchor-sync.ts create mode 100644 lib/drive/extensions/docs-graphic-keyboard.ts create mode 100644 lib/drive/extensions/docs-list-styles.ts create mode 100644 lib/drive/extensions/docs-paragraph-indent.ts create mode 100644 lib/drive/extensions/docs-paragraph-spacing.ts create mode 100644 lib/drive/extensions/docs-paragraph-style.ts create mode 100644 lib/drive/extensions/docs-table.ts create mode 100644 lib/drive/open-ultidraw-document.ts create mode 100644 lib/drive/share-path-looks-like-editor.ts create mode 100644 lib/drive/ultidraw-formats.ts create mode 100644 lib/drive/ultidraw-seed.ts create mode 100644 lib/drive/ultidraw-types.ts create mode 100644 lib/drive/use-docs-fonts.ts create mode 100644 lib/drive/use-docs-format-menu.tsx create mode 100644 lib/drive/use-docs-insert-menu.tsx create mode 100644 lib/drive/use-docs-paragraph-styles.ts create mode 100644 public/demo/ultidoc-sample.json delete mode 100644 public/drive/mistral-mark.svg create mode 100644 public/drive/ultiai-mark.svg delete mode 100644 public/mistral-mark.svg create mode 100644 public/ultiai-mark.svg create mode 100644 public/ultisuite-180.png create mode 100644 public/ultisuite-32.png create mode 100644 public/ultisuite-mark.svg create mode 100644 styles/docs-print.css create mode 100644 styles/landing.css diff --git a/.env.example b/.env.example index 6f43c74..979aab3 100644 --- a/.env.example +++ b/.env.example @@ -21,3 +21,5 @@ OIDC_CLIENT_SECRET=changeme NEXT_PUBLIC_ONLYOFFICE_URL=http://localhost/office # Rich text editor (TipTap + Hocuspocus — docs texte) NEXT_PUBLIC_HOCUSPOCUS_URL=ws://localhost/collab +# UltiAI (chemin proxy OpenWebUI — même origine) +NEXT_PUBLIC_AI_PUBLIC_PATH=/ai diff --git a/app/api/demo/richtext-save/route.ts b/app/api/demo/richtext-save/route.ts new file mode 100644 index 0000000..f5081ac --- /dev/null +++ b/app/api/demo/richtext-save/route.ts @@ -0,0 +1,11 @@ +/** + * Sauvegarde factice pour la démo publique UltiDocs. + * Zéro rétention : le corps de la requête est ignoré. + */ +export async function PUT() { + return new Response(null, { status: 204 }) +} + +export async function POST() { + return new Response(null, { status: 204 }) +} diff --git a/app/chat/page.tsx b/app/chat/page.tsx new file mode 100644 index 0000000..96ad825 --- /dev/null +++ b/app/chat/page.tsx @@ -0,0 +1,50 @@ +"use client" + +import { Sparkles } from "lucide-react" +import { AiChatIframe } from "@/components/ai/ai-chat-iframe" +import { useAiConfig, useAiQuota } from "@/lib/api/hooks/use-ai-queries" + +export default function ChatPage() { + const { data: config, isLoading } = useAiConfig() + const { data: quota } = useAiQuota(Boolean(config?.enabled)) + + if (isLoading) { + return ( +
+ Chargement UltiAI… +
+ ) + } + + if (!config?.enabled) { + return ( +
+ +

+ UltiAI n'est pas activé. Activez le plugin dans l'administration. +

+
+ ) + } + + return ( +
+
+
+ + UltiAI +
+ {quota ? ( + + {quota.requests_remaining}/{quota.requests_limit} requêtes aujourd'hui + + ) : null} +
+ +
+ ) +} diff --git a/app/demo/docs/page.tsx b/app/demo/docs/page.tsx new file mode 100644 index 0000000..dc49cbf --- /dev/null +++ b/app/demo/docs/page.tsx @@ -0,0 +1,18 @@ +import type { Metadata } from "next" +import { DemoDocsEditor } from "@/components/demo/demo-docs-editor" +import { suitePageMetadata } from "@/lib/suite/page-metadata" + +export const metadata: Metadata = { + ...suitePageMetadata({ + app: "drive", + title: "Démo UltiDocs", + absoluteTitle: true, + description: + "Essayez l'éditeur de documents UltiDocs sans compte — démo interactive, zéro rétention.", + }), + robots: { index: false }, +} + +export default function DemoDocsPage() { + return +} diff --git a/app/demo/mail/page.tsx b/app/demo/mail/page.tsx new file mode 100644 index 0000000..00dc420 --- /dev/null +++ b/app/demo/mail/page.tsx @@ -0,0 +1,18 @@ +import type { Metadata } from "next" +import { DemoMailApp } from "@/components/demo/demo-mail-app" +import { suitePageMetadata } from "@/lib/suite/page-metadata" + +export const metadata: Metadata = { + ...suitePageMetadata({ + app: "mail", + title: "Démo Ultimail", + absoluteTitle: true, + description: + "Essayez la messagerie Ultimail sans compte — démo interactive, zéro rétention.", + }), + robots: { index: false }, +} + +export default function DemoMailPage() { + return +} diff --git a/app/drive/draw/[fileId]/edit/page.tsx b/app/drive/draw/[fileId]/edit/page.tsx new file mode 100644 index 0000000..9ac1133 --- /dev/null +++ b/app/drive/draw/[fileId]/edit/page.tsx @@ -0,0 +1,20 @@ +"use client" + +import { useParams } from "next/navigation" +import { UltidrawEditor } from "@/components/drive/ultidraw-editor" +import { isDriveFileIdSegment } from "@/lib/drive/drive-url" + +export default function DriveDrawEditPage() { + const params = useParams() + const fileId = params.fileId as string + + if (!isDriveFileIdSegment(fileId)) { + return ( +
+ Identifiant de dessin invalide +
+ ) + } + + return +} diff --git a/app/drive/s/[token]/[[...path]]/page.tsx b/app/drive/s/[token]/[[...path]]/page.tsx index b96c2e6..4827446 100644 --- a/app/drive/s/[token]/[[...path]]/page.tsx +++ b/app/drive/s/[token]/[[...path]]/page.tsx @@ -1,25 +1,37 @@ "use client" -import { useParams } from "next/navigation" +import { useParams, useRouter } from "next/navigation" import { useEffect, useState } from "react" -import { Loader2, Lock } from "lucide-react" +import { Lock } from "lucide-react" import { PublicShareChrome, PublicShareViewPanel, } from "@/components/drive/public-share-view" +import { DocsLoadingSplash } from "@/components/drive/richtext/docs-loading-splash" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { usePublicShare } from "@/lib/api/hooks/use-public-share-queries" import { folderPathFromPublicSegments } from "@/lib/api/public-share" +import { + shouldOpenInOnlyOffice, + shouldOpenInRichTextEditor, + shouldOpenInUltidrawEditor, +} from "@/lib/drive/drive-preview" +import { sharePermCanEdit } from "@/lib/drive/drive-share-permissions" +import { buildPublicShareEditHref } from "@/lib/drive/public-share-url" +import { sharePathLooksLikeEditorFile } from "@/lib/drive/share-path-looks-like-editor" export default function PublicSharePage() { const params = useParams() + const router = useRouter() const token = String(params.token ?? "") const pathSegments = params.path as string[] | undefined const path = folderPathFromPublicSegments(pathSegments) + const pathHintsEditor = sharePathLooksLikeEditorFile(path) const [passwordInput, setPasswordInput] = useState("") const [password, setPassword] = useState(undefined) + const [redirectingToEditor, setRedirectingToEditor] = useState(false) const { data, isLoading, isError, error, refetch, isFetching } = usePublicShare( token, @@ -30,12 +42,48 @@ export default function PublicSharePage() { const needsPassword = isError && error instanceof Error && error.message === "password_required" + const file = data?.item_type === "file" ? data.file : null + const isEditorFile = Boolean( + file && + (shouldOpenInRichTextEditor(file) || + shouldOpenInUltidrawEditor(file) || + shouldOpenInOnlyOffice(file)) + ) + const showDocsSplash = + pathHintsEditor || redirectingToEditor || (Boolean(data) && isEditorFile) + useEffect(() => { if (password && typeof window !== "undefined") { sessionStorage.setItem(`public-share-pw:${token}`, password) } }, [password, token]) + useEffect(() => { + if (!file || !data || !isEditorFile) return + setRedirectingToEditor(true) + const canEdit = sharePermCanEdit(data.permissions ?? 1) + const returnTo = + typeof window !== "undefined" + ? window.location.pathname + window.location.search + : undefined + const editor = shouldOpenInUltidrawEditor(file) + ? "ultidraw" + : shouldOpenInRichTextEditor(file) + ? "richtext" + : "office" + router.replace( + buildPublicShareEditHref( + token, + file.path, + returnTo, + canEdit ? "edit" : "view", + file.name, + editor, + data.item_type + ) + ) + }, [data, file, isEditorFile, router, token]) + const submitPassword = (event: React.FormEvent) => { event.preventDefault() const trimmed = passwordInput.trim() @@ -43,12 +91,19 @@ export default function PublicSharePage() { setPassword(trimmed) } + if ( + showDocsSplash && + !needsPassword && + (isLoading || (isFetching && !data) || redirectingToEditor || isEditorFile) + ) { + const splashTitle = file?.name ?? path.split("/").filter(Boolean).pop() + return + } + return ( {isLoading || (isFetching && !data) ? ( -
- -
+ ) : needsPassword ? (
diff --git a/app/drive/s/[token]/edit/[[...path]]/page.tsx b/app/drive/s/[token]/edit/[[...path]]/page.tsx index ba4d716..e6e8f69 100644 --- a/app/drive/s/[token]/edit/[[...path]]/page.tsx +++ b/app/drive/s/[token]/edit/[[...path]]/page.tsx @@ -4,7 +4,8 @@ import { useParams, useSearchParams } from "next/navigation" import { useEffect, useState } from "react" import { PublicOfficeEditor } from "@/components/drive/public-office-editor" import { PublicRichTextEditor } from "@/components/drive/public-richtext-editor" -import { shouldOpenInRichTextEditor } from "@/lib/drive/drive-preview" +import { PublicUltidrawEditor } from "@/components/drive/public-ultidraw-editor" +import { shouldOpenInRichTextEditor, shouldOpenInUltidrawEditor } from "@/lib/drive/drive-preview" import { filePathFromPublicEditSegments, readPublicShareRootType } from "@/lib/drive/public-share-url" import type { PublicShareRootType } from "@/lib/drive/public-share-url" @@ -36,11 +37,27 @@ export default function PublicShareEditPage() { return sessionStorage.getItem(`public-share-pw:${token}`) ?? undefined }) + const fileName = fileDisplayName ?? filePath.split("/").pop() ?? "" + + const useUltidraw = + editorParam === "ultidraw" || shouldOpenInUltidrawEditor({ name: fileName }) + const useRichText = - editorParam === "richtext" || - shouldOpenInRichTextEditor({ - name: fileDisplayName ?? filePath.split("/").pop() ?? "", - }) + editorParam === "richtext" || shouldOpenInRichTextEditor({ name: fileName }) + + if (useUltidraw) { + return ( + + ) + } if (useRichText) { return ( diff --git a/app/globals.css b/app/globals.css index 5e77a14..0bdba42 100644 --- a/app/globals.css +++ b/app/globals.css @@ -2,6 +2,8 @@ @import 'tw-animate-css'; @import '../styles/onlyoffice-theme.css'; @import '../styles/richtext-editor.css'; +@import '../styles/docs-print.css'; +@import '../styles/landing.css'; @custom-variant dark (&:is(.dark *)); @@ -416,6 +418,18 @@ body { } } +@keyframes splash-logo-spin { + 0% { + transform: rotate(0deg); + } + 14% { + transform: rotate(36deg); + } + 100% { + transform: rotate(360deg); + } +} + @keyframes splash-loader-progress { 0% { transform: translateX(-104%); diff --git a/app/login/page.tsx b/app/login/page.tsx index f294f0a..09a7d39 100644 --- a/app/login/page.tsx +++ b/app/login/page.tsx @@ -10,7 +10,6 @@ import { CardFooter, CardHeader, } from "@/components/ui/card" -import { UltiMailLogo } from "@/components/ultimail-logo" import { getAuthentikEnrollmentUrl } from "@/lib/auth/oidc-config" import { cn } from "@/lib/utils" @@ -32,10 +31,23 @@ function LoginContent() {
- +
+ + + UltiSuite + +
- Connecte-toi avec ton compte Ulti (Authentik) pour accéder à la - messagerie. + Connecte-toi avec ton compte Ulti (Authentik) pour accéder à ta + suite : mail, drive, contacts et IA. {error ? (

diff --git a/app/mail/mail-app-shell.tsx b/app/mail/mail-app-shell.tsx index 25aaede..1e99781 100644 --- a/app/mail/mail-app-shell.tsx +++ b/app/mail/mail-app-shell.tsx @@ -45,6 +45,7 @@ import { MailNotificationsBridge } from "@/components/gmail/mail-notifications-b import { useWebSocket } from "@/lib/api/ws" import { useMailSettingsStore } from "@/lib/stores/mail-settings-store" import { FilePreviewDialog } from "@/components/drive/file-preview-dialog" +import { AiChatPanel } from "@/components/ai/ai-chat-panel" const MAIL_SETTINGS_PATH = "/mail/settings" @@ -191,6 +192,7 @@ function MailAppInner() {

+
{!splitView ? ( @@ -13,5 +24,5 @@ export default async function Home({ if (mail && mail.length > 0) { redirect(`/mail/inbox/message/${encodeURIComponent(mail)}`) } - redirect("/mail/inbox") + return } diff --git a/components/admin/settings/admin-settings-section-view.tsx b/components/admin/settings/admin-settings-section-view.tsx index 995a442..0641248 100644 --- a/components/admin/settings/admin-settings-section-view.tsx +++ b/components/admin/settings/admin-settings-section-view.tsx @@ -20,6 +20,7 @@ import { NextcloudSection } from "@/components/admin/settings/sections/nextcloud import { MailingSection } from "@/components/admin/settings/sections/mailing-section" import { OnlyofficeSection } from "@/components/admin/settings/sections/onlyoffice-section" import { RichtextSection } from "@/components/admin/settings/sections/richtext-section" +import { AiAssistantSection } from "@/components/admin/settings/sections/ai-assistant-section" import { AuditSection } from "@/components/admin/settings/sections/audit-section" const SECTIONS: Record = { @@ -38,6 +39,7 @@ const SECTIONS: Record = { mailing: MailingSection, onlyoffice: OnlyofficeSection, richtext: RichtextSection, + "ai-assistant": AiAssistantSection, audit: AuditSection, } diff --git a/components/admin/settings/sections/ai-assistant-section.tsx b/components/admin/settings/sections/ai-assistant-section.tsx new file mode 100644 index 0000000..68f9cb2 --- /dev/null +++ b/components/admin/settings/sections/ai-assistant-section.tsx @@ -0,0 +1,99 @@ +"use client" + +import { OrgSettingsSection } from "@/components/admin/settings/org-settings-form" +import { useOrgSettingsStore } from "@/lib/admin-settings/org-settings-store" +import { Label } from "@/components/ui/label" +import { Switch } from "@/components/ui/switch" +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" +import { Input } from "@/components/ui/input" + +export function AiAssistantSection() { + const aiAssistant = useOrgSettingsStore((s) => s.aiAssistant) + const setAiAssistant = useOrgSettingsStore((s) => s.setAiAssistant) + const effective = useOrgSettingsStore((s) => s.meta?.effective.ai_assistant) + + const enabled = effective?.enabled ?? aiAssistant.enabled + + return ( + + + +
+
+ Assistant IA + + Chat standalone et panneaux contextuels mail/drive/contacts. + +
+ setAiAssistant({ enabled: v })} + /> +
+
+ +
+ + setAiAssistant({ public_path: e.target.value })} + placeholder="/ai" + /> +
+
+ + setAiAssistant({ openwebui_internal_url: e.target.value })} + placeholder="http://openwebui:8080" + /> +
+
+ + setAiAssistant({ default_model: e.target.value })} + placeholder="gpt-4o" + /> +
+
+ + setAiAssistant({ chat_nc_path: e.target.value })} + placeholder="/.ultimail/ai/chats" + /> +
+
+
+ +

+ Les panneaux mail/drive/contacts ne sauvegardent pas l'historique. +

+
+ setAiAssistant({ embed_default_temporary: v })} + /> +
+
+
+ +

+ Pipeline OpenWebUI → fichiers .ultichat.json sur le drive utilisateur. +

+
+ setAiAssistant({ chat_sync_enabled: v })} + /> +
+
+
+
+ ) +} diff --git a/components/ai/ai-chat-iframe.tsx b/components/ai/ai-chat-iframe.tsx new file mode 100644 index 0000000..cadae40 --- /dev/null +++ b/components/ai/ai-chat-iframe.tsx @@ -0,0 +1,55 @@ +"use client" + +import { useEffect, useMemo, useRef } from "react" +import type { AiChatContext } from "@/lib/ai/chat-context" +import { buildEmbedSearchParams } from "@/lib/ai/chat-context" +import { useTheme } from "next-themes" + +type AiChatIframeProps = { + publicPath?: string + context: AiChatContext + className?: string +} + +export function AiChatIframe({ publicPath = "/ai", context, className }: AiChatIframeProps) { + const iframeRef = useRef(null) + const { resolvedTheme } = useTheme() + const src = useMemo(() => { + const base = publicPath.replace(/\/$/, "") + const qs = buildEmbedSearchParams(context) + return qs ? `${base}/?${qs}` : `${base}/` + }, [publicPath, context]) + + useEffect(() => { + const iframe = iframeRef.current + if (!iframe?.contentWindow) return + iframe.contentWindow.postMessage( + { type: "ULTI_THEME", theme: resolvedTheme === "dark" ? "dark" : "light" }, + window.location.origin + ) + }, [resolvedTheme]) + + useEffect(() => { + const iframe = iframeRef.current + if (!iframe?.contentWindow) return + iframe.contentWindow.postMessage( + { + type: "ULTI_CONTEXT_UPDATE", + context, + systemPrompt: context.systemPromptExtra, + }, + window.location.origin + ) + }, [context]) + + return ( +