Monorepo with centralized build props, npm workspaces, LlamaSharp AI, SQLite/SQLCipher storage, Svelte frontend, and unified smoke tests. Co-Authored-By: Oz <oz-agent@warp.dev>
99 lines
3.1 KiB
C#
99 lines
3.1 KiB
C#
namespace Journal.Core.Models;
|
|
|
|
public class JournalEntry
|
|
{
|
|
public string Date { get; set; }
|
|
public List<Fragment> Fragments { get; set; }
|
|
public string RawContent { get; set; }
|
|
public Dictionary<string, ParsedSection> Sections { get; set; }
|
|
|
|
public JournalEntry(
|
|
string date,
|
|
IEnumerable<Fragment>? fragments = null,
|
|
string rawContent = "",
|
|
IDictionary<string, ParsedSection>? sections = null)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(date))
|
|
throw new ArgumentException("Date is required", nameof(date));
|
|
|
|
Date = date.Trim();
|
|
Fragments = fragments is null ? [] : [.. fragments];
|
|
RawContent = rawContent ?? "";
|
|
Sections = sections is null ? [] : new Dictionary<string, ParsedSection>(sections);
|
|
}
|
|
|
|
public string GetSection(string sectionTitle)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(sectionTitle))
|
|
return "";
|
|
if (!Sections.TryGetValue(sectionTitle, out var section))
|
|
return "";
|
|
return string.Join("\n", section.Content);
|
|
}
|
|
|
|
public bool? GetCheckboxState(string sectionTitle, string checkboxText)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(sectionTitle) || string.IsNullOrWhiteSpace(checkboxText))
|
|
return null;
|
|
if (!Sections.TryGetValue(sectionTitle, out var section))
|
|
return null;
|
|
return section.Checkboxes.TryGetValue(checkboxText, out var checkedState) ? checkedState : null;
|
|
}
|
|
|
|
public void MergeWith(JournalEntry otherEntry)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(otherEntry);
|
|
|
|
foreach (var (title, newSection) in otherEntry.Sections)
|
|
{
|
|
if (newSection.Content.Any(line => !string.IsNullOrWhiteSpace(line)))
|
|
Sections[title] = newSection;
|
|
}
|
|
|
|
var existingFragmentDescriptions = Fragments
|
|
.Select(fragment => fragment.Description)
|
|
.ToHashSet(StringComparer.Ordinal);
|
|
|
|
foreach (var newFragment in otherEntry.Fragments)
|
|
{
|
|
if (!existingFragmentDescriptions.Contains(newFragment.Description))
|
|
Fragments.Add(newFragment);
|
|
}
|
|
}
|
|
|
|
public string ToMarkdown()
|
|
{
|
|
var lines = new List<string>
|
|
{
|
|
"---",
|
|
"type: journal",
|
|
"---",
|
|
$"**Date:** {Date}\n"
|
|
};
|
|
|
|
foreach (var title in SectionTitles.Canonical)
|
|
{
|
|
if (!Sections.TryGetValue(title, out var section))
|
|
continue;
|
|
|
|
lines.Add($"## {section.Title}\n");
|
|
lines.AddRange(section.Content);
|
|
lines.Add("");
|
|
}
|
|
|
|
if (Fragments.Count > 0)
|
|
{
|
|
lines.Add("# Fragments\n");
|
|
foreach (var fragment in Fragments)
|
|
{
|
|
var timeStr = fragment.Time != default ? $"@{fragment.Time:O}" : "";
|
|
var tagsStr = string.Join(" ", fragment.Tags.Select(tag => $"#{tag}"));
|
|
var header = $"{fragment.Type} {timeStr} {tagsStr}".Trim();
|
|
lines.Add($"{header}\n{fragment.Description}\n");
|
|
}
|
|
}
|
|
|
|
return string.Join("\n", lines);
|
|
}
|
|
}
|