Web editor refactoring and some calendar occurrence summary stuff.
This commit is contained in:
@@ -93,10 +93,11 @@ private string searchQuery = string.Empty;
|
|||||||
|
|
||||||
## Localization
|
## Localization
|
||||||
|
|
||||||
1. Add English strings ONLY to `Wino.Core.Domain/Translations/en_US/resources.json`
|
1. Add English strings ONLY to Wino.Core.Domain/Translations/en_US/resources.json
|
||||||
2. Build project - source generators create Translator properties
|
2. Build project - source generators create Translator properties
|
||||||
3. Use `Translator.{PropertyName}` in code/XAML
|
3. Use Translator.{PropertyName} in code/XAML
|
||||||
4. **NEVER** edit other language files - Crowdin manages translations
|
4. NEVER edit any resources.json file outside Wino.Core.Domain/Translations/en_US/resources.json
|
||||||
|
5. Treat all non-en_US translation files as managed externally and leave them untouched, even when adding new localization keys
|
||||||
|
|
||||||
## Storage
|
## Storage
|
||||||
|
|
||||||
@@ -133,3 +134,4 @@ private string searchQuery = string.Empty;
|
|||||||
- In `EventDetailsPageViewModel.LoadAttendeesAsync`, never mutate `CurrentEvent.Attendees` outside `ExecuteUIThread(...)`.
|
- In `EventDetailsPageViewModel.LoadAttendeesAsync`, never mutate `CurrentEvent.Attendees` outside `ExecuteUIThread(...)`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -507,55 +507,21 @@ public partial class CalendarEventComposePageViewModel : CalendarBaseViewModel
|
|||||||
{
|
{
|
||||||
var effectiveStart = GetEffectiveStartDateTime();
|
var effectiveStart = GetEffectiveStartDateTime();
|
||||||
var effectiveEnd = GetEffectiveEndDateTime();
|
var effectiveEnd = GetEffectiveEndDateTime();
|
||||||
var timeSummary = IsAllDay
|
|
||||||
? Translator.CalendarItemAllDay
|
|
||||||
: string.Format(
|
|
||||||
CultureInfo.CurrentCulture,
|
|
||||||
Translator.CalendarEventCompose_TimeRangeSummary,
|
|
||||||
effectiveStart.ToString(CurrentSettings.DayHeaderDisplayType == DayHeaderDisplayType.TwentyFourHour ? "HH:mm" : "h:mm tt", CultureInfo.CurrentCulture),
|
|
||||||
effectiveEnd.ToString(CurrentSettings.DayHeaderDisplayType == DayHeaderDisplayType.TwentyFourHour ? "HH:mm" : "h:mm tt", CultureInfo.CurrentCulture));
|
|
||||||
|
|
||||||
if (!IsRecurring)
|
|
||||||
{
|
|
||||||
RecurrenceSummary = string.Format(
|
|
||||||
CultureInfo.CurrentCulture,
|
|
||||||
Translator.CalendarEventCompose_SingleOccurrenceSummary,
|
|
||||||
effectiveStart.ToString("dddd yyyy-MM-dd", CultureInfo.CurrentCulture),
|
|
||||||
timeSummary);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var frequencyLabel = SelectedRecurrenceFrequencyOption?.PluralLabel(SelectedRecurrenceInterval)
|
|
||||||
?? Translator.CalendarEventCompose_FrequencyWeekPlural;
|
|
||||||
|
|
||||||
var selectedDays = WeekdayOptions
|
var selectedDays = WeekdayOptions
|
||||||
.Where(option => option.IsSelected)
|
.Where(option => option.IsSelected)
|
||||||
.Select(option => option.FullDayName)
|
.Select(option => option.DayOfWeek)
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var weekdaySummary = selectedDays.Count == 0
|
RecurrenceSummary = CalendarRecurrenceSummaryFormatter.BuildSummary(
|
||||||
? string.Empty
|
IsRecurring,
|
||||||
: string.Format(
|
effectiveStart,
|
||||||
CultureInfo.CurrentCulture,
|
effectiveEnd,
|
||||||
Translator.CalendarEventCompose_WeekdaySummary,
|
IsAllDay,
|
||||||
string.Join(", ", selectedDays));
|
CurrentSettings,
|
||||||
|
|
||||||
var untilSummary = RecurrenceEndDate.HasValue
|
|
||||||
? string.Format(
|
|
||||||
CultureInfo.CurrentCulture,
|
|
||||||
Translator.CalendarEventCompose_UntilSummary,
|
|
||||||
RecurrenceEndDate.Value.ToString("ddd yyyy-MM-dd", CultureInfo.CurrentCulture))
|
|
||||||
: string.Empty;
|
|
||||||
|
|
||||||
RecurrenceSummary = string.Format(
|
|
||||||
CultureInfo.CurrentCulture,
|
|
||||||
Translator.CalendarEventCompose_RecurringSummary,
|
|
||||||
SelectedRecurrenceInterval,
|
SelectedRecurrenceInterval,
|
||||||
frequencyLabel,
|
SelectedRecurrenceFrequencyOption?.Frequency ?? CalendarItemRecurrenceFrequency.Weekly,
|
||||||
weekdaySummary,
|
selectedDays,
|
||||||
timeSummary,
|
RecurrenceEndDate);
|
||||||
effectiveStart.ToString("dddd yyyy-MM-dd", CultureInfo.CurrentCulture),
|
|
||||||
untilSummary).Trim();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string BuildRecurrenceRule()
|
private string BuildRecurrenceRule()
|
||||||
@@ -707,3 +673,5 @@ public partial class CalendarComposeWeekdayOption : ObservableObject
|
|||||||
Label = label;
|
Label = label;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using Wino.Core.Domain.Enums;
|
||||||
|
using Wino.Core.Domain.Models.Calendar;
|
||||||
|
|
||||||
|
namespace Wino.Core.Domain;
|
||||||
|
|
||||||
|
public static class CalendarRecurrenceSummaryFormatter
|
||||||
|
{
|
||||||
|
private static readonly DayOfWeek[] OrderedDays =
|
||||||
|
[
|
||||||
|
DayOfWeek.Monday,
|
||||||
|
DayOfWeek.Tuesday,
|
||||||
|
DayOfWeek.Wednesday,
|
||||||
|
DayOfWeek.Thursday,
|
||||||
|
DayOfWeek.Friday,
|
||||||
|
DayOfWeek.Saturday,
|
||||||
|
DayOfWeek.Sunday
|
||||||
|
];
|
||||||
|
|
||||||
|
public static string BuildSummary(
|
||||||
|
bool isRecurring,
|
||||||
|
DateTimeOffset effectiveStart,
|
||||||
|
DateTimeOffset effectiveEnd,
|
||||||
|
bool isAllDay,
|
||||||
|
CalendarSettings settings,
|
||||||
|
int interval,
|
||||||
|
CalendarItemRecurrenceFrequency frequency,
|
||||||
|
IReadOnlyCollection<DayOfWeek> daysOfWeek,
|
||||||
|
DateTimeOffset? recurrenceEndDate)
|
||||||
|
{
|
||||||
|
var culture = settings?.CultureInfo ?? CultureInfo.CurrentCulture;
|
||||||
|
var timeSummary = isAllDay
|
||||||
|
? Translator.CalendarItemAllDay
|
||||||
|
: string.Format(
|
||||||
|
culture,
|
||||||
|
Translator.CalendarEventCompose_TimeRangeSummary,
|
||||||
|
effectiveStart.ToString(settings?.DayHeaderDisplayType == DayHeaderDisplayType.TwentyFourHour ? "HH:mm" : "h:mm tt", culture),
|
||||||
|
effectiveEnd.ToString(settings?.DayHeaderDisplayType == DayHeaderDisplayType.TwentyFourHour ? "HH:mm" : "h:mm tt", culture));
|
||||||
|
|
||||||
|
if (!isRecurring)
|
||||||
|
{
|
||||||
|
return string.Format(
|
||||||
|
culture,
|
||||||
|
Translator.CalendarEventCompose_SingleOccurrenceSummary,
|
||||||
|
effectiveStart.ToString("dddd yyyy-MM-dd", culture),
|
||||||
|
timeSummary);
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalizedDays = NormalizeDays(daysOfWeek);
|
||||||
|
var isEveryDay = (frequency == CalendarItemRecurrenceFrequency.Daily && interval == 1) ||
|
||||||
|
(frequency == CalendarItemRecurrenceFrequency.Weekly && interval == 1 && normalizedDays.Count == 7);
|
||||||
|
|
||||||
|
var cadenceSummary = isEveryDay
|
||||||
|
? $"{Translator.CalendarEventCompose_Every} {Translator.CalendarEventCompose_FrequencyDay}"
|
||||||
|
: interval == 1
|
||||||
|
? $"{Translator.CalendarEventCompose_Every} {GetSingularFrequencyLabel(frequency)}"
|
||||||
|
: $"{Translator.CalendarEventCompose_Every} {interval.ToString(culture)} {GetPluralFrequencyLabel(frequency)}";
|
||||||
|
|
||||||
|
var weekdaySummary = string.Empty;
|
||||||
|
if (frequency == CalendarItemRecurrenceFrequency.Weekly && normalizedDays.Count > 0 && normalizedDays.Count < 7)
|
||||||
|
{
|
||||||
|
weekdaySummary = string.Format(
|
||||||
|
culture,
|
||||||
|
Translator.CalendarEventCompose_WeekdaySummary,
|
||||||
|
string.Join(", ", normalizedDays.Select(day => culture.DateTimeFormat.GetDayName(day))));
|
||||||
|
}
|
||||||
|
|
||||||
|
var untilSummary = recurrenceEndDate.HasValue
|
||||||
|
? string.Format(
|
||||||
|
culture,
|
||||||
|
Translator.CalendarEventCompose_UntilSummary,
|
||||||
|
recurrenceEndDate.Value.ToString("ddd yyyy-MM-dd", culture))
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
|
return string.Format(
|
||||||
|
culture,
|
||||||
|
Translator.GetTranslatedString("CalendarEventCompose_RecurringSummarySmart"),
|
||||||
|
cadenceSummary,
|
||||||
|
weekdaySummary,
|
||||||
|
timeSummary,
|
||||||
|
effectiveStart.ToString("dddd yyyy-MM-dd", culture),
|
||||||
|
untilSummary).Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<DayOfWeek> NormalizeDays(IReadOnlyCollection<DayOfWeek> daysOfWeek)
|
||||||
|
{
|
||||||
|
if (daysOfWeek == null || daysOfWeek.Count == 0)
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return daysOfWeek
|
||||||
|
.Distinct()
|
||||||
|
.OrderBy(day => Array.IndexOf(OrderedDays, day))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetSingularFrequencyLabel(CalendarItemRecurrenceFrequency frequency)
|
||||||
|
{
|
||||||
|
return frequency switch
|
||||||
|
{
|
||||||
|
CalendarItemRecurrenceFrequency.Daily => Translator.CalendarEventCompose_FrequencyDay,
|
||||||
|
CalendarItemRecurrenceFrequency.Weekly => Translator.CalendarEventCompose_FrequencyWeek,
|
||||||
|
CalendarItemRecurrenceFrequency.Monthly => Translator.CalendarEventCompose_FrequencyMonth,
|
||||||
|
CalendarItemRecurrenceFrequency.Yearly => Translator.CalendarEventCompose_FrequencyYear,
|
||||||
|
_ => Translator.CalendarEventCompose_FrequencyWeek
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetPluralFrequencyLabel(CalendarItemRecurrenceFrequency frequency)
|
||||||
|
{
|
||||||
|
return frequency switch
|
||||||
|
{
|
||||||
|
CalendarItemRecurrenceFrequency.Daily => Translator.CalendarEventCompose_FrequencyDayPlural,
|
||||||
|
CalendarItemRecurrenceFrequency.Weekly => Translator.CalendarEventCompose_FrequencyWeekPlural,
|
||||||
|
CalendarItemRecurrenceFrequency.Monthly => Translator.CalendarEventCompose_FrequencyMonthPlural,
|
||||||
|
CalendarItemRecurrenceFrequency.Yearly => Translator.CalendarEventCompose_FrequencyYearPlural,
|
||||||
|
_ => Translator.CalendarEventCompose_FrequencyWeekPlural
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"AccountAlias_Column_Alias": "Alias",
|
"AccountAlias_Column_Alias": "Alias",
|
||||||
"AccountAlias_Column_IsPrimaryAlias": "Primary",
|
"AccountAlias_Column_IsPrimaryAlias": "Primary",
|
||||||
"AccountAlias_Column_Verified": "Verified",
|
"AccountAlias_Column_Verified": "Verified",
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.UI;
|
||||||
|
using Microsoft.UI.Xaml.Media;
|
||||||
|
using Windows.UI;
|
||||||
|
|
||||||
|
namespace Wino.Mail.Controls;
|
||||||
|
|
||||||
|
public interface IEditorCommandTarget
|
||||||
|
{
|
||||||
|
EditorState CurrentState { get; }
|
||||||
|
EditorCapabilities Capabilities { get; }
|
||||||
|
event EventHandler<EditorState>? StateChanged;
|
||||||
|
event EventHandler<EditorCapabilities>? CapabilitiesChanged;
|
||||||
|
Task ExecuteCommandAsync(EditorCommand command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IEditorCommandControl
|
||||||
|
{
|
||||||
|
IEditorCommandTarget? CommandTarget { get; set; }
|
||||||
|
void AttachCommandTarget(IEditorCommandTarget? target);
|
||||||
|
void DetachCommandTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EditorCommandKind
|
||||||
|
{
|
||||||
|
ToggleBold,
|
||||||
|
ToggleItalic,
|
||||||
|
ToggleUnderline,
|
||||||
|
ToggleStrikethrough,
|
||||||
|
ToggleOrderedList,
|
||||||
|
ToggleUnorderedList,
|
||||||
|
Indent,
|
||||||
|
Outdent,
|
||||||
|
SetAlignment,
|
||||||
|
SetFontFamily,
|
||||||
|
SetFontSize,
|
||||||
|
SetParagraphStyle,
|
||||||
|
SetTextColor,
|
||||||
|
SetHighlightColor,
|
||||||
|
SetLineHeight,
|
||||||
|
InsertImage,
|
||||||
|
InsertLink,
|
||||||
|
RemoveLink,
|
||||||
|
InsertEmoji,
|
||||||
|
InsertTable,
|
||||||
|
ToggleBuiltInToolbar,
|
||||||
|
ToggleTheme,
|
||||||
|
ToggleSpellCheck
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EditorTextAlignment
|
||||||
|
{
|
||||||
|
Left,
|
||||||
|
Center,
|
||||||
|
Right,
|
||||||
|
Justify
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record class EditorCommand(EditorCommandKind Kind, object? Value = null)
|
||||||
|
{
|
||||||
|
public static EditorCommand ToggleBold() => new(EditorCommandKind.ToggleBold);
|
||||||
|
public static EditorCommand ToggleItalic() => new(EditorCommandKind.ToggleItalic);
|
||||||
|
public static EditorCommand ToggleUnderline() => new(EditorCommandKind.ToggleUnderline);
|
||||||
|
public static EditorCommand ToggleStrikethrough() => new(EditorCommandKind.ToggleStrikethrough);
|
||||||
|
public static EditorCommand ToggleOrderedList() => new(EditorCommandKind.ToggleOrderedList);
|
||||||
|
public static EditorCommand ToggleUnorderedList() => new(EditorCommandKind.ToggleUnorderedList);
|
||||||
|
public static EditorCommand Indent() => new(EditorCommandKind.Indent);
|
||||||
|
public static EditorCommand Outdent() => new(EditorCommandKind.Outdent);
|
||||||
|
public static EditorCommand SetAlignment(EditorTextAlignment alignment) => new(EditorCommandKind.SetAlignment, alignment);
|
||||||
|
public static EditorCommand SetFontFamily(string fontFamily) => new(EditorCommandKind.SetFontFamily, fontFamily);
|
||||||
|
public static EditorCommand SetFontSize(int fontSize) => new(EditorCommandKind.SetFontSize, fontSize);
|
||||||
|
public static EditorCommand SetParagraphStyle(string tagName) => new(EditorCommandKind.SetParagraphStyle, tagName);
|
||||||
|
public static EditorCommand SetTextColor(string color) => new(EditorCommandKind.SetTextColor, color);
|
||||||
|
public static EditorCommand SetHighlightColor(string color) => new(EditorCommandKind.SetHighlightColor, color);
|
||||||
|
public static EditorCommand SetLineHeight(string lineHeight) => new(EditorCommandKind.SetLineHeight, lineHeight);
|
||||||
|
public static EditorCommand InsertImage() => new(EditorCommandKind.InsertImage);
|
||||||
|
public static EditorCommand InsertEmoji() => new(EditorCommandKind.InsertEmoji);
|
||||||
|
public static EditorCommand InsertLink(EditorLinkCommandArgs args) => new(EditorCommandKind.InsertLink, args);
|
||||||
|
public static EditorCommand RemoveLink() => new(EditorCommandKind.RemoveLink);
|
||||||
|
public static EditorCommand InsertTable(EditorTableCommandArgs args) => new(EditorCommandKind.InsertTable, args);
|
||||||
|
public static EditorCommand ToggleBuiltInToolbar(bool isVisible) => new(EditorCommandKind.ToggleBuiltInToolbar, isVisible);
|
||||||
|
public static EditorCommand ToggleTheme(bool isDarkMode) => new(EditorCommandKind.ToggleTheme, isDarkMode);
|
||||||
|
public static EditorCommand ToggleSpellCheck(bool isEnabled) => new(EditorCommandKind.ToggleSpellCheck, isEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record class EditorLinkCommandArgs(
|
||||||
|
[property: JsonPropertyName("url")] string Url,
|
||||||
|
[property: JsonPropertyName("text")] string? Text = null,
|
||||||
|
[property: JsonPropertyName("openInNewWindow")] bool OpenInNewWindow = true);
|
||||||
|
|
||||||
|
public sealed record class EditorTableCommandArgs(
|
||||||
|
[property: JsonPropertyName("rows")] int Rows,
|
||||||
|
[property: JsonPropertyName("columns")] int Columns);
|
||||||
|
|
||||||
|
public sealed record class EditorColorOption(string Name, string Value)
|
||||||
|
{
|
||||||
|
public SolidColorBrush Brush => new(ParseColor(Value));
|
||||||
|
|
||||||
|
private static Color ParseColor(string? value)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
return Colors.Transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hex = value.Trim().TrimStart('#');
|
||||||
|
if (hex.Length == 6)
|
||||||
|
{
|
||||||
|
hex = $"FF{hex}";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hex.Length != 8 || !uint.TryParse(hex, System.Globalization.NumberStyles.HexNumber, null, out var argb))
|
||||||
|
{
|
||||||
|
return Colors.Transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Color.FromArgb(
|
||||||
|
(byte)((argb >> 24) & 0xFF),
|
||||||
|
(byte)((argb >> 16) & 0xFF),
|
||||||
|
(byte)((argb >> 8) & 0xFF),
|
||||||
|
(byte)(argb & 0xFF));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record class EditorParagraphStyleOption(string Name, string Tag);
|
||||||
|
|
||||||
|
public sealed record class EditorCapabilities
|
||||||
|
{
|
||||||
|
public IReadOnlyList<string> Fonts { get; init; } = Array.Empty<string>();
|
||||||
|
public IReadOnlyList<int> FontSizes { get; init; } = Array.Empty<int>();
|
||||||
|
public IReadOnlyList<EditorColorOption> TextColors { get; init; } = Array.Empty<EditorColorOption>();
|
||||||
|
public IReadOnlyList<EditorColorOption> HighlightColors { get; init; } = Array.Empty<EditorColorOption>();
|
||||||
|
public IReadOnlyList<EditorParagraphStyleOption> ParagraphStyles { get; init; } = Array.Empty<EditorParagraphStyleOption>();
|
||||||
|
public IReadOnlyList<string> LineHeights { get; init; } = Array.Empty<string>();
|
||||||
|
public IReadOnlyList<EditorTextAlignment> Alignments { get; init; } = Array.Empty<EditorTextAlignment>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record class EditorState
|
||||||
|
{
|
||||||
|
public bool IsBold { get; init; }
|
||||||
|
public bool IsItalic { get; init; }
|
||||||
|
public bool IsUnderline { get; init; }
|
||||||
|
public bool IsStrikethrough { get; init; }
|
||||||
|
public bool IsOrderedList { get; init; }
|
||||||
|
public bool IsUnorderedList { get; init; }
|
||||||
|
public bool CanIndent { get; init; } = true;
|
||||||
|
public bool CanOutdent { get; init; }
|
||||||
|
public bool HasSelection { get; init; }
|
||||||
|
public bool IsDarkMode { get; init; }
|
||||||
|
public bool IsBuiltInToolbarVisible { get; init; }
|
||||||
|
public bool IsSpellCheckEnabled { get; init; } = true;
|
||||||
|
public EditorTextAlignment Alignment { get; init; } = EditorTextAlignment.Left;
|
||||||
|
public string? FontFamily { get; init; }
|
||||||
|
public int? FontSize { get; init; }
|
||||||
|
public string? ParagraphStyle { get; init; }
|
||||||
|
public string? TextColor { get; init; }
|
||||||
|
public string? HighlightColor { get; init; }
|
||||||
|
public string? LineHeight { get; init; }
|
||||||
|
public string? LinkUrl { get; init; }
|
||||||
|
public string? SelectedText { get; init; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
<UserControl
|
||||||
|
x:Class="Wino.Mail.Controls.EditorTabbedCommandBarControl"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:controls="using:Wino.Mail.WinUI.Controls"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mail="using:Wino.Mail.Controls"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:toolkit="using:CommunityToolkit.WinUI.Controls"
|
||||||
|
x:Name="Root"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<UserControl.Resources>
|
||||||
|
<Style x:Key="CompactComboBoxStyle" TargetType="ComboBox">
|
||||||
|
<Setter Property="MinWidth" Value="88" />
|
||||||
|
<Setter Property="MaxWidth" Value="136" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="CompactPickerContainerStyle" TargetType="AppBarElementContainer">
|
||||||
|
<Setter Property="MinWidth" Value="0" />
|
||||||
|
<Setter Property="Margin" Value="0" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<DataTemplate x:Key="ColorOptionTemplate" x:DataType="mail:EditorColorOption">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
|
<Grid Width="14" Height="14">
|
||||||
|
<Rectangle
|
||||||
|
RadiusX="3"
|
||||||
|
RadiusY="3"
|
||||||
|
Fill="{x:Bind Brush}"
|
||||||
|
Stroke="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
|
StrokeThickness="1" />
|
||||||
|
</Grid>
|
||||||
|
<TextBlock VerticalAlignment="Center" Text="{x:Bind Name}" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<toolkit:TabbedCommandBar>
|
||||||
|
<toolkit:TabbedCommandBar.Resources>
|
||||||
|
<SolidColorBrush x:Key="TabContentContentBorderBackground" Color="Transparent" />
|
||||||
|
<SolidColorBrush x:Key="TabContentContentBorderBorderBrush" Color="Transparent" />
|
||||||
|
<Thickness x:Key="TabContentBorderBorderThickness">0</Thickness>
|
||||||
|
</toolkit:TabbedCommandBar.Resources>
|
||||||
|
|
||||||
|
<toolkit:TabbedCommandBar.PaneCustomContent>
|
||||||
|
<ContentPresenter Content="{x:Bind PaneCustomContent, Mode=OneWay}" />
|
||||||
|
</toolkit:TabbedCommandBar.PaneCustomContent>
|
||||||
|
|
||||||
|
<toolkit:TabbedCommandBar.MenuItems>
|
||||||
|
<toolkit:TabbedCommandBarItem DefaultLabelPosition="Collapsed" Header="Format">
|
||||||
|
<AppBarToggleButton x:Name="BoldButton" Click="BoldButton_Click" Label="Bold" ToolTipService.ToolTip="Bold (Ctrl+B)">
|
||||||
|
<AppBarToggleButton.Icon>
|
||||||
|
<PathIcon Data="{StaticResource BoldPathIcon}" />
|
||||||
|
</AppBarToggleButton.Icon>
|
||||||
|
</AppBarToggleButton>
|
||||||
|
|
||||||
|
<AppBarToggleButton x:Name="ItalicButton" Click="ItalicButton_Click" Label="Italic" ToolTipService.ToolTip="Italic (Ctrl+I)">
|
||||||
|
<AppBarToggleButton.Icon>
|
||||||
|
<PathIcon Data="{StaticResource ItalicPathIcon}" />
|
||||||
|
</AppBarToggleButton.Icon>
|
||||||
|
</AppBarToggleButton>
|
||||||
|
|
||||||
|
<AppBarToggleButton x:Name="UnderlineButton" Click="UnderlineButton_Click" Label="Underline" ToolTipService.ToolTip="Underline (Ctrl+U)">
|
||||||
|
<AppBarToggleButton.Icon>
|
||||||
|
<PathIcon Data="{StaticResource UnderlinePathIcon}" />
|
||||||
|
</AppBarToggleButton.Icon>
|
||||||
|
</AppBarToggleButton>
|
||||||
|
|
||||||
|
<AppBarToggleButton x:Name="StrikeButton" Click="StrikeButton_Click" Label="Strikethrough" ToolTipService.ToolTip="Strikethrough">
|
||||||
|
<AppBarToggleButton.Icon>
|
||||||
|
<PathIcon Data="{StaticResource StrikePathIcon}" />
|
||||||
|
</AppBarToggleButton.Icon>
|
||||||
|
</AppBarToggleButton>
|
||||||
|
|
||||||
|
<AppBarSeparator />
|
||||||
|
|
||||||
|
<AppBarToggleButton x:Name="BulletListButton" Click="BulletListButton_Click" Label="Bullets" ToolTipService.ToolTip="Bulleted list">
|
||||||
|
<AppBarToggleButton.Icon>
|
||||||
|
<PathIcon Data="{StaticResource BulletedListPathIcon}" />
|
||||||
|
</AppBarToggleButton.Icon>
|
||||||
|
</AppBarToggleButton>
|
||||||
|
|
||||||
|
<AppBarToggleButton x:Name="OrderedListButton" Click="OrderedListButton_Click" Label="Numbered list" ToolTipService.ToolTip="Numbered list">
|
||||||
|
<AppBarToggleButton.Icon>
|
||||||
|
<PathIcon Data="{StaticResource OrderedListPathIcon}" />
|
||||||
|
</AppBarToggleButton.Icon>
|
||||||
|
</AppBarToggleButton>
|
||||||
|
|
||||||
|
<AppBarButton x:Name="OutdentButton" Click="OutdentButton_Click" Label="Outdent" ToolTipService.ToolTip="Outdent">
|
||||||
|
<AppBarButton.Icon>
|
||||||
|
<PathIcon Data="{StaticResource DecreaseIndentPathIcon}" />
|
||||||
|
</AppBarButton.Icon>
|
||||||
|
</AppBarButton>
|
||||||
|
|
||||||
|
<AppBarButton x:Name="IndentButton" Click="IndentButton_Click" Label="Indent" ToolTipService.ToolTip="Indent">
|
||||||
|
<AppBarButton.Icon>
|
||||||
|
<PathIcon Data="{StaticResource IncreaseIndentPathIcon}" />
|
||||||
|
</AppBarButton.Icon>
|
||||||
|
</AppBarButton>
|
||||||
|
|
||||||
|
<AppBarElementContainer Style="{StaticResource CompactPickerContainerStyle}" ToolTipService.ToolTip="Text alignment">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
|
<PathIcon
|
||||||
|
Width="14"
|
||||||
|
Height="14"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Data="{StaticResource AlignLeftPathIcon}" />
|
||||||
|
<ComboBox
|
||||||
|
x:Name="AlignmentComboBox"
|
||||||
|
Style="{StaticResource CompactComboBoxStyle}"
|
||||||
|
MinWidth="108"
|
||||||
|
SelectionChanged="AlignmentComboBox_SelectionChanged" />
|
||||||
|
</StackPanel>
|
||||||
|
</AppBarElementContainer>
|
||||||
|
|
||||||
|
<AppBarElementContainer Style="{StaticResource CompactPickerContainerStyle}" ToolTipService.ToolTip="Font family">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
|
<FontIcon VerticalAlignment="Center" FontSize="14" Glyph="" />
|
||||||
|
<ComboBox
|
||||||
|
x:Name="FontFamilyComboBox"
|
||||||
|
Style="{StaticResource CompactComboBoxStyle}"
|
||||||
|
MinWidth="120"
|
||||||
|
PlaceholderText="Font"
|
||||||
|
SelectionChanged="FontFamilyComboBox_SelectionChanged" />
|
||||||
|
</StackPanel>
|
||||||
|
</AppBarElementContainer>
|
||||||
|
|
||||||
|
<AppBarElementContainer Style="{StaticResource CompactPickerContainerStyle}" ToolTipService.ToolTip="Font size">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontWeight="SemiBold" Text="12" />
|
||||||
|
<ComboBox
|
||||||
|
x:Name="FontSizeComboBox"
|
||||||
|
Style="{StaticResource CompactComboBoxStyle}"
|
||||||
|
MinWidth="80"
|
||||||
|
PlaceholderText="Size"
|
||||||
|
SelectionChanged="FontSizeComboBox_SelectionChanged" />
|
||||||
|
</StackPanel>
|
||||||
|
</AppBarElementContainer>
|
||||||
|
|
||||||
|
<AppBarElementContainer Style="{StaticResource CompactPickerContainerStyle}" ToolTipService.ToolTip="Paragraph style">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
|
<PathIcon
|
||||||
|
Width="14"
|
||||||
|
Height="14"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Data="{StaticResource ParagraphPathIcon}" />
|
||||||
|
<ComboBox
|
||||||
|
x:Name="ParagraphStyleComboBox"
|
||||||
|
Style="{StaticResource CompactComboBoxStyle}"
|
||||||
|
MinWidth="110"
|
||||||
|
DisplayMemberPath="Name"
|
||||||
|
PlaceholderText="Paragraph"
|
||||||
|
SelectionChanged="ParagraphStyleComboBox_SelectionChanged" />
|
||||||
|
</StackPanel>
|
||||||
|
</AppBarElementContainer>
|
||||||
|
|
||||||
|
<AppBarElementContainer Style="{StaticResource CompactPickerContainerStyle}" ToolTipService.ToolTip="Text color">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
|
<TextBlock VerticalAlignment="Center" FontWeight="SemiBold" Text="A" />
|
||||||
|
<ComboBox
|
||||||
|
x:Name="TextColorComboBox"
|
||||||
|
Style="{StaticResource CompactComboBoxStyle}"
|
||||||
|
MinWidth="116"
|
||||||
|
ItemTemplate="{StaticResource ColorOptionTemplate}"
|
||||||
|
PlaceholderText="Text"
|
||||||
|
SelectionChanged="TextColorComboBox_SelectionChanged" />
|
||||||
|
</StackPanel>
|
||||||
|
</AppBarElementContainer>
|
||||||
|
|
||||||
|
<AppBarElementContainer Style="{StaticResource CompactPickerContainerStyle}" ToolTipService.ToolTip="Highlight color">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
|
<FontIcon VerticalAlignment="Center" FontSize="14" Glyph="" />
|
||||||
|
<ComboBox
|
||||||
|
x:Name="HighlightColorComboBox"
|
||||||
|
Style="{StaticResource CompactComboBoxStyle}"
|
||||||
|
MinWidth="122"
|
||||||
|
ItemTemplate="{StaticResource ColorOptionTemplate}"
|
||||||
|
PlaceholderText="Highlight"
|
||||||
|
SelectionChanged="HighlightColorComboBox_SelectionChanged" />
|
||||||
|
</StackPanel>
|
||||||
|
</AppBarElementContainer>
|
||||||
|
|
||||||
|
<AppBarElementContainer Style="{StaticResource CompactPickerContainerStyle}" ToolTipService.ToolTip="Line height">
|
||||||
|
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||||
|
<FontIcon VerticalAlignment="Center" FontSize="14" Glyph="" />
|
||||||
|
<ComboBox
|
||||||
|
x:Name="LineHeightComboBox"
|
||||||
|
Style="{StaticResource CompactComboBoxStyle}"
|
||||||
|
MinWidth="86"
|
||||||
|
PlaceholderText="Line"
|
||||||
|
SelectionChanged="LineHeightComboBox_SelectionChanged" />
|
||||||
|
</StackPanel>
|
||||||
|
</AppBarElementContainer>
|
||||||
|
</toolkit:TabbedCommandBarItem>
|
||||||
|
|
||||||
|
<toolkit:TabbedCommandBarItem DefaultLabelPosition="Collapsed" Header="Insert">
|
||||||
|
<AppBarButton x:Name="ImageButton" Click="ImageButton_Click" Label="Image" ToolTipService.ToolTip="Insert image">
|
||||||
|
<AppBarButton.Icon>
|
||||||
|
<PathIcon Data="{StaticResource AddPhotoPathIcon}" />
|
||||||
|
</AppBarButton.Icon>
|
||||||
|
</AppBarButton>
|
||||||
|
|
||||||
|
<AppBarButton x:Name="EmojiButton" Click="EmojiButton_Click" Label="Emoji" ToolTipService.ToolTip="Insert emoji">
|
||||||
|
<AppBarButton.Icon>
|
||||||
|
<PathIcon Data="{StaticResource EmojiPathIcon}" />
|
||||||
|
</AppBarButton.Icon>
|
||||||
|
</AppBarButton>
|
||||||
|
|
||||||
|
<AppBarButton x:Name="LinkButton" Click="LinkButton_Click" Label="Link" ToolTipService.ToolTip="Insert or edit link">
|
||||||
|
<AppBarButton.Icon>
|
||||||
|
<PathIcon Data="{StaticResource AddLinkPathIcon}" />
|
||||||
|
</AppBarButton.Icon>
|
||||||
|
</AppBarButton>
|
||||||
|
|
||||||
|
<AppBarButton
|
||||||
|
x:Name="RemoveLinkButton"
|
||||||
|
Click="RemoveLinkButton_Click"
|
||||||
|
Label="Remove link"
|
||||||
|
ToolTipService.ToolTip="Remove link"
|
||||||
|
Visibility="Collapsed">
|
||||||
|
<AppBarButton.Icon>
|
||||||
|
<SymbolIcon Symbol="Remove" />
|
||||||
|
</AppBarButton.Icon>
|
||||||
|
</AppBarButton>
|
||||||
|
|
||||||
|
<AppBarButton x:Name="TableButton" Click="TableButton_Click" Label="Table" ToolTipService.ToolTip="Insert table">
|
||||||
|
<AppBarButton.Icon>
|
||||||
|
<FontIcon Glyph="" />
|
||||||
|
</AppBarButton.Icon>
|
||||||
|
</AppBarButton>
|
||||||
|
|
||||||
|
<AppBarElementContainer ToolTipService.ToolTip="Insert actions">
|
||||||
|
<ContentPresenter Content="{x:Bind InsertCustomContent, Mode=OneWay}" />
|
||||||
|
</AppBarElementContainer>
|
||||||
|
</toolkit:TabbedCommandBarItem>
|
||||||
|
|
||||||
|
<toolkit:TabbedCommandBarItem DefaultLabelPosition="Collapsed" Header="Options">
|
||||||
|
<AppBarToggleButton x:Name="BuiltInToolbarButton" Click="BuiltInToolbarButton_Click" Label="Web toolbar" ToolTipService.ToolTip="Toggle built-in web toolbar">
|
||||||
|
<AppBarToggleButton.Icon>
|
||||||
|
<PathIcon Data="{StaticResource WebviewToolBarPathIcon}" />
|
||||||
|
</AppBarToggleButton.Icon>
|
||||||
|
</AppBarToggleButton>
|
||||||
|
|
||||||
|
<AppBarToggleButton x:Name="SpellCheckButton" Click="SpellCheckButton_Click" Label="Spell check" ToolTipService.ToolTip="Toggle spell check">
|
||||||
|
<AppBarToggleButton.Icon>
|
||||||
|
<FontIcon Glyph="" />
|
||||||
|
</AppBarToggleButton.Icon>
|
||||||
|
</AppBarToggleButton>
|
||||||
|
|
||||||
|
<AppBarElementContainer ToolTipService.ToolTip="Composer options">
|
||||||
|
<ContentPresenter Content="{x:Bind OptionsCustomContent, Mode=OneWay}" />
|
||||||
|
</AppBarElementContainer>
|
||||||
|
</toolkit:TabbedCommandBarItem>
|
||||||
|
</toolkit:TabbedCommandBar.MenuItems>
|
||||||
|
</toolkit:TabbedCommandBar>
|
||||||
|
</UserControl>
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,418 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
|
||||||
|
namespace Wino.Mail.Controls;
|
||||||
|
|
||||||
|
public sealed partial class EditorTabbedCommandBarControl : UserControl, IEditorCommandControl
|
||||||
|
{
|
||||||
|
public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register(
|
||||||
|
nameof(CommandTarget),
|
||||||
|
typeof(IEditorCommandTarget),
|
||||||
|
typeof(EditorTabbedCommandBarControl),
|
||||||
|
new PropertyMetadata(null, OnCommandTargetChanged));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty PaneCustomContentProperty = DependencyProperty.Register(
|
||||||
|
nameof(PaneCustomContent),
|
||||||
|
typeof(object),
|
||||||
|
typeof(EditorTabbedCommandBarControl),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty InsertCustomContentProperty = DependencyProperty.Register(
|
||||||
|
nameof(InsertCustomContent),
|
||||||
|
typeof(object),
|
||||||
|
typeof(EditorTabbedCommandBarControl),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
public static readonly DependencyProperty OptionsCustomContentProperty = DependencyProperty.Register(
|
||||||
|
nameof(OptionsCustomContent),
|
||||||
|
typeof(object),
|
||||||
|
typeof(EditorTabbedCommandBarControl),
|
||||||
|
new PropertyMetadata(null));
|
||||||
|
|
||||||
|
private bool _isApplyingState;
|
||||||
|
private IEditorCommandTarget? _subscribedTarget;
|
||||||
|
|
||||||
|
public IEditorCommandTarget? CommandTarget
|
||||||
|
{
|
||||||
|
get => (IEditorCommandTarget?)GetValue(CommandTargetProperty);
|
||||||
|
set => SetValue(CommandTargetProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? PaneCustomContent
|
||||||
|
{
|
||||||
|
get => GetValue(PaneCustomContentProperty);
|
||||||
|
set => SetValue(PaneCustomContentProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? InsertCustomContent
|
||||||
|
{
|
||||||
|
get => GetValue(InsertCustomContentProperty);
|
||||||
|
set => SetValue(InsertCustomContentProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object? OptionsCustomContent
|
||||||
|
{
|
||||||
|
get => GetValue(OptionsCustomContentProperty);
|
||||||
|
set => SetValue(OptionsCustomContentProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EditorTabbedCommandBarControl()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
Loaded += OnLoaded;
|
||||||
|
Unloaded += OnUnloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AttachCommandTarget(IEditorCommandTarget? target)
|
||||||
|
{
|
||||||
|
if (_subscribedTarget == target)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_subscribedTarget != null)
|
||||||
|
{
|
||||||
|
_subscribedTarget.StateChanged -= CommandTarget_StateChanged;
|
||||||
|
_subscribedTarget.CapabilitiesChanged -= CommandTarget_CapabilitiesChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
_subscribedTarget = target;
|
||||||
|
|
||||||
|
if (_subscribedTarget != null)
|
||||||
|
{
|
||||||
|
_subscribedTarget.StateChanged += CommandTarget_StateChanged;
|
||||||
|
_subscribedTarget.CapabilitiesChanged += CommandTarget_CapabilitiesChanged;
|
||||||
|
ApplyCapabilities(_subscribedTarget.Capabilities);
|
||||||
|
ApplyState(_subscribedTarget.CurrentState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DetachCommandTarget()
|
||||||
|
{
|
||||||
|
if (_subscribedTarget == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_subscribedTarget.StateChanged -= CommandTarget_StateChanged;
|
||||||
|
_subscribedTarget.CapabilitiesChanged -= CommandTarget_CapabilitiesChanged;
|
||||||
|
_subscribedTarget = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnCommandTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
var control = (EditorTabbedCommandBarControl)d;
|
||||||
|
control.AttachCommandTarget((IEditorCommandTarget?)e.NewValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnLoaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
AttachCommandTarget(CommandTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnUnloaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
DetachCommandTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CommandTarget_StateChanged(object? sender, EditorState e)
|
||||||
|
{
|
||||||
|
ApplyState(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CommandTarget_CapabilitiesChanged(object? sender, EditorCapabilities e)
|
||||||
|
{
|
||||||
|
ApplyCapabilities(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyCapabilities(EditorCapabilities capabilities)
|
||||||
|
{
|
||||||
|
FontFamilyComboBox.ItemsSource = capabilities.Fonts;
|
||||||
|
FontSizeComboBox.ItemsSource = capabilities.FontSizes;
|
||||||
|
AlignmentComboBox.ItemsSource = capabilities.Alignments;
|
||||||
|
ParagraphStyleComboBox.ItemsSource = capabilities.ParagraphStyles;
|
||||||
|
TextColorComboBox.ItemsSource = capabilities.TextColors;
|
||||||
|
HighlightColorComboBox.ItemsSource = capabilities.HighlightColors;
|
||||||
|
LineHeightComboBox.ItemsSource = capabilities.LineHeights;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyState(EditorState state)
|
||||||
|
{
|
||||||
|
_isApplyingState = true;
|
||||||
|
|
||||||
|
BoldButton.IsChecked = state.IsBold;
|
||||||
|
ItalicButton.IsChecked = state.IsItalic;
|
||||||
|
UnderlineButton.IsChecked = state.IsUnderline;
|
||||||
|
StrikeButton.IsChecked = state.IsStrikethrough;
|
||||||
|
BulletListButton.IsChecked = state.IsUnorderedList;
|
||||||
|
OrderedListButton.IsChecked = state.IsOrderedList;
|
||||||
|
IndentButton.IsEnabled = state.CanIndent;
|
||||||
|
OutdentButton.IsEnabled = state.CanOutdent;
|
||||||
|
RemoveLinkButton.IsEnabled = !string.IsNullOrWhiteSpace(state.LinkUrl);
|
||||||
|
RemoveLinkButton.Visibility = RemoveLinkButton.IsEnabled ? Visibility.Visible : Visibility.Collapsed;
|
||||||
|
BuiltInToolbarButton.IsChecked = state.IsBuiltInToolbarVisible;
|
||||||
|
SpellCheckButton.IsChecked = state.IsSpellCheckEnabled;
|
||||||
|
|
||||||
|
AlignmentComboBox.SelectedItem = state.Alignment;
|
||||||
|
FontFamilyComboBox.SelectedItem = MatchStringItem(FontFamilyComboBox.ItemsSource, state.FontFamily);
|
||||||
|
FontSizeComboBox.SelectedItem = MatchValueItem<int>(FontSizeComboBox.ItemsSource, state.FontSize);
|
||||||
|
LineHeightComboBox.SelectedItem = MatchStringItem(LineHeightComboBox.ItemsSource, state.LineHeight);
|
||||||
|
ParagraphStyleComboBox.SelectedItem = MatchParagraphItem(state.ParagraphStyle);
|
||||||
|
TextColorComboBox.SelectedItem = MatchColorItem(TextColorComboBox.ItemsSource, state.TextColor);
|
||||||
|
HighlightColorComboBox.SelectedItem = MatchColorItem(HighlightColorComboBox.ItemsSource, state.HighlightColor);
|
||||||
|
|
||||||
|
_isApplyingState = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object? MatchStringItem(object? itemsSource, string? value)
|
||||||
|
{
|
||||||
|
if (itemsSource is IEnumerable<string> strings)
|
||||||
|
{
|
||||||
|
return strings.FirstOrDefault(item => string.Equals(item, value, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object? MatchValueItem<T>(object? itemsSource, T? value) where T : struct
|
||||||
|
{
|
||||||
|
if (!value.HasValue || itemsSource is not IEnumerable<T> values)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in values)
|
||||||
|
{
|
||||||
|
if (EqualityComparer<T>.Default.Equals(item, value.Value))
|
||||||
|
{
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private object? MatchParagraphItem(string? tag)
|
||||||
|
{
|
||||||
|
if (ParagraphStyleComboBox.ItemsSource is not IEnumerable<EditorParagraphStyleOption> styles)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return styles.FirstOrDefault(item => string.Equals(item.Tag, tag, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object? MatchColorItem(object? itemsSource, string? value)
|
||||||
|
{
|
||||||
|
if (itemsSource is not IEnumerable<EditorColorOption> colors)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return colors.FirstOrDefault(item => string.Equals(item.Value, value ?? string.Empty, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ExecuteAsync(EditorCommand command)
|
||||||
|
{
|
||||||
|
if (_isApplyingState || CommandTarget == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await CommandTarget.ExecuteCommandAsync(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void BoldButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleBold());
|
||||||
|
private async void ItalicButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleItalic());
|
||||||
|
private async void UnderlineButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleUnderline());
|
||||||
|
private async void StrikeButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleStrikethrough());
|
||||||
|
private async void BulletListButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleUnorderedList());
|
||||||
|
private async void OrderedListButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.ToggleOrderedList());
|
||||||
|
private async void IndentButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.Indent());
|
||||||
|
private async void OutdentButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.Outdent());
|
||||||
|
private async void ImageButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.InsertImage());
|
||||||
|
private async void EmojiButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.InsertEmoji());
|
||||||
|
private async void RemoveLinkButton_Click(object sender, RoutedEventArgs e) => await ExecuteAsync(EditorCommand.RemoveLink());
|
||||||
|
|
||||||
|
private async void AlignmentComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_isApplyingState || AlignmentComboBox.SelectedItem is not EditorTextAlignment alignment)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ExecuteAsync(EditorCommand.SetAlignment(alignment));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void FontFamilyComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_isApplyingState || FontFamilyComboBox.SelectedItem is not string fontFamily || string.IsNullOrWhiteSpace(fontFamily))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ExecuteAsync(EditorCommand.SetFontFamily(fontFamily));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void FontSizeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_isApplyingState || FontSizeComboBox.SelectedItem is not int fontSize)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ExecuteAsync(EditorCommand.SetFontSize(fontSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void ParagraphStyleComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_isApplyingState || ParagraphStyleComboBox.SelectedItem is not EditorParagraphStyleOption paragraphStyle)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ExecuteAsync(EditorCommand.SetParagraphStyle(paragraphStyle.Tag));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void TextColorComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_isApplyingState || TextColorComboBox.SelectedItem is not EditorColorOption color)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ExecuteAsync(EditorCommand.SetTextColor(color.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void HighlightColorComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_isApplyingState || HighlightColorComboBox.SelectedItem is not EditorColorOption color)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ExecuteAsync(EditorCommand.SetHighlightColor(color.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void LineHeightComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (_isApplyingState || LineHeightComboBox.SelectedItem is not string lineHeight || string.IsNullOrWhiteSpace(lineHeight))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ExecuteAsync(EditorCommand.SetLineHeight(lineHeight));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void BuiltInToolbarButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
await ExecuteAsync(EditorCommand.ToggleBuiltInToolbar(BuiltInToolbarButton.IsChecked == true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void SpellCheckButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
await ExecuteAsync(EditorCommand.ToggleSpellCheck(SpellCheckButton.IsChecked == true));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void LinkButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (CommandTarget == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentState = CommandTarget.CurrentState;
|
||||||
|
var urlTextBox = new TextBox
|
||||||
|
{
|
||||||
|
Header = "URL",
|
||||||
|
Text = currentState.LinkUrl ?? string.Empty,
|
||||||
|
PlaceholderText = "https://example.com"
|
||||||
|
};
|
||||||
|
var textTextBox = new TextBox
|
||||||
|
{
|
||||||
|
Header = "Text",
|
||||||
|
Text = currentState.SelectedText ?? string.Empty,
|
||||||
|
PlaceholderText = "Link text"
|
||||||
|
};
|
||||||
|
var openInNewWindow = new CheckBox
|
||||||
|
{
|
||||||
|
Content = "Open in new window",
|
||||||
|
IsChecked = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = new ContentDialog
|
||||||
|
{
|
||||||
|
XamlRoot = XamlRoot,
|
||||||
|
Title = "Insert link",
|
||||||
|
PrimaryButtonText = "Apply",
|
||||||
|
CloseButtonText = "Cancel",
|
||||||
|
DefaultButton = ContentDialogButton.Primary,
|
||||||
|
Content = new StackPanel
|
||||||
|
{
|
||||||
|
Spacing = 12,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
urlTextBox,
|
||||||
|
textTextBox,
|
||||||
|
openInNewWindow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (await dialog.ShowAsync() == ContentDialogResult.Primary && !string.IsNullOrWhiteSpace(urlTextBox.Text))
|
||||||
|
{
|
||||||
|
await ExecuteAsync(EditorCommand.InsertLink(new EditorLinkCommandArgs(urlTextBox.Text.Trim(), textTextBox.Text.Trim(), openInNewWindow.IsChecked == true)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void TableButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var rowsBox = new NumberBox
|
||||||
|
{
|
||||||
|
Header = "Rows",
|
||||||
|
Minimum = 1,
|
||||||
|
Maximum = 10,
|
||||||
|
SmallChange = 1,
|
||||||
|
Value = 2
|
||||||
|
};
|
||||||
|
var columnsBox = new NumberBox
|
||||||
|
{
|
||||||
|
Header = "Columns",
|
||||||
|
Minimum = 1,
|
||||||
|
Maximum = 10,
|
||||||
|
SmallChange = 1,
|
||||||
|
Value = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
var dialog = new ContentDialog
|
||||||
|
{
|
||||||
|
XamlRoot = XamlRoot,
|
||||||
|
Title = "Insert table",
|
||||||
|
PrimaryButtonText = "Insert",
|
||||||
|
CloseButtonText = "Cancel",
|
||||||
|
DefaultButton = ContentDialogButton.Primary,
|
||||||
|
Content = new StackPanel
|
||||||
|
{
|
||||||
|
Spacing = 12,
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
rowsBox,
|
||||||
|
columnsBox
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (await dialog.ShowAsync() == ContentDialogResult.Primary)
|
||||||
|
{
|
||||||
|
await ExecuteAsync(EditorCommand.InsertTable(new EditorTableCommandArgs((int)Math.Max(1, rowsBox.Value), (int)Math.Max(1, columnsBox.Value))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -47,212 +47,7 @@
|
|||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="300" />
|
<RowDefinition Height="300" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
<controls2:EditorTabbedCommandBarControl CommandTarget="{x:Bind WebViewEditor}" />
|
||||||
<CommandBar
|
|
||||||
Grid.Row="0"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
Background="Transparent"
|
|
||||||
DefaultLabelPosition="Collapsed"
|
|
||||||
IsOpen="False">
|
|
||||||
<AppBarButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.ToggleEditorTheme}"
|
|
||||||
LabelPosition="Collapsed"
|
|
||||||
ToolTipService.ToolTip="Light Theme"
|
|
||||||
Visibility="{x:Bind WebViewEditor.IsEditorDarkMode, Mode=OneWay}">
|
|
||||||
<AppBarButton.Icon>
|
|
||||||
<coreControls:WinoFontIcon Icon="LightEditor" />
|
|
||||||
</AppBarButton.Icon>
|
|
||||||
</AppBarButton>
|
|
||||||
|
|
||||||
<AppBarButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.ToggleEditorTheme}"
|
|
||||||
LabelPosition="Collapsed"
|
|
||||||
ToolTipService.ToolTip="Dark Theme"
|
|
||||||
Visibility="{x:Bind WebViewEditor.IsEditorDarkMode, Mode=OneWay, Converter={StaticResource ReverseBooleanToVisibilityConverter}}">
|
|
||||||
<AppBarButton.Icon>
|
|
||||||
<coreControls:WinoFontIcon Icon="DarkEditor" />
|
|
||||||
</AppBarButton.Icon>
|
|
||||||
</AppBarButton>
|
|
||||||
|
|
||||||
<AppBarSeparator />
|
|
||||||
|
|
||||||
<AppBarToggleButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorBold, Mode=TwoWay}"
|
|
||||||
Label="Bold"
|
|
||||||
ToolTipService.ToolTip="Bold">
|
|
||||||
<AppBarToggleButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource BoldPathIcon}" />
|
|
||||||
</AppBarToggleButton.Icon>
|
|
||||||
</AppBarToggleButton>
|
|
||||||
<AppBarToggleButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorItalic, Mode=TwoWay}"
|
|
||||||
Label="Italic"
|
|
||||||
ToolTipService.ToolTip="Italic">
|
|
||||||
<AppBarToggleButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource ItalicPathIcon}" />
|
|
||||||
</AppBarToggleButton.Icon>
|
|
||||||
</AppBarToggleButton>
|
|
||||||
<AppBarToggleButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorUnderline, Mode=TwoWay}"
|
|
||||||
Label="Underline"
|
|
||||||
ToolTipService.ToolTip="Underline">
|
|
||||||
<AppBarToggleButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource UnderlinePathIcon}" />
|
|
||||||
</AppBarToggleButton.Icon>
|
|
||||||
</AppBarToggleButton>
|
|
||||||
<AppBarToggleButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorStrikethrough, Mode=TwoWay}"
|
|
||||||
Label="Stroke"
|
|
||||||
ToolTipService.ToolTip="Stroke">
|
|
||||||
<AppBarToggleButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource StrikePathIcon}" />
|
|
||||||
</AppBarToggleButton.Icon>
|
|
||||||
</AppBarToggleButton>
|
|
||||||
<AppBarSeparator />
|
|
||||||
<AppBarToggleButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorUl, Mode=TwoWay}"
|
|
||||||
Label="Bullet List"
|
|
||||||
ToolTipService.ToolTip="Bullet List">
|
|
||||||
<AppBarToggleButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource BulletedListPathIcon}" />
|
|
||||||
</AppBarToggleButton.Icon>
|
|
||||||
</AppBarToggleButton>
|
|
||||||
<AppBarToggleButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorOl, Mode=TwoWay}"
|
|
||||||
Label="Ordered List"
|
|
||||||
ToolTipService.ToolTip="Ordered List">
|
|
||||||
<AppBarToggleButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource OrderedListPathIcon}" />
|
|
||||||
</AppBarToggleButton.Icon>
|
|
||||||
</AppBarToggleButton>
|
|
||||||
|
|
||||||
<AppBarSeparator />
|
|
||||||
|
|
||||||
<AppBarButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.EditorOutdentAsync}"
|
|
||||||
IsEnabled="{x:Bind WebViewEditor.IsEditorOutdentEnabled, Mode=OneWay}"
|
|
||||||
Label="Decrease Indent"
|
|
||||||
ToolTipService.ToolTip="Decrease Indent">
|
|
||||||
<AppBarButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource DecreaseIndentPathIcon}" />
|
|
||||||
</AppBarButton.Icon>
|
|
||||||
</AppBarButton>
|
|
||||||
<AppBarButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.EditorIndentAsync}"
|
|
||||||
IsEnabled="{x:Bind WebViewEditor.IsEditorIndentEnabled, Mode=OneWay}"
|
|
||||||
Label="Increase Indent"
|
|
||||||
ToolTipService.ToolTip="Increase Indent">
|
|
||||||
<AppBarButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource IncreaseIndentPathIcon}" />
|
|
||||||
</AppBarButton.Icon>
|
|
||||||
</AppBarButton>
|
|
||||||
|
|
||||||
<AppBarElementContainer
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
VerticalAlignment="Center">
|
|
||||||
<ComboBox
|
|
||||||
Background="Transparent"
|
|
||||||
BorderBrush="Transparent"
|
|
||||||
SelectedIndex="{x:Bind WebViewEditor.EditorAlignmentSelectedIndex, Mode=TwoWay}">
|
|
||||||
<ComboBoxItem IsSelected="True" Tag="left">
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<Viewbox Width="16">
|
|
||||||
<PathIcon Data="{StaticResource AlignLeftPathIcon}" />
|
|
||||||
</Viewbox>
|
|
||||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Left}" />
|
|
||||||
</StackPanel>
|
|
||||||
</ComboBoxItem>
|
|
||||||
|
|
||||||
<ComboBoxItem Tag="center">
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<Viewbox Width="16">
|
|
||||||
<PathIcon Data="{StaticResource AlignCenterPathIcon}" />
|
|
||||||
</Viewbox>
|
|
||||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Center}" />
|
|
||||||
</StackPanel>
|
|
||||||
</ComboBoxItem>
|
|
||||||
|
|
||||||
<ComboBoxItem Tag="right">
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<Viewbox Width="16">
|
|
||||||
<PathIcon Data="{StaticResource AlignRightPathIcon}" />
|
|
||||||
</Viewbox>
|
|
||||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Right}" />
|
|
||||||
</StackPanel>
|
|
||||||
</ComboBoxItem>
|
|
||||||
|
|
||||||
<ComboBoxItem Tag="justify">
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<Viewbox Width="16">
|
|
||||||
<PathIcon Data="{StaticResource AlignJustifyPathIcon}" />
|
|
||||||
</Viewbox>
|
|
||||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Justify}" />
|
|
||||||
</StackPanel>
|
|
||||||
</ComboBoxItem>
|
|
||||||
</ComboBox>
|
|
||||||
</AppBarElementContainer>
|
|
||||||
<AppBarSeparator />
|
|
||||||
<AppBarButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.ShowImagePicker}"
|
|
||||||
Label="Add Image"
|
|
||||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Photos}">
|
|
||||||
<AppBarButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource AddPhotoPathIcon}" />
|
|
||||||
</AppBarButton.Icon>
|
|
||||||
<AppBarButton.Content>
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<Viewbox Width="16" VerticalAlignment="Center">
|
|
||||||
<PathIcon Data="{StaticResource AddPhotoPathIcon}" />
|
|
||||||
</Viewbox>
|
|
||||||
<TextBlock Text="{x:Bind domain:Translator.Photos}" />
|
|
||||||
</StackPanel>
|
|
||||||
</AppBarButton.Content>
|
|
||||||
</AppBarButton>
|
|
||||||
<AppBarButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.ShowEmojiPicker}"
|
|
||||||
Label="Add Emoji"
|
|
||||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Emoji}">
|
|
||||||
<AppBarButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource EmojiPathIcon}" />
|
|
||||||
</AppBarButton.Icon>
|
|
||||||
</AppBarButton>
|
|
||||||
|
|
||||||
<AppBarToggleButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorWebViewEditor, Mode=TwoWay}"
|
|
||||||
Label="Webview ToolBar"
|
|
||||||
ToolTipService.ToolTip="Webview ToolBar">
|
|
||||||
<AppBarToggleButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource WebviewToolBarPathIcon}" />
|
|
||||||
</AppBarToggleButton.Icon>
|
|
||||||
</AppBarToggleButton>
|
|
||||||
</CommandBar>
|
|
||||||
<Border
|
<Border
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Margin="0,8,0,0"
|
Margin="0,8,0,0"
|
||||||
@@ -264,3 +59,4 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</ContentDialog>
|
</ContentDialog>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Linq;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using Ical.Net.CalendarComponents;
|
using Ical.Net.CalendarComponents;
|
||||||
using Ical.Net.DataTypes;
|
using Ical.Net.DataTypes;
|
||||||
@@ -17,9 +18,6 @@ public static class CalendarXamlHelpers
|
|||||||
public static CalendarItemViewModel GetFirstAllDayEvent(CalendarEventCollection collection)
|
public static CalendarItemViewModel GetFirstAllDayEvent(CalendarEventCollection collection)
|
||||||
=> collection.AllDayEvents.OfType<CalendarItemViewModel>().FirstOrDefault()!;
|
=> collection.AllDayEvents.OfType<CalendarItemViewModel>().FirstOrDefault()!;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns full date + duration info in Event Details page details title.
|
|
||||||
/// </summary>
|
|
||||||
public static string GetEventDetailsDateString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
public static string GetEventDetailsDateString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
||||||
{
|
{
|
||||||
if (calendarItemViewModel == null || settings == null) return string.Empty;
|
if (calendarItemViewModel == null || settings == null) return string.Empty;
|
||||||
@@ -34,18 +32,17 @@ public static class CalendarXamlHelpers
|
|||||||
{
|
{
|
||||||
return $"{start.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)} - {end.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)}";
|
return $"{start.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)} - {end.ToString($"dd MMMM ddd {timeFormat}", settings.CultureInfo)}";
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
return $"{start.ToString(dateFormat, settings.CultureInfo)} - {end.ToString(timeFormat, settings.CultureInfo)}";
|
||||||
return $"{start.ToString(dateFormat, settings.CultureInfo)} - {end.ToString(timeFormat, settings.CultureInfo)}";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetRecurrenceString(CalendarItemViewModel calendarItemViewModel)
|
public static string GetRecurrenceString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
||||||
{
|
{
|
||||||
// TODO: This is incorrect.
|
if (calendarItemViewModel == null || string.IsNullOrEmpty(calendarItemViewModel.CalendarItem.Recurrence))
|
||||||
if (calendarItemViewModel == null || string.IsNullOrEmpty(calendarItemViewModel.CalendarItem.Recurrence)) return string.Empty;
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
// Parse recurrence rules
|
|
||||||
var calendarEvent = new CalendarEvent
|
var calendarEvent = new CalendarEvent
|
||||||
{
|
{
|
||||||
Start = new CalDateTime(calendarItemViewModel.StartDate),
|
Start = new CalDateTime(calendarItemViewModel.StartDate),
|
||||||
@@ -53,44 +50,50 @@ public static class CalendarXamlHelpers
|
|||||||
};
|
};
|
||||||
|
|
||||||
var recurrenceLines = Regex.Split(calendarItemViewModel.CalendarItem.Recurrence, Constants.CalendarEventRecurrenceRuleSeperator);
|
var recurrenceLines = Regex.Split(calendarItemViewModel.CalendarItem.Recurrence, Constants.CalendarEventRecurrenceRuleSeperator);
|
||||||
|
foreach (var line in recurrenceLines.Where(line => !string.IsNullOrWhiteSpace(line)))
|
||||||
foreach (var line in recurrenceLines)
|
|
||||||
{
|
{
|
||||||
calendarEvent.RecurrenceRules.Add(new RecurrencePattern(line));
|
calendarEvent.RecurrenceRules.Add(new RecurrencePattern(line));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (calendarEvent.RecurrenceRules == null || !calendarEvent.RecurrenceRules.Any())
|
var recurrenceRule = calendarEvent.RecurrenceRules.FirstOrDefault();
|
||||||
|
if (recurrenceRule == null)
|
||||||
{
|
{
|
||||||
return "No recurrence pattern.";
|
return string.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
var recurrenceRule = calendarEvent.RecurrenceRules.First();
|
var frequency = MapFrequency(recurrenceRule.Frequency.ToString());
|
||||||
var daysOfWeek = string.Join(", ", recurrenceRule.ByDay.Select(day => day.DayOfWeek.ToString()));
|
if (!frequency.HasValue)
|
||||||
string timeZone = calendarEvent.DtStart.TzId ?? "UTC";
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
return $"Every {daysOfWeek}, effective {calendarEvent.DtStart.Value.ToShortDateString()} " +
|
return CalendarRecurrenceSummaryFormatter.BuildSummary(
|
||||||
$"from {calendarEvent.DtStart.Value.ToShortTimeString()} to {calendarEvent.DtEnd.Value.ToShortTimeString()} " +
|
isRecurring: true,
|
||||||
$"{timeZone}.";
|
effectiveStart: calendarItemViewModel.Period.Start,
|
||||||
|
effectiveEnd: calendarItemViewModel.Period.End,
|
||||||
|
isAllDay: calendarItemViewModel.IsAllDayEvent,
|
||||||
|
settings: settings,
|
||||||
|
interval: recurrenceRule.Interval <= 0 ? 1 : recurrenceRule.Interval,
|
||||||
|
frequency: frequency.Value,
|
||||||
|
daysOfWeek: recurrenceRule.ByDay?.Select(day => day.DayOfWeek).ToList() ?? [],
|
||||||
|
recurrenceEndDate: recurrenceRule.Until == default ? null : new DateTimeOffset(recurrenceRule.Until));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string GetDetailsPopupDurationString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
public static string GetDetailsPopupDurationString(CalendarItemViewModel calendarItemViewModel, CalendarSettings settings)
|
||||||
{
|
{
|
||||||
if (calendarItemViewModel == null || settings == null) return string.Empty;
|
if (calendarItemViewModel == null || settings == null) return string.Empty;
|
||||||
|
|
||||||
// Single event in a day.
|
|
||||||
if (!calendarItemViewModel.IsAllDayEvent && !calendarItemViewModel.IsMultiDayEvent)
|
if (!calendarItemViewModel.IsAllDayEvent && !calendarItemViewModel.IsMultiDayEvent)
|
||||||
{
|
{
|
||||||
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} {settings.GetTimeString(calendarItemViewModel.Period.Duration)}";
|
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} {settings.GetTimeString(calendarItemViewModel.Period.Duration)}";
|
||||||
}
|
}
|
||||||
else if (calendarItemViewModel.IsMultiDayEvent)
|
|
||||||
|
if (calendarItemViewModel.IsMultiDayEvent)
|
||||||
{
|
{
|
||||||
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} - {calendarItemViewModel.Period.End.ToString("d", settings.CultureInfo)}";
|
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} - {calendarItemViewModel.Period.End.ToString("d", settings.CultureInfo)}";
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} ({Translator.CalendarItemAllDay})";
|
||||||
// All day event.
|
|
||||||
return $"{calendarItemViewModel.Period.Start.ToString("d", settings.CultureInfo)} ({Translator.CalendarItemAllDay})";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PopupPlacementMode GetDesiredPlacementModeForEventsDetailsPopup(
|
public static PopupPlacementMode GetDesiredPlacementModeForEventsDetailsPopup(
|
||||||
@@ -99,21 +102,14 @@ public static class CalendarXamlHelpers
|
|||||||
{
|
{
|
||||||
if (calendarItemViewModel == null) return PopupPlacementMode.Auto;
|
if (calendarItemViewModel == null) return PopupPlacementMode.Auto;
|
||||||
|
|
||||||
// All and/or multi day events always go to the top of the screen.
|
|
||||||
if (calendarItemViewModel.IsAllDayEvent || calendarItemViewModel.IsMultiDayEvent) return PopupPlacementMode.Bottom;
|
if (calendarItemViewModel.IsAllDayEvent || calendarItemViewModel.IsMultiDayEvent) return PopupPlacementMode.Bottom;
|
||||||
|
|
||||||
return XamlHelpers.GetPlaccementModeForCalendarType(calendarDisplayType);
|
return XamlHelpers.GetPlaccementModeForCalendarType(calendarDisplayType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns true if the calendar item has an online meeting link.
|
|
||||||
/// </summary>
|
|
||||||
public static bool HasOnlineMeetingLink(CalendarItemViewModel calendarItemViewModel)
|
public static bool HasOnlineMeetingLink(CalendarItemViewModel calendarItemViewModel)
|
||||||
=> calendarItemViewModel != null && !string.IsNullOrEmpty(calendarItemViewModel.CalendarItem?.HtmlLink);
|
=> calendarItemViewModel != null && !string.IsNullOrEmpty(calendarItemViewModel.CalendarItem?.HtmlLink);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns the text representation of an attendee's status.
|
|
||||||
/// </summary>
|
|
||||||
public static string GetAttendeeStatusText(AttendeeStatus status)
|
public static string GetAttendeeStatusText(AttendeeStatus status)
|
||||||
{
|
{
|
||||||
return status switch
|
return status switch
|
||||||
@@ -126,13 +122,21 @@ public static class CalendarXamlHelpers
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Returns visibility for attendee status badge.
|
|
||||||
/// Always shows status for all attendees.
|
|
||||||
/// </summary>
|
|
||||||
public static Microsoft.UI.Xaml.Visibility GetAttendeeStatusVisibility(AttendeeStatus status)
|
public static Microsoft.UI.Xaml.Visibility GetAttendeeStatusVisibility(AttendeeStatus status)
|
||||||
{
|
{
|
||||||
// Always show status
|
|
||||||
return Microsoft.UI.Xaml.Visibility.Visible;
|
return Microsoft.UI.Xaml.Visibility.Visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static CalendarItemRecurrenceFrequency? MapFrequency(string frequency)
|
||||||
|
{
|
||||||
|
return frequency.ToUpperInvariant() switch
|
||||||
|
{
|
||||||
|
"DAILY" => CalendarItemRecurrenceFrequency.Daily,
|
||||||
|
"WEEKLY" => CalendarItemRecurrenceFrequency.Weekly,
|
||||||
|
"MONTHLY" => CalendarItemRecurrenceFrequency.Monthly,
|
||||||
|
"YEARLY" => CalendarItemRecurrenceFrequency.Yearly,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+697
-71
@@ -8,6 +8,7 @@ const joditConfig = {
|
|||||||
"showCharsCounter": false,
|
"showCharsCounter": false,
|
||||||
"showWordsCounter": false,
|
"showWordsCounter": false,
|
||||||
"showXPathInStatusbar": false,
|
"showXPathInStatusbar": false,
|
||||||
|
"spellcheck": true,
|
||||||
"link": {
|
"link": {
|
||||||
"processVideoLink": false
|
"processVideoLink": false
|
||||||
},
|
},
|
||||||
@@ -17,92 +18,78 @@ const joditConfig = {
|
|||||||
"insertImageAsBase64URI": true
|
"insertImageAsBase64URI": true
|
||||||
},
|
},
|
||||||
"enter": "DIV"
|
"enter": "DIV"
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let editor;
|
||||||
|
let editorDomObserver;
|
||||||
|
let selectionChangeHandler;
|
||||||
|
let stateSyncQueued = false;
|
||||||
|
let imageInputBound = false;
|
||||||
|
let inlineFontsPluginRegistered = false;
|
||||||
|
let lastKnownRange = null;
|
||||||
|
|
||||||
// This method should be called first all the time.
|
|
||||||
function initializeJodit(fonts, defaultComposerFont, defaultComposerFontSize, defaultReaderFont, defaultReaderFontSize) {
|
function initializeJodit(fonts, defaultComposerFont, defaultComposerFontSize, defaultReaderFont, defaultReaderFontSize) {
|
||||||
const fontsWithFallabckObject = fonts.reduce((acc, font) => { acc[`'${font}',Arial,sans-serif`] = font; return acc; }, {});
|
if (editor) {
|
||||||
|
scheduleStateSync();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fontsWithFallbackObject = fonts.reduce((acc, font) => {
|
||||||
|
acc[`'${font}',Arial,sans-serif`] = font;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
const mergedConfig = {
|
const mergedConfig = {
|
||||||
...joditConfig,
|
...joditConfig,
|
||||||
controls: {
|
controls: {
|
||||||
font: {
|
font: {
|
||||||
list: Jodit.atom(fontsWithFallabckObject)
|
list: Jodit.atom(fontsWithFallbackObject)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style: { font: `${defaultReaderFontSize}px ${defaultReaderFont}` },
|
style: { font: `${defaultReaderFontSize}px ${defaultReaderFont}` }
|
||||||
}
|
};
|
||||||
|
|
||||||
Jodit.plugins.add('inlineFonts', jodit => {
|
if (!inlineFontsPluginRegistered) {
|
||||||
jodit.events.on('afterEnter', e => {
|
Jodit.plugins.add('inlineFonts', jodit => {
|
||||||
const current = jodit.selection.current().parentNode;
|
jodit.events.on('afterEnter', () => {
|
||||||
current.style.fontFamily = `'${defaultComposerFont}',Arial,sans-serif`;
|
const current = getSelectionElement();
|
||||||
current.style.fontSize = `${defaultComposerFontSize}px`;
|
if (!current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
current.style.fontFamily = `'${defaultComposerFont}',Arial,sans-serif`;
|
||||||
|
current.style.fontSize = `${defaultComposerFontSize}px`;
|
||||||
|
rememberSelection();
|
||||||
|
scheduleStateSync();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// Don't add const/let/var here, it should be global
|
inlineFontsPluginRegistered = true;
|
||||||
editor = Jodit.make("#editor", mergedConfig);
|
|
||||||
|
|
||||||
// Handle the image input change event
|
|
||||||
imageInput.addEventListener('change', () => {
|
|
||||||
const file = imageInput.files[0];
|
|
||||||
if (file) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = function (event) {
|
|
||||||
const base64Image = event.target.result;
|
|
||||||
insertImages([{ data: base64Image, name: file.name }]);
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Listeners for button events
|
|
||||||
const disabledButtons = ["indent", "outdent"];
|
|
||||||
const ariaPressedButtons = ["bold", "italic", "underline", "strikethrough", "ul", "ol"];
|
|
||||||
|
|
||||||
const alignmentButton = document.querySelector(`[ref='left']`).firstChild.firstChild;
|
|
||||||
const alignmentObserver = new MutationObserver(function () {
|
|
||||||
const value = alignmentButton.firstChild.getAttribute('class').split(' ')[0];
|
|
||||||
window.chrome.webview.postMessage({ type: 'alignment', value: value });
|
|
||||||
});
|
|
||||||
alignmentObserver.observe(alignmentButton, { childList: true, attributes: true, attributeFilter: ["class"] });
|
|
||||||
|
|
||||||
const ariaObservers = ariaPressedButtons.map(button => {
|
|
||||||
const buttonContainer = document.querySelector(`[ref='${button}']`);
|
|
||||||
const observer = new MutationObserver(function () { pressedChanged(buttonContainer) });
|
|
||||||
observer.observe(buttonContainer.firstChild, { attributes: true, attributeFilter: ["aria-pressed"] });
|
|
||||||
|
|
||||||
return observer;
|
|
||||||
});
|
|
||||||
|
|
||||||
const disabledObservers = disabledButtons.map(button => {
|
|
||||||
const buttonContainer = document.querySelector(`[ref='${button}']`);
|
|
||||||
const observer = new MutationObserver(function () { disabledButtonChanged(buttonContainer) });
|
|
||||||
observer.observe(buttonContainer.firstChild, { attributes: true, attributeFilter: ["disabled"] });
|
|
||||||
|
|
||||||
return observer;
|
|
||||||
});
|
|
||||||
|
|
||||||
function pressedChanged(buttonContainer) {
|
|
||||||
const ref = buttonContainer.getAttribute('ref');
|
|
||||||
const value = buttonContainer.firstChild.getAttribute('aria-pressed');
|
|
||||||
window.chrome.webview.postMessage({ type: ref, value: value });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function disabledButtonChanged(buttonContainer) {
|
editor = Jodit.make('#editor', mergedConfig);
|
||||||
const ref = buttonContainer.getAttribute('ref');
|
|
||||||
const value = buttonContainer.firstChild.getAttribute('disabled');
|
bindImageInput();
|
||||||
window.chrome.webview.postMessage({ type: ref, value: value });
|
bindEditorStateTracking();
|
||||||
}
|
toggleToolbar(false);
|
||||||
|
scheduleStateSync();
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function RenderHTML(htmlString) {
|
function RenderHTML(htmlString) {
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
editor.value = htmlString;
|
editor.value = htmlString;
|
||||||
editor.synchronizeValues();
|
editor.synchronizeValues();
|
||||||
|
rememberSelection();
|
||||||
|
scheduleStateSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
function GetHTMLContent() {
|
function GetHTMLContent() {
|
||||||
return editor.value;
|
return editor ? editor.value : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
function SetLightEditor() {
|
function SetLightEditor() {
|
||||||
@@ -115,16 +102,655 @@ function SetDarkEditor() {
|
|||||||
|
|
||||||
function toggleToolbar(enable) {
|
function toggleToolbar(enable) {
|
||||||
const toolbar = document.querySelector('.jodit-toolbar__box');
|
const toolbar = document.querySelector('.jodit-toolbar__box');
|
||||||
if (enable) {
|
if (toolbar) {
|
||||||
toolbar.style.display = 'flex';
|
toolbar.style.display = enable ? 'flex' : 'none';
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
toolbar.style.display = 'none';
|
scheduleStateSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setSpellCheck(enable) {
|
||||||
|
if (!editor || !editor.editor) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isEnabled = !!enable;
|
||||||
|
editor.options.spellcheck = isEnabled;
|
||||||
|
editor.editor.spellcheck = isEnabled;
|
||||||
|
editor.editor.setAttribute('spellcheck', isEnabled ? 'true' : 'false');
|
||||||
|
scheduleStateSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertImages(imagesInfo) {
|
function insertImages(imagesInfo) {
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreEditorSelection();
|
||||||
|
|
||||||
imagesInfo.forEach(imageInfo => {
|
imagesInfo.forEach(imageInfo => {
|
||||||
editor.selection.insertHTML(`<img src="${imageInfo.data}" alt="${imageInfo.name}">`);
|
editor.selection.insertHTML(`<img src="${escapeHtmlAttribute(imageInfo.data)}" alt="${escapeHtmlAttribute(imageInfo.name)}">`);
|
||||||
});
|
});
|
||||||
};
|
|
||||||
|
rememberSelection();
|
||||||
|
scheduleStateSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
function focusEditor() {
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (restoreEditorSelection()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.selection.focus();
|
||||||
|
|
||||||
|
const lastChild = editor.editor.lastChild;
|
||||||
|
if (lastChild) {
|
||||||
|
editor.selection.setCursorIn(lastChild, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEditorState() {
|
||||||
|
return buildEditorState();
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeEditorCommand(commandName) {
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreEditorSelection();
|
||||||
|
editor.execCommand(commandName);
|
||||||
|
rememberSelection();
|
||||||
|
scheduleStateSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFontFamily(fontFamily) {
|
||||||
|
applyInlineStyleToSelection({ fontFamily: `'${fontFamily}',Arial,sans-serif` });
|
||||||
|
}
|
||||||
|
|
||||||
|
function setFontSize(fontSize) {
|
||||||
|
applyInlineStyleToSelection({ fontSize: `${fontSize}px` });
|
||||||
|
}
|
||||||
|
|
||||||
|
function setTextColor(color) {
|
||||||
|
applyInlineStyleToSelection({ color: color || '' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function setHighlightColor(color) {
|
||||||
|
applyInlineStyleToSelection({ backgroundColor: color || '' });
|
||||||
|
}
|
||||||
|
|
||||||
|
function setParagraphStyle(tagName) {
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreEditorSelection();
|
||||||
|
|
||||||
|
const normalizedTag = (tagName || 'div').toLowerCase();
|
||||||
|
|
||||||
|
try {
|
||||||
|
document.execCommand('formatBlock', false, normalizedTag);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
const block = getCurrentBlockElement();
|
||||||
|
if (block && block.tagName.toLowerCase() !== normalizedTag) {
|
||||||
|
const replacement = document.createElement(normalizedTag);
|
||||||
|
while (block.firstChild) {
|
||||||
|
replacement.appendChild(block.firstChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
block.parentNode.replaceChild(replacement, block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rememberSelection();
|
||||||
|
scheduleStateSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setLineHeight(lineHeight) {
|
||||||
|
restoreEditorSelection();
|
||||||
|
|
||||||
|
const block = getCurrentBlockElement();
|
||||||
|
if (!block) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
block.style.lineHeight = lineHeight || '';
|
||||||
|
rememberSelection();
|
||||||
|
scheduleStateSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
function upsertLink(linkArgs) {
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreEditorSelection();
|
||||||
|
|
||||||
|
const normalizedUrl = normalizeLinkUrl(linkArgs && linkArgs.url ? linkArgs.url : '');
|
||||||
|
if (!normalizedUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const linkText = linkArgs && linkArgs.text ? linkArgs.text.trim() : '';
|
||||||
|
const existingLink = getSelectionElement() ? getSelectionElement().closest('a[href]') : null;
|
||||||
|
|
||||||
|
if (existingLink) {
|
||||||
|
existingLink.setAttribute('href', normalizedUrl);
|
||||||
|
if (linkArgs.openInNewWindow) {
|
||||||
|
existingLink.setAttribute('target', '_blank');
|
||||||
|
existingLink.setAttribute('rel', 'noopener noreferrer');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
existingLink.removeAttribute('target');
|
||||||
|
existingLink.removeAttribute('rel');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (linkText) {
|
||||||
|
existingLink.textContent = linkText;
|
||||||
|
}
|
||||||
|
|
||||||
|
rememberSelection();
|
||||||
|
scheduleStateSync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (selection && selection.rangeCount > 0 && !selection.isCollapsed && isSelectionInsideEditor()) {
|
||||||
|
try {
|
||||||
|
document.execCommand('createLink', false, normalizedUrl);
|
||||||
|
const createdLink = getSelectionElement() ? getSelectionElement().closest('a[href]') : null;
|
||||||
|
if (createdLink) {
|
||||||
|
if (linkArgs.openInNewWindow) {
|
||||||
|
createdLink.setAttribute('target', '_blank');
|
||||||
|
createdLink.setAttribute('rel', 'noopener noreferrer');
|
||||||
|
}
|
||||||
|
if (linkText) {
|
||||||
|
createdLink.textContent = linkText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
const selectedText = linkText || selection.toString() || normalizedUrl;
|
||||||
|
editor.selection.insertHTML(`<a href="${escapeHtmlAttribute(normalizedUrl)}" target="_blank" rel="noopener noreferrer">${escapeHtmlText(selectedText)}</a>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
rememberSelection();
|
||||||
|
scheduleStateSync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = linkText || normalizedUrl;
|
||||||
|
editor.selection.insertHTML(`<a href="${escapeHtmlAttribute(normalizedUrl)}" target="_blank" rel="noopener noreferrer">${escapeHtmlText(text)}</a>`);
|
||||||
|
rememberSelection();
|
||||||
|
scheduleStateSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeLink() {
|
||||||
|
restoreEditorSelection();
|
||||||
|
|
||||||
|
const selectionElement = getSelectionElement();
|
||||||
|
const linkElement = selectionElement ? selectionElement.closest('a[href]') : null;
|
||||||
|
if (!linkElement) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
document.execCommand('unlink');
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
unwrapElement(linkElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
rememberSelection();
|
||||||
|
scheduleStateSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
function insertTableHtml(tableArgs) {
|
||||||
|
if (!editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreEditorSelection();
|
||||||
|
|
||||||
|
const rows = clampInteger(tableArgs && tableArgs.rows, 1, 10);
|
||||||
|
const columns = clampInteger(tableArgs && tableArgs.columns, 1, 10);
|
||||||
|
const htmlRows = [];
|
||||||
|
|
||||||
|
for (let rowIndex = 0; rowIndex < rows; rowIndex += 1) {
|
||||||
|
const cells = [];
|
||||||
|
for (let columnIndex = 0; columnIndex < columns; columnIndex += 1) {
|
||||||
|
cells.push('<td style="border:1px solid #c7c7c7;padding:6px;min-width:32px;"><br></td>');
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlRows.push(`<tr>${cells.join('')}</tr>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.selection.insertHTML(`<table style="border-collapse:collapse;width:100%;">${htmlRows.join('')}</table><div><br></div>`);
|
||||||
|
rememberSelection();
|
||||||
|
scheduleStateSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindImageInput() {
|
||||||
|
if (imageInputBound) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
imageInput.addEventListener('change', () => {
|
||||||
|
const file = imageInput.files[0];
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = event => {
|
||||||
|
const base64Image = event.target.result;
|
||||||
|
insertImages([{ data: base64Image, name: file.name }]);
|
||||||
|
imageInput.value = '';
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
|
||||||
|
imageInputBound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function bindEditorStateTracking() {
|
||||||
|
if (!editor || !editor.editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncHandler = () => {
|
||||||
|
rememberSelection();
|
||||||
|
scheduleStateSync();
|
||||||
|
};
|
||||||
|
|
||||||
|
['keyup', 'mouseup', 'click', 'input', 'focus', 'blur'].forEach(eventName => {
|
||||||
|
editor.editor.addEventListener(eventName, syncHandler);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (editor.events && editor.events.on) {
|
||||||
|
editor.events.on('afterSetMode change afterCommand', syncHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
editorDomObserver = new MutationObserver(() => scheduleStateSync());
|
||||||
|
editorDomObserver.observe(editor.editor, {
|
||||||
|
subtree: true,
|
||||||
|
childList: true,
|
||||||
|
attributes: true,
|
||||||
|
characterData: true,
|
||||||
|
attributeFilter: ['style', 'class', 'href', 'spellcheck']
|
||||||
|
});
|
||||||
|
|
||||||
|
selectionChangeHandler = () => {
|
||||||
|
if (isSelectionInsideEditor()) {
|
||||||
|
rememberSelection();
|
||||||
|
scheduleStateSync();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('selectionchange', selectionChangeHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
function scheduleStateSync() {
|
||||||
|
if (stateSyncQueued) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
stateSyncQueued = true;
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
stateSyncQueued = false;
|
||||||
|
notifyState();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyState() {
|
||||||
|
if (!window.chrome || !window.chrome.webview) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.chrome.webview.postMessage({
|
||||||
|
type: 'state',
|
||||||
|
state: buildEditorState()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildEditorState() {
|
||||||
|
const selectionElement = getSelectionElement();
|
||||||
|
const contextElement = selectionElement || (editor && editor.editor ? editor.editor : document.body);
|
||||||
|
const blockElement = getCurrentBlockElement() || contextElement;
|
||||||
|
const style = window.getComputedStyle(contextElement);
|
||||||
|
const blockStyle = window.getComputedStyle(blockElement);
|
||||||
|
const selection = window.getSelection();
|
||||||
|
const listElement = selectionElement ? selectionElement.closest('ol,ul') : null;
|
||||||
|
const linkElement = selectionElement ? selectionElement.closest('a[href]') : null;
|
||||||
|
const fontSize = parsePixelSize(style.fontSize);
|
||||||
|
|
||||||
|
return {
|
||||||
|
bold: queryCommandState('bold', isBoldStyle(style)),
|
||||||
|
italic: queryCommandState('italic', style.fontStyle === 'italic'),
|
||||||
|
underline: queryCommandState('underline', (style.textDecorationLine || '').includes('underline')),
|
||||||
|
strikethrough: queryCommandState('strikeThrough', (style.textDecorationLine || '').includes('line-through')),
|
||||||
|
orderedList: !!(listElement && listElement.tagName.toLowerCase() === 'ol'),
|
||||||
|
unorderedList: !!(listElement && listElement.tagName.toLowerCase() === 'ul'),
|
||||||
|
canIndent: queryCommandEnabled('indent', true),
|
||||||
|
canOutdent: queryCommandEnabled('outdent', !!listElement || !!(selectionElement && selectionElement.closest('blockquote'))),
|
||||||
|
hasSelection: !!(selection && selection.rangeCount > 0 && !selection.isCollapsed && isSelectionInsideEditor()),
|
||||||
|
isSpellCheckEnabled: !!(editor && editor.editor && editor.editor.spellcheck),
|
||||||
|
alignment: normalizeAlignment(blockStyle.textAlign),
|
||||||
|
fontFamily: normalizeFontFamily(style.fontFamily),
|
||||||
|
fontSize: fontSize,
|
||||||
|
paragraphStyle: normalizeParagraphTag(blockElement),
|
||||||
|
textColor: normalizeColor(style.color),
|
||||||
|
highlightColor: normalizeColor(style.backgroundColor),
|
||||||
|
lineHeight: normalizeLineHeight(blockStyle.lineHeight, fontSize),
|
||||||
|
linkUrl: linkElement ? linkElement.getAttribute('href') || '' : '',
|
||||||
|
selectedText: selection && isSelectionInsideEditor() ? selection.toString() : ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSelectionElement() {
|
||||||
|
if (!editor || !editor.editor) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (!selection || selection.rangeCount === 0) {
|
||||||
|
return editor.editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const node = selection.anchorNode;
|
||||||
|
if (!node) {
|
||||||
|
return editor.editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
const element = node.nodeType === Node.TEXT_NODE ? node.parentElement : node;
|
||||||
|
if (!element || !editor.editor.contains(element)) {
|
||||||
|
return editor.editor;
|
||||||
|
}
|
||||||
|
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentBlockElement() {
|
||||||
|
const selectionElement = getSelectionElement();
|
||||||
|
if (!selectionElement) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return selectionElement.closest('h1,h2,h3,h4,h5,h6,p,blockquote,pre,div,li,td,th') || selectionElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rememberSelection() {
|
||||||
|
if (!editor || !editor.editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (!selection || selection.rangeCount === 0 || !isSelectionInsideEditor()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
lastKnownRange = selection.getRangeAt(0).cloneRange();
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
lastKnownRange = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function restoreEditorSelection() {
|
||||||
|
if (!editor || !editor.editor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
editor.selection.focus();
|
||||||
|
|
||||||
|
if (!lastKnownRange) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (!selection) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const restoredRange = lastKnownRange.cloneRange();
|
||||||
|
selection.removeAllRanges();
|
||||||
|
selection.addRange(restoredRange);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
lastKnownRange = null;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isSelectionInsideEditor() {
|
||||||
|
if (!editor || !editor.editor) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (!selection || selection.rangeCount === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const anchorNode = selection.anchorNode;
|
||||||
|
const focusNode = selection.focusNode;
|
||||||
|
return !!anchorNode && !!focusNode && editor.editor.contains(anchorNode) && editor.editor.contains(focusNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryCommandState(commandName, fallbackValue) {
|
||||||
|
try {
|
||||||
|
const value = document.queryCommandState(commandName);
|
||||||
|
return typeof value === 'boolean' ? value : fallbackValue;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return fallbackValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function queryCommandEnabled(commandName, fallbackValue) {
|
||||||
|
try {
|
||||||
|
const value = document.queryCommandEnabled(commandName);
|
||||||
|
return typeof value === 'boolean' ? value : fallbackValue;
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return fallbackValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBoldStyle(style) {
|
||||||
|
const fontWeight = style.fontWeight || '400';
|
||||||
|
const numericWeight = parseInt(fontWeight, 10);
|
||||||
|
return fontWeight === 'bold' || Number.isFinite(numericWeight) && numericWeight >= 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAlignment(value) {
|
||||||
|
const normalized = (value || '').toLowerCase();
|
||||||
|
if (normalized === 'center' || normalized === 'right' || normalized === 'justify') {
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'left';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeFontFamily(value) {
|
||||||
|
if (!value) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.split(',')[0].replace(/["']/g, '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeParagraphTag(element) {
|
||||||
|
return element && element.tagName ? element.tagName.toLowerCase() : 'div';
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLineHeight(value, fontSize) {
|
||||||
|
if (!value || value === 'normal') {
|
||||||
|
return 'normal';
|
||||||
|
}
|
||||||
|
|
||||||
|
const numericValue = parseFloat(value);
|
||||||
|
if (!Number.isFinite(numericValue)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.endsWith('px') && fontSize) {
|
||||||
|
const ratio = numericValue / fontSize;
|
||||||
|
return Number.isInteger(ratio) ? `${ratio}` : ratio.toFixed(2).replace(/0+$/, '').replace(/\.$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Number.isInteger(numericValue) ? `${numericValue}` : numericValue.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function parsePixelSize(value) {
|
||||||
|
const numericValue = parseFloat(value || '');
|
||||||
|
return Number.isFinite(numericValue) ? Math.round(numericValue) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeColor(value) {
|
||||||
|
if (!value || value === 'transparent' || value === 'rgba(0, 0, 0, 0)') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.startsWith('#')) {
|
||||||
|
return value.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
const rgbaMatch = value.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)/i);
|
||||||
|
if (!rgbaMatch) {
|
||||||
|
return value.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
const [, red, green, blue] = rgbaMatch;
|
||||||
|
return `#${toHex(red)}${toHex(green)}${toHex(blue)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toHex(value) {
|
||||||
|
return Number(value).toString(16).padStart(2, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyInlineStyleToSelection(styles) {
|
||||||
|
if (!editor || !editor.editor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreEditorSelection();
|
||||||
|
|
||||||
|
const selection = window.getSelection();
|
||||||
|
if (!selection || selection.rangeCount === 0 || !isSelectionInsideEditor()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
if (selection.isCollapsed) {
|
||||||
|
const contextElement = getSelectionElement();
|
||||||
|
if (contextElement) {
|
||||||
|
Object.entries(styles).forEach(([propertyName, propertyValue]) => {
|
||||||
|
contextElement.style[propertyName] = propertyValue || '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
rememberSelection();
|
||||||
|
scheduleStateSync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const span = document.createElement('span');
|
||||||
|
Object.entries(styles).forEach(([propertyName, propertyValue]) => {
|
||||||
|
if (propertyValue) {
|
||||||
|
span.style[propertyName] = propertyValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
span.appendChild(range.extractContents());
|
||||||
|
range.insertNode(span);
|
||||||
|
selection.removeAllRanges();
|
||||||
|
const newRange = document.createRange();
|
||||||
|
newRange.selectNodeContents(span);
|
||||||
|
selection.addRange(newRange);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
const css = styleObjectToCss(styles);
|
||||||
|
const selectedText = escapeHtmlText(selection.toString());
|
||||||
|
editor.selection.insertHTML(`<span style="${css}">${selectedText}</span>`);
|
||||||
|
}
|
||||||
|
|
||||||
|
rememberSelection();
|
||||||
|
scheduleStateSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
function styleObjectToCss(styles) {
|
||||||
|
return Object.entries(styles)
|
||||||
|
.filter(([, value]) => value)
|
||||||
|
.map(([propertyName, propertyValue]) => `${camelToKebabCase(propertyName)}:${propertyValue}`)
|
||||||
|
.join(';');
|
||||||
|
}
|
||||||
|
|
||||||
|
function camelToKebabCase(value) {
|
||||||
|
return value.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
function unwrapElement(element) {
|
||||||
|
const parent = element.parentNode;
|
||||||
|
if (!parent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (element.firstChild) {
|
||||||
|
parent.insertBefore(element.firstChild, element);
|
||||||
|
}
|
||||||
|
|
||||||
|
parent.removeChild(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clampInteger(value, min, max) {
|
||||||
|
const numericValue = parseInt(value, 10);
|
||||||
|
if (!Number.isFinite(numericValue)) {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.min(max, Math.max(min, numericValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeLinkUrl(url) {
|
||||||
|
const trimmed = (url || '').trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^[a-z]+:/i.test(trimmed)) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trimmed.includes('@') && !trimmed.includes('/')) {
|
||||||
|
return `mailto:${trimmed}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `https://${trimmed}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtmlAttribute(value) {
|
||||||
|
return `${value || ''}`
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtmlText(value) {
|
||||||
|
return `${value || ''}`
|
||||||
|
.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>');
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -187,6 +187,9 @@ public class NavigationService : NavigationServiceBase, INavigationService
|
|||||||
|
|
||||||
// Update the application mode in state persistence service
|
// Update the application mode in state persistence service
|
||||||
_statePersistanceService.ApplicationMode = mode;
|
_statePersistanceService.ApplicationMode = mode;
|
||||||
|
_statePersistanceService.CoreWindowTitle = mode == WinoApplicationMode.Calendar
|
||||||
|
? "Wino Calendar"
|
||||||
|
: "Wino Mail";
|
||||||
|
|
||||||
var targetPageType = mode == WinoApplicationMode.Mail ? typeof(MailAppShell) : typeof(CalendarAppShell);
|
var targetPageType = mode == WinoApplicationMode.Mail ? typeof(MailAppShell) : typeof(CalendarAppShell);
|
||||||
var currentPageType = coreFrame.Content?.GetType();
|
var currentPageType = coreFrame.Content?.GetType();
|
||||||
|
|||||||
@@ -4,6 +4,17 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="using:Wino.Mail.WinUI.Styles">
|
xmlns:local="using:Wino.Mail.WinUI.Styles">
|
||||||
|
|
||||||
|
<Style
|
||||||
|
x:Key="TransparentActionButtonStyle"
|
||||||
|
BasedOn="{StaticResource DefaultButtonStyle}"
|
||||||
|
TargetType="Button">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="BorderThickness" Value="0" />
|
||||||
|
<Setter Property="Padding" Value="12,8" />
|
||||||
|
<Setter Property="MinWidth" Value="0" />
|
||||||
|
<Setter Property="MinHeight" Value="0" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
<!-- Horizontally Stretched List View Item Container Style -->
|
<!-- Horizontally Stretched List View Item Container Style -->
|
||||||
<Style x:Key="StretchedItemContainerStyle" TargetType="ListViewItem">
|
<Style x:Key="StretchedItemContainerStyle" TargetType="ListViewItem">
|
||||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||||
|
|||||||
@@ -272,14 +272,6 @@
|
|||||||
</muxc:Expander>
|
</muxc:Expander>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
</ListView.ItemTemplate>
|
</ListView.ItemTemplate>
|
||||||
<ListView.Footer>
|
|
||||||
<Button
|
|
||||||
Margin="14,0"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Bottom"
|
|
||||||
Command="{x:Bind ViewModel.SyncCommand}"
|
|
||||||
Content="Test Sync" />
|
|
||||||
</ListView.Footer>
|
|
||||||
</ListView>
|
</ListView>
|
||||||
|
|
||||||
<!-- Menu Items -->
|
<!-- Menu Items -->
|
||||||
|
|||||||
@@ -19,17 +19,6 @@
|
|||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<Page.Resources>
|
<Page.Resources>
|
||||||
<Style
|
|
||||||
x:Key="TransparentActionButtonStyle"
|
|
||||||
BasedOn="{StaticResource DefaultButtonStyle}"
|
|
||||||
TargetType="Button">
|
|
||||||
<Setter Property="Background" Value="Transparent" />
|
|
||||||
<Setter Property="BorderThickness" Value="0" />
|
|
||||||
<Setter Property="Padding" Value="8" />
|
|
||||||
<Setter Property="MinWidth" Value="0" />
|
|
||||||
<Setter Property="MinHeight" Value="0" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style
|
<Style
|
||||||
x:Key="FieldToggleButtonStyle"
|
x:Key="FieldToggleButtonStyle"
|
||||||
BasedOn="{StaticResource DefaultToggleButtonStyle}"
|
BasedOn="{StaticResource DefaultToggleButtonStyle}"
|
||||||
@@ -59,161 +48,156 @@
|
|||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- Top Bar -->
|
<!-- Top Bar -->
|
||||||
<Grid
|
<Border
|
||||||
Padding="16,10"
|
|
||||||
Background="{ThemeResource WinoContentZoneBackgroud}"
|
Background="{ThemeResource WinoContentZoneBackgroud}"
|
||||||
CornerRadius="{StaticResource ControlCornerRadius}">
|
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
|
||||||
<Grid.ColumnDefinitions>
|
BorderThickness="1"
|
||||||
<ColumnDefinition Width="Auto" />
|
CornerRadius="7">
|
||||||
<ColumnDefinition Width="*" />
|
<Grid Padding="12,6" ColumnSpacing="12">
|
||||||
<ColumnDefinition Width="Auto" />
|
<Grid.ColumnDefinitions>
|
||||||
</Grid.ColumnDefinitions>
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- Left: Save + Discard -->
|
<!-- Left: Calendar, Show As, Reminder -->
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<Button Command="{x:Bind ViewModel.CreateCommand}">
|
|
||||||
<Button.Style>
|
|
||||||
<Style BasedOn="{StaticResource AccentButtonStyle}" TargetType="Button">
|
|
||||||
<Setter Property="Padding" Value="16,6" />
|
|
||||||
</Style>
|
|
||||||
</Button.Style>
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<coreControls:WinoFontIcon FontSize="16" Icon="Save" />
|
|
||||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Buttons_Save}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<Button Command="{x:Bind ViewModel.CancelCommand}" Style="{StaticResource TransparentActionButtonStyle}">
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<coreControls:WinoFontIcon FontSize="16" Icon="Dismiss" />
|
|
||||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Buttons_Discard}" />
|
|
||||||
</StackPanel>
|
|
||||||
</Button>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<!-- Right: Calendar, Show As, Reminder -->
|
|
||||||
<StackPanel
|
|
||||||
Grid.Column="2"
|
|
||||||
Orientation="Horizontal"
|
|
||||||
Spacing="12">
|
|
||||||
|
|
||||||
<!-- Calendar -->
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
VerticalAlignment="Center"
|
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Spacing="8">
|
Spacing="12">
|
||||||
<coreControls:WinoFontIcon FontSize="14" Icon="Calendar" />
|
|
||||||
<Button
|
|
||||||
Padding="10,6"
|
|
||||||
HorizontalContentAlignment="Left"
|
|
||||||
Style="{StaticResource DefaultButtonStyle}">
|
|
||||||
<Button.Flyout>
|
|
||||||
<Flyout Placement="BottomEdgeAlignedLeft">
|
|
||||||
<ScrollViewer MaxHeight="360">
|
|
||||||
<ItemsControl ItemsSource="{x:Bind ViewModel.AvailableCalendarGroups, Mode=OneWay}">
|
|
||||||
<ItemsControl.ItemTemplate>
|
|
||||||
<DataTemplate x:DataType="data:GroupedAccountCalendarViewModel">
|
|
||||||
<StackPanel
|
|
||||||
MinWidth="320"
|
|
||||||
Padding="0,0,0,12"
|
|
||||||
Spacing="6">
|
|
||||||
<TextBlock FontWeight="SemiBold">
|
|
||||||
<Run Text="{x:Bind Account.Name}" />
|
|
||||||
<Run Text=" (" />
|
|
||||||
<Run Text="{x:Bind Account.Address}" />
|
|
||||||
<Run Text=")" />
|
|
||||||
</TextBlock>
|
|
||||||
|
|
||||||
<ListView
|
<!-- Calendar -->
|
||||||
IsItemClickEnabled="True"
|
<StackPanel
|
||||||
ItemClick="ComposeCalendarClicked"
|
VerticalAlignment="Center"
|
||||||
ItemsSource="{x:Bind AccountCalendars}"
|
Orientation="Horizontal"
|
||||||
SelectionMode="None">
|
Spacing="8">
|
||||||
<ListView.ItemTemplate>
|
<coreControls:WinoFontIcon FontSize="14" Icon="Calendar" />
|
||||||
<DataTemplate x:DataType="data:AccountCalendarViewModel">
|
<Button
|
||||||
<Grid ColumnSpacing="10">
|
Padding="10,6"
|
||||||
<Grid.ColumnDefinitions>
|
HorizontalContentAlignment="Left"
|
||||||
<ColumnDefinition Width="Auto" />
|
Style="{StaticResource DefaultButtonStyle}">
|
||||||
<ColumnDefinition Width="*" />
|
<Button.Flyout>
|
||||||
</Grid.ColumnDefinitions>
|
<Flyout Placement="BottomEdgeAlignedLeft">
|
||||||
|
<ScrollViewer MaxHeight="360">
|
||||||
|
<ItemsControl ItemsSource="{x:Bind ViewModel.AvailableCalendarGroups, Mode=OneWay}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="data:GroupedAccountCalendarViewModel">
|
||||||
|
<StackPanel
|
||||||
|
MinWidth="320"
|
||||||
|
Padding="0,0,0,12"
|
||||||
|
Spacing="6">
|
||||||
|
<TextBlock FontWeight="SemiBold">
|
||||||
|
<Run Text="{x:Bind Account.Name}" />
|
||||||
|
<Run Text=" (" />
|
||||||
|
<Run Text="{x:Bind Account.Address}" />
|
||||||
|
<Run Text=")" />
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
<Ellipse
|
<ListView
|
||||||
Width="14"
|
IsItemClickEnabled="True"
|
||||||
Height="14"
|
ItemClick="ComposeCalendarClicked"
|
||||||
VerticalAlignment="Center"
|
ItemsSource="{x:Bind AccountCalendars}"
|
||||||
Fill="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(BackgroundColorHex), Mode=OneWay}" />
|
SelectionMode="None">
|
||||||
|
<ListView.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="data:AccountCalendarViewModel">
|
||||||
|
<Grid ColumnSpacing="10">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<TextBlock
|
<Ellipse
|
||||||
Grid.Column="1"
|
Width="14"
|
||||||
VerticalAlignment="Center"
|
Height="14"
|
||||||
Text="{x:Bind Name}" />
|
VerticalAlignment="Center"
|
||||||
</Grid>
|
Fill="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(BackgroundColorHex), Mode=OneWay}" />
|
||||||
</DataTemplate>
|
|
||||||
</ListView.ItemTemplate>
|
<TextBlock
|
||||||
</ListView>
|
Grid.Column="1"
|
||||||
</StackPanel>
|
VerticalAlignment="Center"
|
||||||
</DataTemplate>
|
Text="{x:Bind Name}" />
|
||||||
</ItemsControl.ItemTemplate>
|
</Grid>
|
||||||
</ItemsControl>
|
</DataTemplate>
|
||||||
</ScrollViewer>
|
</ListView.ItemTemplate>
|
||||||
</Flyout>
|
</ListView>
|
||||||
</Button.Flyout>
|
</StackPanel>
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
</DataTemplate>
|
||||||
<Ellipse
|
</ItemsControl.ItemTemplate>
|
||||||
Width="10"
|
</ItemsControl>
|
||||||
Height="10"
|
</ScrollViewer>
|
||||||
VerticalAlignment="Center"
|
</Flyout>
|
||||||
Fill="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(ViewModel.SelectedCalendar.BackgroundColorHex), Mode=OneWay}" />
|
</Button.Flyout>
|
||||||
<StackPanel Spacing="0">
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
<TextBlock Text="{x:Bind ViewModel.SelectedCalendarDisplayText, Mode=OneWay}" />
|
<Ellipse
|
||||||
<TextBlock
|
Width="10"
|
||||||
FontSize="11"
|
Height="10"
|
||||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Bind ViewModel.SelectedCalendarAccountText, Mode=OneWay}"
|
Fill="{x:Bind helpers:XamlHelpers.GetSolidColorBrushFromHex(ViewModel.SelectedCalendar.BackgroundColorHex), Mode=OneWay}" />
|
||||||
Visibility="{x:Bind helpers:XamlHelpers.StringToVisibilityConverter(ViewModel.SelectedCalendarAccountText), Mode=OneWay}" />
|
<StackPanel Spacing="0">
|
||||||
|
<TextBlock Text="{x:Bind ViewModel.SelectedCalendarDisplayText, Mode=OneWay}" />
|
||||||
|
<TextBlock
|
||||||
|
FontSize="11"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
|
Text="{x:Bind ViewModel.SelectedCalendarAccountText, Mode=OneWay}"
|
||||||
|
Visibility="{x:Bind helpers:XamlHelpers.StringToVisibilityConverter(ViewModel.SelectedCalendarAccountText), Mode=OneWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
<FontIcon Glyph="" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<FontIcon Glyph="" />
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Button>
|
|
||||||
|
<!-- Show As -->
|
||||||
|
<StackPanel
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="8">
|
||||||
|
<coreControls:WinoFontIcon FontSize="14" Icon="CalendarShowAs" />
|
||||||
|
<ComboBox
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
ItemsSource="{x:Bind ViewModel.ShowAsOptions}"
|
||||||
|
SelectedItem="{x:Bind ViewModel.SelectedShowAsOption, Mode=TwoWay}">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="calendarViewModels:ShowAsOption">
|
||||||
|
<TextBlock Text="{x:Bind DisplayText}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Reminder -->
|
||||||
|
<StackPanel
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Orientation="Horizontal"
|
||||||
|
Spacing="8">
|
||||||
|
<coreControls:WinoFontIcon FontSize="14" Icon="Reminder" />
|
||||||
|
<ComboBox
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
ItemsSource="{x:Bind ViewModel.ReminderOptions}"
|
||||||
|
SelectedItem="{x:Bind ViewModel.SelectedReminderOption, Mode=TwoWay}">
|
||||||
|
<ComboBox.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="calendarViewModels:ReminderOption">
|
||||||
|
<TextBlock Text="{x:Bind DisplayText}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ComboBox.ItemTemplate>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Show As -->
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
VerticalAlignment="Center"
|
Grid.Column="1"
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
Spacing="8">
|
Spacing="4">
|
||||||
<coreControls:WinoFontIcon FontSize="14" Icon="CalendarShowAs" />
|
<AppBarButton Command="{x:Bind ViewModel.CancelCommand}" Label="{x:Bind domain:Translator.Buttons_Discard}">
|
||||||
<ComboBox
|
<AppBarButton.Icon>
|
||||||
VerticalAlignment="Center"
|
<coreControls:WinoFontIcon Icon="Delete" />
|
||||||
ItemsSource="{x:Bind ViewModel.ShowAsOptions}"
|
</AppBarButton.Icon>
|
||||||
SelectedItem="{x:Bind ViewModel.SelectedShowAsOption, Mode=TwoWay}">
|
</AppBarButton>
|
||||||
<ComboBox.ItemTemplate>
|
<AppBarButton Command="{x:Bind ViewModel.CreateCommand}" Label="{x:Bind domain:Translator.Buttons_Save}">
|
||||||
<DataTemplate x:DataType="calendarViewModels:ShowAsOption">
|
<AppBarButton.Icon>
|
||||||
<TextBlock Text="{x:Bind DisplayText}" />
|
<coreControls:WinoFontIcon Icon="Save" />
|
||||||
</DataTemplate>
|
</AppBarButton.Icon>
|
||||||
</ComboBox.ItemTemplate>
|
</AppBarButton>
|
||||||
</ComboBox>
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
<!-- Reminder -->
|
</Border>
|
||||||
<StackPanel
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Orientation="Horizontal"
|
|
||||||
Spacing="8">
|
|
||||||
<coreControls:WinoFontIcon FontSize="14" Icon="Reminder" />
|
|
||||||
<ComboBox
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
ItemsSource="{x:Bind ViewModel.ReminderOptions}"
|
|
||||||
SelectedItem="{x:Bind ViewModel.SelectedReminderOption, Mode=TwoWay}">
|
|
||||||
<ComboBox.ItemTemplate>
|
|
||||||
<DataTemplate x:DataType="calendarViewModels:ReminderOption">
|
|
||||||
<TextBlock Text="{x:Bind DisplayText}" />
|
|
||||||
</DataTemplate>
|
|
||||||
</ComboBox.ItemTemplate>
|
|
||||||
</ComboBox>
|
|
||||||
</StackPanel>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<!-- Scrollable Content -->
|
<!-- Scrollable Content -->
|
||||||
<ScrollViewer Grid.Row="1" MaxWidth="1200">
|
<ScrollViewer Grid.Row="1" MaxWidth="1200">
|
||||||
@@ -572,14 +556,14 @@
|
|||||||
Icon="Draft" />
|
Icon="Draft" />
|
||||||
<TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind domain:Translator.CalendarEventCompose_Notes}" />
|
<TextBlock Style="{StaticResource BodyStrongTextBlockStyle}" Text="{x:Bind domain:Translator.CalendarEventCompose_Notes}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<!-- Notes Editor -->
|
<!-- Notes Editor -->
|
||||||
|
<mailControls:EditorTabbedCommandBarControl CommandTarget="{x:Bind NotesEditor}" />
|
||||||
<mailControls:WebViewEditorControl
|
<mailControls:WebViewEditorControl
|
||||||
x:Name="NotesEditor"
|
x:Name="NotesEditor"
|
||||||
MinHeight="600"
|
MinHeight="600"
|
||||||
IsEditorDarkMode="{x:Bind ViewModel.IsDarkWebviewRenderer, Mode=OneWay}"
|
IsEditorDarkMode="{x:Bind ViewModel.IsDarkWebviewRenderer, Mode=OneWay}" />
|
||||||
IsEditorWebViewEditor="True" />
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
</Grid>
|
</Grid>
|
||||||
</abstract:CalendarEventComposePageAbstract>
|
</abstract:CalendarEventComposePageAbstract>
|
||||||
|
|
||||||
|
|||||||
@@ -57,17 +57,6 @@
|
|||||||
</selectors:RsvpStatusIconTemplateSelector.CancelledTemplate>
|
</selectors:RsvpStatusIconTemplateSelector.CancelledTemplate>
|
||||||
</selectors:RsvpStatusIconTemplateSelector>
|
</selectors:RsvpStatusIconTemplateSelector>
|
||||||
|
|
||||||
<Style
|
|
||||||
x:Key="TransparentActionButtonStyle"
|
|
||||||
BasedOn="{StaticResource DefaultButtonStyle}"
|
|
||||||
TargetType="Button">
|
|
||||||
<Setter Property="Background" Value="Transparent" />
|
|
||||||
<Setter Property="BorderThickness" Value="0" />
|
|
||||||
<Setter Property="Padding" Value="12,8" />
|
|
||||||
<Setter Property="MinWidth" Value="0" />
|
|
||||||
<Setter Property="MinHeight" Value="0" />
|
|
||||||
</Style>
|
|
||||||
|
|
||||||
<Style x:Key="ActionBarElementContainerStackStyle" TargetType="StackPanel">
|
<Style x:Key="ActionBarElementContainerStackStyle" TargetType="StackPanel">
|
||||||
<Setter Property="Spacing" Value="6" />
|
<Setter Property="Spacing" Value="6" />
|
||||||
<Setter Property="Padding" Value="10,0,4,0" />
|
<Setter Property="Padding" Value="10,0,4,0" />
|
||||||
@@ -407,7 +396,7 @@
|
|||||||
<TextBlock
|
<TextBlock
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Text="{x:Bind calendarHelpers:CalendarXamlHelpers.GetRecurrenceString(ViewModel.CurrentEvent), Mode=OneWay}"
|
Text="{x:Bind calendarHelpers:CalendarXamlHelpers.GetRecurrenceString(ViewModel.CurrentEvent, ViewModel.CurrentSettings), Mode=OneWay}"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
@@ -670,3 +659,4 @@
|
|||||||
</VisualStateManager.VisualStateGroups>
|
</VisualStateManager.VisualStateGroups>
|
||||||
</Grid>
|
</Grid>
|
||||||
</abstract:EventDetailsPageAbstract>
|
</abstract:EventDetailsPageAbstract>
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<controls:ImagePreviewControl PreviewContact="{x:Bind}" />
|
<controls:ImagePreviewControl PreviewContact="{x:Bind}" />
|
||||||
<TextBlock Grid.Column="1">
|
<TextBlock Grid.Column="1">
|
||||||
<Run FontWeight="SemiBold" Text="{x:Bind Name}" /><LineBreak /><Run Text="{x:Bind Address}" />
|
<Run FontWeight="SemiBold" Text="{x:Bind Name}" /><LineBreak /><Run Text="{x:Bind Address}" />
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
@@ -173,13 +173,8 @@
|
|||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<!-- Format -->
|
<!-- Format -->
|
||||||
<toolkit:TabbedCommandBar>
|
<controls1:EditorTabbedCommandBarControl CommandTarget="{x:Bind WebViewEditor}">
|
||||||
<toolkit:TabbedCommandBar.Resources>
|
<controls1:EditorTabbedCommandBarControl.PaneCustomContent>
|
||||||
<SolidColorBrush x:Key="TabContentContentBorderBackground" Color="Transparent" />
|
|
||||||
<SolidColorBrush x:Key="TabContentContentBorderBorderBrush" Color="Transparent" />
|
|
||||||
<Thickness x:Key="TabContentBorderBorderThickness">0</Thickness>
|
|
||||||
</toolkit:TabbedCommandBar.Resources>
|
|
||||||
<toolkit:TabbedCommandBar.PaneCustomContent>
|
|
||||||
<toolkit:TabbedCommandBarItem
|
<toolkit:TabbedCommandBarItem
|
||||||
CommandAlignment="Right"
|
CommandAlignment="Right"
|
||||||
IsDynamicOverflowEnabled="True"
|
IsDynamicOverflowEnabled="True"
|
||||||
@@ -192,35 +187,20 @@
|
|||||||
<ProgressRing IsActive="True" />
|
<ProgressRing IsActive="True" />
|
||||||
</AppBarButton>
|
</AppBarButton>
|
||||||
|
|
||||||
<AppBarButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.ToggleEditorTheme}"
|
|
||||||
LabelPosition="Collapsed"
|
|
||||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_LightTheme}"
|
|
||||||
Visibility="{x:Bind WebViewEditor.IsEditorDarkMode, Mode=OneWay}">
|
|
||||||
<AppBarButton.Icon>
|
|
||||||
<coreControls:WinoFontIcon Icon="LightEditor" />
|
|
||||||
</AppBarButton.Icon>
|
|
||||||
</AppBarButton>
|
|
||||||
|
|
||||||
<AppBarButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.ToggleEditorTheme}"
|
|
||||||
LabelPosition="Collapsed"
|
|
||||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_DarkTheme}"
|
|
||||||
Visibility="{x:Bind WebViewEditor.IsEditorDarkMode, Mode=OneWay, Converter={StaticResource ReverseBooleanToVisibilityConverter}}">
|
|
||||||
<AppBarButton.Icon>
|
|
||||||
<coreControls:WinoFontIcon Icon="DarkEditor" />
|
|
||||||
</AppBarButton.Icon>
|
|
||||||
</AppBarButton>
|
|
||||||
|
|
||||||
<AppBarButton Command="{x:Bind ViewModel.DiscardCommand}" Label="{x:Bind domain:Translator.Buttons_Discard}">
|
<AppBarButton Command="{x:Bind ViewModel.DiscardCommand}" Label="{x:Bind domain:Translator.Buttons_Discard}">
|
||||||
<AppBarButton.Icon>
|
<AppBarButton.Icon>
|
||||||
<coreControls:WinoFontIcon Icon="Delete" />
|
<coreControls:WinoFontIcon Icon="Delete" />
|
||||||
</AppBarButton.Icon>
|
</AppBarButton.Icon>
|
||||||
</AppBarButton>
|
</AppBarButton>
|
||||||
|
<AppBarToggleButton
|
||||||
|
x:Name="EditorThemeToggleButton"
|
||||||
|
IsChecked="{x:Bind WebViewEditor.IsEditorDarkMode, Mode=TwoWay}"
|
||||||
|
Label=""
|
||||||
|
ToolTipService.ToolTip="Toggle editor dark mode">
|
||||||
|
<AppBarToggleButton.Icon>
|
||||||
|
<coreControls:WinoFontIcon Icon="DarkEditor" />
|
||||||
|
</AppBarToggleButton.Icon>
|
||||||
|
</AppBarToggleButton>
|
||||||
<AppBarButton
|
<AppBarButton
|
||||||
Command="{x:Bind ViewModel.SendCommand}"
|
Command="{x:Bind ViewModel.SendCommand}"
|
||||||
Label="{x:Bind domain:Translator.Buttons_Send}"
|
Label="{x:Bind domain:Translator.Buttons_Send}"
|
||||||
@@ -239,161 +219,10 @@
|
|||||||
</AppBarButton.Icon>
|
</AppBarButton.Icon>
|
||||||
</AppBarButton>
|
</AppBarButton>
|
||||||
</toolkit:TabbedCommandBarItem>
|
</toolkit:TabbedCommandBarItem>
|
||||||
</toolkit:TabbedCommandBar.PaneCustomContent>
|
</controls1:EditorTabbedCommandBarControl.PaneCustomContent>
|
||||||
<toolkit:TabbedCommandBar.MenuItems>
|
|
||||||
<toolkit:TabbedCommandBarItem DefaultLabelPosition="Collapsed" Header="{x:Bind domain:Translator.EditorToolbarOption_Format}">
|
|
||||||
<AppBarToggleButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorBold, Mode=TwoWay}"
|
|
||||||
Label="{x:Bind domain:Translator.Composer_Bold}">
|
|
||||||
<AppBarToggleButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource BoldPathIcon}" />
|
|
||||||
</AppBarToggleButton.Icon>
|
|
||||||
</AppBarToggleButton>
|
|
||||||
<AppBarToggleButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorItalic, Mode=TwoWay}"
|
|
||||||
Label="{x:Bind domain:Translator.Composer_Italic}">
|
|
||||||
<AppBarToggleButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource ItalicPathIcon}" />
|
|
||||||
</AppBarToggleButton.Icon>
|
|
||||||
</AppBarToggleButton>
|
|
||||||
<AppBarToggleButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorUnderline, Mode=TwoWay}"
|
|
||||||
Label="{x:Bind domain:Translator.Composer_Underline}">
|
|
||||||
<AppBarToggleButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource UnderlinePathIcon}" />
|
|
||||||
</AppBarToggleButton.Icon>
|
|
||||||
</AppBarToggleButton>
|
|
||||||
<AppBarToggleButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorStrikethrough, Mode=TwoWay}"
|
|
||||||
Label="{x:Bind domain:Translator.Composer_Stroke}">
|
|
||||||
<AppBarToggleButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource StrikePathIcon}" />
|
|
||||||
</AppBarToggleButton.Icon>
|
|
||||||
</AppBarToggleButton>
|
|
||||||
|
|
||||||
<AppBarSeparator />
|
<controls1:EditorTabbedCommandBarControl.InsertCustomContent>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
<AppBarToggleButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorUl, Mode=TwoWay}"
|
|
||||||
Label="{x:Bind domain:Translator.Composer_BulletList}">
|
|
||||||
<AppBarToggleButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource BulletedListPathIcon}" />
|
|
||||||
</AppBarToggleButton.Icon>
|
|
||||||
</AppBarToggleButton>
|
|
||||||
|
|
||||||
<AppBarToggleButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorOl, Mode=TwoWay}"
|
|
||||||
Label="{x:Bind domain:Translator.Composer_OrderedList}">
|
|
||||||
<AppBarToggleButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource OrderedListPathIcon}" />
|
|
||||||
</AppBarToggleButton.Icon>
|
|
||||||
</AppBarToggleButton>
|
|
||||||
|
|
||||||
<AppBarSeparator />
|
|
||||||
|
|
||||||
<AppBarButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.EditorOutdentAsync}"
|
|
||||||
IsEnabled="{x:Bind WebViewEditor.IsEditorOutdentEnabled, Mode=OneWay}"
|
|
||||||
Label="{x:Bind domain:Translator.Composer_Outdent}">
|
|
||||||
<AppBarButton.Content>
|
|
||||||
<Viewbox Width="16">
|
|
||||||
<PathIcon Data="{StaticResource DecreaseIndentPathIcon}" />
|
|
||||||
</Viewbox>
|
|
||||||
</AppBarButton.Content>
|
|
||||||
</AppBarButton>
|
|
||||||
|
|
||||||
<AppBarButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
Click="{x:Bind WebViewEditor.EditorIndentAsync}"
|
|
||||||
IsEnabled="{x:Bind WebViewEditor.IsEditorIndentEnabled, Mode=OneWay}"
|
|
||||||
Label="{x:Bind domain:Translator.Composer_Indent}">
|
|
||||||
<AppBarButton.Content>
|
|
||||||
<Viewbox Width="16">
|
|
||||||
<PathIcon Data="{StaticResource IncreaseIndentPathIcon}" />
|
|
||||||
</Viewbox>
|
|
||||||
</AppBarButton.Content>
|
|
||||||
</AppBarButton>
|
|
||||||
|
|
||||||
<AppBarElementContainer
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
HorizontalAlignment="Center"
|
|
||||||
VerticalAlignment="Center">
|
|
||||||
<ComboBox
|
|
||||||
x:Name="AlignmentListView"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Background="Transparent"
|
|
||||||
BorderBrush="Transparent"
|
|
||||||
SelectedIndex="{x:Bind WebViewEditor.EditorAlignmentSelectedIndex, Mode=TwoWay}">
|
|
||||||
<ComboBoxItem IsSelected="True" Tag="left">
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<Viewbox Width="16">
|
|
||||||
<PathIcon Data="{StaticResource AlignLeftPathIcon}" />
|
|
||||||
</Viewbox>
|
|
||||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Left}" />
|
|
||||||
</StackPanel>
|
|
||||||
</ComboBoxItem>
|
|
||||||
|
|
||||||
<ComboBoxItem Tag="center">
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<Viewbox Width="16">
|
|
||||||
<PathIcon Data="{StaticResource AlignCenterPathIcon}" />
|
|
||||||
</Viewbox>
|
|
||||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Center}" />
|
|
||||||
</StackPanel>
|
|
||||||
</ComboBoxItem>
|
|
||||||
|
|
||||||
<ComboBoxItem Tag="right">
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<Viewbox Width="16">
|
|
||||||
<PathIcon Data="{StaticResource AlignRightPathIcon}" />
|
|
||||||
</Viewbox>
|
|
||||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Right}" />
|
|
||||||
</StackPanel>
|
|
||||||
</ComboBoxItem>
|
|
||||||
|
|
||||||
<ComboBoxItem Tag="justify">
|
|
||||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
|
||||||
<Viewbox Width="16">
|
|
||||||
<PathIcon Data="{StaticResource AlignJustifyPathIcon}" />
|
|
||||||
</Viewbox>
|
|
||||||
<TextBlock VerticalAlignment="Center" Text="{x:Bind domain:Translator.Justify}" />
|
|
||||||
</StackPanel>
|
|
||||||
</ComboBoxItem>
|
|
||||||
</ComboBox>
|
|
||||||
</AppBarElementContainer>
|
|
||||||
|
|
||||||
<AppBarSeparator />
|
|
||||||
|
|
||||||
<AppBarToggleButton
|
|
||||||
Width="Auto"
|
|
||||||
MinWidth="40"
|
|
||||||
IsChecked="{x:Bind WebViewEditor.IsEditorWebViewEditor, Mode=TwoWay}"
|
|
||||||
Label="{x:Bind domain:Translator.EditorTooltip_WebViewEditor}"
|
|
||||||
ToolTipService.ToolTip="{x:Bind domain:Translator.EditorTooltip_WebViewEditor}">
|
|
||||||
<AppBarToggleButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource WebviewToolBarPathIcon}" />
|
|
||||||
</AppBarToggleButton.Icon>
|
|
||||||
</AppBarToggleButton>
|
|
||||||
</toolkit:TabbedCommandBarItem>
|
|
||||||
|
|
||||||
<!-- Insert -->
|
|
||||||
<toolkit:TabbedCommandBarItem Header="{x:Bind domain:Translator.EditorToolbarOption_Insert}">
|
|
||||||
<AppBarButton
|
<AppBarButton
|
||||||
x:Name="FilesButton"
|
x:Name="FilesButton"
|
||||||
Command="{x:Bind ViewModel.AttachFilesCommand}"
|
Command="{x:Bind ViewModel.AttachFilesCommand}"
|
||||||
@@ -402,113 +231,98 @@
|
|||||||
<PathIcon Data="{StaticResource AttachPathIcon}" />
|
<PathIcon Data="{StaticResource AttachPathIcon}" />
|
||||||
</AppBarButton.Icon>
|
</AppBarButton.Icon>
|
||||||
</AppBarButton>
|
</AppBarButton>
|
||||||
|
</StackPanel>
|
||||||
|
</controls1:EditorTabbedCommandBarControl.InsertCustomContent>
|
||||||
|
|
||||||
<AppBarButton Click="{x:Bind WebViewEditor.ShowImagePicker}" Label="{x:Bind domain:Translator.Photos}">
|
<controls1:EditorTabbedCommandBarControl.OptionsCustomContent>
|
||||||
<AppBarButton.Icon>
|
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||||
<PathIcon Data="{StaticResource AddPhotoPathIcon}" />
|
<ToggleSplitButton x:Name="ImportanceSplitButton" IsChecked="{x:Bind ViewModel.IsImportanceSelected, Mode=TwoWay}">
|
||||||
</AppBarButton.Icon>
|
<ToggleSplitButton.Content>
|
||||||
</AppBarButton>
|
|
||||||
|
|
||||||
<AppBarButton Click="{x:Bind WebViewEditor.ShowEmojiPicker}" Label="{x:Bind domain:Translator.Emoji}">
|
|
||||||
<AppBarButton.Icon>
|
|
||||||
<PathIcon Data="{StaticResource EmojiPathIcon}" />
|
|
||||||
</AppBarButton.Icon>
|
|
||||||
</AppBarButton>
|
|
||||||
</toolkit:TabbedCommandBarItem>
|
|
||||||
|
|
||||||
<!-- Options -->
|
|
||||||
<toolkit:TabbedCommandBarItem Header="{x:Bind domain:Translator.EditorToolbarOption_Options}">
|
|
||||||
<AppBarElementContainer>
|
|
||||||
<ToggleSplitButton x:Name="ImportanceSplitButton" IsChecked="{x:Bind ViewModel.IsImportanceSelected, Mode=TwoWay}">
|
|
||||||
<Viewbox Width="16" Height="16">
|
<Viewbox Width="16" Height="16">
|
||||||
<SymbolIcon x:Name="ImportanceSplitButtonContent" Symbol="Important" />
|
<SymbolIcon x:Name="ImportanceSplitButtonContent" Symbol="Important" />
|
||||||
</Viewbox>
|
</Viewbox>
|
||||||
<ToggleSplitButton.Flyout>
|
</ToggleSplitButton.Content>
|
||||||
<Flyout x:Name="ImportanceFlyout" Placement="Bottom">
|
<ToggleSplitButton.Flyout>
|
||||||
<StackPanel Orientation="Horizontal">
|
<Flyout x:Name="ImportanceFlyout" Placement="Bottom">
|
||||||
<StackPanel.Resources>
|
<StackPanel Orientation="Horizontal">
|
||||||
<Style TargetType="Button">
|
<StackPanel.Resources>
|
||||||
<Setter Property="Padding" Value="4" />
|
<Style TargetType="Button">
|
||||||
<Setter Property="MinWidth" Value="0" />
|
<Setter Property="Padding" Value="4" />
|
||||||
<Setter Property="MinHeight" Value="0" />
|
<Setter Property="MinWidth" Value="0" />
|
||||||
<Setter Property="Margin" Value="6" />
|
<Setter Property="MinHeight" Value="0" />
|
||||||
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}" />
|
<Setter Property="Margin" Value="6" />
|
||||||
</Style>
|
<Setter Property="CornerRadius" Value="{StaticResource ControlCornerRadius}" />
|
||||||
</StackPanel.Resources>
|
</Style>
|
||||||
<Button Click="ImportanceClicked">
|
</StackPanel.Resources>
|
||||||
<Button.Tag>
|
<Button Click="ImportanceClicked">
|
||||||
<mailkit:MessageImportance>High</mailkit:MessageImportance>
|
<Button.Tag>
|
||||||
</Button.Tag>
|
<mailkit:MessageImportance>High</mailkit:MessageImportance>
|
||||||
<SymbolIcon Symbol="Important" />
|
</Button.Tag>
|
||||||
</Button>
|
<SymbolIcon Symbol="Important" />
|
||||||
<Button Click="ImportanceClicked">
|
</Button>
|
||||||
<Button.Tag>
|
<Button Click="ImportanceClicked">
|
||||||
<mailkit:MessageImportance>Low</mailkit:MessageImportance>
|
<Button.Tag>
|
||||||
</Button.Tag>
|
<mailkit:MessageImportance>Low</mailkit:MessageImportance>
|
||||||
<SymbolIcon Symbol="Priority" />
|
</Button.Tag>
|
||||||
</Button>
|
<SymbolIcon Symbol="Priority" />
|
||||||
</StackPanel>
|
</Button>
|
||||||
</Flyout>
|
</StackPanel>
|
||||||
</ToggleSplitButton.Flyout>
|
</Flyout>
|
||||||
</ToggleSplitButton>
|
</ToggleSplitButton.Flyout>
|
||||||
</AppBarElementContainer>
|
</ToggleSplitButton>
|
||||||
<AppBarElementContainer>
|
<muxc:ToggleSplitButton
|
||||||
<muxc:ToggleSplitButton
|
x:Name="SignatureToggleButton"
|
||||||
x:Name="SignatureToggleButton"
|
Margin="8,0,0,0"
|
||||||
Margin="8,0,0,0"
|
IsChecked="{x:Bind ViewModel.IsSmimeSignatureEnabled, Mode=TwoWay}"
|
||||||
IsChecked="{x:Bind ViewModel.IsSmimeSignatureEnabled, Mode=TwoWay}"
|
IsEnabled="{x:Bind ViewModel.AreCertificatesAvailable, Mode=OneWay}"
|
||||||
IsEnabled="{x:Bind ViewModel.AreCertificatesAvailable, Mode=OneWay}"
|
ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_EnableSmimeSignature}">
|
||||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_EnableSmimeSignature}">
|
<muxc:ToggleSplitButton.Content>
|
||||||
<muxc:ToggleSplitButton.Content>
|
<Viewbox
|
||||||
<Viewbox
|
Width="16"
|
||||||
Width="16"
|
Height="16"
|
||||||
Height="16"
|
Margin="0,0,4,0">
|
||||||
Margin="0,0,4,0">
|
<PathIcon Data="M15 18c.835.629 1.875 1.001 3 1.001a4.978 4.978 0 0 0 3-.999v3.246a.75.75 0 0 1-1.09.67l-.09-.055L18 20.591l-1.82 1.272a.75.75 0 0 1-1.172-.51l-.007-.105L15 18.001Zm4.25-14.996a2.75 2.75 0 0 1 2.745 2.582l.005.168.001 5.246a5.027 5.027 0 0 0-1.5-1.331L20.5 5.754a1.25 1.25 0 0 0-1.122-1.244l-.128-.006H4.75a1.25 1.25 0 0 0-1.244 1.122l-.006.128v9.5c0 .647.492 1.18 1.122 1.243l.128.007h8.92c.1.172.21.338.33.496v1.004H4.75a2.75 2.75 0 0 1-2.745-2.583L2 15.254v-9.5a2.75 2.75 0 0 1 2.582-2.745l.168-.005h14.5ZM18 10A4 4 0 1 1 18 18 4 4 0 0 1 18 10Zm-6.75 2.5a.75.75 0 0 1 .102 1.493L11.25 14h-4.5a.75.75 0 0 1-.102-1.493l.102-.007h4.5Zm6-5.5a.75.75 0 0 1 .102 1.493l-.102.007H6.75a.75.75 0 0 1-.102-1.493L6.75 7h10.5Z" />
|
||||||
<PathIcon Data="M15 18c.835.629 1.875 1.001 3 1.001a4.978 4.978 0 0 0 3-.999v3.246a.75.75 0 0 1-1.09.67l-.09-.055L18 20.591l-1.82 1.272a.75.75 0 0 1-1.172-.51l-.007-.105L15 18.001Zm4.25-14.996a2.75 2.75 0 0 1 2.745 2.582l.005.168.001 5.246a5.027 5.027 0 0 0-1.5-1.331L20.5 5.754a1.25 1.25 0 0 0-1.122-1.244l-.128-.006H4.75a1.25 1.25 0 0 0-1.244 1.122l-.006.128v9.5c0 .647.492 1.18 1.122 1.243l.128.007h8.92c.1.172.21.338.33.496v1.004H4.75a2.75 2.75 0 0 1-2.745-2.583L2 15.254v-9.5a2.75 2.75 0 0 1 2.582-2.745l.168-.005h14.5ZM18 10A4 4 0 1 1 18 18 4 4 0 0 1 18 10Zm-6.75 2.5a.75.75 0 0 1 .102 1.493L11.25 14h-4.5a.75.75 0 0 1-.102-1.493l.102-.007h4.5Zm6-5.5a.75.75 0 0 1 .102 1.493l-.102.007H6.75a.75.75 0 0 1-.102-1.493L6.75 7h10.5Z" />
|
</Viewbox>
|
||||||
</Viewbox>
|
</muxc:ToggleSplitButton.Content>
|
||||||
</muxc:ToggleSplitButton.Content>
|
<muxc:ToggleSplitButton.Flyout>
|
||||||
<muxc:ToggleSplitButton.Flyout>
|
<Flyout Placement="Bottom">
|
||||||
<Flyout Placement="Bottom">
|
<ListView
|
||||||
<ListView
|
Width="320"
|
||||||
Width="320"
|
ItemsSource="{x:Bind ViewModel.AvailableCertificates, Mode=OneWay}"
|
||||||
ItemsSource="{x:Bind ViewModel.AvailableCertificates, Mode=OneWay}"
|
SelectedItem="{x:Bind ViewModel.SelectedSigningCertificate, Mode=TwoWay}">
|
||||||
SelectedItem="{x:Bind ViewModel.SelectedSigningCertificate, Mode=TwoWay}">
|
<ListView.ItemTemplate>
|
||||||
<ListView.ItemTemplate>
|
<DataTemplate xmlns:x509="using:System.Security.Cryptography.X509Certificates" x:DataType="x509:X509Certificate2">
|
||||||
<DataTemplate xmlns:x509="using:System.Security.Cryptography.X509Certificates" x:DataType="x509:X509Certificate2">
|
<StackPanel Padding="8,4" Orientation="Vertical">
|
||||||
<StackPanel Padding="8,4" Orientation="Vertical">
|
<TextBlock FontWeight="SemiBold" Text="{x:Bind Subject}" />
|
||||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind Subject}" />
|
<TextBlock FontSize="12" Text="{x:Bind Issuer}" />
|
||||||
<TextBlock FontSize="12" Text="{x:Bind Issuer}" />
|
<TextBlock>
|
||||||
<TextBlock>
|
<Run Text="{x:Bind domain:Translator.Composer_CertificateExpires, Mode=OneTime}" />
|
||||||
<Run Text="{x:Bind domain:Translator.Composer_CertificateExpires, Mode=OneTime}" />
|
<Run Text="{x:Bind NotAfter, Mode=OneTime}" />
|
||||||
<Run Text="{x:Bind NotAfter, Mode=OneTime}" />
|
</TextBlock>
|
||||||
</TextBlock>
|
</StackPanel>
|
||||||
</StackPanel>
|
</DataTemplate>
|
||||||
</DataTemplate>
|
</ListView.ItemTemplate>
|
||||||
</ListView.ItemTemplate>
|
</ListView>
|
||||||
</ListView>
|
</Flyout>
|
||||||
</Flyout>
|
</muxc:ToggleSplitButton.Flyout>
|
||||||
</muxc:ToggleSplitButton.Flyout>
|
</muxc:ToggleSplitButton>
|
||||||
</muxc:ToggleSplitButton>
|
<ToggleButton
|
||||||
</AppBarElementContainer>
|
x:Name="EncryptionToggleButton"
|
||||||
<AppBarElementContainer>
|
Margin="8,0,0,0"
|
||||||
<ToggleButton
|
IsChecked="{x:Bind ViewModel.IsSmimeEncryptionEnabled, Mode=TwoWay}"
|
||||||
x:Name="EncryptionToggleButton"
|
ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_EnableSmimeEncryption}">
|
||||||
Margin="8,0,0,0"
|
<ToggleButton.Content>
|
||||||
IsChecked="{x:Bind ViewModel.IsSmimeEncryptionEnabled, Mode=TwoWay}"
|
<Viewbox
|
||||||
ToolTipService.ToolTip="{x:Bind domain:Translator.Composer_EnableSmimeEncryption}">
|
Width="16"
|
||||||
<ToggleButton.Content>
|
Height="16"
|
||||||
<Viewbox
|
Margin="0,0,4,0">
|
||||||
Width="16"
|
<PathIcon Data="M12 2a4 4 0 0 1 4 4v2h1.75A2.25 2.25 0 0 1 20 10.25v9.5A2.25 2.25 0 0 1 17.75 22H6.25A2.25 2.25 0 0 1 4 19.75v-9.5A2.25 2.25 0 0 1 6.25 8H8V6a4 4 0 0 1 4-4Zm5.75 7.5H6.25a.75.75 0 0 0-.75.75v9.5c0 .414.336.75.75.75h11.5a.75.75 0 0 0 .75-.75v-9.5a.75.75 0 0 0-.75-.75Zm-5.75 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Zm0-10A2.5 2.5 0 0 0 9.5 6v2h5V6A2.5 2.5 0 0 0 12 3.5Z" />
|
||||||
Height="16"
|
</Viewbox>
|
||||||
Margin="0,0,4,0">
|
</ToggleButton.Content>
|
||||||
<PathIcon Data="M12 2a4 4 0 0 1 4 4v2h1.75A2.25 2.25 0 0 1 20 10.25v9.5A2.25 2.25 0 0 1 17.75 22H6.25A2.25 2.25 0 0 1 4 19.75v-9.5A2.25 2.25 0 0 1 6.25 8H8V6a4 4 0 0 1 4-4Zm5.75 7.5H6.25a.75.75 0 0 0-.75.75v9.5c0 .414.336.75.75.75h11.5a.75.75 0 0 0 .75-.75v-9.5a.75.75 0 0 0-.75-.75Zm-5.75 4a1.5 1.5 0 1 1 0 3 1.5 1.5 0 0 1 0-3Zm0-10A2.5 2.5 0 0 0 9.5 6v2h5V6A2.5 2.5 0 0 0 12 3.5Z" />
|
</ToggleButton>
|
||||||
</Viewbox>
|
</StackPanel>
|
||||||
</ToggleButton.Content>
|
</controls1:EditorTabbedCommandBarControl.OptionsCustomContent>
|
||||||
</ToggleButton>
|
</controls1:EditorTabbedCommandBarControl>
|
||||||
</AppBarElementContainer>
|
|
||||||
</toolkit:TabbedCommandBarItem>
|
|
||||||
</toolkit:TabbedCommandBar.MenuItems>
|
|
||||||
</toolkit:TabbedCommandBar>
|
|
||||||
|
|
||||||
<!-- Mime Info -->
|
<!-- Mime Info -->
|
||||||
<Grid
|
<Grid
|
||||||
@@ -779,3 +593,5 @@
|
|||||||
|
|
||||||
|
|
||||||
</abstract:ComposePageAbstract>
|
</abstract:ComposePageAbstract>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user