Skip to content

Commit 0cd6b76

Browse files
authored
Merge pull request #7 from devohmycode/0.3.1
v0.3.1: NotepadWindow enhancements, note links & installer i18n
2 parents 9b9c8df + b7d6199 commit 0cd6b76

10 files changed

Lines changed: 906 additions & 44 deletions

‎AppSettings.cs‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -266,11 +266,11 @@ public static string LoadSortPreference()
266266
var json = File.ReadAllText(SettingsPath);
267267
var doc = JsonDocument.Parse(json);
268268
if (doc.RootElement.TryGetProperty("sortMode", out var prop))
269-
return prop.GetString() ?? "recent";
269+
return prop.GetString() ?? "created";
270270
}
271271
}
272272
catch { }
273-
return "recent";
273+
return "created";
274274
}
275275

276276
public static void SaveSortPreference(string mode)

‎Lang.cs‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ public static class Lang
190190
["close_tab"] = "Close tab",
191191
["open"] = "Open",
192192
["save_as"] = "Save as",
193-
["save_to_notes"] = "Save to notes",
193+
["save_to_notes"] = "Save as new note",
194194
["edit_menu"] = "Edit",
195195
["undo"] = "Undo",
196196
["redo"] = "Redo",
@@ -208,6 +208,11 @@ public static class Lang
208208
["paragraph_style"] = "Paragraph style",
209209
["char_count_one"] = "1 character",
210210
["char_count_many"] = "{0} characters",
211+
["word_count"] = "{0} words",
212+
["word_count_one"] = "1 word",
213+
["open_note"] = "Open a note",
214+
["focus_mode"] = "Focus mode",
215+
["exit_focus_mode"] = "Exit focus mode",
211216

212217
// ── Snippet ───────────────────────────────────────────
213218
["snippet"] = "Snippet",
@@ -562,7 +567,7 @@ public static class Lang
562567
["close_tab"] = "Fermer l'onglet",
563568
["open"] = "Ouvrir",
564569
["save_as"] = "Enregistrer sous",
565-
["save_to_notes"] = "Enregistrer dans les notes",
570+
["save_to_notes"] = "Enregistrer en tant que nouvelle note",
566571
["edit_menu"] = "Modifier",
567572
["undo"] = "Annuler",
568573
["redo"] = "Rétablir",
@@ -580,6 +585,11 @@ public static class Lang
580585
["paragraph_style"] = "Style de paragraphe",
581586
["char_count_one"] = "1 caractère",
582587
["char_count_many"] = "{0} caractères",
588+
["word_count"] = "{0} mots",
589+
["word_count_one"] = "1 mot",
590+
["open_note"] = "Ouvrir une note",
591+
["focus_mode"] = "Mode focus",
592+
["exit_focus_mode"] = "Quitter le mode focus",
583593

584594
// ── Snippet ───────────────────────────────────────────
585595
["snippet"] = "Snippet",

‎MainWindow.xaml.cs‎

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ private enum ViewMode { Notes, Favorites, Tags, TagFilter, Folders, FolderFilter
5959
private RichEditBox? _expandedEditor;
6060
private bool _suppressInlineTextChanged;
6161
private bool _inlineEditorDirty;
62+
private bool _inlineEditorLoaded;
6263

6364
private bool _isDragging;
6465
private POINT _dragStartCursor;
@@ -990,8 +991,7 @@ private void OpenNoteUnlocked(string noteId)
990991
_openNoteWindows.Add(window);
991992
window.NoteChanged += () =>
992993
{
993-
RefreshCurrentView();
994-
// Keep snippet content in sync
994+
// Keep snippet content in sync (no main window refresh — deferred to close)
995995
var n = _notes.Notes.FirstOrDefault(n => n.Id == window.NoteId);
996996
if (n != null) _snippetManager.UpdateContent(n.Id, n.Content);
997997
};
@@ -1140,7 +1140,6 @@ private void OnAttachedNoteShow(NoteEntry note, IntPtr targetHwnd)
11401140

11411141
window.NoteChanged += () => DispatcherQueue.TryEnqueue(() =>
11421142
{
1143-
RefreshCurrentView();
11441143
_attachmentService?.Refresh();
11451144
});
11461145
window.Closed += (_, _) =>
@@ -1249,11 +1248,12 @@ private void ResetExpandedState()
12491248
_expandedCardIndex = -1;
12501249
_expandedEditor = null;
12511250
_inlineEditorDirty = false;
1251+
_inlineEditorLoaded = false;
12521252
}
12531253

12541254
private void SaveInlineEditor()
12551255
{
1256-
if (_expandedEditor == null || _expandedNoteId == null) return;
1256+
if (_expandedEditor == null || _expandedNoteId == null || !_inlineEditorLoaded) return;
12571257

12581258
_expandedEditor.Document.GetText(Microsoft.UI.Text.TextGetOptions.FormatRtf, out var rtf);
12591259

@@ -1578,11 +1578,27 @@ private UIElement CreateExpandedCard(NoteEntry note)
15781578
{
15791579
editor.Document.SetText(Microsoft.UI.Text.TextSetOptions.None, content);
15801580
}
1581+
NoteLinkHelper.RepairHiddenLinks(editor, _notes);
15811582

15821583
_suppressInlineTextChanged = false;
1584+
_inlineEditorLoaded = true;
15831585
editor.Focus(FocusState.Programmatic);
15841586
};
15851587

1588+
editor.AddHandler(UIElement.PointerPressedEvent,
1589+
new Microsoft.UI.Xaml.Input.PointerEventHandler((s, args) =>
1590+
{
1591+
var props = args.GetCurrentPoint(editor).Properties;
1592+
if (!props.IsLeftButtonPressed) return;
1593+
var kbState = Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Control);
1594+
if (!kbState.HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down)) return;
1595+
DispatcherQueue.TryEnqueue(() =>
1596+
{
1597+
var noteId = NoteLinkHelper.DetectLinkAtCursor(editor, _notes);
1598+
if (noteId != null) OpenNote(noteId);
1599+
});
1600+
}), true);
1601+
15861602
editor.TextChanged += (_, _) =>
15871603
{
15881604
if (_suppressInlineTextChanged) return;
@@ -1822,6 +1838,12 @@ private void OpenNotepad()
18221838
_notepadWindow.SetSnippetManager(_snippetManager);
18231839
_notepadWindow.RefreshAiUi();
18241840
_notepadWindow.NoteCreated += () => DispatcherQueue.TryEnqueue(() => RefreshCurrentView());
1841+
_notepadWindow.NoteContentChanged += () => DispatcherQueue.TryEnqueue(() =>
1842+
{
1843+
// Sync open NoteWindows with notepad changes (don't steal focus, no main window refresh)
1844+
foreach (var w in _openNoteWindows)
1845+
w.ReloadFromDisk(focus: false);
1846+
});
18251847
_notepadWindow.Closed += (_, _) =>
18261848
{
18271849
if (_notepadWindow != null)
@@ -1831,6 +1853,7 @@ private void OpenNotepad()
18311853
AppSettings.SaveNotepadPosition(p.X, p.Y, s.Width, s.Height);
18321854
}
18331855
_notepadWindow = null;
1856+
RefreshCurrentView();
18341857
};
18351858

18361859
var savedPos = AppSettings.LoadNotepadPosition();

‎NoteLinkHelper.cs‎

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
using Microsoft.UI.Text;
2+
using Microsoft.UI.Xaml.Controls;
3+
4+
namespace NoteUI;
5+
6+
/// <summary>
7+
/// Repairs note link formatting that is lost during RTF round-trips
8+
/// and provides shared link detection logic.
9+
/// </summary>
10+
internal static class NoteLinkHelper
11+
{
12+
internal static readonly Windows.UI.Color LinkColor = Windows.UI.Color.FromArgb(255, 140, 200, 120);
13+
14+
/// <summary>
15+
/// Scans the editor for note link IDs (GUID between zero-width spaces)
16+
/// and re-applies Hidden formatting, link color, and underline.
17+
/// Uses NotesManager to look up the title by GUID for robust title detection.
18+
/// </summary>
19+
internal static void RepairHiddenLinks(RichEditBox editor, NotesManager? notesManager = null)
20+
{
21+
editor.Document.GetText(TextGetOptions.None, out var text);
22+
var len = text.TrimEnd('\r', '\n').Length;
23+
if (len == 0) return;
24+
25+
int pos = 0;
26+
while (pos < len)
27+
{
28+
int start = text.IndexOf('\u200B', pos);
29+
if (start < 0) break;
30+
31+
int end = text.IndexOf('\u200B', start + 1);
32+
if (end < 0) break;
33+
34+
var between = text[(start + 1)..end];
35+
36+
if (between.Length == 36 && Guid.TryParse(between, out _))
37+
{
38+
// Restore Hidden + link color on the GUID range
39+
var idRange = editor.Document.GetRange(start, end + 1);
40+
idRange.CharacterFormat.Hidden = FormatEffect.On;
41+
idRange.CharacterFormat.ForegroundColor = LinkColor;
42+
43+
// Find the link title before the GUID
44+
int titleEnd = start;
45+
int titleStart = titleEnd;
46+
47+
// Strategy 1: look up note title by GUID
48+
string? expectedTitle = null;
49+
if (notesManager != null)
50+
{
51+
var note = notesManager.Notes.FirstOrDefault(n => n.Id == between);
52+
if (note != null) expectedTitle = note.Title;
53+
}
54+
55+
if (expectedTitle != null && titleEnd >= expectedTitle.Length)
56+
{
57+
// Check if the text before the GUID matches the expected title
58+
var candidate = text.Substring(titleEnd - expectedTitle.Length, expectedTitle.Length);
59+
if (candidate == expectedTitle)
60+
titleStart = titleEnd - expectedTitle.Length;
61+
}
62+
63+
// Strategy 2: fallback — walk backwards while underlined
64+
if (titleStart == titleEnd)
65+
{
66+
while (titleStart > 0)
67+
{
68+
var ch = editor.Document.GetRange(titleStart - 1, titleStart);
69+
ch.GetText(TextGetOptions.None, out var c);
70+
if (c == "\r" || c == "\n" || c == "\u200B") break;
71+
if (ch.CharacterFormat.Underline == UnderlineType.None) break;
72+
titleStart--;
73+
}
74+
}
75+
76+
if (titleStart < titleEnd)
77+
{
78+
var titleRange = editor.Document.GetRange(titleStart, titleEnd);
79+
titleRange.CharacterFormat.ForegroundColor = LinkColor;
80+
titleRange.CharacterFormat.Underline = UnderlineType.Single;
81+
}
82+
}
83+
84+
pos = end + 1;
85+
}
86+
}
87+
88+
/// <summary>
89+
/// Detects if the cursor is on a note link and returns the target note ID.
90+
/// </summary>
91+
internal static string? DetectLinkAtCursor(RichEditBox editor, NotesManager notesManager)
92+
{
93+
var sel = editor.Document.Selection;
94+
if (sel == null) return null;
95+
96+
var pos = sel.StartPosition;
97+
98+
var fg = editor.Document.GetRange(pos, pos + 1).CharacterFormat.ForegroundColor;
99+
if (!IsLinkColor(fg) && pos > 0)
100+
fg = editor.Document.GetRange(pos - 1, pos).CharacterFormat.ForegroundColor;
101+
if (!IsLinkColor(fg)) return null;
102+
103+
int end = pos;
104+
editor.Document.GetText(TextGetOptions.None, out var plainText);
105+
int textLen = plainText.TrimEnd('\r', '\n').Length;
106+
while (end < textLen)
107+
{
108+
var next = editor.Document.GetRange(end, end + 1);
109+
if (!IsLinkColor(next.CharacterFormat.ForegroundColor)) break;
110+
end++;
111+
}
112+
113+
int start = pos > 0 ? pos - 1 : 0;
114+
while (start > 0)
115+
{
116+
var prev = editor.Document.GetRange(start - 1, start);
117+
prev.GetText(TextGetOptions.None, out var ch);
118+
if (ch == "\r" || ch == "\n" || !IsLinkColor(prev.CharacterFormat.ForegroundColor)) break;
119+
start--;
120+
}
121+
122+
var linkRange = editor.Document.GetRange(start, end);
123+
linkRange.GetText(TextGetOptions.IncludeNumbering, out var visibleText);
124+
linkRange.GetText(TextGetOptions.None, out var fullText);
125+
visibleText = visibleText?.TrimEnd('\r', '\n') ?? "";
126+
fullText = fullText?.TrimEnd('\r', '\n') ?? "";
127+
128+
string? noteId = null;
129+
if (fullText.Length > visibleText.Length && fullText.StartsWith(visibleText, StringComparison.Ordinal))
130+
noteId = fullText[visibleText.Length..].Trim('\u200B');
131+
132+
if (string.IsNullOrEmpty(noteId))
133+
{
134+
var target = notesManager.GetByTitle(visibleText);
135+
if (target != null) noteId = target.Id;
136+
}
137+
138+
return noteId;
139+
}
140+
141+
internal static bool IsLinkColor(Windows.UI.Color c)
142+
=> c.R == LinkColor.R && c.G == LinkColor.G && c.B == LinkColor.B;
143+
}

0 commit comments

Comments
 (0)