Compare commits

...

2 Commits

Author SHA1 Message Date
4775e35b3c Merge branch 'main' of sobina-git:jbourdon/social-media
All checks were successful
deploy-socialize / image (push) Successful in 2m7s
deploy-socialize / deploy (push) Successful in 32s
2026-05-05 23:26:59 -04:00
a7535d460d feat: refine content calendar experience 2026-05-05 23:25:58 -04:00
71 changed files with 3230 additions and 1310 deletions

View File

@@ -12,7 +12,7 @@ using Socialize.Api.Data;
namespace Socialize.Api.Migrations namespace Socialize.Api.Migrations
{ {
[DbContext(typeof(AppDbContext))] [DbContext(typeof(AppDbContext))]
[Migration("20260505192305_Initial")] [Migration("20260505204545_Initial")]
partial class Initial partial class Initial
{ {
/// <inheritdoc /> /// <inheritdoc />
@@ -912,6 +912,29 @@ namespace Socialize.Api.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<string>("AttachmentBlobContainerName")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("AttachmentBlobName")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<string>("AttachmentBlobUrl")
.HasMaxLength(1024)
.HasColumnType("character varying(1024)");
b.Property<string>("AttachmentContentType")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("AttachmentFileName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<long?>("AttachmentSizeBytes")
.HasColumnType("bigint");
b.Property<string>("AuthorDisplayName") b.Property<string>("AuthorDisplayName")
.IsRequired() .IsRequired()
.HasMaxLength(256) .HasMaxLength(256)

View File

@@ -282,6 +282,12 @@ namespace Socialize.Api.Migrations
AuthorDisplayName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false), AuthorDisplayName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
AuthorEmail = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false), AuthorEmail = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
Body = table.Column<string>(type: "character varying(4000)", maxLength: 4000, nullable: false), Body = table.Column<string>(type: "character varying(4000)", maxLength: 4000, nullable: false),
AttachmentFileName = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
AttachmentContentType = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
AttachmentSizeBytes = table.Column<long>(type: "bigint", nullable: true),
AttachmentBlobContainerName = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
AttachmentBlobName = table.Column<string>(type: "character varying(512)", maxLength: 512, nullable: true),
AttachmentBlobUrl = table.Column<string>(type: "character varying(1024)", maxLength: 1024, nullable: true),
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP") CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
}, },
constraints: table => constraints: table =>

View File

@@ -909,6 +909,29 @@ namespace Socialize.Api.Migrations
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnType("uuid"); .HasColumnType("uuid");
b.Property<string>("AttachmentBlobContainerName")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("AttachmentBlobName")
.HasMaxLength(512)
.HasColumnType("character varying(512)");
b.Property<string>("AttachmentBlobUrl")
.HasMaxLength(1024)
.HasColumnType("character varying(1024)");
b.Property<string>("AttachmentContentType")
.HasMaxLength(128)
.HasColumnType("character varying(128)");
b.Property<string>("AttachmentFileName")
.HasMaxLength(256)
.HasColumnType("character varying(256)");
b.Property<long?>("AttachmentSizeBytes")
.HasColumnType("bigint");
b.Property<string>("AuthorDisplayName") b.Property<string>("AuthorDisplayName")
.IsRequired() .IsRequired()
.HasMaxLength(256) .HasMaxLength(256)

View File

@@ -10,5 +10,11 @@ public class Comment
public required string AuthorDisplayName { get; set; } public required string AuthorDisplayName { get; set; }
public required string AuthorEmail { get; set; } public required string AuthorEmail { get; set; }
public required string Body { get; set; } public required string Body { get; set; }
public string? AttachmentFileName { get; set; }
public string? AttachmentContentType { get; set; }
public long? AttachmentSizeBytes { get; set; }
public string? AttachmentBlobContainerName { get; set; }
public string? AttachmentBlobName { get; set; }
public string? AttachmentBlobUrl { get; set; }
public DateTimeOffset CreatedAt { get; init; } public DateTimeOffset CreatedAt { get; init; }
} }

View File

@@ -13,6 +13,11 @@ public static class CommentModelConfiguration
comment.Property(x => x.AuthorDisplayName).HasMaxLength(256).IsRequired(); comment.Property(x => x.AuthorDisplayName).HasMaxLength(256).IsRequired();
comment.Property(x => x.AuthorEmail).HasMaxLength(256).IsRequired(); comment.Property(x => x.AuthorEmail).HasMaxLength(256).IsRequired();
comment.Property(x => x.Body).HasMaxLength(4000).IsRequired(); comment.Property(x => x.Body).HasMaxLength(4000).IsRequired();
comment.Property(x => x.AttachmentFileName).HasMaxLength(256);
comment.Property(x => x.AttachmentContentType).HasMaxLength(128);
comment.Property(x => x.AttachmentBlobContainerName).HasMaxLength(128);
comment.Property(x => x.AttachmentBlobName).HasMaxLength(512);
comment.Property(x => x.AttachmentBlobUrl).HasMaxLength(1024);
comment.Property(x => x.CreatedAt) comment.Property(x => x.CreatedAt)
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasDefaultValueSql("CURRENT_TIMESTAMP"); .HasDefaultValueSql("CURRENT_TIMESTAMP");

View File

@@ -1,6 +1,7 @@
using FastEndpoints; using FastEndpoints;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Socialize.Api.Data; using Socialize.Api.Data;
using Socialize.Api.Infrastructure.BlobStorage.Contracts;
using Socialize.Api.Infrastructure.Security; using Socialize.Api.Infrastructure.Security;
using Socialize.Api.Modules.ContentItems.Contracts; using Socialize.Api.Modules.ContentItems.Contracts;
using Socialize.Api.Modules.ContentItems.Data; using Socialize.Api.Modules.ContentItems.Data;
@@ -14,7 +15,8 @@ public record CreateCommentRequest(
Guid WorkspaceId, Guid WorkspaceId,
Guid ContentItemId, Guid ContentItemId,
Guid? ParentCommentId, Guid? ParentCommentId,
string Body); string Body,
IFormFile? Attachment);
public class CreateCommentRequestValidator public class CreateCommentRequestValidator
: Validator<CreateCommentRequest> : Validator<CreateCommentRequest>
@@ -23,13 +25,14 @@ public class CreateCommentRequestValidator
{ {
RuleFor(x => x.WorkspaceId).NotEmpty(); RuleFor(x => x.WorkspaceId).NotEmpty();
RuleFor(x => x.ContentItemId).NotEmpty(); RuleFor(x => x.ContentItemId).NotEmpty();
RuleFor(x => x.Body).NotEmpty().MaximumLength(4000); RuleFor(x => x.Body).MaximumLength(4000);
} }
} }
public class CreateCommentHandler( public class CreateCommentHandler(
AppDbContext dbContext, AppDbContext dbContext,
AccessScopeService accessScopeService, AccessScopeService accessScopeService,
IBlobStorage blobStorage,
IContentItemActivityWriter activityWriter, IContentItemActivityWriter activityWriter,
INotificationEventWriter notificationEventWriter) INotificationEventWriter notificationEventWriter)
: Endpoint<CreateCommentRequest, CommentDto> : Endpoint<CreateCommentRequest, CommentDto>
@@ -38,10 +41,19 @@ public class CreateCommentHandler(
{ {
Post("/api/comments"); Post("/api/comments");
Options(o => o.WithTags("Comments")); Options(o => o.WithTags("Comments"));
AllowFileUploads();
} }
public override async Task HandleAsync(CreateCommentRequest request, CancellationToken ct) public override async Task HandleAsync(CreateCommentRequest request, CancellationToken ct)
{ {
string body = request.Body?.Trim() ?? string.Empty;
if (string.IsNullOrWhiteSpace(body) && request.Attachment is null)
{
AddError(request => request.Body, "A comment body or attachment is required.");
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
return;
}
ContentItem? contentItem = await dbContext.ContentItems ContentItem? contentItem = await dbContext.ContentItems
.SingleOrDefaultAsync( .SingleOrDefaultAsync(
candidate => candidate.Id == request.ContentItemId && candidate.WorkspaceId == request.WorkspaceId, candidate => candidate.Id == request.ContentItemId && candidate.WorkspaceId == request.WorkspaceId,
@@ -75,16 +87,70 @@ public class CreateCommentHandler(
} }
} }
Guid commentId = Guid.NewGuid();
string? attachmentFileName = null;
string? attachmentContentType = null;
long? attachmentSizeBytes = null;
string? attachmentBlobName = null;
string? attachmentBlobUrl = null;
if (request.Attachment is not null)
{
string normalizedContentType = request.Attachment.ContentType.Trim().ToLowerInvariant();
if (request.Attachment.Length <= 0)
{
AddError(request => request.Attachment, "The attachment must not be empty.");
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
return;
}
if (!IsInlineAttachmentContentType(normalizedContentType))
{
AddError(request => request.Attachment, "The attachment must be a PNG or JPEG image.");
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
return;
}
attachmentFileName = NormalizeFileName(request.Attachment.FileName, normalizedContentType);
attachmentContentType = normalizedContentType;
attachmentSizeBytes = request.Attachment.Length;
attachmentBlobName =
$"{contentItem.WorkspaceId}/{SubDirectoryNames.Contents}/{contentItem.Id}/comments/{commentId}/{attachmentFileName}";
try
{
attachmentBlobUrl = await blobStorage.UploadFileAsync(
ContainerNames.Workspaces,
attachmentBlobName,
request.Attachment.OpenReadStream(),
normalizedContentType,
ct);
}
catch (InvalidOperationException)
{
AddError(request => request.Attachment, "The attachment file is invalid or unsupported.");
await SendErrorsAsync(StatusCodes.Status400BadRequest, ct);
return;
}
}
Comment comment = new() Comment comment = new()
{ {
Id = Guid.NewGuid(), Id = commentId,
WorkspaceId = request.WorkspaceId, WorkspaceId = request.WorkspaceId,
ContentItemId = request.ContentItemId, ContentItemId = request.ContentItemId,
ParentCommentId = request.ParentCommentId, ParentCommentId = request.ParentCommentId,
AuthorUserId = User.GetUserId(), AuthorUserId = User.GetUserId(),
AuthorDisplayName = User.GetAlias() ?? User.GetName(), AuthorDisplayName = User.GetAlias() ?? User.GetName(),
AuthorEmail = User.GetEmail(), AuthorEmail = User.GetEmail(),
Body = request.Body.Trim(), Body = body,
AttachmentFileName = attachmentFileName,
AttachmentContentType = attachmentContentType,
AttachmentSizeBytes = attachmentSizeBytes,
AttachmentBlobContainerName = attachmentBlobName is not null ? ContainerNames.Workspaces : null,
AttachmentBlobName = attachmentBlobName,
AttachmentBlobUrl = attachmentBlobUrl,
CreatedAt = DateTimeOffset.UtcNow, CreatedAt = DateTimeOffset.UtcNow,
}; };
@@ -109,6 +175,7 @@ public class CreateCommentHandler(
JsonSerializer.Serialize(new JsonSerializer.Serialize(new
{ {
parentCommentId = comment.ParentCommentId, parentCommentId = comment.ParentCommentId,
attachmentFileName = comment.AttachmentFileName,
})), })),
ct); ct);
@@ -135,8 +202,34 @@ public class CreateCommentHandler(
comment.AuthorEmail, comment.AuthorEmail,
authorPortraitUrl, authorPortraitUrl,
comment.Body, comment.Body,
comment.AttachmentFileName,
comment.AttachmentContentType,
comment.AttachmentSizeBytes,
comment.AttachmentBlobUrl,
comment.CreatedAt); comment.CreatedAt);
await SendAsync(dto, StatusCodes.Status201Created, ct); await SendAsync(dto, StatusCodes.Status201Created, ct);
} }
private static bool IsInlineAttachmentContentType(string contentType)
{
return contentType.Trim().ToLowerInvariant() is "image/png" or "image/jpeg" or "image/jpg";
}
private static string NormalizeFileName(string? fileName, string contentType)
{
string extension = contentType.Trim().ToLowerInvariant() switch
{
"image/png" => ".png",
"image/jpeg" or "image/jpg" => ".jpg",
_ => string.Empty,
};
string normalized = Path.GetFileName(fileName ?? string.Empty).Trim();
if (string.IsNullOrWhiteSpace(normalized))
{
return $"comment-attachment{extension}";
}
return normalized.Length > 256 ? normalized[..256] : normalized;
}
} }

View File

@@ -19,6 +19,10 @@ public record CommentDto(
string AuthorEmail, string AuthorEmail,
string? AuthorPortraitUrl, string? AuthorPortraitUrl,
string Body, string Body,
string? AttachmentFileName,
string? AttachmentContentType,
long? AttachmentSizeBytes,
string? AttachmentBlobUrl,
DateTimeOffset CreatedAt); DateTimeOffset CreatedAt);
public class GetCommentsHandler( public class GetCommentsHandler(
@@ -73,6 +77,10 @@ public class GetCommentsHandler(
comment.AuthorEmail, comment.AuthorEmail,
authorPortraits.GetValueOrDefault(comment.AuthorUserId), authorPortraits.GetValueOrDefault(comment.AuthorUserId),
comment.Body, comment.Body,
comment.AttachmentFileName,
comment.AttachmentContentType,
comment.AttachmentSizeBytes,
comment.AttachmentBlobUrl,
comment.CreatedAt)) comment.CreatedAt))
.ToList(); .ToList();

View File

@@ -1,60 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
</startup>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Build" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Build.Framework" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Build.Utilities.Core" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Build.Tasks.Core" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.IO.Redist" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.1" newVersion="6.0.0.1"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-4.0.1.2" newVersion="4.0.1.2"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.Unsafe" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@@ -1,260 +0,0 @@
{
"runtimeTarget": {
"name": ".NETCoreApp,Version=v6.0",
"signature": ""
},
"compilationOptions": {},
"targets": {
".NETCoreApp,Version=v6.0": {
"Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost/4.14.0-3.25262.10": {
"dependencies": {
"Microsoft.Build.Locator": "1.6.10",
"Microsoft.CodeAnalysis.NetAnalyzers": "8.0.0-preview.23468.1",
"Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers": "3.3.4-beta1.22504.1",
"Microsoft.DotNet.XliffTasks": "9.0.0-beta.25255.5",
"Microsoft.VisualStudio.Threading.Analyzers": "17.13.2",
"Newtonsoft.Json": "13.0.3",
"Roslyn.Diagnostics.Analyzers": "3.11.0-beta1.24081.1",
"System.Collections.Immutable": "9.0.0",
"System.CommandLine": "2.0.0-beta4.24528.1"
},
"runtime": {
"Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll": {}
},
"resources": {
"cs/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
"locale": "cs"
},
"de/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
"locale": "de"
},
"es/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
"locale": "es"
},
"fr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
"locale": "fr"
},
"it/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
"locale": "it"
},
"ja/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
"locale": "ja"
},
"ko/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
"locale": "ko"
},
"pl/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
"locale": "pl"
},
"pt-BR/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
"locale": "pt-BR"
},
"ru/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
"locale": "ru"
},
"tr/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
"locale": "tr"
},
"zh-Hans/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
"locale": "zh-Hans"
},
"zh-Hant/Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll": {
"locale": "zh-Hant"
}
}
},
"Microsoft.Build.Locator/1.6.10": {
"runtime": {
"lib/net6.0/Microsoft.Build.Locator.dll": {
"assemblyVersion": "1.0.0.0",
"fileVersion": "1.6.10.57384"
}
}
},
"Microsoft.CodeAnalysis.BannedApiAnalyzers/3.11.0-beta1.24081.1": {},
"Microsoft.CodeAnalysis.NetAnalyzers/8.0.0-preview.23468.1": {},
"Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers/3.3.4-beta1.22504.1": {},
"Microsoft.CodeAnalysis.PublicApiAnalyzers/3.11.0-beta1.24081.1": {},
"Microsoft.DotNet.XliffTasks/9.0.0-beta.25255.5": {},
"Microsoft.VisualStudio.Threading.Analyzers/17.13.2": {},
"Newtonsoft.Json/13.0.3": {
"runtime": {
"lib/net6.0/Newtonsoft.Json.dll": {
"assemblyVersion": "13.0.0.0",
"fileVersion": "13.0.3.27908"
}
}
},
"Roslyn.Diagnostics.Analyzers/3.11.0-beta1.24081.1": {
"dependencies": {
"Microsoft.CodeAnalysis.BannedApiAnalyzers": "3.11.0-beta1.24081.1",
"Microsoft.CodeAnalysis.PublicApiAnalyzers": "3.11.0-beta1.24081.1"
}
},
"System.Collections.Immutable/9.0.0": {
"dependencies": {
"System.Memory": "4.5.5",
"System.Runtime.CompilerServices.Unsafe": "6.0.0"
},
"runtime": {
"lib/netstandard2.0/System.Collections.Immutable.dll": {
"assemblyVersion": "9.0.0.0",
"fileVersion": "9.0.24.52809"
}
}
},
"System.CommandLine/2.0.0-beta4.24528.1": {
"dependencies": {
"System.Memory": "4.5.5"
},
"runtime": {
"lib/netstandard2.0/System.CommandLine.dll": {
"assemblyVersion": "2.0.0.0",
"fileVersion": "2.0.24.52801"
}
},
"resources": {
"lib/netstandard2.0/cs/System.CommandLine.resources.dll": {
"locale": "cs"
},
"lib/netstandard2.0/de/System.CommandLine.resources.dll": {
"locale": "de"
},
"lib/netstandard2.0/es/System.CommandLine.resources.dll": {
"locale": "es"
},
"lib/netstandard2.0/fr/System.CommandLine.resources.dll": {
"locale": "fr"
},
"lib/netstandard2.0/it/System.CommandLine.resources.dll": {
"locale": "it"
},
"lib/netstandard2.0/ja/System.CommandLine.resources.dll": {
"locale": "ja"
},
"lib/netstandard2.0/ko/System.CommandLine.resources.dll": {
"locale": "ko"
},
"lib/netstandard2.0/pl/System.CommandLine.resources.dll": {
"locale": "pl"
},
"lib/netstandard2.0/pt-BR/System.CommandLine.resources.dll": {
"locale": "pt-BR"
},
"lib/netstandard2.0/ru/System.CommandLine.resources.dll": {
"locale": "ru"
},
"lib/netstandard2.0/tr/System.CommandLine.resources.dll": {
"locale": "tr"
},
"lib/netstandard2.0/zh-Hans/System.CommandLine.resources.dll": {
"locale": "zh-Hans"
},
"lib/netstandard2.0/zh-Hant/System.CommandLine.resources.dll": {
"locale": "zh-Hant"
}
}
},
"System.Memory/4.5.5": {},
"System.Runtime.CompilerServices.Unsafe/6.0.0": {}
}
},
"libraries": {
"Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost/4.14.0-3.25262.10": {
"type": "project",
"serviceable": false,
"sha512": ""
},
"Microsoft.Build.Locator/1.6.10": {
"type": "package",
"serviceable": true,
"sha512": "sha512-DJhCkTGqy1LMJzEmG/2qxRTMHwdPc3WdVoGQI5o5mKHVo4dsHrCMLIyruwU/NSvPNSdvONlaf7jdFXnAMuxAuA==",
"path": "microsoft.build.locator/1.6.10",
"hashPath": "microsoft.build.locator.1.6.10.nupkg.sha512"
},
"Microsoft.CodeAnalysis.BannedApiAnalyzers/3.11.0-beta1.24081.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-DH6L3rsbjppLrHM2l2/NKbnMaYd0NFHx2pjZaFdrVcRkONrV3i9FHv6Id8Dp6/TmjhXQsJVJJFbhhjkpuP1xxg==",
"path": "microsoft.codeanalysis.bannedapianalyzers/3.11.0-beta1.24081.1",
"hashPath": "microsoft.codeanalysis.bannedapianalyzers.3.11.0-beta1.24081.1.nupkg.sha512"
},
"Microsoft.CodeAnalysis.NetAnalyzers/8.0.0-preview.23468.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-ZhIvyxmUCqb8OiU/VQfxfuAmIB4lQsjqhMVYKeoyxzSI+d7uR5Pzx3ZKoaIhPizQ15wa4lnyD6wg3TnSJ6P4LA==",
"path": "microsoft.codeanalysis.netanalyzers/8.0.0-preview.23468.1",
"hashPath": "microsoft.codeanalysis.netanalyzers.8.0.0-preview.23468.1.nupkg.sha512"
},
"Microsoft.CodeAnalysis.PerformanceSensitiveAnalyzers/3.3.4-beta1.22504.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-2XRlqPAzVke7Sb80+UqaC7o57OwfK+tIr+aIOxrx41RWDMeR2SBUW7kL4sd6hfLFfBNsLo3W5PT+UwfvwPaOzA==",
"path": "microsoft.codeanalysis.performancesensitiveanalyzers/3.3.4-beta1.22504.1",
"hashPath": "microsoft.codeanalysis.performancesensitiveanalyzers.3.3.4-beta1.22504.1.nupkg.sha512"
},
"Microsoft.CodeAnalysis.PublicApiAnalyzers/3.11.0-beta1.24081.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-3bYGBihvoNO0rhCOG1U9O50/4Q8suZ+glHqQLIAcKvnodSnSW+dYWYzTNb1UbS8pUS8nAUfxSFMwuMup/G5DtQ==",
"path": "microsoft.codeanalysis.publicapianalyzers/3.11.0-beta1.24081.1",
"hashPath": "microsoft.codeanalysis.publicapianalyzers.3.11.0-beta1.24081.1.nupkg.sha512"
},
"Microsoft.DotNet.XliffTasks/9.0.0-beta.25255.5": {
"type": "package",
"serviceable": true,
"sha512": "sha512-bb0fZB5ViPscdfYeWlmtyXJMzNkgcpkV5RWmXktfV9lwIUZgNZmFotUXrdcTyZzrN7v1tQK/Y6BGnbkP9gEsXg==",
"path": "microsoft.dotnet.xlifftasks/9.0.0-beta.25255.5",
"hashPath": "microsoft.dotnet.xlifftasks.9.0.0-beta.25255.5.nupkg.sha512"
},
"Microsoft.VisualStudio.Threading.Analyzers/17.13.2": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Qcd8IlaTXZVq3wolBnzby1P7kWihdWaExtD8riumiKuG1sHa8EgjV/o70TMjTaeUMhomBbhfdC9OPwAHoZfnjQ==",
"path": "microsoft.visualstudio.threading.analyzers/17.13.2",
"hashPath": "microsoft.visualstudio.threading.analyzers.17.13.2.nupkg.sha512"
},
"Newtonsoft.Json/13.0.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==",
"path": "newtonsoft.json/13.0.3",
"hashPath": "newtonsoft.json.13.0.3.nupkg.sha512"
},
"Roslyn.Diagnostics.Analyzers/3.11.0-beta1.24081.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-reHqZCDKifA+DURcL8jUfYkMGL4FpgNt5LI0uWTS6IpM8kKVbu/kO8byZsqfhBu4wUzT3MBDcoMfzhZPdENIpg==",
"path": "roslyn.diagnostics.analyzers/3.11.0-beta1.24081.1",
"hashPath": "roslyn.diagnostics.analyzers.3.11.0-beta1.24081.1.nupkg.sha512"
},
"System.Collections.Immutable/9.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-QhkXUl2gNrQtvPmtBTQHb0YsUrDiDQ2QS09YbtTTiSjGcf7NBqtYbrG/BE06zcBPCKEwQGzIv13IVdXNOSub2w==",
"path": "system.collections.immutable/9.0.0",
"hashPath": "system.collections.immutable.9.0.0.nupkg.sha512"
},
"System.CommandLine/2.0.0-beta4.24528.1": {
"type": "package",
"serviceable": true,
"sha512": "sha512-Xt8tsSU8yd0ZpbT9gl5DAwkMYWLo8PV1fq2R/belrUbHVVOIKqhLfbWksbdknUDpmzMHZenBtD6AGAp9uJTa2w==",
"path": "system.commandline/2.0.0-beta4.24528.1",
"hashPath": "system.commandline.2.0.0-beta4.24528.1.nupkg.sha512"
},
"System.Memory/4.5.5": {
"type": "package",
"serviceable": true,
"sha512": "sha512-XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==",
"path": "system.memory/4.5.5",
"hashPath": "system.memory.4.5.5.nupkg.sha512"
},
"System.Runtime.CompilerServices.Unsafe/6.0.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==",
"path": "system.runtime.compilerservices.unsafe/6.0.0",
"hashPath": "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512"
}
}
}

View File

@@ -1,659 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Build" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Build.Framework" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Build.Utilities.Core" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Build.Tasks.Core" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-15.1.0.0" newVersion="15.1.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Collections.Immutable" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.VisualBasic.Core" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-11.0.0.0" newVersion="11.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Win32.Primitives" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="Microsoft.Win32.Registry" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Buffers" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Collections.Concurrent" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Collections.NonGeneric" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Collections.Specialized" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Collections" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.ComponentModel.Annotations" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.ComponentModel.EventBasedAsync" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.ComponentModel.Primitives" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.ComponentModel.TypeConverter" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.ComponentModel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Console" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Data.Common" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.Contracts" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.FileVersionInfo" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.Process" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.StackTrace" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.TextWriterTraceListener" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.TraceSource" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Diagnostics.Tracing" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Drawing.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.IO.Compression.ZipFile" publicKeyToken="b77a5c561934e089"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.IO.Compression" publicKeyToken="b77a5c561934e089" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.IO.FileSystem.AccessControl" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.IO.FileSystem.DriveInfo" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.IO.FileSystem.Watcher" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.IO.IsolatedStorage" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.IO.MemoryMappedFiles" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.IO.Pipes.AccessControl" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.IO.Pipes" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Linq.Expressions" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Linq.Parallel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Linq.Queryable" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Linq" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Memory" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.HttpListener" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.Mail" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.NameResolution" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.NetworkInformation" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.Ping" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.Primitives" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.Requests" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.Security" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.ServicePoint" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.Sockets" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.WebClient" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.WebHeaderCollection" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.WebProxy" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.WebSockets.Client" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Net.WebSockets" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Numerics.Vectors" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.ObjectModel" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Emit.ILGeneration" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Emit.Lightweight" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Emit" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Reflection.Primitives" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Resources.Writer" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.CompilerServices.VisualC" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.InteropServices.RuntimeInformation"
publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.InteropServices" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.Numerics" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.Serialization.Formatters" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.Serialization.Json" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.Serialization.Primitives" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime.Serialization.Xml" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Runtime" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Security.AccessControl" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Security.Claims" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Security.Cryptography.Algorithms" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Security.Cryptography.Cng" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Security.Cryptography.Csp" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Security.Cryptography.Encoding" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Security.Cryptography.Primitives" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Security.Cryptography.X509Certificates" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Security.Principal.Windows" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Text.Encoding.Extensions" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Text.RegularExpressions" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Threading.Overlapped" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Threading.Tasks.Parallel" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Threading.Thread" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Threading.ThreadPool" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Threading" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Transactions.Local" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Web.HttpUtility" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Xml.ReaderWriter" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Xml.XDocument" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Xml.XPath.XDocument" publicKeyToken="b03f5f7f11d50a3a"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Xml.XPath" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Xml.XmlSerializer" publicKeyToken="b03f5f7f11d50a3a" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="netstandard" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-2.1.0.0" newVersion="2.1.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Configuration.ConfigurationManager" publicKeyToken="cc7b13ffcd2ddd51"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.Security.Cryptography.Xml" publicKeyToken="cc7b13ffcd2ddd51"
culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="System.CodeDom" publicKeyToken="cc7b13ffcd2ddd51" culture="neutral"/>
<bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0"/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>

View File

@@ -1,13 +0,0 @@
{
"runtimeOptions": {
"tfm": "net6.0",
"framework": {
"name": "Microsoft.NETCore.App",
"version": "6.0.0"
},
"rollForward": "Major",
"configProperties": {
"System.Reflection.Metadata.MetadataUpdater.IsSupported": false
}
}
}

View File

@@ -0,0 +1,34 @@
# Task: Polish content detail comment composer
## Goal
Make the content detail production comments composer feel like a collaboration input instead of a detached form.
## Scope
- Show the current user's avatar inside the comment composer.
- Put the comment textarea to the right of the avatar.
- Move the post action into the composer bottom row.
- Add bottom-row controls for internal comment intent, media references, and member mentions.
- Upload comment attachments from the user's computer to workspace blob storage.
- Show uploaded comment attachments inline in the comment thread.
- Add the minimal comments API contract and persistence needed for inline attachments.
## Relevant Files
- `frontend/src/features/content/views/ContentItemDetailView.vue`
- `frontend/src/features/content/components/ContentCommentComposer.vue`
- `frontend/src/features/content/components/ContentCommentFeed.vue`
- `frontend/src/features/content/stores/contentItemDetailStore.js`
- `backend/src/Socialize.Api/Modules/Comments/`
- `shared/openapi/openapi.json`
## Validation
```bash
cd frontend
npm run build
cd ..
dotnet build backend/src/Socialize.Api/Socialize.Api.csproj -v minimal
dotnet test backend/tests/Socialize.Tests/Socialize.Tests.csproj --no-build -v minimal
```

View File

@@ -116,6 +116,22 @@ export interface paths {
patch?: never; patch?: never;
trace?: never; trace?: never;
}; };
"/api/organizations/{organizationId}/logo": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post: operations["SocializeApiModulesOrganizationsHandlersChangeOrganizationLogoHandler"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/organizations/{organizationId}": { "/api/organizations/{organizationId}": {
parameters: { parameters: {
query?: never; query?: never;
@@ -708,6 +724,22 @@ export interface paths {
patch?: never; patch?: never;
trace?: never; trace?: never;
}; };
"/api/content-items/{id}/activity": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["SocializeApiModulesContentItemsHandlersGetContentItemActivityHandler"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/content-items/{id}/status": { "/api/content-items/{id}/status": {
parameters: { parameters: {
query?: never; query?: never;
@@ -740,22 +772,6 @@ export interface paths {
patch?: never; patch?: never;
trace?: never; trace?: never;
}; };
"/api/comments/{id}/resolve": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post: operations["SocializeApiModulesCommentsHandlersResolveCommentHandler"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/clients/{id}/portrait": { "/api/clients/{id}/portrait": {
parameters: { parameters: {
query?: never; query?: never;
@@ -868,6 +884,118 @@ export interface paths {
patch?: never; patch?: never;
trace?: never; trace?: never;
}; };
"/api/calendar-integrations/catalog": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["SocializeApiModulesCalendarIntegrationsHandlersListCalendarCatalogHandler"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/calendar-integrations/events": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["SocializeApiModulesCalendarIntegrationsHandlersListCalendarEventsHandler"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/calendar-integrations/sources/{sourceId}/refresh": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post: operations["SocializeApiModulesCalendarIntegrationsHandlersRefreshCalendarSourceHandler"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/calendar-integrations/export-feed": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["SocializeApiModulesCalendarIntegrationsHandlersGetUserCalendarExportFeedHandler"];
put?: never;
post?: never;
delete: operations["SocializeApiModulesCalendarIntegrationsHandlersRevokeUserCalendarExportFeedHandler"];
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/calendar-integrations/export-feed/enable": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post: operations["SocializeApiModulesCalendarIntegrationsHandlersEnableUserCalendarExportFeedHandler"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/calendar-integrations/export-feed/regenerate": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get?: never;
put?: never;
post: operations["SocializeApiModulesCalendarIntegrationsHandlersRegenerateUserCalendarExportFeedHandler"];
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/calendar-integrations/export-feed/{token}.ics": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
get: operations["SocializeApiModulesCalendarIntegrationsHandlersGetUserCalendarExportFeedIcsHandler"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/assets/{id}/revisions": { "/api/assets/{id}/revisions": {
parameters: { parameters: {
query?: never; query?: never;
@@ -1067,18 +1195,38 @@ export interface components {
email: string; email: string;
role: string; role: string;
}; };
SocializeApiModulesOrganizationsHandlersChangeOrganizationLogoResponse: {
blobUrl?: string;
};
SocializeApiModulesOrganizationsHandlersChangeOrganizationLogoRequest: {
/** Format: binary */
file: string;
};
SocializeApiModulesOrganizationsHandlersOrganizationDto: { SocializeApiModulesOrganizationsHandlersOrganizationDto: {
/** Format: guid */ /** Format: guid */
id?: string; id?: string;
name?: string; name?: string;
logoUrl?: string | null;
/** Format: guid */ /** Format: guid */
ownerUserId?: string; ownerUserId?: string;
currentUserPermissions?: string[]; currentUserPermissions?: string[];
members?: components["schemas"]["SocializeApiModulesOrganizationsHandlersOrganizationMemberDto"][]; members?: components["schemas"]["SocializeApiModulesOrganizationsHandlersOrganizationMemberDto"][];
workspaces?: components["schemas"]["SocializeApiModulesWorkspacesHandlersWorkspaceDto"][]; workspaces?: components["schemas"]["SocializeApiModulesWorkspacesHandlersWorkspaceDto"][];
usage?: components["schemas"]["SocializeApiModulesOrganizationsHandlersOrganizationUsageDto"] | null;
/** Format: date-time */ /** Format: date-time */
createdAt?: string; createdAt?: string;
}; };
SocializeApiModulesOrganizationsHandlersOrganizationUsageDto: {
planName?: string;
items?: components["schemas"]["SocializeApiModulesOrganizationsHandlersOrganizationUsageItemDto"][];
};
SocializeApiModulesOrganizationsHandlersOrganizationUsageItemDto: {
key?: string;
/** Format: int32 */
used?: number;
/** Format: int32 */
limit?: number | null;
};
SocializeApiModulesOrganizationsHandlersUpdateOrganizationRequest: { SocializeApiModulesOrganizationsHandlersUpdateOrganizationRequest: {
name: string; name: string;
}; };
@@ -1386,6 +1534,8 @@ export interface components {
publicationTargets: string; publicationTargets: string;
hashtags?: string | null; hashtags?: string | null;
changeSummary?: string | null; changeSummary?: string | null;
/** Format: date-time */
dueDate?: string | null;
}; };
SocializeApiModulesContentItemsHandlersContentItemDetailDto: { SocializeApiModulesContentItemsHandlersContentItemDetailDto: {
/** Format: guid */ /** Format: guid */
@@ -1409,6 +1559,25 @@ export interface components {
/** Format: date-time */ /** Format: date-time */
createdAt?: string; createdAt?: string;
}; };
SocializeApiModulesContentItemsHandlersContentItemActivityEntryDto: {
/** Format: guid */
id?: string;
/** Format: guid */
workspaceId?: string;
/** Format: guid */
contentItemId?: string;
eventType?: string;
entityType?: string;
/** Format: guid */
entityId?: string;
summary?: string;
/** Format: guid */
actorUserId?: string | null;
actorEmail?: string | null;
metadataJson?: string | null;
/** Format: date-time */
createdAt?: string;
};
SocializeApiModulesContentItemsHandlersGetContentItemsRequest: Record<string, never>; SocializeApiModulesContentItemsHandlersGetContentItemsRequest: Record<string, never>;
SocializeApiModulesContentItemsHandlersUpdateContentItemStatusRequest: { SocializeApiModulesContentItemsHandlersUpdateContentItemStatusRequest: {
status: string; status: string;
@@ -1428,11 +1597,13 @@ export interface components {
authorEmail?: string; authorEmail?: string;
authorPortraitUrl?: string | null; authorPortraitUrl?: string | null;
body?: string; body?: string;
isResolved?: boolean; attachmentFileName?: string | null;
attachmentContentType?: string | null;
/** Format: int64 */
attachmentSizeBytes?: number | null;
attachmentBlobUrl?: string | null;
/** Format: date-time */ /** Format: date-time */
createdAt?: string; createdAt?: string;
/** Format: date-time */
resolvedAt?: string | null;
}; };
SocializeApiModulesCommentsHandlersCreateCommentRequest: { SocializeApiModulesCommentsHandlersCreateCommentRequest: {
/** Format: guid */ /** Format: guid */
@@ -1441,7 +1612,9 @@ export interface components {
contentItemId: string; contentItemId: string;
/** Format: guid */ /** Format: guid */
parentCommentId?: string | null; parentCommentId?: string | null;
body: string; body?: string;
/** Format: binary */
attachment?: string | null;
}; };
SocializeApiModulesCommentsHandlersGetCommentsRequest: Record<string, never>; SocializeApiModulesCommentsHandlersGetCommentsRequest: Record<string, never>;
SocializeApiModulesClientsHandlersChangeClientPortraitResponse: { SocializeApiModulesClientsHandlersChangeClientPortraitResponse: {
@@ -1576,7 +1749,65 @@ export interface components {
isEnabled?: boolean; isEnabled?: boolean;
inheritanceMode?: string | null; inheritanceMode?: string | null;
}; };
SocializeApiModulesCalendarIntegrationsHandlersCalendarCatalogEntryDto: {
/** Format: guid */
id?: string;
title?: string;
description?: string;
country?: string | null;
region?: string | null;
language?: string;
category?: string;
cultureOrReligion?: string | null;
providerName?: string;
sourceUrl?: string;
trustLevel?: string;
defaultColor?: string;
};
SocializeApiModulesCalendarIntegrationsHandlersListCalendarCatalogRequest: Record<string, never>;
SocializeApiModulesCalendarIntegrationsHandlersCalendarEventDto: {
/** Format: guid */
id?: string;
/** Format: guid */
calendarSourceId?: string;
sourceEventUid?: string;
title?: string;
description?: string | null;
isAllDay?: boolean;
isFloatingTime?: boolean;
/** Format: date */
startDate?: string;
/** Format: date */
endDate?: string;
/** Format: date-time */
startLocalDateTime?: string | null;
/** Format: date-time */
endLocalDateTime?: string | null;
/** Format: date-time */
startUtc?: string | null;
/** Format: date-time */
endUtc?: string | null;
timeZoneId?: string | null;
recurrenceId?: string | null;
location?: string | null;
sourceUrl?: string | null;
/** Format: date-time */
sourceLastModifiedAt?: string | null;
/** Format: date-time */
importedAt?: string;
};
SocializeApiModulesCalendarIntegrationsHandlersListCalendarEventsRequest: Record<string, never>;
SocializeApiModulesCalendarIntegrationsHandlersListCalendarSourcesRequest: Record<string, never>; SocializeApiModulesCalendarIntegrationsHandlersListCalendarSourcesRequest: Record<string, never>;
SocializeApiModulesCalendarIntegrationsHandlersUserCalendarExportFeedDto: {
isEnabled?: boolean;
feedUrl?: string | null;
/** Format: date-time */
createdAt?: string | null;
/** Format: date-time */
updatedAt?: string | null;
/** Format: date-time */
revokedAt?: string | null;
};
SocializeApiModulesAssetsHandlersAssetRevisionDto: { SocializeApiModulesAssetsHandlersAssetRevisionDto: {
/** Format: guid */ /** Format: guid */
id?: string; id?: string;
@@ -2001,6 +2232,48 @@ export interface operations {
}; };
}; };
}; };
SocializeApiModulesOrganizationsHandlersChangeOrganizationLogoHandler: {
parameters: {
query?: never;
header?: never;
path: {
organizationId: string;
};
cookie?: never;
};
requestBody: {
content: {
"multipart/form-data": components["schemas"]["SocializeApiModulesOrganizationsHandlersChangeOrganizationLogoRequest"];
};
};
responses: {
/** @description Success */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["SocializeApiModulesOrganizationsHandlersChangeOrganizationLogoResponse"];
};
};
/** @description Bad Request */
400: {
headers: {
[name: string]: unknown;
};
content: {
"application/problem+json": components["schemas"]["FastEndpointsErrorResponse"];
};
};
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
SocializeApiModulesOrganizationsHandlersGetOrganizationHandler: { SocializeApiModulesOrganizationsHandlersGetOrganizationHandler: {
parameters: { parameters: {
query?: never; query?: never;
@@ -3353,6 +3626,35 @@ export interface operations {
}; };
}; };
}; };
SocializeApiModulesContentItemsHandlersGetContentItemActivityHandler: {
parameters: {
query?: never;
header?: never;
path: {
id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Success */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["SocializeApiModulesContentItemsHandlersContentItemActivityEntryDto"][];
};
};
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
SocializeApiModulesContentItemsHandlersUpdateContentItemStatusHandler: { SocializeApiModulesContentItemsHandlersUpdateContentItemStatusHandler: {
parameters: { parameters: {
query?: never; query?: never;
@@ -3433,7 +3735,7 @@ export interface operations {
}; };
requestBody: { requestBody: {
content: { content: {
"application/json": components["schemas"]["SocializeApiModulesCommentsHandlersCreateCommentRequest"]; "multipart/form-data": components["schemas"]["SocializeApiModulesCommentsHandlersCreateCommentRequest"];
}; };
}; };
responses: { responses: {
@@ -3464,35 +3766,6 @@ export interface operations {
}; };
}; };
}; };
SocializeApiModulesCommentsHandlersResolveCommentHandler: {
parameters: {
query?: never;
header?: never;
path: {
id: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Success */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["SocializeApiModulesCommentsHandlersCommentDto"];
};
};
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
SocializeApiModulesClientsHandlersChangeClientPortraitHandler: { SocializeApiModulesClientsHandlersChangeClientPortraitHandler: {
parameters: { parameters: {
query?: never; query?: never;
@@ -3923,6 +4196,229 @@ export interface operations {
}; };
}; };
}; };
SocializeApiModulesCalendarIntegrationsHandlersListCalendarCatalogHandler: {
parameters: {
query?: {
search?: string | null;
country?: string | null;
region?: string | null;
language?: string | null;
category?: string | null;
cultureOrReligion?: string | null;
provider?: string | null;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Success */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["SocializeApiModulesCalendarIntegrationsHandlersCalendarCatalogEntryDto"][];
};
};
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
SocializeApiModulesCalendarIntegrationsHandlersListCalendarEventsHandler: {
parameters: {
query?: {
workspaceId?: string | null;
startDate?: string | null;
endDate?: string | null;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Success */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["SocializeApiModulesCalendarIntegrationsHandlersCalendarEventDto"][];
};
};
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
SocializeApiModulesCalendarIntegrationsHandlersRefreshCalendarSourceHandler: {
parameters: {
query?: never;
header?: never;
path: {
sourceId: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Success */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["SocializeApiModulesCalendarIntegrationsHandlersCalendarSourceDto"];
};
};
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
SocializeApiModulesCalendarIntegrationsHandlersGetUserCalendarExportFeedHandler: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Success */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["SocializeApiModulesCalendarIntegrationsHandlersUserCalendarExportFeedDto"];
};
};
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
SocializeApiModulesCalendarIntegrationsHandlersRevokeUserCalendarExportFeedHandler: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Success */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["SocializeApiModulesCalendarIntegrationsHandlersUserCalendarExportFeedDto"];
};
};
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
SocializeApiModulesCalendarIntegrationsHandlersEnableUserCalendarExportFeedHandler: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Success */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["SocializeApiModulesCalendarIntegrationsHandlersUserCalendarExportFeedDto"];
};
};
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
SocializeApiModulesCalendarIntegrationsHandlersRegenerateUserCalendarExportFeedHandler: {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Success */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["SocializeApiModulesCalendarIntegrationsHandlersUserCalendarExportFeedDto"];
};
};
/** @description Unauthorized */
401: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
SocializeApiModulesCalendarIntegrationsHandlersGetUserCalendarExportFeedIcsHandler: {
parameters: {
query?: never;
header?: never;
path: {
token: string;
};
cookie?: never;
};
requestBody?: never;
responses: {
/** @description No Content */
204: {
headers: {
[name: string]: unknown;
};
content?: never;
};
};
};
SocializeApiModulesAssetsHandlersCreateAssetRevisionHandler: { SocializeApiModulesAssetsHandlersCreateAssetRevisionHandler: {
parameters: { parameters: {
query?: never; query?: never;

View File

@@ -5,6 +5,10 @@
:root { :root {
--socialize-primary: #172033; --socialize-primary: #172033;
--socialize-accent: #ff8a3d; --socialize-accent: #ff8a3d;
--socialize-accent-strong: #ef4444;
--socialize-brand-gradient: linear-gradient(135deg, var(--socialize-accent) 0%, var(--socialize-accent-strong) 100%);
--socialize-accent-shadow: rgba(255, 138, 61, 0.28);
--socialize-accent-strong-shadow: rgba(239, 68, 68, 0.28);
--socialize-highlight: #2fa58d; --socialize-highlight: #2fa58d;
--h-background: #f4f6f3; --h-background: #f4f6f3;
--h-on-background: #172033; --h-on-background: #172033;

View File

@@ -218,7 +218,7 @@
.login-brand-mark { .login-brand-mark {
@apply flex h-11 w-11 items-center justify-center rounded-2xl text-lg font-black; @apply flex h-11 w-11 items-center justify-center rounded-2xl text-lg font-black;
background: linear-gradient(135deg, #ff8a3d 0%, #ef4444 100%); background: var(--socialize-brand-gradient);
color: #fffaf2; color: #fffaf2;
} }

View File

@@ -0,0 +1,69 @@
<script setup>
const props = defineProps({
modelValue: {
type: String,
default: '#2F80ED',
},
colors: {
type: Array,
default: () => [
'#2F80ED',
'#0F766E',
'#16A34A',
'#F59E0B',
'#EF4444',
'#EC4899',
'#8B5CF6',
'#475569',
],
},
});
const emit = defineEmits(['update:modelValue']);
</script>
<template>
<div class="color-palette">
<button
v-for="color in props.colors"
:key="color"
class="color-option"
:class="{ active: color.toLowerCase() === props.modelValue.toLowerCase() }"
type="button"
:style="{ background: color }"
:aria-label="color"
@click="emit('update:modelValue', color)"
/>
</div>
</template>
<style scoped>
.color-palette {
display: grid;
grid-template-columns: repeat(4, 1.75rem);
gap: 0.5rem;
width: max-content;
padding: 0.5rem;
border: 1px solid rgba(23, 32, 51, 0.1);
border-radius: 0.75rem;
background: #ffffff;
border-color: rgba(23, 32, 51, 0.1);
box-shadow: 0 18px 40px rgba(23, 32, 51, 0.14);
}
.color-option {
width: 1.75rem;
height: 1.75rem;
border: 1px solid rgba(255, 255, 255, 0.9);
border-radius: 9999px;
border-color: rgba(255, 255, 255, 0.9);
box-shadow: 0 0 0 1px rgba(23, 32, 51, 0.12);
transition: box-shadow 0.15s ease, transform 0.15s ease;
}
.color-option:hover,
.color-option.active {
transform: scale(1.08);
box-shadow: 0 0 0 2px #172033;
}
</style>

View File

@@ -0,0 +1,389 @@
<script setup>
import { computed, reactive, ref } from 'vue';
import { mdiAt, mdiClose, mdiImagePlusOutline, mdiLockOutline, mdiSend } from '@mdi/js';
import AppAvatar from '@/components/AppAvatar.vue';
import { useUserProfileStore } from '@/features/user-profile/stores/userProfileStore.js';
const props = defineProps({
members: {
type: Array,
default: () => [],
},
isPosting: {
type: Boolean,
default: false,
},
replyTarget: {
type: Object,
default: null,
},
variant: {
type: String,
default: 'default',
},
});
const emit = defineEmits(['submit-comment', 'cancel-reply']);
const userProfileStore = useUserProfileStore();
const mediaFileInput = ref(null);
const form = reactive({
body: '',
isInternal: false,
mediaFile: null,
showMentionPicker: false,
});
const currentUserEmail = computed(() => userProfileStore.user?.email ?? '');
const currentUserName = computed(() => userProfileStore.alias);
const isReplyVariant = computed(() => props.variant === 'reply');
const canSubmit = computed(() =>
Boolean(form.body.trim() || form.mediaFile) &&
!props.isPosting
);
function submitComment() {
if (!canSubmit.value) {
return;
}
const bodyParts = [];
const body = form.body.trim();
if (form.isInternal) {
bodyParts.push('[Internal]');
}
if (body) {
bodyParts.push(body);
}
emit('submit-comment', {
body: bodyParts.join('\n\n'),
isInternal: form.isInternal,
mediaReference: form.mediaFile?.name ?? null,
mediaFile: form.mediaFile,
mediaFileName: form.mediaFile?.name ?? null,
mediaFileSize: form.mediaFile?.size ?? null,
mediaFileType: form.mediaFile?.type || null,
parentCommentId: props.replyTarget?.id ?? null,
});
form.body = '';
form.isInternal = false;
form.mediaFile = null;
form.showMentionPicker = false;
if (mediaFileInput.value) {
mediaFileInput.value.value = '';
}
}
function openMediaPicker() {
mediaFileInput.value?.click();
form.showMentionPicker = false;
}
function selectMediaFile(event) {
form.mediaFile = event.target.files?.[0] ?? null;
}
function clearMediaFile() {
form.mediaFile = null;
if (mediaFileInput.value) {
mediaFileInput.value.value = '';
}
}
function toggleMentionPicker() {
form.showMentionPicker = !form.showMentionPicker;
}
function insertMention(member) {
const label = member.displayName || member.email;
if (!label) {
return;
}
const mention = `@${label.replace(/\s+/g, '')}`;
const separator = form.body && !/\s$/.test(form.body) ? ' ' : '';
form.body = `${form.body}${separator}${mention} `;
form.showMentionPicker = false;
}
</script>
<template>
<div
class="comment-composer"
:class="variant"
>
<div
v-if="replyTarget && !isReplyVariant"
class="reply-context"
>
<div>
<span>Replying to</span>
<strong>{{ replyTarget.authorDisplayName }}</strong>
</div>
<button
type="button"
title="Cancel reply"
@click="emit('cancel-reply')"
>
<v-icon :icon="mdiClose" />
</button>
</div>
<div class="comment-composer-main">
<AppAvatar
v-if="!isReplyVariant"
:name="currentUserName"
:email="currentUserEmail"
:src="userProfileStore.portraitUrl"
size="md"
/>
<textarea
v-model="form.body"
class="comment-textarea"
:placeholder="replyTarget ? 'Write a reply...' : 'Write a comment...'"
></textarea>
</div>
<div
v-if="form.mediaFile"
class="selected-media-file"
>
<span>{{ form.mediaFile.name }}</span>
<button
type="button"
title="Remove selected media"
@click="clearMediaFile"
>
<v-icon :icon="mdiClose" />
</button>
</div>
<div
v-if="form.showMentionPicker"
class="mention-picker"
>
<button
v-for="member in members"
:key="member.id"
class="mention-option"
type="button"
@click="insertMention(member)"
>
<AppAvatar
:name="member.displayName"
:email="member.email"
size="sm"
/>
<span>{{ member.displayName }}</span>
</button>
<div
v-if="!members.length"
class="empty-note"
>
No workspace members are available to mention.
</div>
</div>
<div class="comment-composer-toolbar">
<div class="comment-tool-actions">
<label
class="icon-tool-button internal-toggle"
:class="{ active: form.isInternal }"
title="Internal comment"
>
<input
v-model="form.isInternal"
type="checkbox"
/>
<v-icon :icon="mdiLockOutline" />
</label>
<button
class="icon-tool-button"
type="button"
title="Upload media from computer"
:class="{ active: form.mediaFile }"
@click="openMediaPicker"
>
<v-icon :icon="mdiImagePlusOutline" />
</button>
<input
ref="mediaFileInput"
class="sr-only"
type="file"
accept="image/png,image/jpeg,image/jpg"
@change="selectMediaFile"
/>
<button
class="icon-tool-button"
type="button"
title="Mention a member"
:class="{ active: form.showMentionPicker }"
@click="toggleMentionPicker"
>
<v-icon :icon="mdiAt" />
</button>
<button
class="post-button"
type="button"
:disabled="!canSubmit"
@click="submitComment"
>
<v-icon :icon="mdiSend" />
{{ isPosting ? 'Posting...' : 'Post' }}
</button>
</div>
</div>
</div>
</template>
<style scoped>
.comment-composer {
@apply flex flex-col gap-3 rounded-[1.25rem] border p-4;
background: #fffdf8;
border-color: rgba(23, 32, 51, 0.12);
}
.comment-composer.reply {
@apply rounded-[1rem] p-3;
background: rgba(255, 253, 248, 0.84);
}
.comment-composer-main {
@apply flex items-start gap-3;
}
.reply-context {
@apply flex items-center justify-between gap-3 rounded-[1rem] border px-3 py-2;
background: rgba(15, 118, 110, 0.06);
border-color: rgba(15, 118, 110, 0.14);
}
.reply-context div {
@apply flex min-w-0 items-center gap-2 text-sm;
}
.reply-context span {
color: #526178;
}
.reply-context strong {
@apply truncate;
color: #172033;
}
.reply-context button {
@apply inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full transition;
color: #526178;
}
.reply-context button:hover,
.reply-context button:focus-visible {
background: rgba(15, 118, 110, 0.12);
color: #0f766e;
}
.comment-textarea {
@apply min-h-24 flex-1 resize-y border-0 bg-transparent text-sm leading-6;
color: #172033;
outline: none;
}
.comment-textarea::placeholder {
color: #7c8798;
}
.selected-media-file {
@apply flex items-center justify-between gap-3 rounded-[1rem] border px-3 py-2 text-sm;
background: rgba(23, 32, 51, 0.03);
border-color: rgba(23, 32, 51, 0.08);
}
.selected-media-file span {
@apply min-w-0 truncate font-semibold;
color: #172033;
}
.selected-media-file button {
@apply inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full transition;
color: #526178;
}
.selected-media-file button:hover,
.selected-media-file button:focus-visible {
background: rgba(185, 28, 28, 0.1);
color: #b91c1c;
}
.mention-picker {
@apply grid max-h-52 gap-2 overflow-y-auto rounded-[1rem] border p-2;
background: rgba(23, 32, 51, 0.03);
border-color: rgba(23, 32, 51, 0.08);
}
.mention-option {
@apply flex items-center gap-3 rounded-[0.875rem] px-2 py-2 text-left text-sm font-semibold transition;
color: #172033;
}
.mention-option:hover {
background: rgba(15, 118, 110, 0.1);
color: #0f766e;
}
.empty-note {
@apply text-sm leading-6;
color: #526178;
}
.comment-composer-toolbar {
@apply flex items-center justify-end gap-2 border-t pt-3;
border-color: rgba(23, 32, 51, 0.08);
}
.internal-toggle {
@apply cursor-pointer;
}
.internal-toggle input {
@apply sr-only;
}
.comment-tool-actions {
@apply flex min-w-0 items-center justify-end gap-2;
}
.icon-tool-button,
.post-button {
@apply inline-flex min-h-10 items-center justify-center gap-2 rounded-full px-3 text-sm font-bold transition;
}
.icon-tool-button {
@apply w-10;
background: rgba(23, 32, 51, 0.06);
color: #526178;
}
.icon-tool-button:hover,
.icon-tool-button.active {
background: rgba(15, 118, 110, 0.12);
color: #0f766e;
}
.post-button {
@apply px-4;
background: #172033;
color: #fffaf2;
}
.post-button:disabled {
cursor: not-allowed;
opacity: 0.55;
}
</style>

View File

@@ -0,0 +1,386 @@
<script setup>
import { computed, ref } from 'vue';
import {
mdiCheckCircleOutline,
mdiDeleteOutline,
mdiDotsVertical,
mdiEmoticonPlusOutline,
mdiPencilOutline,
mdiReplyOutline,
} from '@mdi/js';
import AppAvatar from '@/components/AppAvatar.vue';
import ContentCommentComposer from '@/features/content/components/ContentCommentComposer.vue';
const props = defineProps({
comments: {
type: Array,
default: () => [],
},
members: {
type: Array,
default: () => [],
},
isPosting: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(['submit-comment']);
const activeReplyCommentId = ref(null);
const commentThreads = computed(() => {
const repliesByParentId = new Map();
const roots = [];
for (const comment of props.comments) {
if (comment.parentCommentId) {
const existing = repliesByParentId.get(comment.parentCommentId) ?? [];
existing.push(comment);
repliesByParentId.set(comment.parentCommentId, existing);
} else {
roots.push(comment);
}
}
return roots.map(comment => ({
comment,
replies: repliesByParentId.get(comment.id) ?? [],
}));
});
function formatDateTime(value) {
return value ? new Date(value).toLocaleString() : '';
}
function hasImageAttachment(comment) {
return Boolean(comment.attachmentBlobUrl && comment.attachmentContentType?.startsWith('image/'));
}
function submitReply(payload) {
emit('submit-comment', payload);
activeReplyCommentId.value = null;
}
</script>
<template>
<div class="timeline-list">
<article
v-for="thread in commentThreads"
:key="thread.comment.id"
class="comment-row"
tabindex="0"
>
<div class="comment-row-header">
<div class="comment-author">
<AppAvatar
:name="thread.comment.authorDisplayName"
:email="thread.comment.authorEmail"
:src="thread.comment.authorPortraitUrl"
size="sm"
/>
<div class="comment-author-meta">
<strong>{{ thread.comment.authorDisplayName }}</strong>
<small>{{ formatDateTime(thread.comment.createdAt) }}</small>
</div>
</div>
<div class="comment-actions">
<button
class="comment-action-button"
type="button"
title="Add reaction"
>
<v-icon :icon="mdiEmoticonPlusOutline" />
</button>
<button
class="comment-action-button"
type="button"
title="Resolve"
>
<v-icon :icon="mdiCheckCircleOutline" />
</button>
<button
class="comment-action-button"
type="button"
title="Reply"
@click="activeReplyCommentId = thread.comment.id"
>
<v-icon :icon="mdiReplyOutline" />
</button>
<details class="comment-more-menu">
<summary
class="comment-action-button"
title="More comment actions"
>
<v-icon :icon="mdiDotsVertical" />
</summary>
<div class="comment-action-menu">
<button
class="comment-menu-item"
type="button"
>
<v-icon :icon="mdiPencilOutline" />
Edit
</button>
<button
class="comment-menu-item danger"
type="button"
>
<v-icon :icon="mdiDeleteOutline" />
Delete
</button>
</div>
</details>
</div>
</div>
<p
v-if="thread.comment.body"
class="comment-body"
>
{{ thread.comment.body }}
</p>
<a
v-if="hasImageAttachment(thread.comment)"
class="comment-attachment"
:href="thread.comment.attachmentBlobUrl"
target="_blank"
rel="noreferrer"
>
<img
:src="thread.comment.attachmentBlobUrl"
:alt="thread.comment.attachmentFileName || 'Comment attachment'"
/>
</a>
<div
v-if="thread.replies.length"
class="reply-list"
>
<article
v-for="reply in thread.replies"
:key="reply.id"
class="reply-row"
>
<AppAvatar
:name="reply.authorDisplayName"
:email="reply.authorEmail"
:src="reply.authorPortraitUrl"
size="sm"
/>
<div>
<div class="reply-meta">
<strong>{{ reply.authorDisplayName }}</strong>
<small>{{ formatDateTime(reply.createdAt) }}</small>
</div>
<p v-if="reply.body">{{ reply.body }}</p>
<a
v-if="hasImageAttachment(reply)"
class="comment-attachment reply-attachment"
:href="reply.attachmentBlobUrl"
target="_blank"
rel="noreferrer"
>
<img
:src="reply.attachmentBlobUrl"
:alt="reply.attachmentFileName || 'Reply attachment'"
/>
</a>
</div>
</article>
</div>
<ContentCommentComposer
v-if="activeReplyCommentId === thread.comment.id"
variant="reply"
:members="members"
:is-posting="isPosting"
:reply-target="thread.comment"
@submit-comment="submitReply"
@cancel-reply="activeReplyCommentId = null"
/>
</article>
<div
v-if="!commentThreads.length"
class="empty-note"
>
No comments yet.
</div>
</div>
</template>
<style scoped>
.timeline-list {
@apply flex flex-col gap-4;
}
.empty-note {
@apply text-sm leading-6;
color: #526178;
}
.comment-row {
@apply relative flex flex-col gap-3 rounded-[1rem] border p-4 transition;
background: #f8fafc;
border-color: rgba(23, 32, 51, 0.08);
outline: none;
}
.comment-row:hover,
.comment-row:focus-within,
.comment-row:focus {
background: #fffdf8;
border-color: rgba(15, 118, 110, 0.24);
box-shadow: 0 16px 34px rgba(23, 32, 51, 0.08);
}
.comment-row-header {
@apply flex min-h-9 w-full items-center;
}
.comment-author {
@apply flex w-full min-w-0 items-center gap-3;
}
.comment-author-meta {
@apply flex w-full min-w-0 flex-col;
}
.comment-author strong {
@apply truncate text-sm;
color: #172033;
}
.comment-author small {
@apply text-xs leading-5;
color: #7c8798;
}
.comment-actions {
@apply absolute right-3 top-3 z-20 flex items-center gap-1 rounded-full border px-1 py-1 opacity-0 shadow-lg transition;
background: rgba(255, 255, 255, 0.92);
border-color: rgba(23, 32, 51, 0.08);
pointer-events: none;
backdrop-filter: blur(10px);
}
.comment-row:hover .comment-actions,
.comment-row:focus-within .comment-actions,
.comment-row:focus .comment-actions {
opacity: 1;
pointer-events: auto;
}
.comment-action-button {
@apply inline-flex h-8 w-8 items-center justify-center rounded-full transition;
color: #526178;
}
.comment-action-button:hover,
.comment-action-button:focus-visible {
background: rgba(15, 118, 110, 0.12);
color: #0f766e;
}
.comment-more-menu {
@apply relative;
}
.comment-more-menu summary {
list-style: none;
}
.comment-more-menu summary::-webkit-details-marker {
display: none;
}
.comment-more-menu[open] .comment-action-button {
background: rgba(15, 118, 110, 0.12);
color: #0f766e;
}
.comment-more-menu[open] .comment-action-menu,
.comment-more-menu:hover .comment-action-menu,
.comment-more-menu:focus-within .comment-action-menu {
display: flex;
}
.comment-action-menu {
@apply absolute right-0 top-[calc(100%+0.375rem)] z-20 hidden min-w-36 flex-col gap-1 rounded-[0.875rem] border p-1 shadow-xl;
background: #ffffff;
border-color: rgba(23, 32, 51, 0.1);
}
.comment-menu-item {
@apply flex items-center gap-2 rounded-[0.625rem] px-3 py-2 text-sm font-semibold transition;
color: #172033;
}
.comment-menu-item:hover,
.comment-menu-item:focus-visible {
background: rgba(15, 118, 110, 0.1);
color: #0f766e;
}
.comment-menu-item.danger {
color: #b91c1c;
}
.comment-menu-item.danger:hover,
.comment-menu-item.danger:focus-visible {
background: rgba(185, 28, 28, 0.1);
color: #b91c1c;
}
.comment-body {
@apply whitespace-pre-line text-sm leading-6;
color: #172033;
}
.comment-attachment {
@apply block w-fit max-w-full overflow-hidden rounded-[0.875rem] border;
border-color: rgba(23, 32, 51, 0.1);
background: #ffffff;
}
.comment-attachment img {
@apply block max-h-72 max-w-full object-contain;
}
.reply-attachment img {
@apply max-h-56;
}
.reply-list {
@apply ml-2 mt-2 flex flex-col gap-4;
}
.reply-row {
@apply flex items-start gap-3;
}
.reply-row > div {
@apply flex min-w-0 flex-col gap-1;
}
.reply-meta {
@apply flex min-w-0 flex-col;
}
.reply-meta strong {
@apply truncate text-sm;
color: #172033;
}
.reply-meta small {
@apply text-xs leading-5;
color: #7c8798;
}
.reply-row p {
@apply whitespace-pre-line text-sm leading-6;
color: #172033;
}
</style>

View File

@@ -128,6 +128,29 @@ export const useCalendarIntegrationsStore = defineStore('calendar-integrations',
} }
} }
async function updateSource(sourceId, payload) {
if (!sourceId) {
return null;
}
error.value = null;
try {
const response = await client.put(`/api/calendar-integrations/sources/${sourceId}`, payload);
const updatedSource = response.data;
if (updatedSource) {
sources.value = sources.value.map(source =>
source.id === updatedSource.id ? updatedSource : source
);
}
return updatedSource;
} catch (updateError) {
console.error('Failed to update calendar source:', updateError);
error.value = 'Failed to update calendar source.';
throw updateError;
}
}
async function refreshSource(sourceId) { async function refreshSource(sourceId) {
if (!sourceId) { if (!sourceId) {
return null; return null;
@@ -176,6 +199,7 @@ export const useCalendarIntegrationsStore = defineStore('calendar-integrations',
fetchEvents, fetchEvents,
searchCatalog, searchCatalog,
createSource, createSource,
updateSource,
refreshSource, refreshSource,
toggleSourceVisibility, toggleSourceVisibility,
}; };

View File

@@ -129,11 +129,8 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
actions.comment = true; actions.comment = true;
try { try {
const response = await client.post('/api/comments', { const requestPayload = buildCommentPayload(contentItemId, payload);
...payload, const response = await client.post('/api/comments', requestPayload);
contentItemId,
workspaceId: currentItemWorkspaceId(),
});
if (response.data) { if (response.data) {
comments.value = [...comments.value, response.data]; comments.value = [...comments.value, response.data];
await fetchActivity(contentItemId); await fetchActivity(contentItemId);
@@ -144,6 +141,22 @@ export const useContentItemDetailStore = defineStore('content-item-detail', () =
} }
} }
function buildCommentPayload(contentItemId, payload) {
const workspaceId = currentItemWorkspaceId();
const formData = new FormData();
formData.append('workspaceId', workspaceId);
formData.append('contentItemId', contentItemId);
formData.append('body', payload.body ?? '');
if (payload.parentCommentId) {
formData.append('parentCommentId', payload.parentCommentId);
}
if (payload.mediaFile) {
formData.append('attachment', payload.mediaFile, payload.mediaFile.name || 'comment-attachment');
}
return formData;
}
async function submitDecision(contentItemId, approvalId, payload) { async function submitDecision(contentItemId, approvalId, payload) {
actions.decision = true; actions.decision = true;

View File

@@ -4,10 +4,11 @@
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { useSessionStorage } from '@vueuse/core'; import { useSessionStorage } from '@vueuse/core';
import { mdiArrowLeft } from '@mdi/js'; import { mdiArrowLeft } from '@mdi/js';
import AppAvatar from '@/components/AppAvatar.vue';
import { useChannelsStore } from '@/features/channels/stores/channelsStore.js'; import { useChannelsStore } from '@/features/channels/stores/channelsStore.js';
import { useClientsStore } from '@/features/clients/stores/clientsStore.js'; import { useClientsStore } from '@/features/clients/stores/clientsStore.js';
import ContentApprovalPanel from '@/features/content/components/ContentApprovalPanel.vue'; import ContentApprovalPanel from '@/features/content/components/ContentApprovalPanel.vue';
import ContentCommentComposer from '@/features/content/components/ContentCommentComposer.vue';
import ContentCommentFeed from '@/features/content/components/ContentCommentFeed.vue';
import { useCalendarIntegrationsStore } from '@/features/content/stores/calendarIntegrationsStore.js'; import { useCalendarIntegrationsStore } from '@/features/content/stores/calendarIntegrationsStore.js';
import { useContentItemDetailStore } from '@/features/content/stores/contentItemDetailStore.js'; import { useContentItemDetailStore } from '@/features/content/stores/contentItemDetailStore.js';
import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js'; import { useContentItemsStore } from '@/features/content/stores/contentItemsStore.js';
@@ -39,10 +40,6 @@
placements: [], placements: [],
}); });
const commentForm = reactive({
body: '',
});
const assetForm = reactive({ const assetForm = reactive({
assetType: 'Image', assetType: 'Image',
displayName: '', displayName: '',
@@ -102,6 +99,11 @@
{ key: 'assets', label: 'Assets', count: detailStore.assets.length }, { key: 'assets', label: 'Assets', count: detailStore.assets.length },
{ key: 'activity', label: 'Activity', count: detailStore.activity.length }, { key: 'activity', label: 'Activity', count: detailStore.activity.length },
]); ]);
const workspaceMembers = computed(() =>
contentWorkspaceId.value
? workspaceStore.membersByWorkspace[contentWorkspaceId.value] ?? []
: []
);
const selectedDateKey = computed(() => /^\d{4}-\d{2}-\d{2}$/.test(form.dueDate) ? form.dueDate : ''); const selectedDateKey = computed(() => /^\d{4}-\d{2}-\d{2}$/.test(form.dueDate) ? form.dueDate : '');
const contextAnchorDate = computed(() => selectedDateKey.value ? parseDateKey(selectedDateKey.value) : startOfDay(new Date())); const contextAnchorDate = computed(() => selectedDateKey.value ? parseDateKey(selectedDateKey.value) : startOfDay(new Date()));
const calendarFetchRange = computed(() => { const calendarFetchRange = computed(() => {
@@ -450,13 +452,12 @@
await detailStore.submitDecision(contentItemId.value, approvalId, payload); await detailStore.submitDecision(contentItemId.value, approvalId, payload);
} }
async function submitComment() { async function submitComment(payload) {
if (!contentItemId.value || !commentForm.body.trim()) { if (!contentItemId.value || !payload?.body?.trim()) {
return; return;
} }
await detailStore.addComment(contentItemId.value, { body: commentForm.body.trim() }); await detailStore.addComment(contentItemId.value, payload);
commentForm.body = '';
} }
function inferGoogleDriveFileId(value) { function inferGoogleDriveFileId(value) {
@@ -674,6 +675,7 @@
await Promise.all([ await Promise.all([
calendarStore.fetchSources(workspaceId), calendarStore.fetchSources(workspaceId),
calendarStore.fetchEvents({ workspaceId, startDate, endDate }), calendarStore.fetchEvents({ workspaceId, startDate, endDate }),
workspaceStore.fetchMembers(workspaceId),
]); ]);
}, },
{ immediate: true } { immediate: true }
@@ -1127,50 +1129,18 @@
</div> </div>
<template v-if="activeProductionTab === 'comments'"> <template v-if="activeProductionTab === 'comments'">
<div class="panel-stack"> <ContentCommentComposer
<label class="field field-wide"> :members="workspaceMembers"
<span>New comment</span> :is-posting="detailStore.actions.comment"
<textarea v-model="commentForm.body"></textarea> @submit-comment="submitComment"
</label> />
<button
class="primary-button"
:disabled="detailStore.actions.comment"
@click="submitComment"
>
{{ detailStore.actions.comment ? 'Posting...' : 'Post comment' }}
</button>
</div>
<div class="timeline-list"> <ContentCommentFeed
<article :comments="detailStore.comments"
v-for="comment in detailStore.comments" :members="workspaceMembers"
:key="comment.id" :is-posting="detailStore.actions.comment"
class="timeline-row" @submit-comment="submitComment"
> />
<div class="identity-row align-start">
<AppAvatar
:name="comment.authorDisplayName"
:email="comment.authorEmail"
:src="comment.authorPortraitUrl"
size="sm"
/>
<div>
<strong>{{ comment.authorDisplayName }}</strong>
<span>{{ comment.body }}</span>
</div>
</div>
<div class="timeline-actions">
<small>{{ formatDateTime(comment.createdAt) }}</small>
</div>
</article>
<div
v-if="!detailStore.comments.length"
class="empty-note"
>
No comments yet.
</div>
</div>
</template> </template>
<template v-else-if="activeProductionTab === 'revisions'"> <template v-else-if="activeProductionTab === 'revisions'">

File diff suppressed because it is too large Load Diff

View File

@@ -35,13 +35,20 @@
.feedback-entry-button { .feedback-entry-button {
@apply flex h-12 items-center gap-2 rounded-full border px-4 text-sm font-bold shadow-lg transition-colors; @apply flex h-12 items-center gap-2 rounded-full border px-4 text-sm font-bold shadow-lg transition-colors;
background: #172033; background: var(--socialize-accent-strong);
border-color: rgba(255, 255, 255, 0.4); border-color: rgba(255, 255, 255, 0.55);
color: #fffaf2; color: #ffffff;
box-shadow: 0 16px 34px var(--socialize-accent-strong-shadow);
} }
.feedback-entry-button:hover { .feedback-entry-button:hover {
background: #0f766e; background: color-mix(in srgb, var(--socialize-accent-strong) 82%, var(--socialize-primary));
box-shadow: 0 18px 38px var(--socialize-accent-strong-shadow);
}
.feedback-entry-button:focus-visible {
outline: 3px solid color-mix(in srgb, var(--socialize-accent) 35%, transparent);
outline-offset: 3px;
} }
.feedback-entry-button span { .feedback-entry-button span {

View File

@@ -1,18 +1,78 @@
<script setup> <script setup>
import { computed } from 'vue'; import { computed, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useAuthStore } from '@/features/auth/stores/authStore.js'; import { useAuthStore } from '@/features/auth/stores/authStore.js';
import WorkspaceSelector from './WorkspaceSelector.vue'; import WorkspaceSelector from './WorkspaceSelector.vue';
import { import {
mdiCalendar,
mdiChevronDown,
mdiCogOutline, mdiCogOutline,
mdiFormatListBulleted,
mdiLogin, mdiLogin,
mdiPlus, mdiPlus,
mdiTable,
} from '@mdi/js'; } from '@mdi/js';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const authStore = useAuthStore(); const authStore = useAuthStore();
const isContentViewMenuOpen = ref(false);
const contentViewActions = computed(() => {
if (route.name !== 'content-items') {
return [];
}
const query = route.query;
const activeView = ['upcoming', 'table'].includes(query.view) ? query.view : 'calendar';
return [
{
key: 'calendar',
label: t('contentItems.views.calendar'),
icon: mdiCalendar,
active: activeView === 'calendar',
route: {
name: 'content-items',
query: {
...query,
view: query.view === 'week' ? 'week' : 'month',
},
},
},
{
key: 'upcoming',
label: t('contentItems.upcoming'),
icon: mdiFormatListBulleted,
active: activeView === 'upcoming',
route: {
name: 'content-items',
query: {
...query,
view: 'upcoming',
},
},
},
{
key: 'table',
label: t('contentItems.views.table'),
icon: mdiTable,
active: activeView === 'table',
route: {
name: 'content-items',
query: {
...query,
view: 'table',
},
},
},
];
});
const activeContentViewAction = computed(() =>
contentViewActions.value.find(action => action.active) ?? contentViewActions.value[0]
);
const appBarActions = computed(() => { const appBarActions = computed(() => {
if (!authStore.isAuthenticated) { if (!authStore.isAuthenticated) {
@@ -79,6 +139,46 @@
</router-link> </router-link>
</template> </template>
<div
v-if="contentViewActions.length"
class="view-selector"
>
<button
class="menu-item-action view-selector-button"
type="button"
@click="isContentViewMenuOpen = !isContentViewMenuOpen"
>
<v-icon :icon="activeContentViewAction.icon" />
<span class="label">{{ activeContentViewAction.label }}</span>
<v-icon
class="selector-chevron"
:icon="mdiChevronDown"
/>
</button>
<div
v-if="isContentViewMenuOpen"
class="view-selector-menu"
>
<router-link
v-for="action in contentViewActions"
:key="action.key"
:to="action.route"
class="menu-action-link"
@click="isContentViewMenuOpen = false"
>
<button
class="view-selector-option"
:class="{ 'view-selector-option-active': action.active }"
type="button"
>
<v-icon :icon="action.icon" />
<span>{{ action.label }}</span>
</button>
</router-link>
</div>
</div>
<router-link <router-link
v-for="action in appBarActions" v-for="action in appBarActions"
:key="action.key" :key="action.key"
@@ -121,6 +221,24 @@
@apply justify-end; @apply justify-end;
} }
.view-selector {
@apply relative;
}
.view-selector-button {
@apply min-w-11 justify-between;
}
.selector-chevron {
@apply text-base;
}
.view-selector-menu {
@apply absolute right-0 top-[calc(100%+0.5rem)] z-30 flex min-w-52 flex-col gap-1 rounded-[1rem] border p-2 shadow-xl;
background: #ffffff;
border-color: rgba(23, 32, 51, 0.1);
}
.label { .label {
@apply hidden text-nowrap md:inline; @apply hidden text-nowrap md:inline;
} }
@@ -137,6 +255,17 @@
color: #fffaf2; color: #fffaf2;
} }
.view-selector-option {
@apply flex min-h-11 w-full items-center gap-3 rounded-[0.75rem] px-3 text-left text-sm font-semibold transition;
color: #172033;
}
.view-selector-option:hover,
.view-selector-option-active {
background: #172033;
color: #fffaf2;
}
.menu-item-action i { .menu-item-action i {
@apply text-xl; @apply text-xl;
} }

View File

@@ -665,7 +665,7 @@
.brand-mark { .brand-mark {
@apply flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-[1.1rem] text-xl font-black; @apply flex h-11 w-11 flex-shrink-0 items-center justify-center rounded-[1.1rem] text-xl font-black;
background: linear-gradient(135deg, #ff8a3d 0%, #ef4444 100%); background: var(--socialize-brand-gradient);
color: #fffaf2; color: #fffaf2;
} }

View File

@@ -859,6 +859,17 @@
"newItem": "New content item", "newItem": "New content item",
"createTitle": "Create content item", "createTitle": "Create content item",
"upcoming": "Upcoming", "upcoming": "Upcoming",
"views": {
"calendar": "Calendar view",
"table": "Table"
},
"table": {
"content": "Content",
"status": "Status",
"channels": "Channels",
"revision": "Revision",
"dueDate": "Due date"
},
"loading": "Loading content items...", "loading": "Loading content items...",
"empty": "No content items are available for the active workspace.", "empty": "No content items are available for the active workspace.",
"noDueDate": "No due date", "noDueDate": "No due date",
@@ -879,9 +890,14 @@
"category": "Category", "category": "Category",
"calendarName": "Calendar name", "calendarName": "Calendar name",
"icsUrl": "ICS URL", "icsUrl": "ICS URL",
"editColor": "Edit calendar color",
"allDay": "All day", "allDay": "All day",
"context": "Calendar context", "context": "Calendar context",
"importedEvent": "Imported calendar", "importedEvent": "Imported calendar",
"previousWeek": "Previous week",
"nextWeek": "Next week",
"previousMonth": "Previous month",
"nextMonth": "Next month",
"errors": { "errors": {
"required": "Calendar name and URL are required.", "required": "Calendar name and URL are required.",
"duplicate": "This calendar has already been added.", "duplicate": "This calendar has already been added.",

View File

@@ -859,6 +859,17 @@
"newItem": "Nouvel élément de contenu", "newItem": "Nouvel élément de contenu",
"createTitle": "Créer un élément de contenu", "createTitle": "Créer un élément de contenu",
"upcoming": "À venir", "upcoming": "À venir",
"views": {
"calendar": "Vue calendrier",
"table": "Tableau"
},
"table": {
"content": "Contenu",
"status": "Statut",
"channels": "Canaux",
"revision": "Révision",
"dueDate": "Échéance"
},
"loading": "Chargement des éléments de contenu...", "loading": "Chargement des éléments de contenu...",
"empty": "Aucun élément de contenu n'est disponible pour l'espace actif.", "empty": "Aucun élément de contenu n'est disponible pour l'espace actif.",
"noDueDate": "Aucune échéance", "noDueDate": "Aucune échéance",
@@ -879,9 +890,14 @@
"category": "Catégorie", "category": "Catégorie",
"calendarName": "Nom du calendrier", "calendarName": "Nom du calendrier",
"icsUrl": "URL ICS", "icsUrl": "URL ICS",
"editColor": "Modifier la couleur du calendrier",
"allDay": "Toute la journée", "allDay": "Toute la journée",
"context": "Contexte calendrier", "context": "Contexte calendrier",
"importedEvent": "Calendrier importé", "importedEvent": "Calendrier importé",
"previousWeek": "Semaine précédente",
"nextWeek": "Semaine suivante",
"previousMonth": "Mois précédent",
"nextMonth": "Mois suivant",
"errors": { "errors": {
"required": "Le nom et l'URL du calendrier sont requis.", "required": "Le nom et l'URL du calendrier sont requis.",
"duplicate": "Ce calendrier a déjà été ajouté.", "duplicate": "Ce calendrier a déjà été ajouté.",

View File

@@ -278,7 +278,7 @@
.site-brand-mark { .site-brand-mark {
@apply flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-2xl text-base font-black; @apply flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-2xl text-base font-black;
background: linear-gradient(135deg, #ff8a3d 0%, #ef4444 100%); background: var(--socialize-brand-gradient);
color: #fffaf2; color: #fffaf2;
} }

View File

@@ -7,7 +7,7 @@
}, },
"servers": [ "servers": [
{ {
"url": "http://localhost:5081" "url": "http://localhost:5080"
} }
], ],
"paths": { "paths": {
@@ -448,6 +448,69 @@
] ]
} }
}, },
"/api/organizations/{organizationId}/logo": {
"post": {
"tags": [
"Organizations",
"Api"
],
"operationId": "SocializeApiModulesOrganizationsHandlersChangeOrganizationLogoHandler",
"parameters": [
{
"name": "organizationId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "guid"
}
}
],
"requestBody": {
"x-name": "ChangeOrganizationLogoRequest",
"description": "",
"content": {
"multipart/form-data": {
"schema": {
"$ref": "#/components/schemas/SocializeApiModulesOrganizationsHandlersChangeOrganizationLogoRequest"
}
}
},
"required": true,
"x-position": 1
},
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SocializeApiModulesOrganizationsHandlersChangeOrganizationLogoResponse"
}
}
}
},
"400": {
"description": "Bad Request",
"content": {
"application/problem+json": {
"schema": {
"$ref": "#/components/schemas/FastEndpointsErrorResponse"
}
}
}
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"JWTBearerAuth": []
}
]
}
},
"/api/organizations/{organizationId}": { "/api/organizations/{organizationId}": {
"get": { "get": {
"tags": [ "tags": [
@@ -2252,6 +2315,48 @@
] ]
} }
}, },
"/api/content-items/{id}/activity": {
"get": {
"tags": [
"Content Items",
"Api"
],
"operationId": "SocializeApiModulesContentItemsHandlersGetContentItemActivityHandler",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SocializeApiModulesContentItemsHandlersContentItemActivityEntryDto"
}
}
}
}
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"JWTBearerAuth": []
}
]
}
},
"/api/content-items/{id}/status": { "/api/content-items/{id}/status": {
"post": { "post": {
"tags": [ "tags": [
@@ -2325,7 +2430,7 @@
"x-name": "CreateCommentRequest", "x-name": "CreateCommentRequest",
"description": "", "description": "",
"content": { "content": {
"application/json": { "multipart/form-data": {
"schema": { "schema": {
"$ref": "#/components/schemas/SocializeApiModulesCommentsHandlersCreateCommentRequest" "$ref": "#/components/schemas/SocializeApiModulesCommentsHandlersCreateCommentRequest"
} }
@@ -2407,45 +2512,6 @@
] ]
} }
}, },
"/api/comments/{id}/resolve": {
"post": {
"tags": [
"Comments",
"Api"
],
"operationId": "SocializeApiModulesCommentsHandlersResolveCommentHandler",
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SocializeApiModulesCommentsHandlersCommentDto"
}
}
}
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"JWTBearerAuth": []
}
]
}
},
"/api/clients/{id}/portrait": { "/api/clients/{id}/portrait": {
"post": { "post": {
"tags": [ "tags": [
@@ -3045,6 +3111,335 @@
] ]
} }
}, },
"/api/calendar-integrations/catalog": {
"get": {
"tags": [
"Calendar Integrations",
"Api"
],
"operationId": "SocializeApiModulesCalendarIntegrationsHandlersListCalendarCatalogHandler",
"parameters": [
{
"name": "search",
"in": "query",
"schema": {
"type": "string",
"nullable": true
}
},
{
"name": "country",
"in": "query",
"schema": {
"type": "string",
"nullable": true
}
},
{
"name": "region",
"in": "query",
"schema": {
"type": "string",
"nullable": true
}
},
{
"name": "language",
"in": "query",
"schema": {
"type": "string",
"nullable": true
}
},
{
"name": "category",
"in": "query",
"schema": {
"type": "string",
"nullable": true
}
},
{
"name": "cultureOrReligion",
"in": "query",
"schema": {
"type": "string",
"nullable": true
}
},
{
"name": "provider",
"in": "query",
"schema": {
"type": "string",
"nullable": true
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SocializeApiModulesCalendarIntegrationsHandlersCalendarCatalogEntryDto"
}
}
}
}
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"JWTBearerAuth": []
}
]
}
},
"/api/calendar-integrations/events": {
"get": {
"tags": [
"Calendar Integrations",
"Api"
],
"operationId": "SocializeApiModulesCalendarIntegrationsHandlersListCalendarEventsHandler",
"parameters": [
{
"name": "workspaceId",
"in": "query",
"schema": {
"type": "string",
"format": "guid",
"nullable": true
}
},
{
"name": "startDate",
"in": "query",
"schema": {
"type": "string",
"format": "date",
"nullable": true
}
},
{
"name": "endDate",
"in": "query",
"schema": {
"type": "string",
"format": "date",
"nullable": true
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SocializeApiModulesCalendarIntegrationsHandlersCalendarEventDto"
}
}
}
}
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"JWTBearerAuth": []
}
]
}
},
"/api/calendar-integrations/sources/{sourceId}/refresh": {
"post": {
"tags": [
"Calendar Integrations",
"Api"
],
"operationId": "SocializeApiModulesCalendarIntegrationsHandlersRefreshCalendarSourceHandler",
"parameters": [
{
"name": "sourceId",
"in": "path",
"required": true,
"schema": {
"type": "string",
"format": "guid"
}
}
],
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SocializeApiModulesCalendarIntegrationsHandlersCalendarSourceDto"
}
}
}
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"JWTBearerAuth": []
}
]
}
},
"/api/calendar-integrations/export-feed": {
"get": {
"tags": [
"Calendar Integrations",
"Api"
],
"operationId": "SocializeApiModulesCalendarIntegrationsHandlersGetUserCalendarExportFeedHandler",
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SocializeApiModulesCalendarIntegrationsHandlersUserCalendarExportFeedDto"
}
}
}
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"JWTBearerAuth": []
}
]
},
"delete": {
"tags": [
"Calendar Integrations",
"Api"
],
"operationId": "SocializeApiModulesCalendarIntegrationsHandlersRevokeUserCalendarExportFeedHandler",
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SocializeApiModulesCalendarIntegrationsHandlersUserCalendarExportFeedDto"
}
}
}
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"JWTBearerAuth": []
}
]
}
},
"/api/calendar-integrations/export-feed/enable": {
"post": {
"tags": [
"Calendar Integrations",
"Api"
],
"operationId": "SocializeApiModulesCalendarIntegrationsHandlersEnableUserCalendarExportFeedHandler",
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SocializeApiModulesCalendarIntegrationsHandlersUserCalendarExportFeedDto"
}
}
}
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"JWTBearerAuth": []
}
]
}
},
"/api/calendar-integrations/export-feed/regenerate": {
"post": {
"tags": [
"Calendar Integrations",
"Api"
],
"operationId": "SocializeApiModulesCalendarIntegrationsHandlersRegenerateUserCalendarExportFeedHandler",
"responses": {
"200": {
"description": "Success",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/SocializeApiModulesCalendarIntegrationsHandlersUserCalendarExportFeedDto"
}
}
}
},
"401": {
"description": "Unauthorized"
}
},
"security": [
{
"JWTBearerAuth": []
}
]
}
},
"/api/calendar-integrations/export-feed/{token}.ics": {
"get": {
"tags": [
"Calendar Integrations",
"Api"
],
"operationId": "SocializeApiModulesCalendarIntegrationsHandlersGetUserCalendarExportFeedIcsHandler",
"parameters": [
{
"name": "token",
"in": "path",
"required": true,
"schema": {
"type": "string"
}
}
],
"responses": {
"204": {
"description": "No Content"
}
}
}
},
"/api/assets/{id}/revisions": { "/api/assets/{id}/revisions": {
"post": { "post": {
"tags": [ "tags": [
@@ -3663,6 +4058,30 @@
} }
} }
}, },
"SocializeApiModulesOrganizationsHandlersChangeOrganizationLogoResponse": {
"type": "object",
"additionalProperties": false,
"properties": {
"blobUrl": {
"type": "string"
}
}
},
"SocializeApiModulesOrganizationsHandlersChangeOrganizationLogoRequest": {
"type": "object",
"additionalProperties": false,
"required": [
"file"
],
"properties": {
"file": {
"type": "string",
"format": "binary",
"minLength": 1,
"nullable": false
}
}
},
"SocializeApiModulesOrganizationsHandlersOrganizationDto": { "SocializeApiModulesOrganizationsHandlersOrganizationDto": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
@@ -3674,6 +4093,10 @@
"name": { "name": {
"type": "string" "type": "string"
}, },
"logoUrl": {
"type": "string",
"nullable": true
},
"ownerUserId": { "ownerUserId": {
"type": "string", "type": "string",
"format": "guid" "format": "guid"
@@ -3696,12 +4119,53 @@
"$ref": "#/components/schemas/SocializeApiModulesWorkspacesHandlersWorkspaceDto" "$ref": "#/components/schemas/SocializeApiModulesWorkspacesHandlersWorkspaceDto"
} }
}, },
"usage": {
"nullable": true,
"oneOf": [
{
"$ref": "#/components/schemas/SocializeApiModulesOrganizationsHandlersOrganizationUsageDto"
}
]
},
"createdAt": { "createdAt": {
"type": "string", "type": "string",
"format": "date-time" "format": "date-time"
} }
} }
}, },
"SocializeApiModulesOrganizationsHandlersOrganizationUsageDto": {
"type": "object",
"additionalProperties": false,
"properties": {
"planName": {
"type": "string"
},
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/SocializeApiModulesOrganizationsHandlersOrganizationUsageItemDto"
}
}
}
},
"SocializeApiModulesOrganizationsHandlersOrganizationUsageItemDto": {
"type": "object",
"additionalProperties": false,
"properties": {
"key": {
"type": "string"
},
"used": {
"type": "integer",
"format": "int32"
},
"limit": {
"type": "integer",
"format": "int32",
"nullable": true
}
}
},
"SocializeApiModulesOrganizationsHandlersUpdateOrganizationRequest": { "SocializeApiModulesOrganizationsHandlersUpdateOrganizationRequest": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
@@ -4728,6 +5192,11 @@
"maxLength": 1024, "maxLength": 1024,
"minLength": 0, "minLength": 0,
"nullable": true "nullable": true
},
"dueDate": {
"type": "string",
"format": "date-time",
"nullable": true
} }
} }
}, },
@@ -4785,6 +5254,54 @@
} }
} }
}, },
"SocializeApiModulesContentItemsHandlersContentItemActivityEntryDto": {
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"format": "guid"
},
"workspaceId": {
"type": "string",
"format": "guid"
},
"contentItemId": {
"type": "string",
"format": "guid"
},
"eventType": {
"type": "string"
},
"entityType": {
"type": "string"
},
"entityId": {
"type": "string",
"format": "guid"
},
"summary": {
"type": "string"
},
"actorUserId": {
"type": "string",
"format": "guid",
"nullable": true
},
"actorEmail": {
"type": "string",
"nullable": true
},
"metadataJson": {
"type": "string",
"nullable": true
},
"createdAt": {
"type": "string",
"format": "date-time"
}
}
},
"SocializeApiModulesContentItemsHandlersGetContentItemsRequest": { "SocializeApiModulesContentItemsHandlersGetContentItemsRequest": {
"type": "object", "type": "object",
"additionalProperties": false "additionalProperties": false
@@ -4842,17 +5359,26 @@
"body": { "body": {
"type": "string" "type": "string"
}, },
"isResolved": { "attachmentFileName": {
"type": "boolean" "type": "string",
"nullable": true
},
"attachmentContentType": {
"type": "string",
"nullable": true
},
"attachmentSizeBytes": {
"type": "integer",
"format": "int64",
"nullable": true
},
"attachmentBlobUrl": {
"type": "string",
"nullable": true
}, },
"createdAt": { "createdAt": {
"type": "string", "type": "string",
"format": "date-time" "format": "date-time"
},
"resolvedAt": {
"type": "string",
"format": "date-time",
"nullable": true
} }
} }
}, },
@@ -4861,8 +5387,7 @@
"additionalProperties": false, "additionalProperties": false,
"required": [ "required": [
"workspaceId", "workspaceId",
"contentItemId", "contentItemId"
"body"
], ],
"properties": { "properties": {
"workspaceId": { "workspaceId": {
@@ -4885,8 +5410,12 @@
"body": { "body": {
"type": "string", "type": "string",
"maxLength": 4000, "maxLength": 4000,
"minLength": 0, "minLength": 0
"nullable": false },
"attachment": {
"type": "string",
"format": "binary",
"nullable": true
} }
} }
}, },
@@ -5370,10 +5899,175 @@
} }
} }
}, },
"SocializeApiModulesCalendarIntegrationsHandlersCalendarCatalogEntryDto": {
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"format": "guid"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"country": {
"type": "string",
"nullable": true
},
"region": {
"type": "string",
"nullable": true
},
"language": {
"type": "string"
},
"category": {
"type": "string"
},
"cultureOrReligion": {
"type": "string",
"nullable": true
},
"providerName": {
"type": "string"
},
"sourceUrl": {
"type": "string"
},
"trustLevel": {
"type": "string"
},
"defaultColor": {
"type": "string"
}
}
},
"SocializeApiModulesCalendarIntegrationsHandlersListCalendarCatalogRequest": {
"type": "object",
"additionalProperties": false
},
"SocializeApiModulesCalendarIntegrationsHandlersCalendarEventDto": {
"type": "object",
"additionalProperties": false,
"properties": {
"id": {
"type": "string",
"format": "guid"
},
"calendarSourceId": {
"type": "string",
"format": "guid"
},
"sourceEventUid": {
"type": "string"
},
"title": {
"type": "string"
},
"description": {
"type": "string",
"nullable": true
},
"isAllDay": {
"type": "boolean"
},
"isFloatingTime": {
"type": "boolean"
},
"startDate": {
"type": "string",
"format": "date"
},
"endDate": {
"type": "string",
"format": "date"
},
"startLocalDateTime": {
"type": "string",
"format": "date-time",
"nullable": true
},
"endLocalDateTime": {
"type": "string",
"format": "date-time",
"nullable": true
},
"startUtc": {
"type": "string",
"format": "date-time",
"nullable": true
},
"endUtc": {
"type": "string",
"format": "date-time",
"nullable": true
},
"timeZoneId": {
"type": "string",
"nullable": true
},
"recurrenceId": {
"type": "string",
"nullable": true
},
"location": {
"type": "string",
"nullable": true
},
"sourceUrl": {
"type": "string",
"nullable": true
},
"sourceLastModifiedAt": {
"type": "string",
"format": "date-time",
"nullable": true
},
"importedAt": {
"type": "string",
"format": "date-time"
}
}
},
"SocializeApiModulesCalendarIntegrationsHandlersListCalendarEventsRequest": {
"type": "object",
"additionalProperties": false
},
"SocializeApiModulesCalendarIntegrationsHandlersListCalendarSourcesRequest": { "SocializeApiModulesCalendarIntegrationsHandlersListCalendarSourcesRequest": {
"type": "object", "type": "object",
"additionalProperties": false "additionalProperties": false
}, },
"SocializeApiModulesCalendarIntegrationsHandlersUserCalendarExportFeedDto": {
"type": "object",
"additionalProperties": false,
"properties": {
"isEnabled": {
"type": "boolean"
},
"feedUrl": {
"type": "string",
"nullable": true
},
"createdAt": {
"type": "string",
"format": "date-time",
"nullable": true
},
"updatedAt": {
"type": "string",
"format": "date-time",
"nullable": true
},
"revokedAt": {
"type": "string",
"format": "date-time",
"nullable": true
}
}
},
"SocializeApiModulesAssetsHandlersAssetRevisionDto": { "SocializeApiModulesAssetsHandlersAssetRevisionDto": {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,