0964439441
UI:
- ComposeFromCaseDialog (RTL, search-then-pick a single Case via
GlobalSearch + entityType=Case filter)
- ComposeFromCaseViewModel — debounced GlobalSearch, returns chosen
EspoEntityRef
Host:
- ComposeService.ApplyCaseToMailAsync:
* fetches Case + primary Contact
* stamps UserProperties EspoCaseId, EspoCaseName, OutlookAddinGuid
* pre-fills To/Subject and appends an RTL footer with a CRM deep link
- ItemSend hook (in AddInHost.HookItemSend): when the user sends a
tagged compose, register guid → target with SentItemsWatcher
- SentItemsWatcher: rooted Items reference (prevents GC), 30s expiry
sweep, 2-min default timeout per pending tag, MailMatched event
- AddInHost.OnSentMailMatched: extracts the sent MailItem, files via
FilingService, stamps "Filed: {name}" category
- LaunchComposeFromCaseAsync (Inspector ribbon) and
LaunchComposeFromCaseByIdAsync (URL protocol) entry points
- NamedPipeListener (\.\pipe\MarcusLaw.OutlookAddin) — listens for
"COMPOSE <caseId>" lines, dispatches on WPF Dispatcher
Ribbon:
- InspectorRibbon.xml — "כתוב מתיק" button on Mail Compose ribbon, hidden
for received/read mail via getVisible
- ExplorerRibbon class now serves both Explorer and Inspector ribbons
(single IRibbonExtensibility, dispatches by ribbonID)
URL protocol:
- New OutlookAddinProtocolHandler console project (net48 WinExe):
parses outlookaddin://compose?caseId=<id>, connects to named pipe,
writes "COMPOSE <id>\n"
- tools/install-protocol.ps1 + uninstall-protocol.ps1 register the
scheme under HKCU\Software\Classes\outlookaddin
Core/UI/Tests + protocol handler all build; 38 unit tests still green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
90 lines
2.9 KiB
C#
90 lines
2.9 KiB
C#
using System;
|
|
using System.Collections.Specialized;
|
|
using System.IO;
|
|
using System.IO.Pipes;
|
|
using System.Text;
|
|
using System.Web;
|
|
using System.Windows.Forms;
|
|
|
|
namespace OutlookAddinProtocolHandler
|
|
{
|
|
/// <summary>
|
|
/// Tiny shim launched when the user clicks an <c>outlookaddin://</c> URL.
|
|
/// Parses the URL and forwards a one-line command over the named pipe
|
|
/// owned by the in-process VSTO add-in. If Outlook isn't running, the
|
|
/// pipe connect will fail and we surface a MessageBox.
|
|
/// </summary>
|
|
public static class Program
|
|
{
|
|
private const string PipeName = "MarcusLaw.OutlookAddin";
|
|
|
|
[STAThread]
|
|
public static int Main(string[] args)
|
|
{
|
|
try
|
|
{
|
|
if (args.Length == 0)
|
|
{
|
|
return Fail("השימוש: outlookaddin://compose?caseId=<id>");
|
|
}
|
|
|
|
var raw = args[0];
|
|
if (!raw.StartsWith("outlookaddin://", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
return Fail("URL לא תקין: " + raw);
|
|
}
|
|
|
|
var uri = new Uri(raw);
|
|
var command = (uri.Host ?? "").ToLowerInvariant();
|
|
var query = HttpUtility.ParseQueryString(uri.Query ?? string.Empty);
|
|
|
|
switch (command)
|
|
{
|
|
case "compose":
|
|
return SendCompose(query);
|
|
default:
|
|
return Fail("פעולה לא ידועה: " + command);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Fail("שגיאה: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
private static int SendCompose(NameValueCollection query)
|
|
{
|
|
var caseId = query["caseId"];
|
|
if (string.IsNullOrWhiteSpace(caseId))
|
|
{
|
|
return Fail("חסר caseId בכתובת ה-URL");
|
|
}
|
|
|
|
var payload = "COMPOSE " + caseId.Trim() + "\n";
|
|
try
|
|
{
|
|
using var pipe = new NamedPipeClientStream(".", PipeName, PipeDirection.InOut);
|
|
pipe.Connect(timeout: 3000);
|
|
var bytes = Encoding.UTF8.GetBytes(payload);
|
|
pipe.Write(bytes, 0, bytes.Length);
|
|
pipe.Flush();
|
|
return 0;
|
|
}
|
|
catch (TimeoutException)
|
|
{
|
|
return Fail("Outlook אינו פתוח, או שהתוסף עדיין לא נטען.");
|
|
}
|
|
catch (IOException ex)
|
|
{
|
|
return Fail("תקלת תקשורת עם התוסף: " + ex.Message);
|
|
}
|
|
}
|
|
|
|
private static int Fail(string message)
|
|
{
|
|
MessageBox.Show(message, "Marcus-Law OutlookAddin", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|