⏪ Uses markdown in post as rich text
This commit is contained in:
parent
1c361b94f3
commit
7e7c8fe556
@ -135,6 +135,7 @@ public class AppDatabase(
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
modelBuilder.Entity<Post.Post>()
|
||||
.HasGeneratedTsVectorColumn(p => p.SearchVector, "simple", p => new { p.Title, p.Description, p.Content })
|
||||
.HasIndex(p => p.SearchVector)
|
||||
.HasMethod("GIN");
|
||||
modelBuilder.Entity<Post.Post>()
|
||||
@ -199,7 +200,7 @@ public class AppDatabase(
|
||||
.HasForeignKey(m => m.ForwardedMessageId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
modelBuilder.Entity<Chat.Message>()
|
||||
.HasOne(m => m.RepliedMessage)
|
||||
.HasOne(m => m.RepliedMessage)
|
||||
.WithMany()
|
||||
.HasForeignKey(m => m.RepliedMessageId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
@ -16,7 +16,7 @@ using NpgsqlTypes;
|
||||
namespace DysonNetwork.Sphere.Migrations
|
||||
{
|
||||
[DbContext(typeof(AppDatabase))]
|
||||
[Migration("20250503124624_InitialMigration")]
|
||||
[Migration("20250504170705_InitialMigration")]
|
||||
partial class InitialMigration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
@ -1080,8 +1080,8 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<JsonDocument>("Content")
|
||||
.HasColumnType("jsonb")
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("content");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
@ -1131,8 +1131,12 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnName("replied_post_id");
|
||||
|
||||
b.Property<NpgsqlTsVector>("SearchVector")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("tsvector")
|
||||
.HasColumnName("search_vector");
|
||||
.HasColumnName("search_vector")
|
||||
.HasAnnotation("Npgsql:TsVectorConfig", "simple")
|
||||
.HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" });
|
||||
|
||||
b.Property<long?>("ThreadedPostId")
|
||||
.HasColumnType("bigint")
|
@ -709,7 +709,7 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
edited_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
published_at = table.Column<Instant>(type: "timestamp with time zone", nullable: true),
|
||||
visibility = table.Column<int>(type: "integer", nullable: false),
|
||||
content = table.Column<JsonDocument>(type: "jsonb", nullable: true),
|
||||
content = table.Column<string>(type: "text", nullable: true),
|
||||
type = table.Column<int>(type: "integer", nullable: false),
|
||||
meta = table.Column<Dictionary<string, object>>(type: "jsonb", nullable: true),
|
||||
views_unique = table.Column<int>(type: "integer", nullable: false),
|
||||
@ -719,7 +719,9 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
threaded_post_id = table.Column<long>(type: "bigint", nullable: true),
|
||||
replied_post_id = table.Column<long>(type: "bigint", nullable: true),
|
||||
forwarded_post_id = table.Column<long>(type: "bigint", nullable: true),
|
||||
search_vector = table.Column<NpgsqlTsVector>(type: "tsvector", nullable: true),
|
||||
search_vector = table.Column<NpgsqlTsVector>(type: "tsvector", nullable: false)
|
||||
.Annotation("Npgsql:TsVectorConfig", "simple")
|
||||
.Annotation("Npgsql:TsVectorProperties", new[] { "title", "description", "content" }),
|
||||
publisher_id = table.Column<long>(type: "bigint", nullable: false),
|
||||
created_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
||||
updated_at = table.Column<Instant>(type: "timestamp with time zone", nullable: false),
|
@ -1077,8 +1077,8 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<long>("Id"));
|
||||
|
||||
b.Property<JsonDocument>("Content")
|
||||
.HasColumnType("jsonb")
|
||||
b.Property<string>("Content")
|
||||
.HasColumnType("text")
|
||||
.HasColumnName("content");
|
||||
|
||||
b.Property<Instant>("CreatedAt")
|
||||
@ -1128,8 +1128,12 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnName("replied_post_id");
|
||||
|
||||
b.Property<NpgsqlTsVector>("SearchVector")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("tsvector")
|
||||
.HasColumnName("search_vector");
|
||||
.HasColumnName("search_vector")
|
||||
.HasAnnotation("Npgsql:TsVectorConfig", "simple")
|
||||
.HasAnnotation("Npgsql:TsVectorProperties", new[] { "Title", "Description", "Content" });
|
||||
|
||||
b.Property<long?>("ThreadedPostId")
|
||||
.HasColumnType("bigint")
|
||||
|
@ -32,8 +32,9 @@ public class Post : ModelBase
|
||||
public Instant? EditedAt { get; set; }
|
||||
public Instant? PublishedAt { get; set; }
|
||||
public PostVisibility Visibility { get; set; } = PostVisibility.Public;
|
||||
|
||||
[Column(TypeName = "jsonb")] public JsonDocument? Content { get; set; }
|
||||
|
||||
// ReSharper disable once EntityFramework.ModelValidation.UnlimitedStringLength
|
||||
public string? Content { get; set; }
|
||||
|
||||
public PostType Type { get; set; }
|
||||
[Column(TypeName = "jsonb")] public Dictionary<string, object>? Meta { get; set; }
|
||||
@ -51,8 +52,8 @@ public class Post : ModelBase
|
||||
public long? ForwardedPostId { get; set; }
|
||||
public Post? ForwardedPost { get; set; }
|
||||
public ICollection<CloudFile> Attachments { get; set; } = new List<CloudFile>();
|
||||
|
||||
[JsonIgnore] public NpgsqlTsVector? SearchVector { get; set; }
|
||||
|
||||
[JsonIgnore] public NpgsqlTsVector SearchVector { get; set; } = null!;
|
||||
|
||||
public Publisher Publisher { get; set; } = null!;
|
||||
public ICollection<PostReaction> Reactions { get; set; } = new List<PostReaction>();
|
||||
|
@ -119,7 +119,7 @@ public class PostController(AppDatabase db, PostService ps, RelationshipService
|
||||
{
|
||||
[MaxLength(1024)] public string? Title { get; set; }
|
||||
[MaxLength(4096)] public string? Description { get; set; }
|
||||
public JsonDocument? Content { get; set; }
|
||||
public string? Content { get; set; }
|
||||
public PostVisibility? Visibility { get; set; }
|
||||
public PostType? Type { get; set; }
|
||||
[MaxLength(16)] public List<string>? Tags { get; set; }
|
||||
|
@ -10,57 +10,12 @@ public class PostService(AppDatabase db, FileService fs, ActivityService act)
|
||||
{
|
||||
public static List<Post> TruncatePostContent(List<Post> input)
|
||||
{
|
||||
// This truncate post content is designed for quill delta
|
||||
const int maxLength = 256;
|
||||
foreach (var item in input)
|
||||
{
|
||||
if (item.Content is not { RootElement: var rootElement }) continue;
|
||||
|
||||
if (rootElement.ValueKind != JsonValueKind.Array) continue;
|
||||
var totalLength = 0;
|
||||
var truncatedArrayElements = new List<JsonElement>();
|
||||
|
||||
foreach (var element in rootElement.EnumerateArray())
|
||||
{
|
||||
if (element is { ValueKind: JsonValueKind.Object } &&
|
||||
element.TryGetProperty("insert", out var insertProperty))
|
||||
{
|
||||
if (insertProperty is { ValueKind: JsonValueKind.String })
|
||||
{
|
||||
var textContent = insertProperty.GetString()!;
|
||||
if (totalLength + textContent.Length <= maxLength)
|
||||
{
|
||||
truncatedArrayElements.Add(element);
|
||||
totalLength += textContent.Length;
|
||||
}
|
||||
else
|
||||
{
|
||||
var remainingLength = maxLength - totalLength;
|
||||
if (remainingLength > 0)
|
||||
{
|
||||
using var truncatedElementDocument =
|
||||
JsonDocument.Parse(
|
||||
$@"{{ ""insert"": ""{textContent.Substring(0, remainingLength)}"" }}"
|
||||
);
|
||||
truncatedArrayElements.Add(truncatedElementDocument.RootElement.Clone());
|
||||
totalLength = maxLength;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
truncatedArrayElements.Add(element);
|
||||
}
|
||||
else
|
||||
truncatedArrayElements.Add(element);
|
||||
|
||||
if (totalLength >= maxLength)
|
||||
break;
|
||||
}
|
||||
|
||||
var newDocument = JsonDocument.Parse(JsonSerializer.Serialize(truncatedArrayElements));
|
||||
item.Content = newDocument;
|
||||
if (!(item.Content?.Length > maxLength)) continue;
|
||||
item.Content = item.Content[..maxLength];
|
||||
item.IsTruncated = true;
|
||||
}
|
||||
|
||||
return input;
|
||||
@ -121,29 +76,6 @@ public class PostService(AppDatabase db, FileService fs, ActivityService act)
|
||||
throw new InvalidOperationException("Categories contains one or more categories that wasn't exists.");
|
||||
}
|
||||
|
||||
// Vectorize the quill delta content
|
||||
if (post.Content?.RootElement is { ValueKind: JsonValueKind.Array })
|
||||
{
|
||||
var searchTextBuilder = new System.Text.StringBuilder();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(post.Title))
|
||||
searchTextBuilder.AppendLine(post.Title);
|
||||
if (!string.IsNullOrWhiteSpace(post.Description))
|
||||
searchTextBuilder.AppendLine(post.Description);
|
||||
|
||||
foreach (var element in post.Content.RootElement.EnumerateArray())
|
||||
{
|
||||
if (element is { ValueKind: JsonValueKind.Object } &&
|
||||
element.TryGetProperty("insert", out var insertProperty) &&
|
||||
insertProperty.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
searchTextBuilder.Append(insertProperty.GetString());
|
||||
}
|
||||
}
|
||||
|
||||
post.SearchVector = EF.Functions.ToTsVector(searchTextBuilder.ToString().Trim());
|
||||
}
|
||||
|
||||
// TODO Notify the subscribers
|
||||
|
||||
db.Posts.Add(post);
|
||||
@ -222,29 +154,6 @@ public class PostService(AppDatabase db, FileService fs, ActivityService act)
|
||||
throw new InvalidOperationException("Categories contains one or more categories that wasn't exists.");
|
||||
}
|
||||
|
||||
// Vectorize the quill delta content
|
||||
if (post.Content?.RootElement is { ValueKind: JsonValueKind.Array })
|
||||
{
|
||||
var searchTextBuilder = new System.Text.StringBuilder();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(post.Title))
|
||||
searchTextBuilder.AppendLine(post.Title);
|
||||
if (!string.IsNullOrWhiteSpace(post.Description))
|
||||
searchTextBuilder.AppendLine(post.Description);
|
||||
|
||||
foreach (var element in post.Content.RootElement.EnumerateArray())
|
||||
{
|
||||
if (element is { ValueKind: JsonValueKind.Object } &&
|
||||
element.TryGetProperty("insert", out var insertProperty) &&
|
||||
insertProperty.ValueKind == JsonValueKind.String)
|
||||
{
|
||||
searchTextBuilder.Append(insertProperty.GetString());
|
||||
}
|
||||
}
|
||||
|
||||
post.SearchVector = EF.Functions.ToTsVector(searchTextBuilder.ToString().Trim());
|
||||
}
|
||||
|
||||
db.Update(post);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
|
@ -40,6 +40,7 @@
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMicrosoftDependencyInjectionJobFactory_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F1edbd6e24d7b430fabce72177269baa19200_003Fa8_003F91b091de_003FMicrosoftDependencyInjectionJobFactory_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANotFoundResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F28_003F290250f5_003FNotFoundResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANotFound_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003Ff2c049af93e430aac427e8ff3cc9edd8763d5c9f006d7121ed1c5921585cba_003FNotFound_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ANpgsqlEntityTypeBuilderExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fccb1faacaea4420db96b09857fc56178a1600_003Fd9_003F9acf9507_003FNpgsqlEntityTypeBuilderExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOk_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F01d30b32e2ff422cb80129ca2a441c4242600_003F3b_003F237bf104_003FOk_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AOptionsConfigurationServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F6622dea924b14dc7aa3ee69d7c84e5735000_003Fe0_003F024ba0b7_003FOptionsConfigurationServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APath_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb6f0571a6bc744b0b551fd4578292582e54c00_003Fd3_003F7b05b2bd_003FPath_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
|
Loading…
x
Reference in New Issue
Block a user