From c685f4f91dff36e5e5b3f7c0269eec96553b375c Mon Sep 17 00:00:00 2001 From: PointStar Date: Sun, 31 May 2026 13:49:16 +0300 Subject: [PATCH] =?UTF-8?q?publish:=201.2.13=20=E2=80=94=20preselect=20sen?= =?UTF-8?q?der=20case,=20save=20images,=20save-from-open-mail?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes to the save-attachments flow: 1. Preselect the identified client case. When the mail sender already maps to a CRM contact, the "שמור קבצים" dialog now seeds its case list from the sidebar match (cached lookup) instead of opening empty — single linked case auto-selects (and resolves the target folder); several show for the user to pick. Search still overrides. 2. Save image attachments. EspoCRM's Document.file field is configured to accept document types only, so JPEG/PNG uploads were rejected with 403 "Not allowed file type". The save-attachments flow never creates a Document — it only pushes the blob to the network share — so we now POST the Attachment unbound (no relatedType=Document/field=file), skipping the per-field accept check and letting every type through. 3. Save from an open message. Adds a "שמור קבצים" button to the read-mail inspector ribbon (Microsoft.Outlook.Mail.Read) so the user no longer has to return to the message list to file attachments. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../Services/EspoCrmClient.cs | 13 ++++- .../ViewModels/SaveAttachmentsViewModel.cs | 32 ++++++++++++ src/OutlookAddin/OutlookAddin.csproj | 3 ++ src/OutlookAddin/Properties/AssemblyInfo.cs | 4 +- src/OutlookAddin/Ribbon/ExplorerRibbon.cs | 49 +++++++++++++++++++ src/OutlookAddin/Ribbon/ReadMailRibbon.xml | 19 +++++++ src/OutlookAddin/Services/AddInHost.cs | 31 ++++++++++++ 7 files changed, 147 insertions(+), 4 deletions(-) create mode 100644 src/OutlookAddin/Ribbon/ReadMailRibbon.xml diff --git a/src/OutlookAddin.Core/Services/EspoCrmClient.cs b/src/OutlookAddin.Core/Services/EspoCrmClient.cs index 51a2a29..a08ea98 100644 --- a/src/OutlookAddin.Core/Services/EspoCrmClient.cs +++ b/src/OutlookAddin.Core/Services/EspoCrmClient.cs @@ -213,13 +213,22 @@ namespace MarcusLaw.OutlookAddin.Core.Services // is sent in the "file" field as a data-URI. The previous extra // "contents" alias was an undocumented legacy that strict tenants // reject with 400. + // + // We deliberately DO NOT bind the upload to Document.file + // (relatedType=Document / field=file). EspoCRM only enforces the + // per-field "allowed file types" list when a field is supplied, and + // Marcus-Law's Document.file field is configured to accept document + // types only (PDF/Word) — images (JPEG/PNG) were rejected with + // 403 "Not allowed file type". This flow never creates a Document; + // the attachment is a standalone blob we immediately push to the + // network share, so an unbound role=Attachment upload is the + // correct shape and lets every file type through. (Server-side fix: + // widen Document.file's accepted types in the Entity Manager.) var payload = new Dictionary { ["name"] = fileName, ["type"] = mime, ["role"] = "Attachment", - ["relatedType"] = "Document", - ["field"] = "file", ["file"] = dataUri }; diff --git a/src/OutlookAddin.UI/ViewModels/SaveAttachmentsViewModel.cs b/src/OutlookAddin.UI/ViewModels/SaveAttachmentsViewModel.cs index c114e7b..bc87f21 100644 --- a/src/OutlookAddin.UI/ViewModels/SaveAttachmentsViewModel.cs +++ b/src/OutlookAddin.UI/ViewModels/SaveAttachmentsViewModel.cs @@ -107,6 +107,38 @@ namespace MarcusLaw.OutlookAddin.UI.ViewModels } } + /// + /// Seeds the case list with the cases already resolved for the mail's + /// sender (from the sidebar match) so the user doesn't have to search + /// for a client we've already identified. When exactly one case is + /// known we auto-select it (which kicks the subfolder/folder-path + /// resolve); with several we just show them and let the user pick. + /// The user can still type in the search box to override — that + /// repopulates from a fresh server search. + /// + public void PreselectCases(IReadOnlyList cases) + { + if (cases == null || cases.Count == 0) return; + + Cases.Clear(); + foreach (var c in cases) + { + if (c == null || string.IsNullOrEmpty(c.Id)) continue; + Cases.Add(c); + } + if (Cases.Count == 0) return; + + if (Cases.Count == 1) + { + SelectedCase = Cases[0]; + StatusMessage = "התיק זוהה אוטומטית מהשולח"; + } + else + { + StatusMessage = $"{Cases.Count} תיקים מקושרים לשולח — בחר/י תיק"; + } + } + private void ResetSubfolders() { Subfolders.Clear(); diff --git a/src/OutlookAddin/OutlookAddin.csproj b/src/OutlookAddin/OutlookAddin.csproj index c211660..088d77f 100644 --- a/src/OutlookAddin/OutlookAddin.csproj +++ b/src/OutlookAddin/OutlookAddin.csproj @@ -257,6 +257,9 @@ OutlookAddin.Ribbon.InspectorRibbon.xml + + OutlookAddin.Ribbon.ReadMailRibbon.xml + OutlookAddin.Images.klear-file.png diff --git a/src/OutlookAddin/Properties/AssemblyInfo.cs b/src/OutlookAddin/Properties/AssemblyInfo.cs index a8df476..2815697 100644 --- a/src/OutlookAddin/Properties/AssemblyInfo.cs +++ b/src/OutlookAddin/Properties/AssemblyInfo.cs @@ -33,6 +33,6 @@ using System.Security; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.12.0")] -[assembly: AssemblyFileVersion("1.2.12.0")] +[assembly: AssemblyVersion("1.2.13.0")] +[assembly: AssemblyFileVersion("1.2.13.0")] diff --git a/src/OutlookAddin/Ribbon/ExplorerRibbon.cs b/src/OutlookAddin/Ribbon/ExplorerRibbon.cs index 0ff3267..0be3539 100644 --- a/src/OutlookAddin/Ribbon/ExplorerRibbon.cs +++ b/src/OutlookAddin/Ribbon/ExplorerRibbon.cs @@ -19,6 +19,7 @@ namespace OutlookAddin.Ribbon { private const string ExplorerResource = "OutlookAddin.Ribbon.ExplorerRibbon.xml"; private const string InspectorResource = "OutlookAddin.Ribbon.InspectorRibbon.xml"; + private const string ReadMailResource = "OutlookAddin.Ribbon.ReadMailRibbon.xml"; // Every button whose `getEnabled` depends on the current Explorer // selection must be re-evaluated on every SelectionChange — Office @@ -40,6 +41,8 @@ namespace OutlookAddin.Ribbon return LoadResource(ExplorerResource); case "Microsoft.Outlook.Mail.Compose": return LoadResource(InspectorResource); + case "Microsoft.Outlook.Mail.Read": + return LoadResource(ReadMailResource); default: return string.Empty; } @@ -77,6 +80,51 @@ namespace OutlookAddin.Ribbon _inspectorRibbon = ribbon; } + // The read-mail inspector ribbon loads fresh for each opened message, + // so its getEnabled callback re-runs per window — no cached IRibbonUI + // or invalidation is needed here. + public void OnReadMailRibbonLoad(IRibbonUI ribbon) + { + } + + /// + /// Enabled when the message open in this read inspector has at least + /// one attachment. Mirrors + /// but reads the item from the Inspector context rather than the + /// Explorer selection. + /// + public bool OnGetSaveAttachmentsInspectorEnabled(IRibbonControl control) + { + try + { + var inspector = control?.Context as Outlook.Inspector; + if (inspector?.CurrentItem is not Outlook.MailItem mail) return false; + var attachments = mail.Attachments; + if (attachments == null) return false; + return attachments.Count > 0; + } + catch + { + return false; + } + } + + public void OnSaveAttachmentsFromInspector(IRibbonControl control) + { + try + { + var inspector = control?.Context as Outlook.Inspector; + if (inspector?.CurrentItem is Outlook.MailItem mail) + { + _ = Globals.ThisAddIn.AddInHost.LaunchSaveAttachmentsAsync(mail); + } + } + catch (Exception ex) + { + Globals.ThisAddIn.AddInHost.Logger.Warning(ex, "OnSaveAttachmentsFromInspector failed"); + } + } + public bool OnGetFileToEspoCrmEnabled(IRibbonControl control) { try @@ -206,6 +254,7 @@ namespace OutlookAddin.Ribbon { case "FileToEspoCrm": fileName = "klear-file.png"; break; case "SaveAttachments": fileName = "klear-attach.png"; break; + case "SaveAttachmentsRead": fileName = "klear-attach.png"; break; case "ShowSidebar": fileName = "klear-sidebar.png"; break; case "OpenEspoCrmSettings": fileName = "klear-settings.png"; break; case "ComposeFromCase": fileName = "klear-compose.png"; break; diff --git a/src/OutlookAddin/Ribbon/ReadMailRibbon.xml b/src/OutlookAddin/Ribbon/ReadMailRibbon.xml new file mode 100644 index 0000000..24dbe78 --- /dev/null +++ b/src/OutlookAddin/Ribbon/ReadMailRibbon.xml @@ -0,0 +1,19 @@ + + + + + + +