Skip to content

Commit

Permalink
feat: more nice things
Browse files Browse the repository at this point in the history
  • Loading branch information
SonicGD committed Apr 4, 2024
1 parent 752df09 commit fe841fd
Show file tree
Hide file tree
Showing 34 changed files with 643 additions and 200 deletions.
15 changes: 15 additions & 0 deletions Sitko.Blockly.sln
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sitko.Blockly.Data", "apps\
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sitko.EditorJS", "src\Sitko.EditorJS\Sitko.EditorJS.csproj", "{8103ED8B-B508-438F-8180-B2EF88ED20F2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sitko.EditorJS.Tests", "tests\Sitko.EditorJS.Tests\Sitko.EditorJS.Tests.csproj", "{0C314690-0274-4840-A9CE-98C931BC7AB6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -217,6 +219,18 @@ Global
{8103ED8B-B508-438F-8180-B2EF88ED20F2}.Release|x64.Build.0 = Release|Any CPU
{8103ED8B-B508-438F-8180-B2EF88ED20F2}.Release|x86.ActiveCfg = Release|Any CPU
{8103ED8B-B508-438F-8180-B2EF88ED20F2}.Release|x86.Build.0 = Release|Any CPU
{0C314690-0274-4840-A9CE-98C931BC7AB6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0C314690-0274-4840-A9CE-98C931BC7AB6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0C314690-0274-4840-A9CE-98C931BC7AB6}.Debug|x64.ActiveCfg = Debug|Any CPU
{0C314690-0274-4840-A9CE-98C931BC7AB6}.Debug|x64.Build.0 = Debug|Any CPU
{0C314690-0274-4840-A9CE-98C931BC7AB6}.Debug|x86.ActiveCfg = Debug|Any CPU
{0C314690-0274-4840-A9CE-98C931BC7AB6}.Debug|x86.Build.0 = Debug|Any CPU
{0C314690-0274-4840-A9CE-98C931BC7AB6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0C314690-0274-4840-A9CE-98C931BC7AB6}.Release|Any CPU.Build.0 = Release|Any CPU
{0C314690-0274-4840-A9CE-98C931BC7AB6}.Release|x64.ActiveCfg = Release|Any CPU
{0C314690-0274-4840-A9CE-98C931BC7AB6}.Release|x64.Build.0 = Release|Any CPU
{0C314690-0274-4840-A9CE-98C931BC7AB6}.Release|x86.ActiveCfg = Release|Any CPU
{0C314690-0274-4840-A9CE-98C931BC7AB6}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{0367FAD9-C3FD-4E07-9CBE-103A17666A1D} = {DA312F56-FDB4-4853-A7A1-A8B2527399FC}
Expand All @@ -232,5 +246,6 @@ Global
{4C374AF7-0748-4915-988D-0338996B6AF9} = {32D6C989-DF2C-4C06-BD48-C322E72888B9}
{88B081A7-F838-4E90-BB2E-581BAFAECEA6} = {32D6C989-DF2C-4C06-BD48-C322E72888B9}
{8103ED8B-B508-438F-8180-B2EF88ED20F2} = {DA312F56-FDB4-4853-A7A1-A8B2527399FC}
{0C314690-0274-4840-A9CE-98C931BC7AB6} = {BE2C5E4A-BFBD-4645-AE0A-8B57A9EC1CB2}
EndGlobalSection
EndGlobal
23 changes: 23 additions & 0 deletions src/Sitko.EditorJS/Blocks/BlocksAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
namespace Sitko.EditorJS.Blocks;

internal class BlocksAccessor(IEnumerable<IContentBlockAccessor> blockAccessors) : IBlocksAccessor
{
private readonly IContentBlockAccessor[] blockAccessors = blockAccessors.ToArray();

public EditorJSConfig GetConfig(string holder)
{
var config = new EditorJSConfig { Holder = holder };
foreach (var blockOptionsAccessor in blockAccessors)
{
config.Tools[blockOptionsAccessor.Key] = blockOptionsAccessor.Options.GetConfig();
}

return config;
}

public IReadOnlyDictionary<string, string> GetScripts() =>
blockAccessors.ToDictionary(accessor => accessor.Key, accessor => accessor.Options.ScriptUrl);

public IReadOnlyDictionary<string, Type> GetBlockTypes() =>
blockAccessors.ToDictionary(block => block.Key, block => block.Type);
}
16 changes: 16 additions & 0 deletions src/Sitko.EditorJS/Blocks/ContentBlock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Text.Json.Serialization;
using Sitko.EditorJS.Json;

namespace Sitko.EditorJS.Blocks;

[JsonConverter(typeof(ContentBlockConverter))]
public record ContentBlock : IContentBlock
{
[JsonPropertyName("id")] public required string Id { get; init; } = "";
}

public record ContentBlock<TData>
: ContentBlock where TData : ContentBlockData, new()
{
[JsonPropertyName("data")] public required TData Data { get; init; }
}
12 changes: 12 additions & 0 deletions src/Sitko.EditorJS/Blocks/ContentBlockAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Microsoft.Extensions.Options;

namespace Sitko.EditorJS.Blocks;

public class ContentBlockAccessor<TBlock, TBlockOptions>(IOptions<TBlockOptions> options) : IContentBlockAccessor
where TBlock : ContentBlock
where TBlockOptions : class, IContentBlockOptions<TBlock>
{
public Type Type => typeof(TBlock);
public IContentBlockOptions Options { get; } = options.Value;
public string Key { get; } = ContentBlocksRegistry.GetKey<TBlock>().Key;
}
7 changes: 7 additions & 0 deletions src/Sitko.EditorJS/Blocks/ContentBlockAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Sitko.EditorJS.Blocks;

[AttributeUsage(AttributeTargets.Class)]
public class ContentBlockAttribute(string key) : Attribute
{
public string Key { get; } = key;
}
3 changes: 3 additions & 0 deletions src/Sitko.EditorJS/Blocks/ContentBlockConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Sitko.EditorJS.Blocks;

public record ContentBlockConfig;
3 changes: 3 additions & 0 deletions src/Sitko.EditorJS/Blocks/ContentBlockData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Sitko.EditorJS.Blocks;

public abstract record ContentBlockData;
15 changes: 15 additions & 0 deletions src/Sitko.EditorJS/Blocks/ContentBlockOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Sitko.EditorJS.Blocks;

public abstract record ContentBlockOptions<TBlock, TConfig> : IContentBlockOptions<TBlock, TConfig>
where TBlock : ContentBlock
where TConfig : ContentBlockConfig, new()
{
public abstract string ScriptUrl { get; set; }
public abstract string ClassName { get; set; }
public EditorJSToolConfig GetConfig() => new() { ClassName = ClassName, Config = Config };

public TConfig Config { get; } = new();
}

public abstract record ContentBlockOptions<TBlock> : ContentBlockOptions<TBlock, ContentBlockConfig>
where TBlock : ContentBlock;
3 changes: 3 additions & 0 deletions src/Sitko.EditorJS/Blocks/ContentBlockRegistration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namespace Sitko.EditorJS.Blocks;

internal record ContentBlockRegistration(string Key, Type BlockType, Type OptionsType);
48 changes: 48 additions & 0 deletions src/Sitko.EditorJS/Blocks/ContentBlocksRegistry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
namespace Sitko.EditorJS.Blocks;

public static class ContentBlocksRegistry
{
private static readonly Dictionary<string, ContentBlockRegistration> BlocksByKey = new();
private static readonly Dictionary<Type, ContentBlockRegistration> BlocksByType = new();


public static IReadOnlyDictionary<string, Type> GetBlockTypes() =>
BlocksByType.ToDictionary(block => block.Value.Key, block => block.Key);

public static void Register<TBlock, TBlockOptions>() where TBlock : ContentBlock
where TBlockOptions : IContentBlockOptions<TBlock>
{
var attribute = typeof(TBlock).GetCustomAttributes(typeof(ContentBlockAttribute), false)
.OfType<ContentBlockAttribute>().FirstOrDefault() ??
throw new InvalidOperationException(
$"Class {typeof(TBlock)} should have {typeof(ContentBlockAttribute)} attribute");
if (BlocksByType.ContainsKey(typeof(TBlock)))
{
throw new InvalidOperationException($"Block {typeof(TBlock)} already registered");
}

if (BlocksByKey.TryGetValue(attribute.Key, out var blockRegistration))
{
throw new InvalidOperationException(
$"Block with key {attribute.Key} already registered: {blockRegistration.BlockType}");
}

var registration = new ContentBlockRegistration(attribute.Key, typeof(TBlock), typeof(TBlockOptions));
BlocksByType[typeof(TBlock)] = registration;
BlocksByKey[attribute.Key] = registration;
//PolymorphismOptions.DerivedTypes.Add(new JsonDerivedType(typeof(TBlock), attribute.Key));
}

internal static ContentBlockRegistration GetKey<TBlock>() where TBlock : ContentBlock => GetKey(typeof(TBlock));
internal static ContentBlockRegistration GetKey(Type blockType) => BlocksByType[blockType];

internal static ContentBlockRegistration GetBlockMetadata(string key) => BlocksByKey[key];

internal static void Clear()
{
BlocksByType.Clear();
BlocksByKey.Clear();
}
}

// TODO: In .NET 9 we can use this with https://github.com/dotnet/runtime/issues/72604
8 changes: 8 additions & 0 deletions src/Sitko.EditorJS/Blocks/IBlocksAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Sitko.EditorJS.Blocks;

public interface IBlocksAccessor
{
EditorJSConfig GetConfig(string holder);
IReadOnlyDictionary<string, string> GetScripts();
IReadOnlyDictionary<string, Type> GetBlockTypes();
}
6 changes: 6 additions & 0 deletions src/Sitko.EditorJS/Blocks/IContentBlock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Sitko.EditorJS.Blocks;

internal interface IContentBlock
{
string Id { get; }
}
8 changes: 8 additions & 0 deletions src/Sitko.EditorJS/Blocks/IContentBlockAccessor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Sitko.EditorJS.Blocks;

internal interface IContentBlockAccessor
{
public Type Type { get; }
public IContentBlockOptions Options { get; }
public string Key { get; }
}
15 changes: 15 additions & 0 deletions src/Sitko.EditorJS/Blocks/IContentBlockOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace Sitko.EditorJS.Blocks;

public interface IContentBlockOptions
{
string ScriptUrl { get; }
EditorJSToolConfig GetConfig();
}

public interface IContentBlockOptions<TBlock> : IContentBlockOptions where TBlock : ContentBlock;

public interface IContentBlockOptions<TBlock, out TConfig> : IContentBlockOptions<TBlock>
where TConfig : ContentBlockConfig, new() where TBlock : ContentBlock
{
TConfig Config { get; }
}
4 changes: 4 additions & 0 deletions src/Sitko.EditorJS/Blocks/Paragraph/ParagraphBlock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace Sitko.EditorJS.Blocks.Paragraph;

[ContentBlock("paragraph")]
public record ParagraphBlock : ContentBlock<ParagraphBlockData>;
10 changes: 10 additions & 0 deletions src/Sitko.EditorJS/Blocks/Paragraph/ParagraphBlockConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System.Text.Json.Serialization;

namespace Sitko.EditorJS.Blocks.Paragraph;

public record ParagraphBlockConfig : ContentBlockConfig
{
[JsonPropertyName("placeholder")] public string Placeholder { get; set; } = "";

[JsonPropertyName("preserveBlank")] public bool PreserveBlank { get; set; } = false;
}
8 changes: 8 additions & 0 deletions src/Sitko.EditorJS/Blocks/Paragraph/ParagraphBlockData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
using System.Text.Json.Serialization;

namespace Sitko.EditorJS.Blocks.Paragraph;

public record ParagraphBlockData : ContentBlockData
{
[JsonPropertyName("text")] public string Text { get; set; } = "";
}
7 changes: 7 additions & 0 deletions src/Sitko.EditorJS/Blocks/Paragraph/ParagraphBlockOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Sitko.EditorJS.Blocks.Paragraph;

public record ParagraphBlockOptions : ContentBlockOptions<ParagraphBlock, ParagraphBlockConfig>
{
public override string ScriptUrl { get; set; } = "https://cdn.jsdelivr.net/npm/@editorjs/paragraph@latest";
public override string ClassName { get; set; } = "Paragraph";
}
4 changes: 4 additions & 0 deletions src/Sitko.EditorJS/Blocks/SimpleImage/SimpleImageBlock.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace Sitko.EditorJS.Blocks.SimpleImage;

[ContentBlock("image")]
public record SimpleImageBlock : ContentBlock<SimpleImageBlockData>;
16 changes: 16 additions & 0 deletions src/Sitko.EditorJS/Blocks/SimpleImage/SimpleImageBlockData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using System.Text.Json.Serialization;

namespace Sitko.EditorJS.Blocks.SimpleImage;

public record SimpleImageBlockData : ContentBlockData
{
[JsonPropertyName("url")] public string Url { get; set; } = "";

[JsonPropertyName("caption")] public string Caption { get; set; } = "";

[JsonPropertyName("withBorder")] public bool? WithBorder { get; set; }

[JsonPropertyName("withBackground")] public bool? WithBackground { get; set; }

[JsonPropertyName("stretched")] public bool? Stretched { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Sitko.EditorJS.Blocks.SimpleImage;

public record SimpleImageBlockOptions : ContentBlockOptions<SimpleImageBlock>
{
public override string ScriptUrl { get; set; } = "https://cdn.jsdelivr.net/npm/@editorjs/simple-image@latest";
public override string ClassName { get; set; } = "SimpleImage";
}
35 changes: 35 additions & 0 deletions src/Sitko.EditorJS/Configuration/EditorJSBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Scrutor;
using Sitko.Blazor.ScriptInjector;
using Sitko.EditorJS.Blocks;

namespace Sitko.EditorJS.Configuration;

internal class EditorJSBuilder : IEditorJSBuilder
{
private readonly IServiceCollection serviceCollection;

public EditorJSBuilder(IServiceCollection serviceCollection)
{
this.serviceCollection = serviceCollection;
serviceCollection.AddOptions<EditorJSOptions>();
serviceCollection.AddScriptInjector();
serviceCollection.AddSingleton<IBlocksAccessor, BlocksAccessor>();
}

public IEditorJSBuilder AddBlock<TBlock, TBlockOptions>(Action<IConfiguration, TBlockOptions> configure)
where TBlock : ContentBlock where TBlockOptions : class, IContentBlockOptions<TBlock>
{
serviceCollection.Scan(selector =>
selector.FromType<TBlock>().AsSelfWithInterfaces().WithScopedLifetime());
serviceCollection.AddOptions<TBlockOptions>().PostConfigure<IConfiguration>((options,
configuration) =>
{
configure(configuration, options);
});
serviceCollection.AddSingleton<IContentBlockAccessor, ContentBlockAccessor<TBlock, TBlockOptions>>();
ContentBlocksRegistry.Register<TBlock, TBlockOptions>();
return this;
}
}
6 changes: 6 additions & 0 deletions src/Sitko.EditorJS/Configuration/EditorJSOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Sitko.EditorJS.Configuration;

public record EditorJSOptions
{
public string EditorJSScriptUrl { get; set; } = "https://cdn.jsdelivr.net/npm/@editorjs/editorjs@latest";
}
10 changes: 10 additions & 0 deletions src/Sitko.EditorJS/Configuration/IEditorJSBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.Extensions.Configuration;
using Sitko.EditorJS.Blocks;

namespace Sitko.EditorJS.Configuration;

public interface IEditorJSBuilder
{
IEditorJSBuilder AddBlock<TBlock, TBlockOptions>(Action<IConfiguration, TBlockOptions> configure)
where TBlock : ContentBlock where TBlockOptions : class, IContentBlockOptions<TBlock>;
}
12 changes: 12 additions & 0 deletions src/Sitko.EditorJS/Data/EditorJSData.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Text.Json.Serialization;
using Sitko.EditorJS.Blocks;
using Sitko.EditorJS.Helpers;

namespace Sitko.EditorJS.Data;

public record EditorJSData
{
[JsonPropertyName("time")] public long Time { get; set; }
[JsonPropertyName("version")] public string Version { get; set; } = "unknown";
[JsonPropertyName("blocks")] public ValueCollection<ContentBlock> Blocks { get; set; } = new();
}
10 changes: 7 additions & 3 deletions src/Sitko.EditorJS/EditorJS.razor
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
<div style="margin-top: 10px; margin-bottom: 10px;" id="@Id"></div>
@using System.Text.Json
<div style="margin-top: 10px; margin-bottom: 10px;" id="@Id"></div>

<pre>
@Data
@if (Data is not null)
{
<pre>
@JsonSerializer.Serialize(Data, PrettyPrintJsonOptions)
</pre>
}

@code {

Expand Down
Loading

0 comments on commit fe841fd

Please sign in to comment.