Allow for editing event titles:
- Add frontend for editing - Save the original iCal properties when loading - Use these to closely reproduce the original iCal for PUT'ing
This commit is contained in:
parent
024c8282b7
commit
3c2c33736b
6 changed files with 173 additions and 39 deletions
|
@ -15,4 +15,10 @@
|
|||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools.DotNet" Version="2.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Pages\Event.razor.scss">
|
||||
<DependentUpon>Event.razor</DependentUpon>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -3,24 +3,13 @@ using System.Collections.Generic;
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Globalization;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
|
||||
namespace BlazorApp.Data
|
||||
{
|
||||
public class Event
|
||||
{
|
||||
public string Uid { get; set; }
|
||||
public string Summary { get; set; }
|
||||
public DateTime? DtStart { get; set; }
|
||||
public DateTime? DtEnd { get; set; }
|
||||
public string Description { get; set; }
|
||||
public TimeSpan? Duration { get; set; }
|
||||
|
||||
public DateTime? CalculatedEnd => DtEnd ?? DtStart + Duration;
|
||||
}
|
||||
|
||||
public class Calendar
|
||||
{
|
||||
public int Id { get; set; }
|
||||
|
@ -127,17 +116,22 @@ namespace BlazorApp.Data
|
|||
continue;
|
||||
}
|
||||
|
||||
switch (name)
|
||||
{
|
||||
case "END":
|
||||
if (value == "VEVENT")
|
||||
if (name == "END" && value == "VEVENT")
|
||||
{
|
||||
// End of the current event, add it to the list
|
||||
events.Add(@event);
|
||||
@event = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
var parameters = left.Length == 2 ? left[1] : null;
|
||||
|
||||
// Add property to the RawProperties list.
|
||||
// Used for rebuilding the full iCal for updating the event on the server.
|
||||
@event.RawProperties.Add((name, parameters, value));
|
||||
|
||||
switch (name)
|
||||
{
|
||||
case "UID":
|
||||
@event.Uid = value;
|
||||
break;
|
||||
|
@ -162,18 +156,8 @@ namespace BlazorApp.Data
|
|||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* <summary>Retrieves and parses all events from the CalDav server.</summary>
|
||||
*/
|
||||
public async Task<List<Event>> GetEvents()
|
||||
private static List<Event> _filterSortEvents(List<Event> events)
|
||||
{
|
||||
var client = new HttpClient();
|
||||
if (AuthorizationHeader != null)
|
||||
client.DefaultRequestHeaders.Add("Authorization", AuthorizationHeader);
|
||||
|
||||
var body = await client.GetStringAsync(Url);
|
||||
var events = ParseICal(body);
|
||||
|
||||
var now = DateTime.Now;
|
||||
events = events.FindAll(@event => @event.DtStart > now || @event.DtEnd > now);
|
||||
|
||||
|
@ -190,6 +174,26 @@ namespace BlazorApp.Data
|
|||
return events;
|
||||
}
|
||||
|
||||
/**
|
||||
* <summary>Retrieves and parses all events from the CalDav server.</summary>
|
||||
*/
|
||||
public async Task<List<Event>> GetEvents()
|
||||
{
|
||||
var client = new HttpClient();
|
||||
if (AuthorizationHeader != null)
|
||||
client.DefaultRequestHeaders.Add("Authorization", AuthorizationHeader);
|
||||
|
||||
var response = await client.GetAsync(Url);
|
||||
var eTag = response.Headers.ETag?.Tag;
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var events = ParseICal(body);
|
||||
foreach (var @event in events)
|
||||
@event.ETag = eTag;
|
||||
|
||||
return _filterSortEvents(events);
|
||||
}
|
||||
|
||||
/**
|
||||
* <summary>Retrieves and parses an event with UID <code>uid</code> from the CalDav server.</summary>
|
||||
*/
|
||||
|
@ -199,6 +203,7 @@ namespace BlazorApp.Data
|
|||
if (AuthorizationHeader != null)
|
||||
client.DefaultRequestHeaders.Add("Authorization", AuthorizationHeader);
|
||||
|
||||
// Request the event with UID `uid`.
|
||||
var request = new HttpRequestMessage(new HttpMethod("REPORT"), Url);
|
||||
request.Headers.Add("Depth", "1");
|
||||
|
||||
|
@ -225,13 +230,53 @@ namespace BlazorApp.Data
|
|||
var result = await client.SendAsync(request);
|
||||
var body = await result.Content.ReadAsStreamAsync();
|
||||
|
||||
// Parse the received XML
|
||||
var reader = new XmlTextReader(body);
|
||||
var document = new XmlDocument();
|
||||
document.Load(reader);
|
||||
|
||||
var node = document.GetElementsByTagName("C:calendar-data")[0]?.InnerText;
|
||||
var events = ParseICal(node);
|
||||
return events.Count > 0 ? events[0] : null;
|
||||
// Extract and parse the ICal data
|
||||
var dataNode = document.GetElementsByTagName("C:calendar-data")[0]?.InnerText;
|
||||
var events = ParseICal(dataNode);
|
||||
if (events.Count == 0)
|
||||
return null;
|
||||
var @event = events[0];
|
||||
|
||||
// Extract the ETag and event collection href.
|
||||
@event.ETag = document.GetElementsByTagName("getetag")[0]?.InnerText;
|
||||
@event.Href = document.GetElementsByTagName("href")[0]?.InnerText;
|
||||
|
||||
return @event;
|
||||
}
|
||||
|
||||
public async Task<Event> UpdateEvent(Event @event)
|
||||
{
|
||||
var client = new HttpClient();
|
||||
if (AuthorizationHeader != null)
|
||||
client.DefaultRequestHeaders.Add("Authorization", AuthorizationHeader);
|
||||
|
||||
// Setting If-Match header to the ETag is required when updating an event.
|
||||
if (@event.ETag != null)
|
||||
client.DefaultRequestHeaders.IfMatch.Add(new EntityTagHeaderValue(@event.ETag));
|
||||
|
||||
// Build a new URL based on the calendar's URL and the event's collection href.
|
||||
var url = new Uri(new Uri(Url), @event.Href);
|
||||
|
||||
var content = new StringContent(@event.ToICal(), Encoding.UTF8, "text/calendar");
|
||||
var response = await client.PutAsync(url, content);
|
||||
|
||||
// Parse the newly updated event.
|
||||
var body = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var events = ParseICal(body);
|
||||
if (events.Count == 0)
|
||||
return null;
|
||||
var newEvent = events[0];
|
||||
|
||||
// Extract the updated ETag
|
||||
newEvent.ETag = response.Headers.ETag?.Tag;
|
||||
|
||||
return newEvent;
|
||||
}
|
||||
}
|
||||
}
|
59
BlazorApp/Data/Event.cs
Normal file
59
BlazorApp/Data/Event.cs
Normal file
|
@ -0,0 +1,59 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BlazorApp.Data
|
||||
{
|
||||
public class Event
|
||||
{
|
||||
public string Uid { get; set; }
|
||||
public string Summary { get; set; }
|
||||
public DateTime? DtStart { get; set; }
|
||||
public DateTime? DtEnd { get; set; }
|
||||
public string Description { get; set; }
|
||||
public TimeSpan? Duration { get; set; }
|
||||
|
||||
public List<(string, string, string)> RawProperties { get; } = new();
|
||||
public string ETag { get; set; }
|
||||
public string Href { get; set; }
|
||||
|
||||
public DateTime? CalculatedEnd => DtEnd ?? DtStart + Duration;
|
||||
|
||||
private static string _escape(string s)
|
||||
{
|
||||
return s
|
||||
.Replace(@"\", @"\\")
|
||||
.Replace(",", @"\,")
|
||||
.Replace(";", @"\;")
|
||||
.Replace("\n", @"\n");
|
||||
}
|
||||
|
||||
public string ToICal()
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append("BEGIN:VCALENDAR\r\n");
|
||||
sb.Append("VERSION:2.0\r\n");
|
||||
sb.Append("PRODID:-//Sijmen//blazor-calendar//EN\r\n");
|
||||
sb.Append("BEGIN:VEVENT\r\n");
|
||||
|
||||
foreach (var (name, parameters, oldValue) in RawProperties)
|
||||
{
|
||||
sb.Append(name);
|
||||
if (parameters != null)
|
||||
sb.Append($";{parameters}");
|
||||
|
||||
var value = name switch
|
||||
{
|
||||
"SUMMARY" => _escape(Summary),
|
||||
_ => oldValue
|
||||
};
|
||||
|
||||
sb.Append($":{value}\r\n");
|
||||
}
|
||||
|
||||
sb.Append("END:VEVENT\r\n");
|
||||
sb.Append("END:VCALENDAR\r\n");
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,7 +13,21 @@
|
|||
}
|
||||
else
|
||||
{
|
||||
<h1>@_event.Summary</h1>
|
||||
@if (!_editTitle)
|
||||
{
|
||||
<h1 class="event-summary">
|
||||
<span @onclick="() => _editTitle = true">@_event.Summary</span>
|
||||
</h1>
|
||||
}
|
||||
else
|
||||
{
|
||||
<input @bind="@_event.Summary">
|
||||
<button @onclick="UpdateEvent">Save</button>
|
||||
}
|
||||
|
||||
<div>
|
||||
<strong>ETag:</strong> @_event.ETag
|
||||
</div>
|
||||
|
||||
@if (_event.DtStart != null)
|
||||
{
|
||||
|
@ -40,11 +54,18 @@ else
|
|||
[Parameter]
|
||||
public string EventUid { get; set; }
|
||||
|
||||
private Data.Calendar _calendar;
|
||||
private Data.Event _event;
|
||||
private bool _editTitle;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var calendar = await Data.CalendarService.GetCalendarById(CalendarId);
|
||||
_event = await calendar.GetEventByUid(EventUid);
|
||||
_calendar = await Data.CalendarService.GetCalendarById(CalendarId);
|
||||
_event = await _calendar.GetEventByUid(EventUid);
|
||||
}
|
||||
|
||||
private async void UpdateEvent()
|
||||
{
|
||||
_event = await _calendar.UpdateEvent(_event);
|
||||
}
|
||||
}
|
3
BlazorApp/Pages/Event.razor.css
Normal file
3
BlazorApp/Pages/Event.razor.css
Normal file
|
@ -0,0 +1,3 @@
|
|||
.event-summary > span:hover {
|
||||
border: 1px solid rgba(0, 0, 0, 0.2);
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
"BlazorApp": {
|
||||
"commandName": "Project",
|
||||
"dotnetRunMessages": "true",
|
||||
"launchBrowser": true,
|
||||
"launchBrowser": false,
|
||||
"applicationUrl": "http://localhost:5000",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
|
|
Loading…
Reference in a new issue