Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 115 additions & 0 deletions src/NoteBookmark.BlazorApp.Tests/Tests/ReadingNotesReorderingTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
using FluentAssertions;
using NoteBookmark.Domain;
using Xunit;

namespace NoteBookmark.BlazorApp.Tests.Tests;

public class ReadingNotesReorderingTests
{
private ReadingNotes CreateSampleReadingNotes()
{
var rn = new ReadingNotes("1")
{
Title = "Reading Notes #1"
};
rn.Notes["Category A"] = new List<ReadingNote>
{
new ReadingNote { Title = "Note A1", RowKey = "a1" },
new ReadingNote { Title = "Note A2", RowKey = "a2" },
new ReadingNote { Title = "Note A3", RowKey = "a3" }
};
rn.Notes["Category B"] = new List<ReadingNote>
{
new ReadingNote { Title = "Note B1", RowKey = "b1" }
};
rn.Notes["Category C"] = new List<ReadingNote>
{
new ReadingNote { Title = "Note C1", RowKey = "c1" }
};
return rn;
}

[Fact]
public void MoveCategoryUp_SwapsCategoryWithPrevious()
{
var rn = CreateSampleReadingNotes();

bool moved = rn.MoveCategoryUp("Category B");

moved.Should().BeTrue();
rn.Notes.Keys.Should().ContainInConsecutiveOrder("Category B", "Category A", "Category C");
}

[Fact]
public void MoveCategoryUp_OnFirstCategory_ReturnsFalse()
{
var rn = CreateSampleReadingNotes();

bool moved = rn.MoveCategoryUp("Category A");

moved.Should().BeFalse();
rn.Notes.Keys.Should().ContainInConsecutiveOrder("Category A", "Category B", "Category C");
}

[Fact]
public void MoveCategoryDown_SwapsCategoryWithNext()
{
var rn = CreateSampleReadingNotes();

bool moved = rn.MoveCategoryDown("Category A");

moved.Should().BeTrue();
rn.Notes.Keys.Should().ContainInConsecutiveOrder("Category B", "Category A", "Category C");
}

[Fact]
public void MoveCategoryDown_OnLastCategory_ReturnsFalse()
{
var rn = CreateSampleReadingNotes();

bool moved = rn.MoveCategoryDown("Category C");

moved.Should().BeFalse();
rn.Notes.Keys.Should().ContainInConsecutiveOrder("Category A", "Category B", "Category C");
}

[Fact]
public void MoveNoteUp_SwapsNoteWithPrevious()
{
var rn = CreateSampleReadingNotes();

bool moved = rn.MoveNoteUp("Category A", 1);

moved.Should().BeTrue();
rn.Notes["Category A"].Select(n => n.Title).Should().ContainInConsecutiveOrder("Note A2", "Note A1", "Note A3");
}

[Fact]
public void MoveNoteDown_SwapsNoteWithNext()
{
var rn = CreateSampleReadingNotes();

bool moved = rn.MoveNoteDown("Category A", 0);

moved.Should().BeTrue();
rn.Notes["Category A"].Select(n => n.Title).Should().ContainInConsecutiveOrder("Note A2", "Note A1", "Note A3");
}

[Fact]
public void ReorderedNotes_ReflectsInMarkdownGeneration()
{
var rn = CreateSampleReadingNotes();
rn.MoveCategoryUp("Category B");
rn.MoveNoteUp("Category A", 1);

string md = rn.ToMarkDown();

int catBPos = md.IndexOf("## Category B");
int catAPos = md.IndexOf("## Category A");
catBPos.Should().BeLessThan(catAPos);

int noteA2Pos = md.IndexOf("Note A2");
int noteA1Pos = md.IndexOf("Note A1");
noteA2Pos.Should().BeLessThan(noteA1Pos);
}
}
80 changes: 80 additions & 0 deletions src/NoteBookmark.Domain/ReadingNotes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,86 @@ public ReadingNotes(string number)
public string Intro { get; set; } = string.Empty;
public Dictionary<string, List<ReadingNote>> Notes { get; set; }

public bool MoveCategoryUp(string category)
{
if (Notes == null || !Notes.ContainsKey(category)) return false;

var keys = Notes.Keys.ToList();
int index = keys.IndexOf(category);
if (index <= 0) return false;

var newDict = new Dictionary<string, List<ReadingNote>>();
for (int i = 0; i < keys.Count; i++)
{
if (i == index - 1)
{
newDict[category] = Notes[category];
newDict[keys[i]] = Notes[keys[i]];
}
else if (i == index)
{
continue;
}
else
{
newDict[keys[i]] = Notes[keys[i]];
}
}
Notes = newDict;
return true;
}

public bool MoveCategoryDown(string category)
{
if (Notes == null || !Notes.ContainsKey(category)) return false;

var keys = Notes.Keys.ToList();
int index = keys.IndexOf(category);
if (index < 0 || index >= keys.Count - 1) return false;

var newDict = new Dictionary<string, List<ReadingNote>>();
for (int i = 0; i < keys.Count; i++)
{
if (i == index)
{
newDict[keys[i + 1]] = Notes[keys[i + 1]];
newDict[category] = Notes[category];
}
else if (i == index + 1)
{
continue;
}
else
{
newDict[keys[i]] = Notes[keys[i]];
}
}
Notes = newDict;
return true;
}

public bool MoveNoteUp(string category, int noteIndex)
{
if (Notes == null || !Notes.TryGetValue(category, out var list)) return false;
if (noteIndex <= 0 || noteIndex >= list.Count) return false;

var note = list[noteIndex];
list.RemoveAt(noteIndex);
list.Insert(noteIndex - 1, note);
return true;
}

public bool MoveNoteDown(string category, int noteIndex)
{
if (Notes == null || !Notes.TryGetValue(category, out var list)) return false;
if (noteIndex < 0 || noteIndex >= list.Count - 1) return false;

var note = list[noteIndex];
list.RemoveAt(noteIndex);
list.Insert(noteIndex + 1, note);
return true;
}


public string GetAllUniqueTags(){
var uniqueTags = new HashSet<string>();
Expand Down
54 changes: 44 additions & 10 deletions src/NoteBookmark.SharedUI/Components/Pages/SummaryEditor.razor
Original file line number Diff line number Diff line change
Expand Up @@ -45,34 +45,49 @@ else{
IconStart="@(new Icons.Filled.Size20.StarEmphasis())">Generate</FluentButton>

<div>
@foreach (var note in readingNotes!.Notes)
@{
var categoryKeys = readingNotes!.Notes.Keys.ToList();
}
@for (int catIdx = 0; catIdx < categoryKeys.Count; catIdx++)
{
string category = note.Key;
List<ReadingNote> rnList = note.Value;
string category = categoryKeys[catIdx];
List<ReadingNote> rnList = readingNotes.Notes[category];
bool isFirstCategory = catIdx == 0;
bool isLastCategory = catIdx == categoryKeys.Count - 1;
<div style="margin-bottom: 30px;">
<FluentStack Orientation="Orientation.Horizontal">
<FluentStack Orientation="Orientation.Horizontal" VerticalAlignment="VerticalAlignment.Center">
<h4>@category</h4>
<FluentButton Title="Delete ReadingNote" OnClick="@(() => DeleteCategory(category))" Type="ButtonType.Button" Appearance="Appearance.Lightweight" IconStart="@(new Icons.Regular.Size16.Delete())"></FluentButton>
<FluentButton Title="Move Category Up" OnClick="@(() => MoveCategoryUp(category))" Disabled="isFirstCategory" Type="ButtonType.Button" Appearance="Appearance.Lightweight" IconStart="@(new Icons.Regular.Size16.ArrowUp())"></FluentButton>
<FluentButton Title="Move Category Down" OnClick="@(() => MoveCategoryDown(category))" Disabled="isLastCategory" Type="ButtonType.Button" Appearance="Appearance.Lightweight" IconStart="@(new Icons.Regular.Size16.ArrowDown())"></FluentButton>
<FluentButton Title="Delete Category" OnClick="@(() => DeleteCategory(category))" Type="ButtonType.Button" Appearance="Appearance.Lightweight" IconStart="@(new Icons.Regular.Size16.Delete())"></FluentButton>
</FluentStack>

@foreach (ReadingNote rn in rnList)
@for (int noteIdx = 0; noteIdx < rnList.Count; noteIdx++)
{
<div style="margin: 20px; padding: 20px; width: 100%; border: groove; border-radius: 5px; background-color: gray">
<FluentButton Title="Delete ReadingNote" OnClick="@(() => DeleteReadingNote(category, rn.RowKey!))" Type="ButtonType.Button" Appearance="Appearance.Lightweight" IconStart="@(new Icons.Regular.Size16.Delete())" ></FluentButton>
var rn = rnList[noteIdx];
int currentNoteIndex = noteIdx;
bool isFirstNote = noteIdx == 0;
bool isLastNote = noteIdx == rnList.Count - 1;
<div style="margin: 20px; padding: 20px; width: 100%; border: groove; border-radius: 5px; background-color: gray">
<FluentStack Orientation="Orientation.Horizontal" Style="margin-bottom: 10px;">
<FluentButton Title="Move Note Up" OnClick="@(() => MoveNoteUp(category, currentNoteIndex))" Disabled="isFirstNote" Type="ButtonType.Button" Appearance="Appearance.Lightweight" IconStart="@(new Icons.Regular.Size16.ArrowUp())"></FluentButton>
<FluentButton Title="Move Note Down" OnClick="@(() => MoveNoteDown(category, currentNoteIndex))" Disabled="isLastNote" Type="ButtonType.Button" Appearance="Appearance.Lightweight" IconStart="@(new Icons.Regular.Size16.ArrowDown())"></FluentButton>
<FluentButton Title="Delete ReadingNote" OnClick="@(() => DeleteReadingNote(category, rn.RowKey!))" Type="ButtonType.Button" Appearance="Appearance.Lightweight" IconStart="@(new Icons.Regular.Size16.Delete())" ></FluentButton>
</FluentStack>

<FluentTextField Label="Post Title" @bind-Value="rn.Title" style="width: 100%;"/>

<FluentTextField Label="Post Autor" @bind-Value="rn.Author" style="width: 100%;"/>

<FluentStack Orientation="Orientation.Horizontal" Width="100%">
<FluentTextField Label="Post URL" @bind-Value="rn.Url" style="width: 80%;"/>
<FluentButton OnClick="@(async () => await OpenUrlInNewWindow(@rn.Url))" IconEnd="@(new Icons.Regular.Size16.Open())"/>
<FluentButton OnClick="@(async () => await OpenUrlInNewWindow(rn.Url))" IconEnd="@(new Icons.Regular.Size16.Open())"/>
</FluentStack>

<FluentTextArea Label="Comment" @bind-Value="rn.Comment" Resize="TextAreaResize.Both" Cols="100"/>

<FluentTextField Label="Tags" @bind-Value="rn.Tags" style="width: 100%;"/>
</div>

}
<FluentButton OnClick="@(() => AddExtraNote(category))" Type="ButtonType.Button" Appearance="Appearance.Accent" style="margin: 20px;">Add Note</FluentButton>
</div>
Expand Down Expand Up @@ -274,4 +289,23 @@ else{
}
}

private void MoveCategoryUp(string category)
{
readingNotes?.MoveCategoryUp(category);
}

private void MoveCategoryDown(string category)
{
readingNotes?.MoveCategoryDown(category);
}

private void MoveNoteUp(string category, int index)
{
readingNotes?.MoveNoteUp(category, index);
}

private void MoveNoteDown(string category, int index)
{
readingNotes?.MoveNoteDown(category, index);
}
}
Loading