Skip to content

Commit 1cb6508

Browse files
committed
fix multi levels bullets, bold, titles 2 and 3.
1 parent 99ebe20 commit 1cb6508

File tree

8 files changed

+1222
-38
lines changed

8 files changed

+1222
-38
lines changed

NotionSharp.ApiClient.Tests/JsonData/AboutThis.full.children.json

Lines changed: 1129 additions & 1 deletion
Large diffs are not rendered by default.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"id":"c7b44455-3d31-4a5b-b82c-b7e3d85ba585","created_time":"2020-04-30T13:37:00+00:00","last_edited_time":"2023-11-26T17:53:00+00:00","archived":false,"created_by":{"id":"ab9257e1-d027-4494-8792-71d90b63dd35","object":"user"},"last_edited_by":{"id":"ab9257e1-d027-4494-8792-71d90b63dd35","object":"user"},"url":"https://www.notion.so/About-this-c7b444553d314a5bb82cb7e3d85ba585","public_url":"https://wise-spirit-737.notion.site/About-this-c7b444553d314a5bb82cb7e3d85ba585","parent":{"page_id":"18dfbe55-5d7c-416e-9485-7855d4a3949e","type":"page_id"},"properties":{"title":{"title":[{"type":"text","plain_text":"About this","annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"text":{"content":"About this"}}],"id":"title","type":"title"}},"object":"page"}
1+
{"id":"c7b44455-3d31-4a5b-b82c-b7e3d85ba585","created_time":"2020-04-30T13:37:00+00:00","last_edited_time":"2024-01-12T12:34:00+00:00","archived":false,"created_by":{"id":"ab9257e1-d027-4494-8792-71d90b63dd35","object":"user"},"last_edited_by":{"id":"ab9257e1-d027-4494-8792-71d90b63dd35","object":"user"},"url":"https://www.notion.so/About-this-c7b444553d314a5bb82cb7e3d85ba585","public_url":"https://wise-spirit-737.notion.site/About-this-c7b444553d314a5bb82cb7e3d85ba585","parent":{"page_id":"18dfbe55-5d7c-416e-9485-7855d4a3949e","type":"page_id"},"properties":{"title":{"title":[{"type":"text","plain_text":"About this","annotations":{"bold":false,"italic":false,"strikethrough":false,"underline":false,"code":false,"color":"default"},"text":{"content":"About this"}}],"id":"title","type":"title"}},"object":"page"}

NotionSharp.ApiClient.Tests/TestNotionBase.cs

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -253,34 +253,27 @@ public async Task TestPageAndChildrenDeserialization()
253253
}
254254

255255
[TestMethod]
256-
[Ignore("Run this manually to create the json files")]
256+
//[Ignore("Run this manually to create the json files")]
257257
public async Task TestPageAndChildrenSerialization()
258258
{
259259
var session = new NotionSession(TestUtils.CreateOfficialNotionSessionInfo());
260260
var page = await session.Search(query: "About this", filterOptions: FilterOptions.ObjectPage).FirstAsync();
261261
Assert.AreEqual("About this", page?.Title()?.Title.FirstOrDefault()?.PlainText);
262262

263263
var blocks = await session.GetBlockChildren(page.Id).ToListAsync();
264-
var blockWithChildren = new Queue<Block>(blocks.Where(b => b.HasChildren && BlockTypes.BlocksWithChildren.Contains(b.Type)));
265-
while (blockWithChildren.Count != 0)
266-
{
267-
var block = blockWithChildren.Dequeue();
268-
await session.GetChildren(block);
269-
//recursive
270-
var children = block.Children.Where(b => b.HasChildren && BlockTypes.BlocksWithChildren.Contains(b.Type));
271-
foreach (var child in children)
272-
blockWithChildren.Enqueue(child);
273-
}
264+
var errBlocks = blocks.Where(b => b.HasChildren && !BlockTypes.BlocksWithChildren.Contains(b.Type)).ToList();
265+
Assert.AreEqual(0, errBlocks.Count);
266+
await session.LoadChildBlocks(blocks);
274267

275-
var pageJson = JsonSerializer.Serialize(page, HttpNotionSession.NotionJsonSerializationOptions);
276-
var blocksJson = JsonSerializer.Serialize(blocks, HttpNotionSession.NotionJsonSerializationOptions);
268+
var pageJson = JsonSerializer.Serialize(page, HttpNotionSession.NotionJsonFullSerializationOptions);
269+
var blocksJson = JsonSerializer.Serialize(blocks, HttpNotionSession.NotionJsonFullSerializationOptions);
277270
var path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
278271
await File.WriteAllTextAsync(Path.Combine(path, "AboutThis.full.json"), pageJson);
279272
await File.WriteAllTextAsync(Path.Combine(path, "AboutThis.full.children.json"), blocksJson);
280273

281-
var page2 = JsonSerializer.Deserialize<Page>(pageJson, HttpNotionSession.NotionJsonSerializationOptions);
274+
var page2 = JsonSerializer.Deserialize<Page>(pageJson, HttpNotionSession.NotionJsonFullSerializationOptions);
282275
Assert.IsNotNull(page2);
283-
var blocks2 = JsonSerializer.Deserialize<List<Block>>(blocksJson, HttpNotionSession.NotionJsonSerializationOptions);
276+
var blocks2 = JsonSerializer.Deserialize<List<Block>>(blocksJson, HttpNotionSession.NotionJsonFullSerializationOptions);
284277
Assert.IsNotNull(blocks2);
285278
Assert.AreEqual(blocks.Count,blocks2.Count);
286279
}

NotionSharp.ApiClient.Tests/TestNotionHtml.cs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ public async Task TestGetPage()
5454
public async Task TestGetHtml_Link()
5555
{
5656
var blocksJson = await File.ReadAllTextAsync(Path.Combine(Environment.CurrentDirectory, "JsonData", "AboutThis.full.children.json"));
57-
var blocks = JsonSerializer.Deserialize<List<Block>>(blocksJson, HttpNotionSession.NotionJsonSerializationOptions);
57+
var blocks = JsonSerializer.Deserialize<List<Block>>(blocksJson, HttpNotionSession.NotionJsonFullSerializationOptions);
5858

5959
var expectedFilePath = "AboutThis.blocks.link.html";
6060
var blockIndex = 0;
@@ -69,7 +69,7 @@ public async Task TestGetHtml_Link()
6969
public async Task TestGetHtml_Title1()
7070
{
7171
var blocksJson = await File.ReadAllTextAsync(Path.Combine(Environment.CurrentDirectory, "JsonData", "AboutThis.full.children.json"));
72-
var blocks = JsonSerializer.Deserialize<List<Block>>(blocksJson, HttpNotionSession.NotionJsonSerializationOptions);
72+
var blocks = JsonSerializer.Deserialize<List<Block>>(blocksJson, HttpNotionSession.NotionJsonFullSerializationOptions);
7373

7474
var blockIndex = 2;
7575

@@ -83,7 +83,7 @@ public async Task TestGetHtml_Title1()
8383
public async Task TestGetHtml_Title2()
8484
{
8585
var blocksJson = await File.ReadAllTextAsync(Path.Combine(Environment.CurrentDirectory, "JsonData", "AboutThis.full.children.json"));
86-
var blocks = JsonSerializer.Deserialize<List<Block>>(blocksJson, HttpNotionSession.NotionJsonSerializationOptions);
86+
var blocks = JsonSerializer.Deserialize<List<Block>>(blocksJson, HttpNotionSession.NotionJsonFullSerializationOptions);
8787

8888
var blockIndex = 3;
8989

@@ -92,6 +92,42 @@ public async Task TestGetHtml_Title2()
9292
var expectedHtml = """<h2><div class="notion-line">Title 2</div></h2>""" + "\r\n";
9393
Assert.AreEqual(expectedHtml, html);
9494
}
95+
96+
[TestMethod]
97+
public async Task TestGetHtml_Bold()
98+
{
99+
var blocksJson = await File.ReadAllTextAsync(Path.Combine(Environment.CurrentDirectory, "JsonData", "AboutThis.full.children.json"));
100+
var blocks = JsonSerializer.Deserialize<List<Block>>(blocksJson, HttpNotionSession.NotionJsonFullSerializationOptions);
101+
102+
var blockIndex = 4;
103+
104+
var block = blocks.Skip(blockIndex).Take(1).ToList();
105+
var html = new HtmlRenderer().GetHtml(block);
106+
var expectedHtml = """<div class="notion-paragraph"><div class="notion-line"><span class=" notion-bold">Bold text</span></div></div>""" + "\r\n";
107+
Assert.AreEqual(expectedHtml, html);
108+
}
109+
110+
111+
[TestMethod]
112+
public async Task TestGetHtml_Bullet_Levels2()
113+
{
114+
var blocksJson = await File.ReadAllTextAsync(Path.Combine(Environment.CurrentDirectory, "JsonData", "AboutThis.full.children.json"));
115+
var blocks = JsonSerializer.Deserialize<List<Block>>(blocksJson, HttpNotionSession.NotionJsonFullSerializationOptions);
116+
117+
var blockIndex = 5;
118+
119+
var block = blocks.Skip(blockIndex).Take(1).ToList();
120+
var html = new HtmlRenderer().GetHtml(block);
121+
var expectedHtml = @"<ul><li><div class=""notion-line"">Level1</div><ul><li><div class=""notion-line"">Level 2</div><ul><li><div class=""notion-line"">Level 3</div><ul><li><div class=""notion-line"">Level 4</div><ul><li><div class=""notion-line"">Level 5</div><ul><li><div class=""notion-line"">Level 6</div><ul><li><div class=""notion-line"">Level 7</div></li></ul>
122+
</li></ul>
123+
</li></ul>
124+
</li></ul>
125+
</li></ul>
126+
</li></ul>
127+
</li></ul>
128+
";
129+
Assert.AreEqual(expectedHtml, html);
130+
}
95131

96132
// [TestMethod]
97133
// public void TestGetHtml_SubBullets()

NotionSharp.ApiClient/Lib/HtmlRendering/HtmlRenderer.cs

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -208,13 +208,23 @@ protected virtual void TransformBulletedListItem(Block block, StringBuilder sb)
208208
{
209209
sb.Append("<ul><li>");
210210
Append(block.BulletedListItem, sb);
211+
if (block?.Children != null)
212+
{
213+
foreach (var child in block.Children)
214+
Transform(child, sb, false);
215+
}
211216
sb.AppendLine("</li></ul>");
212217
}
213218

214219
protected virtual void TransformNumberedListItem(Block block, StringBuilder sb)
215220
{
216221
sb.Append("<ol><li>");
217222
Append(block.NumberedListItem, sb);
223+
if (block?.Children != null)
224+
{
225+
foreach (var child in block.Children)
226+
Transform(child, sb, false);
227+
}
218228
sb.AppendLine("</li></ol>");
219229
}
220230

@@ -268,29 +278,31 @@ protected virtual StringBuilder Append(RichText? line, StringBuilder sb)
268278
return sb;
269279

270280
sb.Append("<div class=\"notion-line\">");
271-
var tag = line.HasAttribute ? (line.Href != null ? "a" : "span") : null;
281+
var hasAnnotations = line.Annotation?.HasAnnotation == true;
282+
var hasLink = !string.IsNullOrWhiteSpace(line.Href);
283+
var tag = hasLink ? "a" : hasAnnotations ? "span" : null;
272284

273285
if (tag != null)
274286
{
275287
sb.Append("<").Append(tag);
276288

277-
if (line.Href != null)
289+
if (hasLink)
278290
sb.Append(" href=\"").Append(Uri.EscapeUriString(line.Href)).Append("\"");
279291

280-
if (line.HasStyle)
292+
if (hasAnnotations)
281293
{
282294
sb.Append(" class=\"");
283-
if (line.Annotation.Bold)
295+
if (line.Annotation!.Bold)
284296
sb.Append(" notion-bold");
285297
if (line.Annotation.Italic)
286298
sb.Append(" notion-italic");
287299
if (line.Annotation.Strikethrough)
288300
sb.Append(" notion-strikethrough");
289301
if (line.Annotation.Underline)
290302
sb.Append(" notion-underline");
291-
if (line.Annotation.Color != null)
303+
if (line.Annotation.Color is not null and not NotionColor.Default)
292304
sb.Append(" notion-color-").Append(line.Annotation.Color);
293-
if (line.Annotation?.Code != null)
305+
if (line.Annotation.Code)
294306
sb.Append(" notion-code");
295307
sb.Append("\"");
296308
}

NotionSharp.ApiClient/Lib/HttpNotionSession.cs

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,19 +90,34 @@ public class HttpNotionSession
9090
public static JsonSerializerOptions NotionJsonSerializationOptions { get; } = new (JsonSerializerDefaults.General)
9191
{
9292
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
93-
//PropertyNameCaseInsensitive = true,
9493
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
9594
DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower,
96-
TypeInfoResolver = NotionJsonContext.Default.WithAddedModifier(AddModifiers) //JsonTypeInfoResolver.Combine(NotionJsonContext.Default) //.WithAddedModifier(AddNestedDerivedTypes))
95+
TypeInfoResolver = NotionJsonContext.Default.WithAddedModifier(ModifierIgnoreBlockChildren)
96+
//JsonTypeInfoResolver.Combine(NotionJsonContext.Default) //.WithAddedModifier(AddNestedDerivedTypes))
9797
};
9898

99-
private static void AddModifiers(JsonTypeInfo jsonTypeInfo)
99+
public static JsonSerializerOptions NotionJsonFullSerializationOptions { get; } = new (JsonSerializerDefaults.General)
100100
{
101-
if (jsonTypeInfo.Type == typeof(PropertyItem))
101+
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
102+
PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower,
103+
DictionaryKeyPolicy = JsonNamingPolicy.SnakeCaseLower,
104+
TypeInfoResolver = NotionJsonContext.Default
105+
};
106+
107+
private static void ModifierIgnoreBlockChildren(JsonTypeInfo jsonTypeInfo)
108+
{
109+
// if (jsonTypeInfo.Type == typeof(PropertyItem))
110+
// {
111+
// //JsonDerivedType ignores JsonConverter on target type
112+
// var c = jsonTypeInfo.Converter;
113+
// var i = 0;
114+
// }
115+
116+
if (jsonTypeInfo.Type.IsAssignableTo(typeof(Block)))
102117
{
103-
//JsonDerivedType ignores JsonConverter on target type
104-
var c = jsonTypeInfo.Converter;
105-
var i = 0;
118+
var prop = jsonTypeInfo.Properties.FirstOrDefault(p => p.Name == nameof(Block.Children));
119+
if (prop is not null)
120+
prop.ShouldSerialize = (_, _) => false;
106121
}
107122
}
108123

NotionSharp.ApiClient/Lib/PublicApi/Model/Block.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,9 @@ public Block()
107107
public bool Archived { get; init; }
108108
public bool HasChildren { get; init; }
109109

110-
[JsonIgnore]
110+
//Ignore when reading from or writing to Notion api
111+
//Don't ignore for unit testing, as we save and load json files
112+
//Note: Ignored using the Modifiers in HttpNotionSession
111113
public List<Block> Children { get; set; }
112114
#endregion
113115

NotionSharp.ApiClient/Lib/PublicApi/Model/Common/RichText.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,8 @@ public class RichText
2525
[JsonPropertyName("annotations")]
2626
public RichTextAnnotation? Annotation { get; init; }
2727

28-
[JsonIgnore]
29-
public bool HasAttribute => Annotation?.HasAnnotation == true || !string.IsNullOrWhiteSpace(Href);
30-
[JsonIgnore]
31-
public bool HasStyle => Annotation?.HasColor == true;
28+
//[JsonIgnore]
29+
//public bool HasStyle => Annotation?.HasColor == true;
3230
#endregion
3331

3432
public RichTextText? Text { get; init; } //type=text

0 commit comments

Comments
 (0)