Compare commits

..

3 Commits

Author SHA1 Message Date
c1d27dc151
build: add api and admin projects to docker-compose
Some checks failed
.NET / Build & Test (push) Failing after 41s
2024-03-02 05:31:27 +00:00
dd2438153c
feat: add @editorjs/code for editorjs codeblocks 2024-03-02 05:31:10 +00:00
430ab2b50e
feat: populate editor with block content
WIP
2024-03-02 05:30:46 +00:00
13 changed files with 289 additions and 3 deletions

View File

@ -0,0 +1,14 @@
using System.Text.Json;
using Markdig.Renderers;
using Markdig.Syntax;
namespace OliverBooth.Markdown.Renderers.EditorJs;
public class JsonRenderer : RendererBase
{
/// <inheritdoc />
public override object Render(MarkdownObject markdownObject)
{
return JsonDocument.Parse("""{"blocks": []}""");
}
}

View File

@ -0,0 +1,17 @@
using Markdig.Renderers;
using Markdig.Syntax;
namespace OliverBooth.Markdown.Renderers.EditorJs.ObjectRenderers;
public class HeadingObjectRenderer : IMarkdownObjectRenderer
{
public bool Accept(RendererBase renderer, Type objectType)
{
return renderer.GetType() == typeof(JsonRenderer) && objectType == typeof(HeadingBlock);
}
public void Write(RendererBase renderer, MarkdownObject objectToRender)
{
}
}

View File

@ -0,0 +1,53 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Markdig.Helpers;
using Markdig.Syntax;
using Markdig.Syntax.Inlines;
namespace OliverBooth.Markdown.Renderers.JsonConverters;
public sealed class HeadingBlockConverter : JsonConverter<HeadingBlock>
{
/// <inheritdoc />
public override HeadingBlock? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var node = JsonNode.Parse(reader.ValueSpan);
if (node is not JsonObject jsonObject)
{
return null;
}
return new HeadingBlock(null!)
{
Level = jsonObject["level"]?.GetValue<int>() ?? 1,
Lines = new StringLineGroup(jsonObject["text"]?.GetValue<string>() ?? string.Empty)
};
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, HeadingBlock value, JsonSerializerOptions options)
{
if (value.Inline is not { } containerInline)
{
return;
}
writer.WriteStartObject();
writer.WriteString("type", "header");
writer.WriteNumber("level", value.Level);
writer.WritePropertyName("text");
foreach (Inline inline in containerInline)
{
if (inline is LiteralInline literal)
{
var converter = (JsonConverter<LiteralInline>)options.GetConverter(typeof(LiteralInline));
writer.WriteStringValue(literal.Content.Text);
converter.Write(writer, literal, options);
}
}
writer.WriteEndObject();
}
}

View File

@ -0,0 +1,22 @@
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
using Markdig.Syntax.Inlines;
namespace OliverBooth.Markdown.Renderers.JsonConverters;
public class LiteralInlineConverter : JsonConverter<LiteralInline>
{
public override LiteralInline? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
ReadOnlySpan<byte> bytes = reader.ValueSpan;
Span<char> chars = stackalloc char[bytes.Length];
Encoding.UTF8.GetChars(bytes, chars);
return new LiteralInline(chars.ToString());
}
public override void Write(Utf8JsonWriter writer, LiteralInline value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.Content.Text);
}
}

View File

@ -0,0 +1,57 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Markdig.Syntax;
namespace OliverBooth.Markdown.Renderers.JsonConverters;
public sealed class MarkdownDocumentConverter : JsonConverter<MarkdownDocument>
{
/// <inheritdoc />
public override MarkdownDocument Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var document = new MarkdownDocument();
var blocks = JsonSerializer.Deserialize<Block[]>(ref reader, options) ?? Array.Empty<Block>();
foreach (Block block in blocks)
{
document.Add(block);
}
return document;
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, MarkdownDocument value, JsonSerializerOptions options)
{
writer.WriteStartObject();
writer.WriteStartArray("blocks");
foreach (Block block in value)
{
WriteBlock(writer, options, block);
}
writer.WriteEndArray();
writer.WriteEndObject();
}
private static void WriteBlock(Utf8JsonWriter writer, JsonSerializerOptions options, Block block)
{
switch (block)
{
case ParagraphBlock paragraphBlock:
{
var converter = (JsonConverter<ParagraphBlock>)options.GetConverter(typeof(ParagraphBlock));
converter.Write(writer, paragraphBlock, options);
break;
}
case HeadingBlock headingBlock:
{
var converter = (JsonConverter<HeadingBlock>)options.GetConverter(typeof(HeadingBlock));
converter.Write(writer, headingBlock, options);
break;
}
}
}
}

View File

@ -0,0 +1,35 @@
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Markdig.Helpers;
using Markdig.Syntax;
namespace OliverBooth.Markdown.Renderers.JsonConverters;
public sealed class ParagraphBlockConverter : JsonConverter<ParagraphBlock>
{
/// <inheritdoc />
public override ParagraphBlock? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
var node = JsonNode.Parse(reader.ValueSpan);
if (node is not JsonObject jsonObject)
{
return null;
}
return new ParagraphBlock
{
Lines = new StringLineGroup(jsonObject["text"]?.GetValue<string>() ?? string.Empty)
};
}
/// <inheritdoc />
public override void Write(Utf8JsonWriter writer, ParagraphBlock value, JsonSerializerOptions options)
{
string text = string.Join('\n', value.Inline);
writer.WriteStartObject();
writer.WriteString("type", "paragraph");
writer.WriteString("text", text);
writer.WriteEndObject();
}
}

View File

@ -1,3 +1,4 @@
@using System.Text.Json
@using OliverBooth.Common.Data.Blog @using OliverBooth.Common.Data.Blog
@using OliverBooth.Common.Services @using OliverBooth.Common.Services
@implements IDisposable @implements IDisposable
@ -7,6 +8,17 @@
@code { @code {
private DotNetObjectReference<MarkdownEditor>? _dotNetHelper; private DotNetObjectReference<MarkdownEditor>? _dotNetHelper;
[JSInvokable]
public string GetEditorObject(Guid id)
{
if (!BlogPostService.TryGetPost(id, out IBlogPost? post))
{
return JsonSerializer.Serialize(new { blocks = Array.Empty<object>() });
}
return BlogPostService.GetBlogPostEditorObject(post);
}
[JSInvokable] [JSInvokable]
public void Save(Guid id, string content) public void Save(Guid id, string content)
{ {

View File

@ -21,3 +21,43 @@ services:
- SSL_KEY_PATH=${SSL_KEY_PATH} - SSL_KEY_PATH=${SSL_KEY_PATH}
- MASTODON_TOKEN=${MASTODON_TOKEN} - MASTODON_TOKEN=${MASTODON_TOKEN}
- MASTODON_ACCOUNT=${MASTODON_ACCOUNT} - MASTODON_ACCOUNT=${MASTODON_ACCOUNT}
oliverbooth_api:
container_name: api.oliverbooth.dev
pull_policy: build
build:
context: .
dockerfile: OliverBooth.Api/Dockerfile
volumes:
- type: bind
source: /var/log/oliverbooth/api
target: /app/logs
- type: bind
source: /etc/oliverbooth/api
target: /app/data
ports:
- "2844:2844"
restart: always
environment:
- SSL_CERT_PATH=${API_SSL_CERT_PATH}
- SSL_KEY_PATH=${API_SSL_KEY_PATH}
oliverbooth_admin:
container_name: admin.oliverbooth.dev
pull_policy: build
build:
context: .
dockerfile: OliverBooth.Admin/Dockerfile
volumes:
- type: bind
source: /var/log/oliverbooth/admin
target: /app/logs
- type: bind
source: /etc/oliverbooth/admin
target: /app/data
ports:
- "2843:2843"
restart: always
environment:
- SSL_CERT_PATH=${ADMIN_SSL_CERT_PATH}
- SSL_KEY_PATH=${ADMIN_SSL_KEY_PATH}

14
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@editorjs/code": "^2.9.0",
"@editorjs/header": "^2.8.1", "@editorjs/header": "^2.8.1",
"@editorjs/paragraph": "^2.11.3", "@editorjs/paragraph": "^2.11.3",
"codex-notifier": "^1.1.2", "codex-notifier": "^1.1.2",
@ -35,6 +36,19 @@
"resolved": "https://registry.npmjs.org/@codexteam/icons/-/icons-0.0.4.tgz", "resolved": "https://registry.npmjs.org/@codexteam/icons/-/icons-0.0.4.tgz",
"integrity": "sha512-V8N/TY2TGyas4wLrPIFq7bcow68b3gu8DfDt1+rrHPtXxcexadKauRJL6eQgfG7Z0LCrN4boLRawR4S9gjIh/Q==" "integrity": "sha512-V8N/TY2TGyas4wLrPIFq7bcow68b3gu8DfDt1+rrHPtXxcexadKauRJL6eQgfG7Z0LCrN4boLRawR4S9gjIh/Q=="
}, },
"node_modules/@editorjs/code": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/@editorjs/code/-/code-2.9.0.tgz",
"integrity": "sha512-QNOWxF29j6mYl/gM5HJzeGOt3s4laoZCUbuRqj6RkIvLBWMU+ASwjckEiouA61hcYUOMfpw4vQjzhsfC7xm/vA==",
"dependencies": {
"@codexteam/icons": "^0.0.5"
}
},
"node_modules/@editorjs/code/node_modules/@codexteam/icons": {
"version": "0.0.5",
"resolved": "https://registry.npmjs.org/@codexteam/icons/-/icons-0.0.5.tgz",
"integrity": "sha512-s6H2KXhLz2rgbMZSkRm8dsMJvyUNZsEjxobBEg9ztdrb1B2H3pEzY6iTwI4XUPJWJ3c3qRKwV4TrO3J5jUdoQA=="
},
"node_modules/@editorjs/editorjs": { "node_modules/@editorjs/editorjs": {
"version": "2.29.0", "version": "2.29.0",
"resolved": "https://registry.npmjs.org/@editorjs/editorjs/-/editorjs-2.29.0.tgz", "resolved": "https://registry.npmjs.org/@editorjs/editorjs/-/editorjs-2.29.0.tgz",

View File

@ -34,6 +34,7 @@
"webpack-stream": "^7.0.0" "webpack-stream": "^7.0.0"
}, },
"dependencies": { "dependencies": {
"@editorjs/code": "^2.9.0",
"@editorjs/header": "^2.8.1", "@editorjs/header": "^2.8.1",
"@editorjs/paragraph": "^2.11.3", "@editorjs/paragraph": "^2.11.3",
"codex-notifier": "^1.1.2", "codex-notifier": "^1.1.2",

View File

@ -6,20 +6,27 @@ import Interop from "./Interop";
import SaveButtonMode from "./MarkdownEditor/SaveButtonMode"; import SaveButtonMode from "./MarkdownEditor/SaveButtonMode";
import EditorJS from "@editorjs/editorjs"; import EditorJS from "@editorjs/editorjs";
import Header from "@editorjs/header"; import Header from "@editorjs/header";
import CodeTool from "@editorjs/code";
import SimpleImage from "./BlockTools/SimpleImage"; import SimpleImage from "./BlockTools/SimpleImage";
import Utility from "../app/Utility";
(() => { (() => {
getCurrentBlogPost().then(post => { getCurrentBlogPost().then(async post => {
if (!post) { if (!post) {
return; return;
} }
await Utility.delay(1000); // hack to wait for setDotNetHelper invocation. TODO fix this shit
const blocks = JSON.parse(await Interop.invoke<string>("GetEditorObject", post.id));
console.log("JSON object is", blocks);
// UI.init(); // UI.init();
// UI.addSaveButtonListener(savePost); // UI.addSaveButtonListener(savePost);
const editor = new EditorJS({ const editor = new EditorJS({
autofocus: true, autofocus: true,
tools: { tools: {
code: CodeTool,
header: { header: {
class: Header, class: Header,
config: { config: {
@ -29,7 +36,8 @@ import SimpleImage from "./BlockTools/SimpleImage";
} }
}, },
image: SimpleImage image: SimpleImage
} },
data: blocks
}); });
/*const editor = new MarkdownEditor(UI.markdownInput); /*const editor = new MarkdownEditor(UI.markdownInput);

View File

@ -16,6 +16,7 @@ class BlogPost {
private readonly _formattedPublishDate: string; private readonly _formattedPublishDate: string;
private readonly _formattedUpdateDate: string; private readonly _formattedUpdateDate: string;
private readonly _tags: string[]; private readonly _tags: string[];
private readonly _blockData: [{ id: string, type: string, data: any }];
constructor(json: any) { constructor(json: any) {
this._id = json.id; this._id = json.id;
@ -33,6 +34,11 @@ class BlogPost {
this._formattedPublishDate = json.formattedPublishDate; this._formattedPublishDate = json.formattedPublishDate;
this._formattedUpdateDate = json.formattedUpdateDate; this._formattedUpdateDate = json.formattedUpdateDate;
this._tags = json.tags; this._tags = json.tags;
this._blockData = json.blockData;
}
get blockData(): [{ id: string, type: string, data: any }] {
return this._blockData;
} }
get id(): string { get id(): string {
@ -70,7 +76,7 @@ class BlogPost {
get url(): BlogUrl { get url(): BlogUrl {
return this._url; return this._url;
} }
get tags(): string[] { get tags(): string[] {
return this._tags; return this._tags;
} }

7
src/ts/app/Utility.ts Normal file
View File

@ -0,0 +1,7 @@
class Utility {
public static delay(timeout: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, timeout));
}
}
export default Utility;