This repository has been archived on 2026-07-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
OutlookAddin/src/OutlookAddinProtocolHandler/Program.cs
T
PointStar 0964439441 feat(compose): Task #5 — Compose from Case + URL protocol + auto-file
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>
2026-05-11 15:47:36 +03:00

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;
}
}
}