Added support for mailto: links in plugin source URLs

This commit is contained in:
Thorsten Sommer 2026-02-10 15:21:33 +01:00
parent 198d31e7af
commit 11920c5bde
Signed by untrusted user who does not match committer: tsommer
GPG Key ID: 371BBA77A02C0108
8 changed files with 91 additions and 33 deletions

View File

@ -5305,6 +5305,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T158493184"] = "Internal Plugins"
-- Disabled Plugins -- Disabled Plugins
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1724138133"] = "Disabled Plugins" UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1724138133"] = "Disabled Plugins"
-- Send a mail
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1999487139"] = "Send a mail"
-- Enable plugin -- Enable plugin
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2057806005"] = "Enable plugin" UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2057806005"] = "Enable plugin"
@ -6040,15 +6043,18 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2262604281"] = "The
-- The field DESCRIPTION does not exist or is not a valid string. -- The field DESCRIPTION does not exist or is not a valid string.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T229488255"] = "The field DESCRIPTION does not exist or is not a valid string." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T229488255"] = "The field DESCRIPTION does not exist or is not a valid string."
-- The field SOURCE_URL is not a valid URL. The URL must start with 'http://' or 'https://'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2320984047"] = "The field SOURCE_URL is not a valid URL. The URL must start with 'http://' or 'https://'."
-- The field VERSION is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X). -- The field VERSION is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X).
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2538827536"] = "The field VERSION is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X)." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2538827536"] = "The field VERSION is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X)."
-- The field SOURCE_URL is not a valid URL. The URL must start with 'http://', 'https://', or 'mailto:'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2892057533"] = "The field SOURCE_URL is not a valid URL. The URL must start with 'http://', 'https://', or 'mailto:'."
-- The table AUTHORS is empty. At least one author must be specified. -- The table AUTHORS is empty. At least one author must be specified.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2981832540"] = "The table AUTHORS is empty. At least one author must be specified." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2981832540"] = "The table AUTHORS is empty. At least one author must be specified."
-- The field SOURCE_URL is not a valid URL. When the URL starts with 'mailto:', it must contain a valid email address as recipient.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T3165663073"] = "The field SOURCE_URL is not a valid URL. When the URL starts with 'mailto:', it must contain a valid email address as recipient."
-- The field SUPPORT_CONTACT is empty. The support contact must be a non-empty string. -- The field SUPPORT_CONTACT is empty. The support contact must be a non-empty string.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T3524814526"] = "The field SUPPORT_CONTACT is empty. The support contact must be a non-empty string." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T3524814526"] = "The field SUPPORT_CONTACT is empty. The support contact must be a non-empty string."

View File

@ -72,11 +72,22 @@
</MudTooltip> </MudTooltip>
} }
@if (context is { IsInternal: false } && this.TryGetSourceWebsite(context, out var sourceUrl)) @if (context is { IsInternal: false } && !string.IsNullOrWhiteSpace(context.SourceURL))
{ {
<MudTooltip Text="@T("Open website")"> var sourceUrl = context.SourceURL;
<MudIconButton Icon="@Icons.Material.Filled.OpenInBrowser" Href="@sourceUrl" Target="_blank" Size="Size.Medium"/> var isSendingMail = IsSendingMail(sourceUrl);
</MudTooltip> if(isSendingMail)
{
<MudTooltip Text="@T("Send a mail")">
<MudIconButton Icon="@Icons.Material.Filled.Email" Href="@sourceUrl" Target="_blank" Size="Size.Medium"/>
</MudTooltip>
}
else
{
<MudTooltip Text="@T("Open website")">
<MudIconButton Icon="@Icons.Material.Filled.OpenInBrowser" Href="@sourceUrl" Target="_blank" Size="Size.Medium"/>
</MudTooltip>
}
} }
</MudStack> </MudStack>
</MudTd> </MudTd>

View File

@ -50,22 +50,7 @@ public partial class Plugins : MSGComponentBase
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED); await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
} }
private bool TryGetSourceWebsite(IPluginMetadata pluginMeta, out string sourceUrl) private static bool IsSendingMail(string sourceUrl) => sourceUrl.TrimStart().StartsWith("mailto:", StringComparison.OrdinalIgnoreCase);
{
sourceUrl = string.Empty;
if (string.IsNullOrWhiteSpace(pluginMeta.SourceURL))
return false;
var normalizedSourceUrl = pluginMeta.SourceURL.Trim();
if (!Uri.TryCreate(normalizedSourceUrl, UriKind.Absolute, out var sourceUri))
return false;
if (sourceUri.Scheme is not ("http" or "https"))
return false;
sourceUrl = sourceUri.ToString();
return true;
}
#region Overrides of MSGComponentBase #region Overrides of MSGComponentBase

View File

@ -30,7 +30,12 @@ AUTHORS = {"<Company Name>"}
-- The support contact for the plugin: -- The support contact for the plugin:
SUPPORT_CONTACT = "<IT Department of Company Name>" SUPPORT_CONTACT = "<IT Department of Company Name>"
-- The source URL for the plugin: -- The source URL for the plugin. Can be a HTTP(S) URL or an mailto link.
-- You may link to an internal documentation page, a Git repository, or
-- to a support or wiki page.
--
-- A mailto link could look like:
-- SOURCE_URL = "mailto:helpdesk@company.org?subject=Help"
SOURCE_URL = "<Any internal Git repository>" SOURCE_URL = "<Any internal Git repository>"
-- The categories for the plugin: -- The categories for the plugin:

View File

@ -5307,6 +5307,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T158493184"] = "Interne Plugins"
-- Disabled Plugins -- Disabled Plugins
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1724138133"] = "Deaktivierte Plugins" UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1724138133"] = "Deaktivierte Plugins"
-- Send a mail
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1999487139"] = "E-Mail senden"
-- Enable plugin -- Enable plugin
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2057806005"] = "Plugin aktivieren" UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2057806005"] = "Plugin aktivieren"
@ -6042,15 +6045,18 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2262604281"] = "Das
-- The field DESCRIPTION does not exist or is not a valid string. -- The field DESCRIPTION does not exist or is not a valid string.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T229488255"] = "Das Feld DESCRIPTION existiert nicht oder ist keine gültige Zeichenkette." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T229488255"] = "Das Feld DESCRIPTION existiert nicht oder ist keine gültige Zeichenkette."
-- The field SOURCE_URL is not a valid URL. The URL must start with 'http://' or 'https://'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2320984047"] = "Das Feld SOURCE_URL ist keine gültige URL. Die URL muss mit 'http://' oder 'https://' beginnen."
-- The field VERSION is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X). -- The field VERSION is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X).
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2538827536"] = "Das Feld VERSION ist keine gültige Versionsnummer. Die Versionsnummer muss als Zeichenkette im Format major.minor.patch (X.X.X) angegeben werden." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2538827536"] = "Das Feld VERSION ist keine gültige Versionsnummer. Die Versionsnummer muss als Zeichenkette im Format major.minor.patch (X.X.X) angegeben werden."
-- The field SOURCE_URL is not a valid URL. The URL must start with 'http://', 'https://', or 'mailto:'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2892057533"] = "Das Feld SOURCE_URL ist keine gültige URL. Die URL muss mit „http://“, „https://“ oder „mailto:“ beginnen."
-- The table AUTHORS is empty. At least one author must be specified. -- The table AUTHORS is empty. At least one author must be specified.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2981832540"] = "Die Tabelle AUTHORS ist leer. Es muss mindestens ein Autor angegeben werden." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2981832540"] = "Die Tabelle AUTHORS ist leer. Es muss mindestens ein Autor angegeben werden."
-- The field SOURCE_URL is not a valid URL. When the URL starts with 'mailto:', it must contain a valid email address as recipient.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T3165663073"] = "Das Feld SOURCE_URL ist keine gültige URL. Wenn die URL mit „mailto:“ beginnt, muss sie eine gültige E-Mail-Adresse als Empfänger enthalten."
-- The field SUPPORT_CONTACT is empty. The support contact must be a non-empty string. -- The field SUPPORT_CONTACT is empty. The support contact must be a non-empty string.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T3524814526"] = "Das Feld SUPPORT_CONTACT ist leer. Der Support-Kontakt muss eine nicht-leere Zeichenkette sein." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T3524814526"] = "Das Feld SUPPORT_CONTACT ist leer. Der Support-Kontakt muss eine nicht-leere Zeichenkette sein."

View File

@ -5307,6 +5307,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T158493184"] = "Internal Plugins"
-- Disabled Plugins -- Disabled Plugins
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1724138133"] = "Disabled Plugins" UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1724138133"] = "Disabled Plugins"
-- Send a mail
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1999487139"] = "Send a mail"
-- Enable plugin -- Enable plugin
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2057806005"] = "Enable plugin" UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2057806005"] = "Enable plugin"
@ -6042,15 +6045,18 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2262604281"] = "The
-- The field DESCRIPTION does not exist or is not a valid string. -- The field DESCRIPTION does not exist or is not a valid string.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T229488255"] = "The field DESCRIPTION does not exist or is not a valid string." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T229488255"] = "The field DESCRIPTION does not exist or is not a valid string."
-- The field SOURCE_URL is not a valid URL. The URL must start with 'http://' or 'https://'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2320984047"] = "The field SOURCE_URL is not a valid URL. The URL must start with 'http://' or 'https://'."
-- The field VERSION is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X). -- The field VERSION is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X).
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2538827536"] = "The field VERSION is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X)." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2538827536"] = "The field VERSION is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X)."
-- The field SOURCE_URL is not a valid URL. The URL must start with 'http://', 'https://', or 'mailto:'.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2892057533"] = "The field SOURCE_URL is not a valid URL. The URL must start with 'http://', 'https://', or 'mailto:'."
-- The table AUTHORS is empty. At least one author must be specified. -- The table AUTHORS is empty. At least one author must be specified.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2981832540"] = "The table AUTHORS is empty. At least one author must be specified." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2981832540"] = "The table AUTHORS is empty. At least one author must be specified."
-- The field SOURCE_URL is not a valid URL. When the URL starts with 'mailto:', it must contain a valid email address as recipient.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T3165663073"] = "The field SOURCE_URL is not a valid URL. When the URL starts with 'mailto:', it must contain a valid email address as recipient."
-- The field SUPPORT_CONTACT is empty. The support contact must be a non-empty string. -- The field SUPPORT_CONTACT is empty. The support contact must be a non-empty string.
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T3524814526"] = "The field SUPPORT_CONTACT is empty. The support contact must be a non-empty string." UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T3524814526"] = "The field SUPPORT_CONTACT is empty. The support contact must be a non-empty string."

View File

@ -332,17 +332,56 @@ public abstract partial class PluginBase : IPluginMetadata
return false; return false;
} }
if (!url.StartsWith("http://", StringComparison.InvariantCultureIgnoreCase) && !url.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase)) url = url.Trim();
if (!Uri.TryCreate(url, UriKind.Absolute, out var sourceUri))
{ {
url = string.Empty; url = string.Empty;
message = TB("The field SOURCE_URL is not a valid URL. The URL must start with 'http://' or 'https://'."); message = TB("The field SOURCE_URL is not a valid URL. The URL must start with 'http://', 'https://', or 'mailto:'.");
return false; return false;
} }
var isHttp = sourceUri.Scheme.Equals(Uri.UriSchemeHttp, StringComparison.OrdinalIgnoreCase);
var isHttps = sourceUri.Scheme.Equals(Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase);
var isMailTo = sourceUri.Scheme.Equals(Uri.UriSchemeMailto, StringComparison.OrdinalIgnoreCase);
if (!isHttp && !isHttps && !isMailTo)
{
url = string.Empty;
message = TB("The field SOURCE_URL is not a valid URL. The URL must start with 'http://', 'https://', or 'mailto:'.");
return false;
}
if (isMailTo)
{
var recipient = ExtractMailtoRecipient(url);
if (string.IsNullOrWhiteSpace(recipient))
{
url = string.Empty;
message = TB("The field SOURCE_URL is not a valid URL. When the URL starts with 'mailto:', it must contain a valid email address as recipient.");
return false;
}
}
url = sourceUri.ToString();
message = string.Empty; message = string.Empty;
return true; return true;
} }
private static string ExtractMailtoRecipient(string rawUrl)
{
var separatorIndex = rawUrl.IndexOf(':');
if (separatorIndex < 0 || separatorIndex + 1 >= rawUrl.Length)
return string.Empty;
var schemeSpecificPart = rawUrl[(separatorIndex + 1)..];
var queryStart = schemeSpecificPart.IndexOf('?');
var recipient = queryStart >= 0
? schemeSpecificPart[..queryStart]
: schemeSpecificPart;
return recipient.Trim();
}
/// <summary> /// <summary>
/// Tries to read the categories of the plugin. /// Tries to read the categories of the plugin.
/// </summary> /// </summary>

View File

@ -5,6 +5,6 @@
- Added an option in the app settings to create an encryption secret, which is required to encrypt values (for example, API keys) in configuration plugins. This feature only shows up when administration options are enabled. - Added an option in the app settings to create an encryption secret, which is required to encrypt values (for example, API keys) in configuration plugins. This feature only shows up when administration options are enabled.
- Improved the document analysis assistant (in beta) by hiding the export functionality by default. Enable the administration options in the app settings to show and use the export functionality. This streamlines the usage for regular users. - Improved the document analysis assistant (in beta) by hiding the export functionality by default. Enable the administration options in the app settings to show and use the export functionality. This streamlines the usage for regular users.
- Improved the workspaces experience by using a different color for the delete button to avoid confusion. - Improved the workspaces experience by using a different color for the delete button to avoid confusion.
- Improved the plugins page by adding a button to open the plugin's website. The action opens the plugin website in an external browser and is hidden for internal plugins. - Improved the plugins page by adding an action to open the plugin source link. The action opens website URLs in an external browser, supports `mailto:` links for direct email composition.
- Fixed an issue where manually saving chats in workspace manual-storage mode could appear unreliable during response streaming. The save button is now disabled while streaming to prevent partial saves. - Fixed an issue where manually saving chats in workspace manual-storage mode could appear unreliable during response streaming. The save button is now disabled while streaming to prevent partial saves.
- Upgraded dependencies. - Upgraded dependencies.