Skip to content

Commit 0922951

Browse files
authored
Merge pull request #62 from FBoucher/v-next
Enhances summary and post management features
2 parents ab597a7 + f7dc7bf commit 0922951

File tree

14 files changed

+152
-45
lines changed

14 files changed

+152
-45
lines changed

NoteBookmark.Api/DataStorageService.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,4 +288,23 @@ public async Task UpdatePostReadStatus()
288288
}
289289
}
290290

291+
public async Task<string> SaveReadingNotesMarkdown(string markdown, string number)
292+
{
293+
var containerClient = blobClient.GetBlobContainerClient("final-markdown");
294+
await containerClient.CreateIfNotExistsAsync();
295+
296+
var fileName = $"readingnotes-{number}.md";
297+
var markdownBlobClient = containerClient.GetBlobClient(fileName);
298+
299+
byte[] markdownBytes = Encoding.UTF8.GetBytes(markdown);
300+
var response = await markdownBlobClient.UploadAsync(new MemoryStream(markdownBytes), overwrite: true);
301+
302+
if (response.GetRawResponse().Status == 201 || response.GetRawResponse().Status == 200)
303+
{
304+
return markdownBlobClient.Uri.ToString();
305+
}
306+
307+
return string.Empty;
308+
}
309+
291310
}

NoteBookmark.Api/IDataStorageService.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,6 @@ public interface IDataStorageService
3232
public Task UpdatePostReadStatus();
3333

3434
public bool DeletePost(string rowKey);
35+
36+
public Task<string> SaveReadingNotesMarkdown(string markdown, string number);
3537
}

NoteBookmark.Api/NoteBookmark.Api.csproj

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Aspire.Azure.Data.Tables" Version="9.0.0" />
11-
<PackageReference Include="Aspire.Azure.Storage.Blobs" Version="9.0.0" />
12-
<PackageReference Include="Azure.Data.Tables" Version="12.9.1" />
13-
<PackageReference Include="Azure.Storage.Blobs" Version="12.23.0" />
14-
<PackageReference Include="HtmlAgilityPack" Version="1.11.72" />
15-
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
16-
<PackageReference Include="Swashbuckle.AspNetCore" Version="7.0.0" />
17-
<PackageReference Include="System.Text.Json" Version="9.0.0" />
10+
<PackageReference Include="Aspire.Azure.Data.Tables" Version="9.3.0" />
11+
<PackageReference Include="Aspire.Azure.Storage.Blobs" Version="9.3.0" />
12+
<PackageReference Include="Azure.Data.Tables" Version="12.11.0" />
13+
<PackageReference Include="Azure.Storage.Blobs" Version="12.24.0" />
14+
<PackageReference Include="HtmlAgilityPack" Version="1.12.1" />
15+
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.5" />
16+
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.2" />
17+
<PackageReference Include="System.Text.Json" Version="9.0.5" />
1818
</ItemGroup>
1919

2020
<ItemGroup>

NoteBookmark.Api/PostEndpoints.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ public static void MapPostEndpoints(this IEndpointRouteBuilder app)
1717

1818
endpoints.MapGet("/", GetUnreadPosts)
1919
.WithDescription("Get all unread posts");
20+
endpoints.MapGet("/read", GetReadPosts)
21+
.WithDescription("Get all read posts");
2022
endpoints.MapGet("/{id}", Get)
2123
.WithDescription("Get a post by id");
2224
endpoints.MapPost("/", SavePost)
@@ -33,6 +35,12 @@ static List<PostL> GetUnreadPosts(TableServiceClient tblClient, BlobServiceClien
3335
return dataStorageService.GetFilteredPosts("is_read eq false");
3436
}
3537

38+
static List<PostL> GetReadPosts(TableServiceClient tblClient, BlobServiceClient blobClient)
39+
{
40+
var dataStorageService = new DataStorageService(tblClient, blobClient);
41+
return dataStorageService.GetFilteredPosts("is_read eq true");
42+
}
43+
3644
static Results<Ok<Post>, NotFound> Get(string id, TableServiceClient tblClient, BlobServiceClient blobClient)
3745
{
3846
var dataStorageService = new DataStorageService(tblClient, blobClient);

NoteBookmark.Api/SummaryEndpoints.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ public static void MapSummaryEndpoints(this IEndpointRouteBuilder app)
2020

2121
endpoints.MapPost("/summary", SaveSummary)
2222
.WithDescription("Create or update the summary");
23+
24+
endpoints.MapPost("/{number}/markdown", SaveReadingNotesMarkdown)
25+
.WithDescription("Save reading notes as markdown to blob storage");
2326
}
2427
static List<Summary> GetSummaries(TableServiceClient tblClient, BlobServiceClient blobClient)
2528
{
@@ -54,4 +57,25 @@ static async Task<Results<Ok<ReadingNotes>, NotFound>> GetReadingNotes(string nu
5457
return TypedResults.Ok(readingNotes);
5558
}
5659

60+
static async Task<Results<Ok<string>, BadRequest>> SaveReadingNotesMarkdown(string number, MarkdownRequest request, TableServiceClient tblClient, BlobServiceClient blobClient)
61+
{
62+
try
63+
{
64+
var dataStorageService = new DataStorageService(tblClient, blobClient);
65+
var url = await dataStorageService.SaveReadingNotesMarkdown(request.Markdown, number);
66+
if (string.IsNullOrEmpty(url))
67+
{
68+
return TypedResults.BadRequest();
69+
}
70+
return TypedResults.Ok(url);
71+
}
72+
catch (Exception ex)
73+
{
74+
Console.WriteLine($"An error occurred while saving the markdown: {ex.Message}");
75+
return TypedResults.BadRequest();
76+
}
77+
}
78+
5779
}
80+
81+
public record MarkdownRequest(string Markdown);

NoteBookmark.AppHost/NoteBookmark.AppHost.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<Sdk Name="Aspire.AppHost.Sdk" Version="9.0.0" />
3+
<Sdk Name="Aspire.AppHost.Sdk" Version="9.3.0" />
44

55
<PropertyGroup>
66
<OutputType>Exe</OutputType>
@@ -12,8 +12,8 @@
1212
</PropertyGroup>
1313

1414
<ItemGroup>
15-
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.0.0" />
16-
<PackageReference Include="Aspire.Hosting.Azure.Storage" Version="9.0.0" />
15+
<PackageReference Include="Aspire.Hosting.AppHost" Version="9.3.0" />
16+
<PackageReference Include="Aspire.Hosting.Azure.Storage" Version="9.3.0" />
1717
</ItemGroup>
1818

1919
<ItemGroup>

NoteBookmark.BlazorApp/Components/Pages/Posts.razor

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
@page "/posts"
22
@using NoteBookmark.BlazorApp.Components.Shared
33
@using NoteBookmark.Domain
4+
@using Microsoft.FluentUI.AspNetCore.Components
45
@inject PostNoteClient client
56
@inject IJSRuntime jsRuntime
67
@inject IToastService toastService
@@ -12,23 +13,29 @@
1213

1314
<h1>Posts</h1>
1415

15-
<FluentStack Orientation="Orientation.Horizontal">
16-
<FluentButton OnClick="AddNewPost" IconEnd="@(new Icons.Regular.Size24.Add())" Title="Add new post"></FluentButton>
17-
<FluentTextField @bind-Value="newPostUrl" Placeholder="Enter URL" style="width: 100%;"/>
16+
<FluentStack Orientation="Orientation.Vertical">
17+
<FluentStack Orientation="Orientation.Horizontal">
18+
<FluentButton OnClick="AddNewPost" IconEnd="@(new Icons.Regular.Size20.Add())" Title="Add new post"></FluentButton>
19+
<FluentTextField @bind-Value="newPostUrl" Placeholder="Enter URL" style="width: 100%;"/>
20+
</FluentStack>
21+
<FluentSwitch ValueChanged="OnShowReadChanged" Label="Show">
22+
<span slot="checked-message">Read Only</span>
23+
<span slot="unchecked-message">UnRead Only</span>
24+
</FluentSwitch>
1825
</FluentStack>
1926

2027
<FluentDataGrid Id="postgrid" Items="@posts">
2128
<ChildContent>
2229
<TemplateColumn Width="150px">
23-
<FluentButton OnClick="@(async () => await OpenUrlInNewWindow(context!.Url))" IconEnd="@(new Icons.Regular.Size24.Open())" />
24-
<FluentButton OnClick="@(() => EditNote(context!.RowKey))" IconEnd="@(new Icons.Regular.Size24.Edit())" />
30+
<FluentButton OnClick="@(async () => await OpenUrlInNewWindow(context!.Url))" IconEnd="@(new Icons.Regular.Size20.Open())" />
31+
<FluentButton OnClick="@(() => EditNote(context!.RowKey))" IconEnd="@(new Icons.Regular.Size20.Edit())" />
2532
@if (String.IsNullOrEmpty(context!.NoteId))
2633
{
27-
<FluentButton OnClick="@(async () => await CreateNoteForPost(context!.RowKey))" IconEnd="@(new Icons.Regular.Size24.NoteAdd())" />
34+
<FluentButton OnClick="@(async () => await CreateNoteForPost(context!.RowKey))" IconEnd="@(new Icons.Regular.Size20.NoteAdd())" />
2835
}
2936
else
3037
{
31-
<FluentButton IconEnd="@(new Icons.Filled.Size24.NoteEdit())" />
38+
<FluentButton IconEnd="@(new Icons.Filled.Size20.NoteEdit())" />
3239
}
3340
</TemplateColumn>
3441
<PropertyColumn Title="Title" Property="@(c => c!.Title)" Sortable="true"/>
@@ -38,11 +45,11 @@
3845
IsDefaultSortColumn="true"
3946
Width="125px"/>
4047
<TemplateColumn Width="60px">
41-
<FluentButton OnClick="@(async () => await DeletePost(context!.RowKey))" IconEnd="@(new Icons.Regular.Size24.Delete())" />
48+
<FluentButton OnClick="@(async () => await DeletePost(context!.RowKey))" IconEnd="@(new Icons.Regular.Size20.Delete())" />
4249
</TemplateColumn>
4350
</ChildContent>
4451
<EmptyContent>
45-
<FluentIcon Value="@(new Icons.Filled.Size24.Crown())" Color="@Color.Accent" />&nbsp; Nothing to see here. Carry on!
52+
<FluentIcon Value="@(new Icons.Filled.Size20.Crown())" Color="@Color.Accent" />&nbsp; Nothing to see here. Carry on!
4653
</EmptyContent>
4754
</FluentDataGrid>
4855

@@ -51,6 +58,7 @@
5158
private IQueryable<PostL>? posts;
5259
private GridSort<PostL> defSort = GridSort<PostL>.ByDescending(c => c.Date_published);
5360
private string newPostUrl = string.Empty;
61+
private bool showRead = false;
5462

5563
protected override async Task OnInitializedAsync()
5664
{
@@ -59,8 +67,8 @@
5967

6068
private async Task LoadPosts()
6169
{
62-
List<PostL> urPosts = await client.GetUnreadPosts();
63-
posts = urPosts.AsQueryable();
70+
List<PostL> loadedPosts = showRead ? await client.GetReadPosts(): await client.GetUnreadPosts();
71+
posts = loadedPosts.AsQueryable();
6472
}
6573

6674
private async Task OpenUrlInNewWindow(string? url)
@@ -130,4 +138,11 @@
130138
toastService.ShowError("Failed to delete post. Please try again.");
131139
}
132140
}
141+
142+
// Add handler to reload posts when toggle changes
143+
private async Task OnShowReadChanged(bool value)
144+
{
145+
showRead = value;
146+
await LoadPosts();
147+
}
133148
}

NoteBookmark.BlazorApp/Components/Pages/SummaryEditor.razor

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ else{
2121
<FluentTab Label=" Edit Summary" Icon="@(new Icons.Regular.Size20.FormSparkle())" Id="tabEdit">
2222
<EditForm Model="@readingNotes" OnValidSubmit="@HandleValidSubmit" FormName="new_readingNotes">
2323
<FluentStack Orientation="Orientation.Vertical" Width="100%">
24-
<FluentButton Type="ButtonType.Submit" Appearance="Appearance.Accent">Save</FluentButton>
24+
25+
<FluentButton Type="ButtonType.Submit" Appearance="Appearance.Accent">Save as Draft</FluentButton>
26+
2527
<DataAnnotationsValidator />
28+
2629
<div style="width: 80%;">
2730
<FluentTextField Label="Title" @bind-Value="readingNotes!.Title"/>
2831
</div>
@@ -32,6 +35,9 @@ else{
3235
<div style="width: 80%;">
3336
<FluentTextField Label="Published Url" @bind-Value="@readingNotes!.PublishedUrl" style="width: 80%;"/>
3437
</div>
38+
<div style="width: 80%;">
39+
<FluentTextArea Placeholder="Introduction" @bind-Value=@readingNotes.Intro Cols="100" Rows="5"></FluentTextArea>
40+
</div>
3541

3642
<div>
3743
@foreach (var note in readingNotes!.Notes)
@@ -74,19 +80,17 @@ else{
7480
<FluentValidationSummary />
7581
</div>
7682
<FluentButton OnClick="@(async () => await Publish())" Type="ButtonType.Button" Appearance="Appearance.Lightweight">Publish</FluentButton> |
77-
<FluentButton Type="ButtonType.Submit" Appearance="Appearance.Accent">Save</FluentButton>
83+
<FluentButton Type="ButtonType.Submit" Appearance="Appearance.Accent">Save as Draft</FluentButton>
7884
</EditForm>
7985

8086
</FluentTab>
8187

8288
<FluentTab Label=" Markdown" Icon="@(new Icons.Regular.Size20.Markdown())" Id="tabMD">
83-
<FluentLabel Typo="Typography.Body" Color="@Color.Disabled"> Read Only: This Markdown is generated from the form.</FluentLabel>
84-
<FluentTextArea Value="@readingNotesMD" ReadOnly="true" Cols="100" Rows="30"/>
89+
<FluentTextArea @bind-Value="@readingNotesMD" Cols="100" Rows="30"/>
8590
</FluentTab>
8691

8792
<FluentTab Label=" Html" Icon="@(new Icons.Regular.Size20.Code())" Id="tabHTML">
88-
<FluentLabel Typo="Typography.Body" Color="@Color.Disabled"> Read Only: This HTML is generated from the Markdown.</FluentLabel>
89-
<FluentTextArea Value="@readingNotesHTML" ReadOnly="true" Cols="100" Rows="30"/>
93+
<FluentTextArea Value="@readingNotesHTML" Cols="100" Rows="30"/>
9094
</FluentTab>
9195

9296
</FluentTabs>
@@ -196,25 +200,44 @@ else{
196200
private void DeleteReadingNote(string category, string RowKey)
197201
{
198202
readingNotes!.Notes[category].RemoveAll(x => x.RowKey == RowKey);
199-
}
200-
203+
}
204+
201205
private async Task Publish()
202206
{
203-
if(readingNotes is not null)
207+
if (readingNotes is not null)
204208
{
209+
if (string.IsNullOrWhiteSpace(readingNotes.PublishedUrl))
210+
{
211+
toastService.ShowError("Published Url is required to publish.");
212+
return;
213+
}
214+
215+
// Save markdown to blob storage only if readingNotesMD is not null or empty
216+
if (!string.IsNullOrWhiteSpace(readingNotesMD))
217+
{
218+
var markdownSaved = await client.SaveReadingNotesMarkdown(readingNotesMD, readingNotes.Number);
219+
if (!markdownSaved)
220+
{
221+
toastService.ShowError("Failed to save markdown file.");
222+
return;
223+
}
224+
}
225+
205226
await HandleValidSubmit();
206227
var settings = await client.GetSettings();
207-
if(settings is not null)
228+
if (settings is not null)
208229
{
209230
var cnt = Convert.ToInt32(settings!.ReadingNotesCounter);
210231
// Only increment if the current Summary is the most recent one.
211-
if(cnt == Convert.ToInt32(readingNotes!.Number))
232+
if (cnt == Convert.ToInt32(readingNotes!.Number))
212233
{
213234
cnt++;
214235
settings.ReadingNotesCounter = (cnt).ToString();
215236
await client.SaveSettings(settings);
216237
}
217238
}
239+
240+
toastService.ShowSuccess("Reading notes published successfully!");
218241
}
219242
}
220243
}

NoteBookmark.BlazorApp/Components/_Imports.razor

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
@using Microsoft.JSInterop
1010
@using NoteBookmark.BlazorApp
1111
@using NoteBookmark.BlazorApp.Components
12+
@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons

NoteBookmark.BlazorApp/NoteBookmark.BlazorApp.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Markdig" Version="0.38.0" />
11-
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" Version="4.10.4" />
12-
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="4.10.4" />
10+
<PackageReference Include="Markdig" Version="0.41.1" />
11+
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" Version="4.11.9" />
12+
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="4.11.9" />
1313
</ItemGroup>
1414

1515
<ItemGroup>

0 commit comments

Comments
 (0)