From 06d40a7bf15f8460919e52eaa8f11fac5a294cb9 Mon Sep 17 00:00:00 2001 From: biss Date: Mon, 27 Jul 2026 12:53:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E4=BF=A1=E6=94=B9=E4=B8=BA=E4=BA=8B?= =?UTF-8?q?=E5=8A=A1=E5=86=85=E6=AF=8F=20300=20=E4=BA=BA=E5=88=86=E6=89=B9?= =?UTF-8?q?=E5=86=99=E5=85=A5=EF=BC=8C=E9=81=BF=E5=85=8D=E5=85=A8=E6=A0=A1?= =?UTF-8?q?=E7=BE=A4=E5=8F=91=E4=B8=80=E6=AC=A1=E6=8F=92=E5=85=A5=E8=BF=87?= =?UTF-8?q?=E5=A4=A7=EF=BC=9B=E6=95=B0=E6=8D=AE=E5=BA=93=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E7=8E=B0=E5=9C=A8=E8=BF=94=E5=9B=9E=E6=98=8E=E7=A1=AE=E7=9A=84?= =?UTF-8?q?=20503=EF=BC=8C=E4=B8=8D=E5=86=8D=E5=8F=AA=E6=98=BE=E7=A4=BA?= =?UTF-8?q?=E6=A8=A1=E7=B3=8A=20500=E3=80=82=20=E6=A0=A1=E7=BA=A7=E3=80=81?= =?UTF-8?q?=E5=AD=A6=E9=99=A2=E7=BA=A7=E7=AE=A1=E7=90=86=E5=91=98=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E6=8C=89=E5=AD=A6=E9=99=A2=E3=80=81=E8=BA=AB=E4=BB=BD?= =?UTF-8?q?=E3=80=81=E8=A1=8C=E6=94=BF=E7=8F=AD=E3=80=81=E6=95=99=E5=AD=A6?= =?UTF-8?q?=E7=8F=AD=E3=80=81=E5=A7=93=E5=90=8D/=E5=AD=A6=E5=8F=B7/?= =?UTF-8?q?=E5=B7=A5=E5=8F=B7=E7=AD=9B=E9=80=89=EF=BC=8C=E4=B9=9F=E5=8F=AF?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E6=9C=80=E5=A4=9A=20500=20=E5=90=8D=E6=94=B6?= =?UTF-8?q?=E4=BB=B6=E4=BA=BA=E3=80=82=E6=89=80=E6=9C=89=E7=BB=93=E6=9E=9C?= =?UTF-8?q?=E5=9D=87=E7=94=B1=E6=9C=8D=E5=8A=A1=E7=AB=AF=E9=87=8D=E6=96=B0?= =?UTF-8?q?=E6=A0=A1=E9=AA=8C=E6=9D=83=E9=99=90=E3=80=82=20=E6=8E=A5?= =?UTF-8?q?=E5=85=A5=20CKEditor=205=EF=BC=8C=E6=94=AF=E6=8C=81=E6=A0=87?= =?UTF-8?q?=E9=A2=98=E3=80=81=E5=88=97=E8=A1=A8=E3=80=81=E5=BC=95=E7=94=A8?= =?UTF-8?q?=E5=92=8C=E9=93=BE=E6=8E=A5=EF=BC=9B=E6=AD=A3=E6=96=87=E6=89=A9?= =?UTF-8?q?=E5=B1=95=E4=B8=BA=20MySQL=20longtext=E3=80=82=20=E5=AF=8C?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E5=9C=A8=E6=94=B6=E4=BB=B6=E7=AE=B1=E3=80=81?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E5=BC=B9=E7=AA=97=E5=92=8C=E5=B7=B2=E5=8F=91?= =?UTF-8?q?=E9=80=81=E8=AE=B0=E5=BD=95=E4=B8=AD=E7=BB=9F=E4=B8=80=E7=BB=8F?= =?UTF-8?q?=20DOMPurify=20=E5=87=80=E5=8C=96=E5=90=8E=E5=B1=95=E7=A4=BA?= =?UTF-8?q?=E3=80=82=20=E9=A1=B5=E9=9D=A2=E6=94=B9=E7=94=A8=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E7=8E=B0=E6=9C=89=E7=9A=84=E5=86=B7=E7=99=BD=E3=80=81?= =?UTF-8?q?=E9=9D=9B=E8=93=9D=E3=80=81=E9=9D=92=E7=BB=BF=E8=89=B2=E4=BB=A4?= =?UTF-8?q?=E7=89=8C=EF=BC=8C=E6=96=B0=E5=A2=9E=E2=80=9C=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E6=94=B6=E4=BB=B6=E4=BA=BA=20=E2=86=92=20=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E5=86=85=E5=AE=B9=E2=80=9D=E5=B7=A5=E4=BD=9C=E5=8F=B0=E5=92=8C?= =?UTF-8?q?=E7=A7=BB=E5=8A=A8=E7=AB=AF=E9=80=82=E9=85=8D=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controllers/NotificationsController.cs | 564 +- .../Domain/Academic/NotificationEntities.cs | 3 +- .../Persistence/AppDbContext.cs | 4 +- ...60727043019_MessageRichContent.Designer.cs | 4919 +++++++++++++++++ .../20260727043019_MessageRichContent.cs | 54 + .../MySql/AppDbContextModelSnapshot.cs | 6 +- .../NotificationsControllerTests.cs | 83 + web/.env.example | 2 + web/package-lock.json | 2567 ++++++++- web/package.json | 3 + web/src/components.d.ts | 1 + web/src/components/RichMessageContent.vue | 80 + web/src/views/NotificationCenterView.vue | 1115 +++- 13 files changed, 9180 insertions(+), 221 deletions(-) create mode 100644 src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/20260727043019_MessageRichContent.Designer.cs create mode 100644 src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/20260727043019_MessageRichContent.cs create mode 100644 web/src/components/RichMessageContent.vue diff --git a/src/Jiaowu.Api/Controllers/NotificationsController.cs b/src/Jiaowu.Api/Controllers/NotificationsController.cs index ea94303..fc18113 100644 --- a/src/Jiaowu.Api/Controllers/NotificationsController.cs +++ b/src/Jiaowu.Api/Controllers/NotificationsController.cs @@ -1,4 +1,7 @@ using System.ComponentModel.DataAnnotations; +using System.Data; +using System.Net; +using System.Text.RegularExpressions; using Jiaowu.Api.Domain.Academic; using Jiaowu.Api.Domain.Identity; using Jiaowu.Api.Infrastructure.Auth; @@ -14,8 +17,11 @@ namespace Jiaowu.Api.Controllers; [Route("api/notifications")] public sealed class NotificationsController( AppDbContext db, - ICurrentUserDataScope currentUserDataScope) : ControllerBase + ICurrentUserDataScope currentUserDataScope, + ILogger? logger = null) : ControllerBase { + private const int MaximumSelectedRecipients = 500; + private const int NotificationBatchSize = 300; private const string Senders = SystemRoles.SuperAdmin + "," + SystemRoles.AcademicAdmin + "," + @@ -106,17 +112,12 @@ public sealed class NotificationsController( var scope = currentUserDataScope.Current; if (IsSchoolAdministrator(scope)) { - var recipientCount = await db.Users.AsNoTracking() - .CountAsync( - x => x.IsEnabled && x.Id != scope.UserId, - cancellationToken); - return Ok(new - { - AudienceType = MessageAudienceType.School, - AudienceName = "全校已启用账号", - RecipientCount = recipientCount, - TeachingTasks = Array.Empty() - }); + return Ok(await BuildAdministratorComposerAsync( + scope, + null, + MessageAudienceType.School, + "全校已启用账号", + cancellationToken)); } if (scope.IsInRole(SystemRoles.CollegeAdmin) || @@ -138,19 +139,12 @@ public sealed class NotificationsController( if (college is null) return ConflictProblem("当前账号关联的学院不存在。"); - var recipientCount = await db.Users.AsNoTracking() - .CountAsync( - x => x.IsEnabled && - x.Id != scope.UserId && - x.CollegeId == collegeId.Value, - cancellationToken); - return Ok(new - { - AudienceType = MessageAudienceType.College, - AudienceName = $"{college}全院成员", - RecipientCount = recipientCount, - TeachingTasks = Array.Empty() - }); + return Ok(await BuildAdministratorComposerAsync( + scope, + collegeId.Value, + MessageAudienceType.College, + $"{college}全院成员", + cancellationToken)); } if (scope.IsInRole(SystemRoles.Teacher)) @@ -195,6 +189,114 @@ public sealed class NotificationsController( return Forbid(); } + [HttpGet("recipients")] + [Authorize(Roles = Senders)] + public async Task GetRecipients( + Guid? collegeId, + string? role, + Guid? administrativeClassId, + Guid? teachingTaskId, + string? keyword, + int page = 1, + int pageSize = 30, + CancellationToken cancellationToken = default) + { + var scope = currentUserDataScope.Current; + if (scope.IsInRole(SystemRoles.Teacher) && + !IsSchoolAdministrator(scope) && + !scope.IsInRole(SystemRoles.CollegeAdmin) && + !scope.IsInRole(SystemRoles.Counselor)) + { + return Forbid(); + } + + var resolvedCollegeId = IsSchoolAdministrator(scope) + ? null + : await ResolveSenderCollegeIdAsync(scope, cancellationToken); + if (!IsSchoolAdministrator(scope) && !resolvedCollegeId.HasValue) + return ConflictProblem("当前账号未关联学院,暂时无法确定收件人范围。"); + + var filter = new MessageRecipientFilter( + collegeId, + Normalize(role), + administrativeClassId, + teachingTaskId, + Normalize(keyword)); + if (filter.Role is not null && !SystemRoles.All.Contains(filter.Role)) + return ValidationProblem("所选身份类型无效。"); + + page = Math.Max(1, page); + pageSize = Math.Clamp(pageSize, 10, 100); + var source = ApplyRecipientFilter( + ScopedRecipientQuery(scope, resolvedCollegeId), + filter); + var total = await source.CountAsync(cancellationToken); + var users = await source + .OrderBy(x => x.DisplayName) + .ThenBy(x => x.UserName) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .Select(user => new + { + user.Id, + user.DisplayName, + user.UserName, + user.StaffNumber, + CollegeName = db.Colleges + .Where(college => college.Id == user.CollegeId) + .Select(college => college.Name) + .FirstOrDefault(), + StudentNumber = db.Students + .Where(student => student.UserId == user.Id) + .Select(student => student.StudentNumber) + .FirstOrDefault(), + ClassName = db.Students + .Where(student => student.UserId == user.Id) + .Select(student => student.AdministrativeClass!.Name) + .FirstOrDefault(), + TeacherNumber = db.Teachers + .Where(teacher => teacher.UserId == user.Id) + .Select(teacher => teacher.TeacherNumber) + .FirstOrDefault() + }) + .ToListAsync(cancellationToken); + var userIds = users.Select(x => x.Id).ToArray(); + var roleRows = await db.UserRoles.AsNoTracking() + .Where(x => userIds.Contains(x.UserId)) + .Join( + db.Roles.AsNoTracking(), + userRole => userRole.RoleId, + roleEntity => roleEntity.Id, + (userRole, roleEntity) => new + { + userRole.UserId, + RoleName = roleEntity.Name! + }) + .ToListAsync(cancellationToken); + var rolesByUser = roleRows + .GroupBy(x => x.UserId) + .ToDictionary( + group => group.Key, + group => group.Select(x => x.RoleName).Distinct().ToArray()); + + return Ok(new + { + Items = users.Select(user => new + { + user.Id, + user.DisplayName, + user.UserName, + Number = user.StudentNumber ?? user.TeacherNumber ?? user.StaffNumber, + user.CollegeName, + user.ClassName, + Roles = rolesByUser.GetValueOrDefault(user.Id, []) + }), + Total = total, + Page = page, + PageSize = pageSize + }); + } + [HttpPost("send")] [Authorize(Roles = Senders)] public async Task Send( @@ -205,8 +307,10 @@ public sealed class NotificationsController( var content = request.Content.Trim(); if (title.Length == 0) return ValidationProblem("请填写消息标题。"); - if (content.Length == 0) + if (PlainText(content).Length == 0) return ValidationProblem("请填写消息正文。"); + if (content.Length > 20000) + return ValidationProblem("消息正文过长,请精简至 20000 个字符以内。"); var scope = currentUserDataScope.Current; MessageAudienceType audienceType; Guid? audienceId = null; @@ -215,12 +319,16 @@ public sealed class NotificationsController( if (IsSchoolAdministrator(scope)) { - audienceType = MessageAudienceType.School; - audienceName = "全校已启用账号"; - recipientIds = await db.Users.AsNoTracking() - .Where(x => x.IsEnabled && x.Id != scope.UserId) - .Select(x => x.Id) - .ToListAsync(cancellationToken); + var result = await ResolveAdministratorRecipientsAsync( + scope, + null, + request, + "全校已启用账号", + cancellationToken); + if (result.Error is not null) return result.Error; + audienceType = result.AudienceType; + audienceName = result.AudienceName!; + recipientIds = result.RecipientIds!; } else if (scope.IsInRole(SystemRoles.CollegeAdmin) || scope.IsInRole(SystemRoles.Counselor)) @@ -241,16 +349,19 @@ public sealed class NotificationsController( if (collegeName is null) return ConflictProblem("当前账号关联的学院不存在。"); - audienceType = MessageAudienceType.College; - audienceId = collegeId.Value; - audienceName = $"{collegeName}全院成员"; - recipientIds = await db.Users.AsNoTracking() - .Where(x => - x.IsEnabled && - x.Id != scope.UserId && - x.CollegeId == collegeId.Value) - .Select(x => x.Id) - .ToListAsync(cancellationToken); + var result = await ResolveAdministratorRecipientsAsync( + scope, + collegeId.Value, + request, + $"{collegeName}全院成员", + cancellationToken); + if (result.Error is not null) return result.Error; + audienceType = result.AudienceType; + audienceId = audienceType == MessageAudienceType.College + ? collegeId.Value + : null; + audienceName = result.AudienceName!; + recipientIds = result.RecipientIds!; } else if (scope.IsInRole(SystemRoles.Teacher)) { @@ -305,24 +416,54 @@ public sealed class NotificationsController( RecipientCount = recipientIds.Count, LinkUrl = null }; - dispatch.Notifications = recipientIds.Select(userId => new Notification + try { - UserId = userId, - Title = title, - Content = content, - Category = NotificationCategory.General, - LinkUrl = null - }).ToList(); + return await db.ExecuteInRetriableTransactionAsync( + async transaction => + { + db.MessageDispatches.Add(dispatch); + await db.SaveChangesAsync(cancellationToken); + db.ChangeTracker.Clear(); - db.MessageDispatches.Add(dispatch); - await db.SaveChangesAsync(cancellationToken); - return Ok(new + foreach (var batch in recipientIds.Chunk(NotificationBatchSize)) + { + db.Notifications.AddRange(batch.Select(userId => new Notification + { + UserId = userId, + Title = title, + Content = content, + Category = NotificationCategory.General, + LinkUrl = null, + MessageDispatchId = dispatch.Id + })); + await db.SaveChangesAsync(cancellationToken); + db.ChangeTracker.Clear(); + } + + await transaction.CommitAsync(cancellationToken); + return Ok(new + { + dispatch.Id, + dispatch.AudienceName, + dispatch.RecipientCount, + dispatch.CreatedAt + }); + }, + cancellationToken, + IsolationLevel.ReadCommitted); + } + catch (DbUpdateException exception) { - dispatch.Id, - dispatch.AudienceName, - dispatch.RecipientCount, - dispatch.CreatedAt - }); + logger?.LogError( + exception, + "Failed to send notification dispatch {DispatchId} to {RecipientCount} recipients.", + dispatch.Id, + recipientIds.Count); + return Problem( + title: "消息未能发送", + detail: "消息数据保存失败。请确认数据库已完成最新升级后重试;本次消息没有发送。", + statusCode: StatusCodes.Status503ServiceUnavailable); + } } [HttpGet("sent")] @@ -433,6 +574,286 @@ public sealed class NotificationsController( scope.IsInRole(SystemRoles.SuperAdmin) || scope.IsInRole(SystemRoles.AcademicAdmin); + private async Task BuildAdministratorComposerAsync( + CurrentUserScope scope, + Guid? fixedCollegeId, + MessageAudienceType audienceType, + string audienceName, + CancellationToken cancellationToken) + { + var colleges = await db.Colleges.AsNoTracking() + .Where(x => !fixedCollegeId.HasValue || x.Id == fixedCollegeId.Value) + .OrderBy(x => x.Code) + .Select(x => new { x.Id, x.Code, x.Name }) + .ToListAsync(cancellationToken); + var administrativeClasses = await db.AdministrativeClasses.AsNoTracking() + .Where(x => + x.IsEnabled && + (!fixedCollegeId.HasValue || + x.Major!.CollegeId == fixedCollegeId.Value)) + .OrderByDescending(x => x.Grade) + .ThenBy(x => x.Code) + .Select(x => new + { + x.Id, + x.Code, + x.Name, + x.Grade, + CollegeId = x.Major!.CollegeId + }) + .ToListAsync(cancellationToken); + var teachingTasks = await db.TeachingTasks.AsNoTracking() + .Where(x => + x.Status != TeachingTaskStatus.Draft && + (!fixedCollegeId.HasValue || + x.Course!.CollegeId == fixedCollegeId.Value)) + .OrderByDescending(x => x.AcademicTerm!.IsCurrent) + .ThenByDescending(x => x.AcademicTerm!.StartDate) + .ThenBy(x => x.TaskNumber) + .Select(x => new + { + x.Id, + x.TaskNumber, + CourseName = x.Course!.Name, + TermName = x.AcademicTerm!.Name, + CollegeId = x.Course.CollegeId + }) + .ToListAsync(cancellationToken); + var recipientCount = await ScopedRecipientQuery(scope, fixedCollegeId) + .CountAsync(cancellationToken); + + return new + { + AudienceType = audienceType, + AudienceName = audienceName, + RecipientCount = recipientCount, + CanFilterRecipients = true, + MaximumSelectedRecipients, + Colleges = colleges, + Roles = RoleOptions, + AdministrativeClasses = administrativeClasses, + TeachingTasks = teachingTasks + }; + } + + private IQueryable ScopedRecipientQuery( + CurrentUserScope scope, + Guid? fixedCollegeId) + { + var source = db.Users.AsNoTracking() + .Where(x => x.IsEnabled && x.Id != scope.UserId); + if (IsSchoolAdministrator(scope)) return source; + if (!fixedCollegeId.HasValue) return source.Where(_ => false); + var collegeId = fixedCollegeId.Value; + return source.Where(user => + user.CollegeId == collegeId || + db.Students.Any(student => + student.UserId == user.Id && + student.AdministrativeClass!.Major!.CollegeId == collegeId) || + db.Teachers.Any(teacher => + teacher.UserId == user.Id && + teacher.CollegeId == collegeId)); + } + + private IQueryable ApplyRecipientFilter( + IQueryable source, + MessageRecipientFilter? filter) + { + if (filter is null) return source; + if (filter.CollegeId.HasValue) + { + var collegeId = filter.CollegeId.Value; + source = source.Where(user => + user.CollegeId == collegeId || + db.Students.Any(student => + student.UserId == user.Id && + student.AdministrativeClass!.Major!.CollegeId == collegeId) || + db.Teachers.Any(teacher => + teacher.UserId == user.Id && + teacher.CollegeId == collegeId)); + } + if (!string.IsNullOrWhiteSpace(filter.Role)) + { + var role = filter.Role; + source = source.Where(user => + db.UserRoles.Any(userRole => + userRole.UserId == user.Id && + db.Roles.Any(roleEntity => + roleEntity.Id == userRole.RoleId && + roleEntity.Name == role))); + } + if (filter.AdministrativeClassId.HasValue) + { + var classId = filter.AdministrativeClassId.Value; + source = source.Where(user => + db.Students.Any(student => + student.UserId == user.Id && + student.AdministrativeClassId == classId)); + } + if (filter.TeachingTaskId.HasValue) + { + var taskId = filter.TeachingTaskId.Value; + source = source.Where(user => + db.Students.Any(student => + student.UserId == user.Id && + (db.TeachingTaskClasses.Any(item => + item.TeachingTaskId == taskId && + item.AdministrativeClassId == + student.AdministrativeClassId) || + db.CourseEnrollments.Any(enrollment => + enrollment.StudentId == student.Id && + enrollment.Status == CourseEnrollmentStatus.Enrolled && + enrollment.CourseSelectionOffering!.TeachingTaskId == + taskId)))); + } + if (!string.IsNullOrWhiteSpace(filter.Keyword)) + { + var keyword = filter.Keyword; + source = source.Where(user => + user.DisplayName.Contains(keyword) || + (user.UserName != null && user.UserName.Contains(keyword)) || + (user.StaffNumber != null && user.StaffNumber.Contains(keyword)) || + db.Students.Any(student => + student.UserId == user.Id && + (student.Name.Contains(keyword) || + student.StudentNumber.Contains(keyword))) || + db.Teachers.Any(teacher => + teacher.UserId == user.Id && + (teacher.Name.Contains(keyword) || + teacher.TeacherNumber.Contains(keyword)))); + } + + return source; + } + + private async Task ResolveAdministratorRecipientsAsync( + CurrentUserScope scope, + Guid? fixedCollegeId, + SendMessageRequest request, + string defaultAudienceName, + CancellationToken cancellationToken) + { + var source = ScopedRecipientQuery(scope, fixedCollegeId); + if (request.RecipientMode == MessageRecipientMode.Scope) + { + return new RecipientResolution( + fixedCollegeId.HasValue + ? MessageAudienceType.College + : MessageAudienceType.School, + defaultAudienceName, + await source.Select(x => x.Id).ToListAsync(cancellationToken), + null); + } + + if (request.RecipientMode == MessageRecipientMode.Filtered) + { + if (request.RecipientFilter?.Role is not null && + !SystemRoles.All.Contains(request.RecipientFilter.Role)) + { + return new RecipientResolution( + default, + null, + null, + ValidationProblem("所选身份类型无效。")); + } + var recipientIds = await ApplyRecipientFilter( + source, + request.RecipientFilter) + .Select(x => x.Id) + .ToListAsync(cancellationToken); + return new RecipientResolution( + MessageAudienceType.Custom, + BuildFilteredAudienceName(request.RecipientFilter), + recipientIds, + null); + } + + if (request.RecipientMode == MessageRecipientMode.Selected) + { + var requestedIds = request.RecipientUserIds? + .Distinct() + .ToArray() ?? []; + if (requestedIds.Length == 0) + { + return new RecipientResolution( + default, + null, + null, + ValidationProblem("请至少选择一名收件人。")); + } + if (requestedIds.Length > MaximumSelectedRecipients) + { + return new RecipientResolution( + default, + null, + null, + ValidationProblem( + $"单次最多指定 {MaximumSelectedRecipients} 名收件人。")); + } + + var authorizedIds = await source + .Where(x => requestedIds.Contains(x.Id)) + .Select(x => x.Id) + .ToListAsync(cancellationToken); + if (authorizedIds.Count != requestedIds.Length) + { + return new RecipientResolution( + default, + null, + null, + ValidationProblem("所选收件人包含无权限访问或已停用的账号。")); + } + + return new RecipientResolution( + MessageAudienceType.Custom, + $"指定收件人({authorizedIds.Count} 人)", + authorizedIds, + null); + } + + return new RecipientResolution( + default, + null, + null, + ValidationProblem("请选择有效的收件方式。")); + } + + private static string BuildFilteredAudienceName(MessageRecipientFilter? filter) + { + if (filter is null) return "当前权限范围内全部账号"; + var parts = new List(); + if (filter.CollegeId.HasValue) parts.Add("指定学院"); + if (filter.Role is not null) + parts.Add(RoleOptions.FirstOrDefault(x => x.Value == filter.Role)?.Label ?? + filter.Role); + if (filter.AdministrativeClassId.HasValue) parts.Add("指定行政班"); + if (filter.TeachingTaskId.HasValue) parts.Add("指定教学班"); + if (filter.Keyword is not null) parts.Add($"关键词“{filter.Keyword}”"); + return parts.Count == 0 + ? "当前权限范围内全部账号" + : string.Join(" · ", parts); + } + + private static string PlainText(string html) + { + var withoutTags = Regex.Replace(html, "<[^>]+>", " "); + return WebUtility.HtmlDecode(withoutTags).Trim(); + } + + private static string? Normalize(string? value) => + string.IsNullOrWhiteSpace(value) ? null : value.Trim(); + + private static readonly RoleOption[] RoleOptions = + [ + new(SystemRoles.Student, "学生"), + new(SystemRoles.Teacher, "任课教师"), + new(SystemRoles.Counselor, "辅导员"), + new(SystemRoles.CollegeAdmin, "学院管理员"), + new(SystemRoles.AcademicAdmin, "校级教务管理员"), + new(SystemRoles.Leader, "校领导"), + new(SystemRoles.SuperAdmin, "超级管理员") + ]; + private ActionResult ConflictProblem(string detail) => Conflict(new ProblemDetails { @@ -452,8 +873,33 @@ public sealed class NotificationsController( public sealed record SendMessageRequest( [property: Required, StringLength(200)] string Title, - [property: Required, StringLength(1000)] string Content, - Guid? TeachingTaskId = null); + [property: Required, StringLength(20000)] string Content, + Guid? TeachingTaskId = null, + MessageRecipientMode RecipientMode = MessageRecipientMode.Scope, + MessageRecipientFilter? RecipientFilter = null, + IReadOnlyCollection? RecipientUserIds = null); + +public sealed record MessageRecipientFilter( + Guid? CollegeId = null, + string? Role = null, + Guid? AdministrativeClassId = null, + Guid? TeachingTaskId = null, + string? Keyword = null); + +public enum MessageRecipientMode +{ + Scope = 1, + Filtered = 2, + Selected = 3 +} + +public sealed record RoleOption(string Value, string Label); + +internal sealed record RecipientResolution( + MessageAudienceType AudienceType, + string? AudienceName, + List? RecipientIds, + ActionResult? Error); /// /// Centralized helper to send notifications across the app. diff --git a/src/Jiaowu.Api/Domain/Academic/NotificationEntities.cs b/src/Jiaowu.Api/Domain/Academic/NotificationEntities.cs index 764164c..7ba3fe3 100644 --- a/src/Jiaowu.Api/Domain/Academic/NotificationEntities.cs +++ b/src/Jiaowu.Api/Domain/Academic/NotificationEntities.cs @@ -44,5 +44,6 @@ public enum MessageAudienceType { School = 1, College = 2, - TeachingTask = 3 + TeachingTask = 3, + Custom = 4 } diff --git a/src/Jiaowu.Api/Infrastructure/Persistence/AppDbContext.cs b/src/Jiaowu.Api/Infrastructure/Persistence/AppDbContext.cs index caad4b7..f0db228 100644 --- a/src/Jiaowu.Api/Infrastructure/Persistence/AppDbContext.cs +++ b/src/Jiaowu.Api/Infrastructure/Persistence/AppDbContext.cs @@ -980,7 +980,7 @@ public sealed class AppDbContext(DbContextOptions options) builder.Entity(entity => { entity.Property(x => x.Title).HasMaxLength(200); - entity.Property(x => x.Content).HasMaxLength(1000); + entity.Property(x => x.Content).HasColumnType("longtext"); entity.Property(x => x.LinkUrl).HasMaxLength(300); entity.HasIndex(x => new { x.UserId, x.IsRead }); entity.HasIndex(x => new { x.UserId, x.Category, x.CreatedAt }); @@ -996,7 +996,7 @@ public sealed class AppDbContext(DbContextOptions options) { entity.Property(x => x.SenderName).HasMaxLength(100); entity.Property(x => x.Title).HasMaxLength(200); - entity.Property(x => x.Content).HasMaxLength(1000); + entity.Property(x => x.Content).HasColumnType("longtext"); entity.Property(x => x.AudienceName).HasMaxLength(200); entity.Property(x => x.LinkUrl).HasMaxLength(300); entity.HasIndex(x => new { x.SenderUserId, x.CreatedAt }); diff --git a/src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/20260727043019_MessageRichContent.Designer.cs b/src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/20260727043019_MessageRichContent.Designer.cs new file mode 100644 index 0000000..15635ef --- /dev/null +++ b/src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/20260727043019_MessageRichContent.Designer.cs @@ -0,0 +1,4919 @@ +// +using System; +using Jiaowu.Api.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql +{ + [DbContext(typeof(AppDbContext))] + [Migration("20260727043019_MessageRichContent")] + partial class MessageRichContent + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "10.0.10") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.AcademicTerm", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcademicYear") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("ArchivedAt") + .HasColumnType("datetime(6)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndDate") + .HasColumnType("date"); + + b.Property("IsArchived") + .HasColumnType("tinyint(1)"); + + b.Property("IsCurrent") + .HasColumnType("tinyint(1)"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Season") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("StartDate") + .HasColumnType("date"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsArchived"); + + b.HasIndex("IsCurrent"); + + b.HasIndex("IsEnabled", "SortOrder"); + + b.ToTable("AcademicTerms"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.AdministrativeClass", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("CounselorName") + .HasColumnType("longtext"); + + b.Property("CounselorUserId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Grade") + .HasColumnType("int"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("MajorId") + .HasColumnType("char(36)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("CounselorUserId"); + + b.HasIndex("MajorId"); + + b.HasIndex("IsEnabled", "SortOrder"); + + b.ToTable("AdministrativeClasses"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.AttendanceRecord", b => + { + b.Property("AttendanceSheetId") + .HasColumnType("char(36)"); + + b.Property("StudentId") + .HasColumnType("char(36)"); + + b.Property("AppealReason") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("AppealReviewComment") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("AppealReviewedAt") + .HasColumnType("datetime(6)"); + + b.Property("AppealStatus") + .HasColumnType("int"); + + b.Property("AppealSubmittedAt") + .HasColumnType("datetime(6)"); + + b.Property("CheckInAccuracyMeters") + .HasColumnType("double"); + + b.Property("CheckInAt") + .HasColumnType("datetime(6)"); + + b.Property("CheckInDistanceMeters") + .HasColumnType("double"); + + b.Property("CheckInLatitude") + .HasPrecision(10, 7) + .HasColumnType("decimal(10,7)"); + + b.Property("CheckInLongitude") + .HasPrecision(10, 7) + .HasColumnType("decimal(10,7)"); + + b.Property("CheckedInMethod") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("Status") + .HasColumnType("int"); + + b.HasKey("AttendanceSheetId", "StudentId"); + + b.HasIndex("AppealStatus"); + + b.HasIndex("StudentId"); + + b.ToTable("AttendanceRecords"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.AttendanceSheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AttendanceDate") + .HasColumnType("datetime(6)"); + + b.Property("CheckInEndsAt") + .HasColumnType("datetime(6)"); + + b.Property("CheckInMethod") + .HasColumnType("int"); + + b.Property("CheckInStartsAt") + .HasColumnType("datetime(6)"); + + b.Property("CheckInToken") + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("LocationRadiusMeters") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("varchar(120)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("SubmittedAt") + .HasColumnType("datetime(6)"); + + b.Property("TargetLatitude") + .HasPrecision(10, 7) + .HasColumnType("decimal(10,7)"); + + b.Property("TargetLongitude") + .HasPrecision(10, 7) + .HasColumnType("decimal(10,7)"); + + b.Property("TeachingTaskId") + .HasColumnType("char(36)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CheckInToken") + .IsUnique(); + + b.HasIndex("TeachingTaskId", "AttendanceDate"); + + b.ToTable("AttendanceSheets"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.AutomaticScheduleJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ActiveSchedulePlanId") + .HasColumnType("char(36)"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CompletedTasks") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedEntries") + .HasColumnType("int"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("MessagesJson") + .HasColumnType("longtext"); + + b.Property("ProcessedTasks") + .HasColumnType("int"); + + b.Property("RequestedByUserId") + .HasColumnType("char(36)"); + + b.Property("SchedulePlanId") + .HasColumnType("char(36)"); + + b.Property("StartedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalTasks") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ActiveSchedulePlanId") + .IsUnique(); + + b.HasIndex("RequestedByUserId"); + + b.HasIndex("SchedulePlanId", "CreatedAt"); + + b.HasIndex("Status", "CreatedAt"); + + b.ToTable("AutomaticScheduleJobs"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Building", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CampusId") + .HasColumnType("char(36)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CampusId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsEnabled", "SortOrder"); + + b.ToTable("Buildings"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Campus", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Address") + .HasColumnType("longtext"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsEnabled", "SortOrder"); + + b.ToTable("Campuses"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Classroom", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("BuildingId") + .HasColumnType("char(36)"); + + b.Property("Capacity") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Equipment") + .HasColumnType("longtext"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RoomType") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("BuildingId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsEnabled", "SortOrder"); + + b.ToTable("Classrooms"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ClassroomReservation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcademicTermId") + .HasColumnType("char(36)"); + + b.Property("ApplicantCollegeId") + .HasColumnType("char(36)"); + + b.Property("ApplicantName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("ApplicantUserId") + .HasColumnType("char(36)"); + + b.Property("AttendeeCount") + .HasColumnType("int"); + + b.Property("CancelledAt") + .HasColumnType("datetime(6)"); + + b.Property("ClassroomId") + .HasColumnType("char(36)"); + + b.Property("ContactPhone") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PeriodCount") + .HasColumnType("int"); + + b.Property("Purpose") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ReservationDate") + .HasColumnType("date"); + + b.Property("ReviewComment") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReviewedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReviewedByUserId") + .HasColumnType("char(36)"); + + b.Property("StartPeriod") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("AcademicTermId"); + + b.HasIndex("ReviewedByUserId"); + + b.HasIndex("ApplicantCollegeId", "Status", "ReservationDate"); + + b.HasIndex("ApplicantUserId", "Status", "CreatedAt"); + + b.HasIndex("ClassroomId", "ReservationDate", "Status", "StartPeriod"); + + b.ToTable("ClassroomReservations"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.College", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CampusId") + .HasColumnType("char(36)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ShortName") + .HasColumnType("longtext"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CampusId"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsEnabled", "SortOrder"); + + b.ToTable("Colleges"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Course", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AssessmentMethod") + .HasColumnType("int"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("CollegeId") + .HasColumnType("char(36)"); + + b.Property("CourseCategoryId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Credits") + .HasPrecision(5, 2) + .HasColumnType("decimal(5,2)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("EnglishName") + .HasMaxLength(150) + .HasColumnType("varchar(150)"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LectureHours") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Nature") + .HasColumnType("int"); + + b.Property("PracticeHours") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("TotalHours") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("CourseCategoryId"); + + b.HasIndex("CollegeId", "Nature"); + + b.HasIndex("IsEnabled", "SortOrder"); + + b.ToTable("Courses"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseAdjustment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ApplicantUserId") + .HasColumnType("char(36)"); + + b.Property("CancelDate") + .HasColumnType("date"); + + b.Property("CancelWeek") + .HasColumnType("int"); + + b.Property("ClassroomId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DayOfWeek") + .HasColumnType("int"); + + b.Property("PeriodCount") + .HasColumnType("int"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReviewComment") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReviewedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReviewedByUserId") + .HasColumnType("char(36)"); + + b.Property("SourceClassroomId") + .HasColumnType("char(36)"); + + b.Property("SourceDate") + .HasColumnType("date"); + + b.Property("SourcePeriodCount") + .HasColumnType("int"); + + b.Property("SourceScheduleEntryId") + .HasColumnType("char(36)"); + + b.Property("SourceStartPeriod") + .HasColumnType("int"); + + b.Property("SourceWeek") + .HasColumnType("int"); + + b.Property("StartPeriod") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("SubmittedAt") + .HasColumnType("datetime(6)"); + + b.Property("SubstituteTeacherId") + .HasColumnType("char(36)"); + + b.Property("TargetDate") + .HasColumnType("date"); + + b.Property("TeachingTaskId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ApplicantUserId"); + + b.HasIndex("ClassroomId"); + + b.HasIndex("SubstituteTeacherId"); + + b.HasIndex("SourceScheduleEntryId", "SourceWeek"); + + b.HasIndex("Status", "CreatedAt"); + + b.HasIndex("TeachingTaskId", "Status"); + + b.ToTable("CourseAdjustments"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseCategory", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("IsEnabled", "SortOrder"); + + b.ToTable("CourseCategories"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseEnrollment", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CourseSelectionOfferingId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EnrolledAt") + .HasColumnType("datetime(6)"); + + b.Property("EnrollmentType") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StudentId") + .HasColumnType("char(36)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WaitlistedAt") + .HasColumnType("datetime(6)"); + + b.Property("WithdrawnAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CourseSelectionOfferingId", "StudentId") + .IsUnique(); + + b.HasIndex("StudentId", "Status"); + + b.HasIndex("CourseSelectionOfferingId", "Status", "WaitlistedAt") + .HasDatabaseName("IX_CE_Offering_Status_WaitlistedAt"); + + b.ToTable("CourseEnrollments"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseExemption", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReviewComment") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReviewedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReviewedByUserId") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StudentId") + .HasColumnType("char(36)"); + + b.Property("SubmittedAt") + .HasColumnType("datetime(6)"); + + b.Property("TeachingTaskId") + .HasColumnType("char(36)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("TeachingTaskId"); + + b.HasIndex("Status", "CreatedAt"); + + b.HasIndex("StudentId", "TeachingTaskId") + .IsUnique(); + + b.ToTable("CourseExemptions"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CoursePrerequisite", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CourseId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("PrerequisiteCourseId") + .HasColumnType("char(36)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("PrerequisiteCourseId"); + + b.HasIndex("CourseId", "PrerequisiteCourseId") + .IsUnique(); + + b.ToTable("CoursePrerequisites"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseSelectionOffering", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Capacity") + .HasColumnType("int"); + + b.Property("CourseSelectionRoundId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsOpenToAll") + .HasColumnType("tinyint(1)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("TeachingTaskId") + .HasColumnType("char(36)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("TeachingTaskId"); + + b.HasIndex("CourseSelectionRoundId", "TeachingTaskId") + .IsUnique(); + + b.ToTable("CourseSelectionOfferings"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseSelectionRound", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcademicTermId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndsAt") + .HasColumnType("datetime(6)"); + + b.Property("MaxCourseCount") + .HasColumnType("int"); + + b.Property("MaxCredits") + .HasPrecision(6, 1) + .HasColumnType("decimal(6,1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("varchar(120)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("StartsAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WithdrawalEndsAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("AcademicTermId", "Status"); + + b.ToTable("CourseSelectionRounds"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseSelectionRoundGrade", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CourseSelectionRoundId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Grade") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Grade"); + + b.HasIndex("CourseSelectionRoundId", "Grade") + .IsUnique(); + + b.ToTable("CourseSelectionRoundGrades"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseSubstitution", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("OriginalCourseId") + .HasColumnType("char(36)"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReviewComment") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReviewedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReviewedByUserId") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StudentId") + .HasColumnType("char(36)"); + + b.Property("SubmittedAt") + .HasColumnType("datetime(6)"); + + b.Property("SubstituteCourseId") + .HasColumnType("char(36)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("OriginalCourseId"); + + b.HasIndex("SubstituteCourseId"); + + b.HasIndex("Status", "CreatedAt"); + + b.HasIndex("StudentId", "OriginalCourseId") + .IsUnique(); + + b.ToTable("CourseSubstitutions"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CurriculumCourse", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CourseId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurriculumModuleId") + .HasColumnType("char(36)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RecommendedSemester") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.HasIndex("CurriculumModuleId", "CourseId") + .IsUnique(); + + b.ToTable("CurriculumCourses"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CurriculumModule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurriculumPlanId") + .HasColumnType("char(36)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("RequiredCredits") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CurriculumPlanId", "Code") + .IsUnique(); + + b.ToTable("CurriculumModules"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CurriculumPlan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("EffectiveGrade") + .HasColumnType("int"); + + b.Property("MajorId") + .HasColumnType("char(36)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("varchar(120)"); + + b.Property("PublishedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalCredits") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.HasKey("Id"); + + b.HasIndex("Status", "EffectiveGrade"); + + b.HasIndex("MajorId", "EffectiveGrade", "Version") + .IsUnique(); + + b.ToTable("CurriculumPlans"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.DeferredExam", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReviewComment") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReviewedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReviewedByUserId") + .HasColumnType("char(36)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StudentId") + .HasColumnType("char(36)"); + + b.Property("SubmittedAt") + .HasColumnType("datetime(6)"); + + b.Property("TeachingTaskId") + .HasColumnType("char(36)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("TeachingTaskId"); + + b.HasIndex("Status", "CreatedAt"); + + b.HasIndex("StudentId", "TeachingTaskId") + .IsUnique(); + + b.ToTable("DeferredExams"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.DegreeAwardBatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CalculatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DegreeName") + .IsRequired() + .HasMaxLength(80) + .HasColumnType("varchar(80)"); + + b.Property("GraduationYear") + .HasColumnType("int"); + + b.Property("MinimumGradePoint") + .HasPrecision(3, 2) + .HasColumnType("decimal(3,2)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("varchar(120)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PublishedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("GraduationYear", "Status"); + + b.ToTable("DegreeAwardBatches"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.DegreeAwardResult", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AverageGradePoint") + .HasPrecision(4, 2) + .HasColumnType("decimal(4,2)"); + + b.Property("CalculatedConclusion") + .HasColumnType("int"); + + b.Property("Conclusion") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DegreeAwardBatchId") + .HasColumnType("char(36)"); + + b.Property("ExceptionReason") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("GraduationAuditResultId") + .HasColumnType("char(36)"); + + b.Property("IsOverridden") + .HasColumnType("tinyint(1)"); + + b.Property("ReviewComment") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReviewedAt") + .HasColumnType("datetime(6)"); + + b.Property("StudentId") + .HasColumnType("char(36)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("GraduationAuditResultId"); + + b.HasIndex("StudentId"); + + b.HasIndex("Conclusion", "IsOverridden"); + + b.HasIndex("DegreeAwardBatchId", "StudentId") + .IsUnique(); + + b.ToTable("DegreeAwardResults"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.EvaluationDimension", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EvaluationSetupId") + .HasColumnType("char(36)"); + + b.Property("MaxScore") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("EvaluationSetupId", "SortOrder"); + + b.ToTable("EvaluationDimensions"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.EvaluationRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EvaluationSetupId") + .HasColumnType("char(36)"); + + b.Property("StudentId") + .HasColumnType("char(36)"); + + b.Property("SubmittedAt") + .HasColumnType("datetime(6)"); + + b.Property("TeachingTaskId") + .HasColumnType("char(36)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("StudentId"); + + b.HasIndex("TeachingTaskId"); + + b.HasIndex("EvaluationSetupId", "StudentId", "TeachingTaskId") + .IsUnique(); + + b.ToTable("EvaluationRecords"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.EvaluationScore", b => + { + b.Property("EvaluationRecordId") + .HasColumnType("char(36)"); + + b.Property("EvaluationDimensionId") + .HasColumnType("char(36)"); + + b.Property("Score") + .HasColumnType("int"); + + b.HasKey("EvaluationRecordId", "EvaluationDimensionId"); + + b.HasIndex("EvaluationDimensionId"); + + b.ToTable("EvaluationScores"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.EvaluationSetup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcademicTermId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndsAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("varchar(120)"); + + b.Property("StartsAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("AcademicTermId", "Status"); + + b.ToTable("EvaluationSetups"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExamPlan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcademicTermId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("varchar(120)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PublishedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("AcademicTermId", "Status"); + + b.ToTable("ExamPlans"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExamSession", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ClassroomId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndsAt") + .HasColumnType("datetime(6)"); + + b.Property("ExamDate") + .HasColumnType("date"); + + b.Property("ExamPlanId") + .HasColumnType("char(36)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PeriodCount") + .HasColumnType("int"); + + b.Property("RequiredBuildingId") + .HasColumnType("char(36)"); + + b.Property("RequiredInvigilatorCount") + .HasColumnType("int"); + + b.Property("StartPeriod") + .HasColumnType("int"); + + b.Property("StartsAt") + .HasColumnType("datetime(6)"); + + b.Property("TeachingTaskId") + .HasColumnType("char(36)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ClassroomId"); + + b.HasIndex("RequiredBuildingId"); + + b.HasIndex("TeachingTaskId"); + + b.HasIndex("ExamPlanId", "ExamDate"); + + b.HasIndex("ExamPlanId", "StartsAt"); + + b.ToTable("ExamSessions"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExamSessionInvigilator", b => + { + b.Property("ExamSessionId") + .HasColumnType("char(36)"); + + b.Property("TeacherId") + .HasColumnType("char(36)"); + + b.HasKey("ExamSessionId", "TeacherId"); + + b.HasIndex("TeacherId"); + + b.ToTable("ExamSessionInvigilators"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GradeItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("GradeSheetId") + .HasColumnType("char(36)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(60) + .HasColumnType("varchar(60)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Weight") + .HasPrecision(5, 1) + .HasColumnType("decimal(5,1)"); + + b.HasKey("Id"); + + b.HasIndex("GradeSheetId", "SortOrder"); + + b.ToTable("GradeItems"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GradeItemScore", b => + { + b.Property("GradeRecordId") + .HasColumnType("char(36)"); + + b.Property("GradeItemId") + .HasColumnType("char(36)"); + + b.Property("Score") + .HasPrecision(5, 1) + .HasColumnType("decimal(5,1)"); + + b.HasKey("GradeRecordId", "GradeItemId"); + + b.HasIndex("GradeItemId"); + + b.ToTable("GradeItemScores"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GradeModification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ApplicantUserId") + .HasColumnType("char(36)"); + + b.Property("CollegeReviewedAt") + .HasColumnType("datetime(6)"); + + b.Property("CollegeReviewedByUserId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurrentScore") + .HasPrecision(5, 1) + .HasColumnType("decimal(5,1)"); + + b.Property("FinalReviewedAt") + .HasColumnType("datetime(6)"); + + b.Property("FinalReviewedByUserId") + .HasColumnType("char(36)"); + + b.Property("GradeRecordId") + .HasColumnType("char(36)"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("RequestedScore") + .HasPrecision(5, 1) + .HasColumnType("decimal(5,1)"); + + b.Property("ReviewComment") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("SubmittedAt") + .HasColumnType("datetime(6)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("GradeRecordId"); + + b.HasIndex("Status", "CreatedAt"); + + b.ToTable("GradeModifications"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GradeRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ExamStatus") + .HasColumnType("int"); + + b.Property("FinalScore") + .HasPrecision(5, 1) + .HasColumnType("decimal(5,1)"); + + b.Property("GradePoint") + .HasPrecision(3, 1) + .HasColumnType("decimal(3,1)"); + + b.Property("GradeSheetId") + .HasColumnType("char(36)"); + + b.Property("Notes") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RegularScore") + .HasPrecision(5, 1) + .HasColumnType("decimal(5,1)"); + + b.Property("StudentId") + .HasColumnType("char(36)"); + + b.Property("TotalScore") + .HasPrecision(5, 1) + .HasColumnType("decimal(5,1)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("GradeSheetId", "StudentId") + .IsUnique(); + + b.HasIndex("StudentId", "TotalScore"); + + b.ToTable("GradeRecords"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GradeSheet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("FinalWeight") + .HasPrecision(5, 1) + .HasColumnType("decimal(5,1)"); + + b.Property("PublishedAt") + .HasColumnType("datetime(6)"); + + b.Property("RegularWeight") + .HasPrecision(5, 1) + .HasColumnType("decimal(5,1)"); + + b.Property("ReviewComment") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReviewedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("SubmittedAt") + .HasColumnType("datetime(6)"); + + b.Property("TeachingTaskId") + .HasColumnType("char(36)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Status"); + + b.HasIndex("TeachingTaskId") + .IsUnique(); + + b.ToTable("GradeSheets"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationAuditBatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CalculatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EnrollmentYear") + .HasColumnType("int"); + + b.Property("GraduationYear") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("varchar(120)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PublishedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Status"); + + b.HasIndex("GraduationYear", "EnrollmentYear"); + + b.ToTable("GraduationAuditBatches"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationAuditResult", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CalculatedConclusion") + .HasColumnType("int"); + + b.Property("Conclusion") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurriculumPlanId") + .HasColumnType("char(36)"); + + b.Property("EarnedCredits") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("FailedCourseCount") + .HasColumnType("int"); + + b.Property("GraduationAuditBatchId") + .HasColumnType("char(36)"); + + b.Property("IsOverridden") + .HasColumnType("tinyint(1)"); + + b.Property("MissingCourseNames") + .IsRequired() + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("PassedRequiredCourseCount") + .HasColumnType("int"); + + b.Property("RequiredCourseCount") + .HasColumnType("int"); + + b.Property("RequiredCredits") + .HasPrecision(6, 2) + .HasColumnType("decimal(6,2)"); + + b.Property("ReviewComment") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReviewedAt") + .HasColumnType("datetime(6)"); + + b.Property("StudentId") + .HasColumnType("char(36)"); + + b.Property("StudentStatusSnapshot") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CurriculumPlanId"); + + b.HasIndex("StudentId"); + + b.HasIndex("Conclusion", "IsOverridden"); + + b.HasIndex("GraduationAuditBatchId", "StudentId") + .IsUnique(); + + b.ToTable("GraduationAuditResults"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationClearanceBatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ClosedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("GraduationYear") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("varchar(120)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("GraduationYear", "Status"); + + b.ToTable("GraduationClearanceBatches"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationClearanceItem", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("GraduationClearanceBatchId") + .HasColumnType("char(36)"); + + b.Property("IsRequired") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("ResponsibleRole") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("ResponsibleUnit") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("GraduationClearanceBatchId", "Code") + .IsUnique(); + + b.ToTable("GraduationClearanceItems"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationClearanceRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CompletedByUserId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("GraduationClearanceItemId") + .HasColumnType("char(36)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StudentId") + .HasColumnType("char(36)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("GraduationClearanceItemId", "StudentId") + .IsUnique(); + + b.HasIndex("StudentId", "Status"); + + b.ToTable("GraduationClearanceRecords"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Major", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Code") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("CollegeId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DegreeType") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SchoolingYears") + .HasColumnType("int"); + + b.Property("SortOrder") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Code") + .IsUnique(); + + b.HasIndex("CollegeId"); + + b.HasIndex("IsEnabled", "SortOrder"); + + b.ToTable("Majors"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.MakeupExamAutoJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedSessions") + .HasColumnType("int"); + + b.Property("EnrolledStudents") + .HasColumnType("int"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("MakeupExamPlanId") + .HasColumnType("char(36)"); + + b.Property("MessagesJson") + .HasColumnType("longtext"); + + b.Property("ProcessedCourses") + .HasColumnType("int"); + + b.Property("StartedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalCourses") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("MakeupExamPlanId", "CreatedAt"); + + b.HasIndex("Status", "CreatedAt"); + + b.ToTable("MakeupExamAutoJobs"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.MakeupExamEnrollment", b => + { + b.Property("MakeupExamSessionId") + .HasColumnType("char(36)"); + + b.Property("StudentId") + .HasColumnType("char(36)"); + + b.Property("MakeupScore") + .HasPrecision(5, 1) + .HasColumnType("decimal(5,1)"); + + b.Property("Reason") + .HasColumnType("int"); + + b.Property("SourceDeferredExamId") + .HasColumnType("char(36)"); + + b.Property("SourceGradeRecordId") + .HasColumnType("char(36)"); + + b.HasKey("MakeupExamSessionId", "StudentId"); + + b.HasIndex("SourceDeferredExamId"); + + b.HasIndex("SourceGradeRecordId"); + + b.HasIndex("StudentId", "MakeupExamSessionId"); + + b.ToTable("MakeupExamEnrollments"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.MakeupExamPlan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcademicTermId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("varchar(120)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PublishedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("AcademicTermId", "Status"); + + b.ToTable("MakeupExamPlans"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.MakeupExamSession", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ClassroomId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndsAt") + .HasColumnType("datetime(6)"); + + b.Property("ExamDate") + .HasColumnType("date"); + + b.Property("MakeupExamPlanId") + .HasColumnType("char(36)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PeriodCount") + .HasColumnType("int"); + + b.Property("RequiredBuildingId") + .HasColumnType("char(36)"); + + b.Property("RequiredInvigilatorCount") + .HasColumnType("int"); + + b.Property("StartPeriod") + .HasColumnType("int"); + + b.Property("StartsAt") + .HasColumnType("datetime(6)"); + + b.Property("TeachingTaskId") + .HasColumnType("char(36)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ClassroomId"); + + b.HasIndex("RequiredBuildingId"); + + b.HasIndex("TeachingTaskId"); + + b.HasIndex("MakeupExamPlanId", "ExamDate"); + + b.HasIndex("MakeupExamPlanId", "StartsAt"); + + b.ToTable("MakeupExamSessions"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.MakeupExamSessionInvigilator", b => + { + b.Property("MakeupExamSessionId") + .HasColumnType("char(36)"); + + b.Property("TeacherId") + .HasColumnType("char(36)"); + + b.HasKey("MakeupExamSessionId", "TeacherId"); + + b.HasIndex("TeacherId"); + + b.ToTable("MakeupExamSessionInvigilators"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.MessageDispatch", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AudienceId") + .HasColumnType("char(36)"); + + b.Property("AudienceName") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("AudienceType") + .HasColumnType("int"); + + b.Property("Category") + .HasColumnType("int"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("LinkUrl") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("RecipientCount") + .HasColumnType("int"); + + b.Property("SenderName") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("SenderUserId") + .HasColumnType("char(36)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("SenderUserId", "CreatedAt"); + + b.ToTable("MessageDispatches"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Notification", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("Category") + .HasColumnType("int"); + + b.Property("Content") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IsRead") + .HasColumnType("tinyint(1)"); + + b.Property("LinkUrl") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("MessageDispatchId") + .HasColumnType("char(36)"); + + b.Property("Title") + .IsRequired() + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.HasIndex("MessageDispatchId"); + + b.HasIndex("UserId", "IsRead"); + + b.HasIndex("UserId", "Category", "CreatedAt"); + + b.ToTable("Notifications"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.OfficialDocument", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DocumentNumber") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("InvalidatedAt") + .HasColumnType("datetime(6)"); + + b.Property("InvalidatedByUserId") + .HasColumnType("char(36)"); + + b.Property("InvalidationReason") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("IssuedAt") + .HasColumnType("datetime(6)"); + + b.Property("IssuedByUserId") + .HasColumnType("char(36)"); + + b.Property("PdfContent") + .IsRequired() + .HasColumnType("longblob"); + + b.Property("PdfSha256") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("Purpose") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ReissuedFromDocumentId") + .HasColumnType("char(36)"); + + b.Property("SnapshotJson") + .IsRequired() + .HasColumnType("longtext"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StudentId") + .HasColumnType("char(36)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("VerificationCodeHash") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.HasKey("Id"); + + b.HasIndex("DocumentNumber") + .IsUnique(); + + b.HasIndex("InvalidatedByUserId"); + + b.HasIndex("IssuedByUserId"); + + b.HasIndex("ReissuedFromDocumentId") + .IsUnique(); + + b.HasIndex("VerificationCodeHash") + .IsUnique(); + + b.HasIndex("Status", "IssuedAt"); + + b.HasIndex("StudentId", "IssuedAt"); + + b.ToTable("OfficialDocuments"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.OfficialDocumentDownload", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DownloadedByUserId") + .HasColumnType("char(36)"); + + b.Property("IpAddress") + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("OfficialDocumentId") + .HasColumnType("char(36)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UserAgent") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.HasKey("Id"); + + b.HasIndex("DownloadedByUserId", "CreatedAt"); + + b.HasIndex("OfficialDocumentId", "CreatedAt"); + + b.ToTable("OfficialDocumentDownloads"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ScheduleEntry", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ClassroomId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DayOfWeek") + .HasColumnType("int"); + + b.Property("EndWeek") + .HasColumnType("int"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PeriodCount") + .HasColumnType("int"); + + b.Property("SchedulePlanId") + .HasColumnType("char(36)"); + + b.Property("StartPeriod") + .HasColumnType("int"); + + b.Property("StartWeek") + .HasColumnType("int"); + + b.Property("TeachingTaskId") + .HasColumnType("char(36)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WeekPattern") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("ClassroomId"); + + b.HasIndex("TeachingTaskId"); + + b.HasIndex("SchedulePlanId", "DayOfWeek", "StartPeriod"); + + b.ToTable("ScheduleEntries"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.SchedulePlan", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcademicTermId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("varchar(120)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PublishedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Version") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.HasKey("Id"); + + b.HasIndex("AcademicTermId", "Status"); + + b.HasIndex("AcademicTermId", "Version") + .IsUnique(); + + b.ToTable("SchedulePlans"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.SchedulePublishJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcademicTermId") + .HasColumnType("char(36)"); + + b.Property("ActiveAcademicTermId") + .HasColumnType("char(36)"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CompletedSteps") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CurrentStep") + .HasMaxLength(200) + .HasColumnType("varchar(200)"); + + b.Property("ErrorMessage") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("RequestedByUserId") + .HasColumnType("char(36)"); + + b.Property("SchedulePlanId") + .HasColumnType("char(36)"); + + b.Property("StartedAt") + .HasColumnType("datetime(6)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TotalSteps") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("ActiveAcademicTermId") + .IsUnique(); + + b.HasIndex("RequestedByUserId"); + + b.HasIndex("SchedulePlanId", "CreatedAt"); + + b.HasIndex("Status", "CreatedAt"); + + b.ToTable("SchedulePublishJobs"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ScheduleTimeSlot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcademicTermId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndsAt") + .HasColumnType("time"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("PeriodNumber") + .HasColumnType("int"); + + b.Property("StartsAt") + .HasColumnType("time"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("AcademicTermId", "PeriodNumber") + .IsUnique(); + + b.ToTable("ScheduleTimeSlots"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Student", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AdministrativeClassId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DateOfBirth") + .HasColumnType("date"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("EnrollmentDate") + .HasColumnType("date"); + + b.Property("EnrollmentYear") + .HasColumnType("int"); + + b.Property("Gender") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Phone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StudentNumber") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("EnrollmentYear"); + + b.HasIndex("StudentNumber") + .IsUnique(); + + b.HasIndex("UserId"); + + b.HasIndex("AdministrativeClassId", "Status"); + + b.ToTable("Students"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.StudentStatusChange", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ApprovedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("OriginalStatus") + .HasColumnType("int"); + + b.Property("Reason") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("ReviewComment") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReviewedAt") + .HasColumnType("datetime(6)"); + + b.Property("State") + .HasColumnType("int"); + + b.Property("StudentId") + .HasColumnType("char(36)"); + + b.Property("SubmittedAt") + .HasColumnType("datetime(6)"); + + b.Property("TargetStatus") + .HasColumnType("int"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("StudentId", "State"); + + b.ToTable("StudentStatusChanges"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Teacher", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CollegeId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Email") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Gender") + .HasColumnType("int"); + + b.Property("HireDate") + .HasColumnType("date"); + + b.Property("IsExternal") + .HasColumnType("tinyint(1)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Phone") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TeacherNumber") + .IsRequired() + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("Title") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("TeacherNumber") + .IsUnique(); + + b.HasIndex("UserId"); + + b.HasIndex("CollegeId", "Status"); + + b.ToTable("Teachers"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeacherCourseApplication", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcademicTermId") + .HasColumnType("char(36)"); + + b.Property("CourseId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReviewComment") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("ReviewedAt") + .HasColumnType("datetime(6)"); + + b.Property("ReviewedByUserId") + .HasColumnType("char(36)"); + + b.Property("Statement") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("SubmittedAt") + .HasColumnType("datetime(6)"); + + b.Property("TeacherId") + .HasColumnType("char(36)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.HasIndex("ReviewedByUserId"); + + b.HasIndex("TeacherId"); + + b.HasIndex("Status", "AcademicTermId"); + + b.HasIndex("AcademicTermId", "TeacherId", "CourseId") + .IsUnique(); + + b.ToTable("TeacherCourseApplications"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTask", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcademicTermId") + .HasColumnType("char(36)"); + + b.Property("Capacity") + .HasColumnType("int"); + + b.Property("CourseId") + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EndWeek") + .HasColumnType("int"); + + b.Property("GenerationBatchCode") + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("varchar(120)"); + + b.Property("Notes") + .HasMaxLength(500) + .HasColumnType("varchar(500)"); + + b.Property("PublishedAt") + .HasColumnType("datetime(6)"); + + b.Property("SchedulingMode") + .HasColumnType("int"); + + b.Property("StartWeek") + .HasColumnType("int"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("TaskNumber") + .IsRequired() + .HasMaxLength(40) + .HasColumnType("varchar(40)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("WeeklyHours") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CourseId"); + + b.HasIndex("TaskNumber") + .IsUnique(); + + b.HasIndex("AcademicTermId", "Status"); + + b.ToTable("TeachingTasks"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTaskAllowedClassroom", b => + { + b.Property("TeachingTaskScheduleConstraintId") + .HasColumnType("char(36)"); + + b.Property("ClassroomId") + .HasColumnType("char(36)"); + + b.HasKey("TeachingTaskScheduleConstraintId", "ClassroomId"); + + b.HasIndex("ClassroomId"); + + b.ToTable("TeachingTaskAllowedClassrooms"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTaskClass", b => + { + b.Property("TeachingTaskId") + .HasColumnType("char(36)"); + + b.Property("AdministrativeClassId") + .HasColumnType("char(36)"); + + b.HasKey("TeachingTaskId", "AdministrativeClassId"); + + b.HasIndex("AdministrativeClassId"); + + b.ToTable("TeachingTaskClasses"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTaskScheduleConstraint", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AllowedDayOfWeeks") + .HasMaxLength(20) + .HasColumnType("varchar(20)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("EarliestPeriod") + .HasColumnType("int"); + + b.Property("LatestPeriod") + .HasColumnType("int"); + + b.Property("RequiredBuildingId") + .HasColumnType("char(36)"); + + b.Property("RequiredCampusId") + .HasColumnType("char(36)"); + + b.Property("RequiresClassroom") + .HasColumnType("tinyint(1)"); + + b.Property("TeachingTaskId") + .HasColumnType("char(36)"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("RequiredBuildingId"); + + b.HasIndex("RequiredCampusId"); + + b.HasIndex("TeachingTaskId") + .IsUnique(); + + b.ToTable("TeachingTaskScheduleConstraints"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTaskTeacher", b => + { + b.Property("TeachingTaskId") + .HasColumnType("char(36)"); + + b.Property("TeacherId") + .HasColumnType("char(36)"); + + b.Property("IsPrimary") + .HasColumnType("tinyint(1)"); + + b.HasKey("TeachingTaskId", "TeacherId"); + + b.HasIndex("TeacherId"); + + b.ToTable("TeachingTaskTeachers"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.WarningRecord", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcademicTermId") + .HasColumnType("char(36)"); + + b.Property("AcknowledgeComment") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("AcknowledgedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Detail") + .IsRequired() + .HasMaxLength(1000) + .HasColumnType("varchar(1000)"); + + b.Property("Status") + .HasColumnType("int"); + + b.Property("StudentId") + .HasColumnType("char(36)"); + + b.Property("TriggerValue") + .HasPrecision(7, 2) + .HasColumnType("decimal(7,2)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("Status"); + + b.HasIndex("StudentId", "AcademicTermId", "Type") + .IsUnique(); + + b.ToTable("WarningRecords"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.WarningRule", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AcademicTermId") + .HasColumnType("char(36)"); + + b.Property("AutoCheckEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("CheckDayOfWeek") + .HasColumnType("int"); + + b.Property("CheckHour") + .HasColumnType("int"); + + b.Property("CheckMinute") + .HasColumnType("int"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("Description") + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LastCheckAt") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("NotifyCounselor") + .HasColumnType("tinyint(1)"); + + b.Property("NotifyStudent") + .HasColumnType("tinyint(1)"); + + b.Property("Threshold") + .HasPrecision(7, 2) + .HasColumnType("decimal(7,2)"); + + b.Property("Type") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("AcademicTermId", "Type") + .IsUnique(); + + b.ToTable("WarningRules"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Identity.ApplicationRole", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("DataScope") + .HasColumnType("int"); + + b.Property("Description") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Identity.ApplicationUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("AccessFailedCount") + .HasColumnType("int"); + + b.Property("CalendarSubscriptionCreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("CalendarSubscriptionStamp") + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("CollegeId") + .HasColumnType("char(36)"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("longtext"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("DisplayName") + .IsRequired() + .HasMaxLength(50) + .HasColumnType("varchar(50)"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("EmailConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("IsEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LastLoginAt") + .HasColumnType("datetime(6)"); + + b.Property("LockoutEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("LockoutEnd") + .HasColumnType("datetime"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.Property("PasswordHash") + .HasColumnType("longtext"); + + b.Property("PhoneNumber") + .HasColumnType("longtext"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("tinyint(1)"); + + b.Property("SecurityStamp") + .HasColumnType("longtext"); + + b.Property("StaffNumber") + .HasMaxLength(30) + .HasColumnType("varchar(30)"); + + b.Property("TwoFactorEnabled") + .HasColumnType("tinyint(1)"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("varchar(256)"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.HasIndex("StaffNumber"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.System.AuditLog", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("IpAddress") + .HasMaxLength(64) + .HasColumnType("varchar(64)"); + + b.Property("Method") + .IsRequired() + .HasMaxLength(10) + .HasColumnType("varchar(10)"); + + b.Property("Path") + .IsRequired() + .HasMaxLength(300) + .HasColumnType("varchar(300)"); + + b.Property("StatusCode") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("UserName") + .HasMaxLength(100) + .HasColumnType("varchar(100)"); + + b.HasKey("Id"); + + b.HasIndex("CreatedAt"); + + b.ToTable("AuditLogs"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.System.BackgroundJobOutboxMessage", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("char(36)"); + + b.Property("CompletedAt") + .HasColumnType("datetime(6)"); + + b.Property("CreatedAt") + .HasColumnType("datetime(6)"); + + b.Property("JobId") + .HasColumnType("char(36)"); + + b.Property("JobKind") + .HasColumnType("int"); + + b.Property("LastError") + .HasMaxLength(2000) + .HasColumnType("varchar(2000)"); + + b.Property("LeaseExpiresAt") + .HasColumnType("datetime(6)"); + + b.Property("ProcessingAttempts") + .HasColumnType("int"); + + b.Property("ProcessingToken") + .HasColumnType("char(36)"); + + b.Property("PublishAttempts") + .HasColumnType("int"); + + b.Property("PublishedAt") + .HasColumnType("datetime(6)"); + + b.Property("State") + .HasColumnType("int"); + + b.Property("UpdatedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("LeaseExpiresAt"); + + b.HasIndex("JobKind", "JobId") + .IsUnique(); + + b.HasIndex("State", "CompletedAt"); + + b.HasIndex("State", "CreatedAt"); + + b.ToTable("BackgroundJobOutboxMessages"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("RoleId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("int"); + + b.Property("ClaimType") + .HasColumnType("longtext"); + + b.Property("ClaimValue") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("ProviderKey") + .HasColumnType("varchar(255)"); + + b.Property("ProviderDisplayName") + .HasColumnType("longtext"); + + b.Property("UserId") + .HasColumnType("char(36)"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("RoleId") + .HasColumnType("char(36)"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("char(36)"); + + b.Property("LoginProvider") + .HasColumnType("varchar(255)"); + + b.Property("Name") + .HasColumnType("varchar(255)"); + + b.Property("Value") + .HasColumnType("longtext"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.AdministrativeClass", b => + { + b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", "CounselorUser") + .WithMany() + .HasForeignKey("CounselorUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Jiaowu.Api.Domain.Academic.Major", "Major") + .WithMany() + .HasForeignKey("MajorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CounselorUser"); + + b.Navigation("Major"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.AttendanceRecord", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.AttendanceSheet", "AttendanceSheet") + .WithMany("Records") + .HasForeignKey("AttendanceSheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student") + .WithMany() + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AttendanceSheet"); + + b.Navigation("Student"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.AttendanceSheet", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask") + .WithMany() + .HasForeignKey("TeachingTaskId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("TeachingTask"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.AutomaticScheduleJob", b => + { + b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("RequestedByUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Jiaowu.Api.Domain.Academic.SchedulePlan", "SchedulePlan") + .WithMany() + .HasForeignKey("SchedulePlanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SchedulePlan"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Building", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Campus", "Campus") + .WithMany() + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Campus"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Classroom", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Building", "Building") + .WithMany() + .HasForeignKey("BuildingId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Building"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ClassroomReservation", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.AcademicTerm", "AcademicTerm") + .WithMany() + .HasForeignKey("AcademicTermId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.College", "ApplicantCollege") + .WithMany() + .HasForeignKey("ApplicantCollegeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", "ApplicantUser") + .WithMany() + .HasForeignKey("ApplicantUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.Classroom", "Classroom") + .WithMany() + .HasForeignKey("ClassroomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", "ReviewedByUser") + .WithMany() + .HasForeignKey("ReviewedByUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("AcademicTerm"); + + b.Navigation("ApplicantCollege"); + + b.Navigation("ApplicantUser"); + + b.Navigation("Classroom"); + + b.Navigation("ReviewedByUser"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.College", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Campus", "Campus") + .WithMany() + .HasForeignKey("CampusId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("Campus"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Course", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.College", "College") + .WithMany() + .HasForeignKey("CollegeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.CourseCategory", "CourseCategory") + .WithMany() + .HasForeignKey("CourseCategoryId") + .OnDelete(DeleteBehavior.Restrict); + + b.Navigation("College"); + + b.Navigation("CourseCategory"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseAdjustment", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Classroom", "Classroom") + .WithMany() + .HasForeignKey("ClassroomId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Jiaowu.Api.Domain.Academic.Teacher", "SubstituteTeacher") + .WithMany() + .HasForeignKey("SubstituteTeacherId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask") + .WithMany() + .HasForeignKey("TeachingTaskId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Classroom"); + + b.Navigation("SubstituteTeacher"); + + b.Navigation("TeachingTask"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseEnrollment", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.CourseSelectionOffering", "CourseSelectionOffering") + .WithMany("Enrollments") + .HasForeignKey("CourseSelectionOfferingId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student") + .WithMany() + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CourseSelectionOffering"); + + b.Navigation("Student"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseExemption", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student") + .WithMany() + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask") + .WithMany() + .HasForeignKey("TeachingTaskId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Student"); + + b.Navigation("TeachingTask"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CoursePrerequisite", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Course", "Course") + .WithMany("Prerequisites") + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.Course", "PrerequisiteCourse") + .WithMany("RequiredByCourses") + .HasForeignKey("PrerequisiteCourseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Course"); + + b.Navigation("PrerequisiteCourse"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseSelectionOffering", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.CourseSelectionRound", "CourseSelectionRound") + .WithMany("Offerings") + .HasForeignKey("CourseSelectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask") + .WithMany() + .HasForeignKey("TeachingTaskId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CourseSelectionRound"); + + b.Navigation("TeachingTask"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseSelectionRound", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.AcademicTerm", "AcademicTerm") + .WithMany() + .HasForeignKey("AcademicTermId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AcademicTerm"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseSelectionRoundGrade", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.CourseSelectionRound", "CourseSelectionRound") + .WithMany("EligibleGrades") + .HasForeignKey("CourseSelectionRoundId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CourseSelectionRound"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseSubstitution", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Course", "OriginalCourse") + .WithMany() + .HasForeignKey("OriginalCourseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student") + .WithMany() + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.Course", "SubstituteCourse") + .WithMany() + .HasForeignKey("SubstituteCourseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("OriginalCourse"); + + b.Navigation("Student"); + + b.Navigation("SubstituteCourse"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CurriculumCourse", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Course", "Course") + .WithMany() + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.CurriculumModule", "CurriculumModule") + .WithMany("Courses") + .HasForeignKey("CurriculumModuleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Course"); + + b.Navigation("CurriculumModule"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CurriculumModule", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.CurriculumPlan", "CurriculumPlan") + .WithMany("Modules") + .HasForeignKey("CurriculumPlanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("CurriculumPlan"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CurriculumPlan", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Major", "Major") + .WithMany() + .HasForeignKey("MajorId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Major"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.DeferredExam", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student") + .WithMany() + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask") + .WithMany() + .HasForeignKey("TeachingTaskId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Student"); + + b.Navigation("TeachingTask"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.DegreeAwardResult", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.DegreeAwardBatch", "DegreeAwardBatch") + .WithMany("Results") + .HasForeignKey("DegreeAwardBatchId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.GraduationAuditResult", "GraduationAuditResult") + .WithMany() + .HasForeignKey("GraduationAuditResultId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student") + .WithMany() + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("DegreeAwardBatch"); + + b.Navigation("GraduationAuditResult"); + + b.Navigation("Student"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.EvaluationDimension", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.EvaluationSetup", "EvaluationSetup") + .WithMany("Dimensions") + .HasForeignKey("EvaluationSetupId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EvaluationSetup"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.EvaluationRecord", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.EvaluationSetup", "EvaluationSetup") + .WithMany("Records") + .HasForeignKey("EvaluationSetupId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student") + .WithMany() + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask") + .WithMany() + .HasForeignKey("TeachingTaskId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("EvaluationSetup"); + + b.Navigation("Student"); + + b.Navigation("TeachingTask"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.EvaluationScore", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.EvaluationDimension", "EvaluationDimension") + .WithMany("Scores") + .HasForeignKey("EvaluationDimensionId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.EvaluationRecord", "EvaluationRecord") + .WithMany("Scores") + .HasForeignKey("EvaluationRecordId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("EvaluationDimension"); + + b.Navigation("EvaluationRecord"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.EvaluationSetup", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.AcademicTerm", "AcademicTerm") + .WithMany() + .HasForeignKey("AcademicTermId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AcademicTerm"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExamPlan", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.AcademicTerm", "AcademicTerm") + .WithMany() + .HasForeignKey("AcademicTermId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AcademicTerm"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExamSession", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Classroom", "Classroom") + .WithMany() + .HasForeignKey("ClassroomId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Jiaowu.Api.Domain.Academic.ExamPlan", "ExamPlan") + .WithMany("Sessions") + .HasForeignKey("ExamPlanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.Building", "RequiredBuilding") + .WithMany() + .HasForeignKey("RequiredBuildingId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask") + .WithMany() + .HasForeignKey("TeachingTaskId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Classroom"); + + b.Navigation("ExamPlan"); + + b.Navigation("RequiredBuilding"); + + b.Navigation("TeachingTask"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExamSessionInvigilator", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.ExamSession", "ExamSession") + .WithMany("Invigilators") + .HasForeignKey("ExamSessionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.Teacher", "Teacher") + .WithMany() + .HasForeignKey("TeacherId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("ExamSession"); + + b.Navigation("Teacher"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GradeItem", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.GradeSheet", "GradeSheet") + .WithMany("Items") + .HasForeignKey("GradeSheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GradeSheet"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GradeItemScore", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.GradeItem", "GradeItem") + .WithMany("Scores") + .HasForeignKey("GradeItemId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.GradeRecord", "GradeRecord") + .WithMany("ItemScores") + .HasForeignKey("GradeRecordId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GradeItem"); + + b.Navigation("GradeRecord"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GradeModification", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.GradeRecord", "GradeRecord") + .WithMany() + .HasForeignKey("GradeRecordId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("GradeRecord"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GradeRecord", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.GradeSheet", "GradeSheet") + .WithMany("Records") + .HasForeignKey("GradeSheetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student") + .WithMany() + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("GradeSheet"); + + b.Navigation("Student"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GradeSheet", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask") + .WithMany() + .HasForeignKey("TeachingTaskId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("TeachingTask"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationAuditResult", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.CurriculumPlan", "CurriculumPlan") + .WithMany() + .HasForeignKey("CurriculumPlanId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Jiaowu.Api.Domain.Academic.GraduationAuditBatch", "GraduationAuditBatch") + .WithMany("Results") + .HasForeignKey("GraduationAuditBatchId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student") + .WithMany() + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("CurriculumPlan"); + + b.Navigation("GraduationAuditBatch"); + + b.Navigation("Student"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationClearanceItem", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.GraduationClearanceBatch", "GraduationClearanceBatch") + .WithMany("Items") + .HasForeignKey("GraduationClearanceBatchId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("GraduationClearanceBatch"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationClearanceRecord", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.GraduationClearanceItem", "GraduationClearanceItem") + .WithMany("Records") + .HasForeignKey("GraduationClearanceItemId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student") + .WithMany() + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("GraduationClearanceItem"); + + b.Navigation("Student"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Major", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.College", "College") + .WithMany() + .HasForeignKey("CollegeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("College"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.MakeupExamAutoJob", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.MakeupExamPlan", "MakeupExamPlan") + .WithMany() + .HasForeignKey("MakeupExamPlanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("MakeupExamPlan"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.MakeupExamEnrollment", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.MakeupExamSession", "MakeupExamSession") + .WithMany("Enrollments") + .HasForeignKey("MakeupExamSessionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.DeferredExam", "SourceDeferredExam") + .WithMany() + .HasForeignKey("SourceDeferredExamId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Jiaowu.Api.Domain.Academic.GradeRecord", "SourceGradeRecord") + .WithMany() + .HasForeignKey("SourceGradeRecordId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student") + .WithMany() + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("MakeupExamSession"); + + b.Navigation("SourceDeferredExam"); + + b.Navigation("SourceGradeRecord"); + + b.Navigation("Student"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.MakeupExamPlan", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.AcademicTerm", "AcademicTerm") + .WithMany() + .HasForeignKey("AcademicTermId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AcademicTerm"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.MakeupExamSession", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Classroom", "Classroom") + .WithMany() + .HasForeignKey("ClassroomId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Jiaowu.Api.Domain.Academic.MakeupExamPlan", "MakeupExamPlan") + .WithMany("Sessions") + .HasForeignKey("MakeupExamPlanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.Building", "RequiredBuilding") + .WithMany() + .HasForeignKey("RequiredBuildingId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask") + .WithMany() + .HasForeignKey("TeachingTaskId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Classroom"); + + b.Navigation("MakeupExamPlan"); + + b.Navigation("RequiredBuilding"); + + b.Navigation("TeachingTask"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.MakeupExamSessionInvigilator", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.MakeupExamSession", "MakeupExamSession") + .WithMany("Invigilators") + .HasForeignKey("MakeupExamSessionId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.Teacher", "Teacher") + .WithMany() + .HasForeignKey("TeacherId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("MakeupExamSession"); + + b.Navigation("Teacher"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Notification", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.MessageDispatch", "MessageDispatch") + .WithMany("Notifications") + .HasForeignKey("MessageDispatchId") + .OnDelete(DeleteBehavior.Cascade); + + b.Navigation("MessageDispatch"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.OfficialDocument", b => + { + b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", "InvalidatedByUser") + .WithMany() + .HasForeignKey("InvalidatedByUserId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", "IssuedByUser") + .WithMany() + .HasForeignKey("IssuedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.OfficialDocument", "ReissuedFromDocument") + .WithMany() + .HasForeignKey("ReissuedFromDocumentId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student") + .WithMany() + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("InvalidatedByUser"); + + b.Navigation("IssuedByUser"); + + b.Navigation("ReissuedFromDocument"); + + b.Navigation("Student"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.OfficialDocumentDownload", b => + { + b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", "DownloadedByUser") + .WithMany() + .HasForeignKey("DownloadedByUserId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.OfficialDocument", "OfficialDocument") + .WithMany("Downloads") + .HasForeignKey("OfficialDocumentId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("DownloadedByUser"); + + b.Navigation("OfficialDocument"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ScheduleEntry", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Classroom", "Classroom") + .WithMany() + .HasForeignKey("ClassroomId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Jiaowu.Api.Domain.Academic.SchedulePlan", "SchedulePlan") + .WithMany("Entries") + .HasForeignKey("SchedulePlanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask") + .WithMany() + .HasForeignKey("TeachingTaskId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Classroom"); + + b.Navigation("SchedulePlan"); + + b.Navigation("TeachingTask"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.SchedulePlan", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.AcademicTerm", "AcademicTerm") + .WithMany() + .HasForeignKey("AcademicTermId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AcademicTerm"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.SchedulePublishJob", b => + { + b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("RequestedByUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Jiaowu.Api.Domain.Academic.SchedulePlan", "SchedulePlan") + .WithMany() + .HasForeignKey("SchedulePlanId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("SchedulePlan"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ScheduleTimeSlot", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.AcademicTerm", "AcademicTerm") + .WithMany() + .HasForeignKey("AcademicTermId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AcademicTerm"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Student", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.AdministrativeClass", "AdministrativeClass") + .WithMany("Students") + .HasForeignKey("AdministrativeClassId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("AdministrativeClass"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.StudentStatusChange", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student") + .WithMany() + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Student"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Teacher", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.College", "College") + .WithMany() + .HasForeignKey("CollegeId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.SetNull); + + b.Navigation("College"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeacherCourseApplication", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.AcademicTerm", "AcademicTerm") + .WithMany() + .HasForeignKey("AcademicTermId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.Course", "Course") + .WithMany() + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("ReviewedByUserId") + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne("Jiaowu.Api.Domain.Academic.Teacher", "Teacher") + .WithMany() + .HasForeignKey("TeacherId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AcademicTerm"); + + b.Navigation("Course"); + + b.Navigation("Teacher"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTask", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.AcademicTerm", "AcademicTerm") + .WithMany() + .HasForeignKey("AcademicTermId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.Course", "Course") + .WithMany() + .HasForeignKey("CourseId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("AcademicTerm"); + + b.Navigation("Course"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTaskAllowedClassroom", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Classroom", "Classroom") + .WithMany() + .HasForeignKey("ClassroomId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTaskScheduleConstraint", "TeachingTaskScheduleConstraint") + .WithMany("AllowedClassrooms") + .HasForeignKey("TeachingTaskScheduleConstraintId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Classroom"); + + b.Navigation("TeachingTaskScheduleConstraint"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTaskClass", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.AdministrativeClass", "AdministrativeClass") + .WithMany() + .HasForeignKey("AdministrativeClassId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask") + .WithMany("Classes") + .HasForeignKey("TeachingTaskId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AdministrativeClass"); + + b.Navigation("TeachingTask"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTaskScheduleConstraint", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Building", "RequiredBuilding") + .WithMany() + .HasForeignKey("RequiredBuildingId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Jiaowu.Api.Domain.Academic.Campus", "RequiredCampus") + .WithMany() + .HasForeignKey("RequiredCampusId") + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask") + .WithMany() + .HasForeignKey("TeachingTaskId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("RequiredBuilding"); + + b.Navigation("RequiredCampus"); + + b.Navigation("TeachingTask"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTaskTeacher", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Teacher", "Teacher") + .WithMany() + .HasForeignKey("TeacherId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Academic.TeachingTask", "TeachingTask") + .WithMany("Teachers") + .HasForeignKey("TeachingTaskId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("Teacher"); + + b.Navigation("TeachingTask"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.WarningRecord", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.Student", "Student") + .WithMany() + .HasForeignKey("StudentId") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.Navigation("Student"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.WarningRule", b => + { + b.HasOne("Jiaowu.Api.Domain.Academic.AcademicTerm", "AcademicTerm") + .WithMany() + .HasForeignKey("AcademicTermId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.Navigation("AcademicTerm"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("Jiaowu.Api.Domain.Identity.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.AdministrativeClass", b => + { + b.Navigation("Students"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.AttendanceSheet", b => + { + b.Navigation("Records"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.Course", b => + { + b.Navigation("Prerequisites"); + + b.Navigation("RequiredByCourses"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseSelectionOffering", b => + { + b.Navigation("Enrollments"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CourseSelectionRound", b => + { + b.Navigation("EligibleGrades"); + + b.Navigation("Offerings"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CurriculumModule", b => + { + b.Navigation("Courses"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.CurriculumPlan", b => + { + b.Navigation("Modules"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.DegreeAwardBatch", b => + { + b.Navigation("Results"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.EvaluationDimension", b => + { + b.Navigation("Scores"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.EvaluationRecord", b => + { + b.Navigation("Scores"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.EvaluationSetup", b => + { + b.Navigation("Dimensions"); + + b.Navigation("Records"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExamPlan", b => + { + b.Navigation("Sessions"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.ExamSession", b => + { + b.Navigation("Invigilators"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GradeItem", b => + { + b.Navigation("Scores"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GradeRecord", b => + { + b.Navigation("ItemScores"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GradeSheet", b => + { + b.Navigation("Items"); + + b.Navigation("Records"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationAuditBatch", b => + { + b.Navigation("Results"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationClearanceBatch", b => + { + b.Navigation("Items"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.GraduationClearanceItem", b => + { + b.Navigation("Records"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.MakeupExamPlan", b => + { + b.Navigation("Sessions"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.MakeupExamSession", b => + { + b.Navigation("Enrollments"); + + b.Navigation("Invigilators"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.MessageDispatch", b => + { + b.Navigation("Notifications"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.OfficialDocument", b => + { + b.Navigation("Downloads"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.SchedulePlan", b => + { + b.Navigation("Entries"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTask", b => + { + b.Navigation("Classes"); + + b.Navigation("Teachers"); + }); + + modelBuilder.Entity("Jiaowu.Api.Domain.Academic.TeachingTaskScheduleConstraint", b => + { + b.Navigation("AllowedClassrooms"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/20260727043019_MessageRichContent.cs b/src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/20260727043019_MessageRichContent.cs new file mode 100644 index 0000000..0547db1 --- /dev/null +++ b/src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/20260727043019_MessageRichContent.cs @@ -0,0 +1,54 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql +{ + /// + public partial class MessageRichContent : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Content", + table: "Notifications", + type: "longtext", + nullable: false, + oldClrType: typeof(string), + oldType: "varchar(1000)", + oldMaxLength: 1000); + + migrationBuilder.AlterColumn( + name: "Content", + table: "MessageDispatches", + type: "longtext", + nullable: false, + oldClrType: typeof(string), + oldType: "varchar(1000)", + oldMaxLength: 1000); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.AlterColumn( + name: "Content", + table: "Notifications", + type: "varchar(1000)", + maxLength: 1000, + nullable: false, + oldClrType: typeof(string), + oldType: "longtext"); + + migrationBuilder.AlterColumn( + name: "Content", + table: "MessageDispatches", + type: "varchar(1000)", + maxLength: 1000, + nullable: false, + oldClrType: typeof(string), + oldType: "longtext"); + } + } +} diff --git a/src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/AppDbContextModelSnapshot.cs b/src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/AppDbContextModelSnapshot.cs index a9e176f..6d717ae 100644 --- a/src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/AppDbContextModelSnapshot.cs +++ b/src/Jiaowu.Api/Infrastructure/Persistence/Migrations/MySql/AppDbContextModelSnapshot.cs @@ -2366,8 +2366,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql b.Property("Content") .IsRequired() - .HasMaxLength(1000) - .HasColumnType("varchar(1000)"); + .HasColumnType("longtext"); b.Property("CreatedAt") .HasColumnType("datetime(6)"); @@ -2413,8 +2412,7 @@ namespace Jiaowu.Api.Infrastructure.Persistence.Migrations.MySql b.Property("Content") .IsRequired() - .HasMaxLength(1000) - .HasColumnType("varchar(1000)"); + .HasColumnType("longtext"); b.Property("CreatedAt") .HasColumnType("datetime(6)"); diff --git a/tests/Jiaowu.Api.Tests/NotificationsControllerTests.cs b/tests/Jiaowu.Api.Tests/NotificationsControllerTests.cs index f634d45..509a46b 100644 --- a/tests/Jiaowu.Api.Tests/NotificationsControllerTests.cs +++ b/tests/Jiaowu.Api.Tests/NotificationsControllerTests.cs @@ -119,6 +119,85 @@ public sealed class NotificationsControllerTests Assert.Contains(fixture.ClassStudentUser.Id, recipientIds); } + [Fact] + public async Task School_administrator_can_filter_recipients_by_college() + { + await using var fixture = await NotificationFixture.CreateAsync(); + var controller = new NotificationsController( + fixture.Db, + new TestDataScope( + fixture.Sender.Id, + fixture.FirstCollege.Id, + SystemRoles.AcademicAdmin)); + + var result = await controller.Send( + new SendMessageRequest( + "第二学院通知", + "

仅发送给第二学院。

", + RecipientMode: MessageRecipientMode.Filtered, + RecipientFilter: new MessageRecipientFilter( + CollegeId: fixture.SecondCollege.Id)), + CancellationToken.None); + + Assert.IsType(result); + var notification = Assert.Single( + await fixture.Db.Notifications.ToListAsync()); + Assert.Equal(fixture.OutsideRecipient.Id, notification.UserId); + var dispatch = await fixture.Db.MessageDispatches.SingleAsync(); + Assert.Equal(MessageAudienceType.Custom, dispatch.AudienceType); + Assert.Equal("指定学院", dispatch.AudienceName); + } + + [Fact] + public async Task College_administrator_cannot_select_recipient_outside_college() + { + await using var fixture = await NotificationFixture.CreateAsync(); + var controller = new NotificationsController( + fixture.Db, + new TestDataScope( + fixture.Sender.Id, + fixture.FirstCollege.Id, + SystemRoles.CollegeAdmin)); + + var result = await controller.Send( + new SendMessageRequest( + "越权消息", + "

不应发送。

", + RecipientMode: MessageRecipientMode.Selected, + RecipientUserIds: [fixture.OutsideRecipient.Id]), + CancellationToken.None); + + Assert.IsType(result); + Assert.Empty(await fixture.Db.Notifications.ToListAsync()); + Assert.Empty(await fixture.Db.MessageDispatches.ToListAsync()); + } + + [Fact] + public async Task Administrator_can_send_rich_content_larger_than_legacy_limit() + { + await using var fixture = await NotificationFixture.CreateAsync(); + var controller = new NotificationsController( + fixture.Db, + new TestDataScope( + fixture.Sender.Id, + fixture.FirstCollege.Id, + SystemRoles.AcademicAdmin)); + var content = $"

教学安排

{new string('内', 1500)}

"; + + var result = await controller.Send( + new SendMessageRequest( + "富文本通知", + content, + RecipientMode: MessageRecipientMode.Selected, + RecipientUserIds: [fixture.ClassStudentUser.Id]), + CancellationToken.None); + + Assert.IsType(result); + var notification = await fixture.Db.Notifications.SingleAsync(); + Assert.Equal(content, notification.Content); + Assert.Equal(fixture.ClassStudentUser.Id, notification.UserId); + } + [Fact] public async Task Publishing_course_grades_automatically_notifies_roster_students() { @@ -189,6 +268,7 @@ public sealed class NotificationsControllerTests SqliteConnection connection, AppDbContext db, College firstCollege, + College secondCollege, ApplicationUser sender, ApplicationUser collegeRecipient, ApplicationUser outsideRecipient, @@ -201,6 +281,7 @@ public sealed class NotificationsControllerTests this.connection = connection; Db = db; FirstCollege = firstCollege; + SecondCollege = secondCollege; Sender = sender; CollegeRecipient = collegeRecipient; OutsideRecipient = outsideRecipient; @@ -213,6 +294,7 @@ public sealed class NotificationsControllerTests public AppDbContext Db { get; } public College FirstCollege { get; } + public College SecondCollege { get; } public ApplicationUser Sender { get; } public ApplicationUser CollegeRecipient { get; } public ApplicationUser OutsideRecipient { get; } @@ -362,6 +444,7 @@ public sealed class NotificationsControllerTests connection, db, firstCollege, + secondCollege, sender, collegeRecipient, outsideRecipient, diff --git a/web/.env.example b/web/.env.example index 14ea4ad..aa662e2 100644 --- a/web/.env.example +++ b/web/.env.example @@ -1 +1,3 @@ VITE_API_BASE_URL=/api +# 自托管 CKEditor 5:GPL 兼容项目可保留 GPL;商业部署请在构建时填写正式许可证键。 +VITE_CKEDITOR_LICENSE_KEY=GPL diff --git a/web/package-lock.json b/web/package-lock.json index 7da62ad..dc85aca 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,8 +8,11 @@ "name": "web", "version": "0.0.0", "dependencies": { + "@ckeditor/ckeditor5-vue": "^8.2.0", "@element-plus/icons-vue": "^2.3.2", "axios": "^1.18.1", + "ckeditor5": "^48.3.1", + "dompurify": "^3.4.12", "echarts": "^6.1.0", "element-plus": "^2.14.3", "html2canvas": "^1.4.1", @@ -86,6 +89,892 @@ "node": ">=6.9.0" } }, + "node_modules/@ckeditor/ckeditor5-adapter-ckfinder": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-adapter-ckfinder/-/ckeditor5-adapter-ckfinder-48.3.1.tgz", + "integrity": "sha512-xv072kFznzCLzG6Kiro9Pwb6v3FNXMu6/NWQX+NCl0wlqit85hRbIMJW0WyWxuYY4XgcDn4cgB89vRJ5p0zKsQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-upload": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-alignment": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-alignment/-/ckeditor5-alignment-48.3.1.tgz", + "integrity": "sha512-ayiSLBtw4xvtMEPl6AhMX66Io6ajmq+2y2+FePfwu+9B8f8JblEApcWFLlj0HXxfWfJ470sEVMvwUJAwPLH9Jg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-autoformat": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-autoformat/-/ckeditor5-autoformat-48.3.1.tgz", + "integrity": "sha512-65TMkSDpfE63WquypME53ESV209U1iXuhX32x95nc45hSi4CMn25oxqansrCpxIlHWww3d7z3tUgSRFGXpFSag==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-heading": "48.3.1", + "@ckeditor/ckeditor5-typing": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-autosave": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-autosave/-/ckeditor5-autosave-48.3.1.tgz", + "integrity": "sha512-mc9UmTpyVBUn+V/pmzkP8PKJNuijytY8RuV7bckeq9OmxXOz2AD0lqlp+X48OcrgcefCA6e01D1hgElMhHQ/Hw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-basic-styles": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-basic-styles/-/ckeditor5-basic-styles-48.3.1.tgz", + "integrity": "sha512-uVbKZLNScqYyvj/Wg3uP2sWCHVuvYyFCGSNA1osnmI4DwKi3BS4TW/rm3IYUPrD6q2y/GGJK05OyHr3hyw4oig==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-typing": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-block-quote": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-block-quote/-/ckeditor5-block-quote-48.3.1.tgz", + "integrity": "sha512-9T03V/VjWYu6dhyKWZVrlggeOumQXpUt4lygK6NQuSQ9lIMEnhFgzjqtZsGe9l90zvnPaUvV9IC3u1xUFZvzhg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-enter": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-typing": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-bookmark": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-bookmark/-/ckeditor5-bookmark-48.3.1.tgz", + "integrity": "sha512-EC5fmzUT5GKyIwQw+4tnHHJD4kUIBbiwP/+0gjHVZnHisRLfBOC3GAIczzq/AXDBIuaHqC9DZOAQQp+ltItHfQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-link": "48.3.1", + "@ckeditor/ckeditor5-typing": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "@ckeditor/ckeditor5-widget": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-ckbox": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-ckbox/-/ckeditor5-ckbox-48.3.1.tgz", + "integrity": "sha512-ZaonwyuQjqjsho4iKDGSTn/M+SFs/OTl0WMkFU3tOhWpUikWKejNM94z+T6OR9oELWJVb58Jg6ubV6+ZYaLL7A==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-cloud-services": "48.3.1", + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-image": "48.3.1", + "@ckeditor/ckeditor5-link": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-upload": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "blurhash": "2.0.5", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-ckfinder": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-ckfinder/-/ckeditor5-ckfinder-48.3.1.tgz", + "integrity": "sha512-kZBp/eDhr8Y/CcE2UQZxFp3fZ+w4WYdYxmyuQ70mg4cOS1TMV9eh26Zz5khWAt+oAJsxVIDL5KNodak/hFpJ2w==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-adapter-ckfinder": "48.3.1", + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-image": "48.3.1", + "@ckeditor/ckeditor5-link": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-clipboard": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-clipboard/-/ckeditor5-clipboard-48.3.1.tgz", + "integrity": "sha512-PNI7yw9ese+fyQz0LzExRPY1CkjA6q9XqLDeMuLgp92Mkd7Vp1wftbB0ZLVyrzTIw8DEUudKziVsY3A4mT5aWA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "@ckeditor/ckeditor5-widget": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-cloud-services": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-cloud-services/-/ckeditor5-cloud-services-48.3.1.tgz", + "integrity": "sha512-pHJAj5RRhTV7uyexs0ryFj82zZNex91VtmwTOB/z+VPXSHq4jzpxFE1fVuoHgkydUF5S5Dl6TiFru48yYlK4YA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-upload": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-code-block": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-code-block/-/ckeditor5-code-block-48.3.1.tgz", + "integrity": "sha512-9mwgtcrNCTK/06HXH+o8yNbrN/nCAflQtJ6LmAf1Gw/6Fed9gQslMcs5+ADO98Jer5OetpuKeqgmBACAuQUTmg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "48.3.1", + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-enter": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-core": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-core/-/ckeditor5-core-48.3.1.tgz", + "integrity": "sha512-wrAhYK5R8MkGAE0VrOMFQN9LSRpSATS7DFKr4CCJod9EgBT873SCu34Ey0fzSqGxeOqamlcXQJVh/77+3JP4SQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "@ckeditor/ckeditor5-watchdog": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-easy-image": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-easy-image/-/ckeditor5-easy-image-48.3.1.tgz", + "integrity": "sha512-A63AcmaAAGVUhALHVtwHrlBaWEaRjBhxWTwkQoy3RFPpSAXi1k0Cy55HhkVrnqVZSuKmljcX3FttpjOb6MQq8g==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-cloud-services": "48.3.1", + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-image": "48.3.1", + "@ckeditor/ckeditor5-upload": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-editor-balloon": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-editor-balloon/-/ckeditor5-editor-balloon-48.3.1.tgz", + "integrity": "sha512-Unmzb3E+O83gIAvIT/RHyV1EzaxQGOzBiXGJhQahhQg++EvHUt4tmAJ6VzSLgJbtr8z3rMkA+lYpD5+OzpGT4w==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-editor-classic": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-editor-classic/-/ckeditor5-editor-classic-48.3.1.tgz", + "integrity": "sha512-7azS8ry8+c3H8S1RM1Aphq8wCig4mmLpK/kTmdF2/+JkPUszRoVK1HThSmBkstKCHDv4UuSVR+FcJ6Co6Tavog==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-editor-decoupled": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-editor-decoupled/-/ckeditor5-editor-decoupled-48.3.1.tgz", + "integrity": "sha512-S9xGZS7Hl2jWQrZFVwk3o7x9Wwl7tgbQl8TCujr0Bi0zxoM286WkHbBUej9oLVVtDHvq7xDSafmUURbL/K5ZXw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-editor-inline": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-editor-inline/-/ckeditor5-editor-inline-48.3.1.tgz", + "integrity": "sha512-uoXl+lfvGzH6HPHyjgcQcwK/WSDxchmkQvPrMHxTCyeimn4ffzFKzIH4WiOz39t1lXpMjI0hTy66eTB8F/+qvg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-editor-multi-root": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-editor-multi-root/-/ckeditor5-editor-multi-root-48.3.1.tgz", + "integrity": "sha512-5BUDEfVCsj4Al6lHWPDxUCRUymoWwmwwg9hZqd6iSkBtDOuZfiGC9IitMb6qYvrD2Q0MLRoiW0z9n6My4ZMHfQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-emoji": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-emoji/-/ckeditor5-emoji-48.3.1.tgz", + "integrity": "sha512-JQGLX9rMMnMY5/d8bJrF2htxKGxFfrW+E+rAy+Uc5FZ+cOCQCUfI0+kYpZYj/oyt5BH6VXn099e1Uu4xjicgAA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-mention": "48.3.1", + "@ckeditor/ckeditor5-typing": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "es-toolkit": "1.45.1", + "fuzzysort": "3.1.0" + } + }, + "node_modules/@ckeditor/ckeditor5-engine": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-engine/-/ckeditor5-engine-48.3.1.tgz", + "integrity": "sha512-CbOuKrm3g8T2Df6WMEYtQMm4DJTAlmRutQQFj94zaXqDPyb8dSLpdPZ0Z89Qg2tagh9IEw0maG40ei2H4FETiQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-utils": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-enter": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-enter/-/ckeditor5-enter-48.3.1.tgz", + "integrity": "sha512-+gs7yLyWSfYlBofKA1ce4O4fPBoMw4mSXNun9CZP2FgByvsnS/5sOE3c3UTJS2bSLYabNkZRH3SG4XWKKhunGg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-essentials": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-essentials/-/ckeditor5-essentials-48.3.1.tgz", + "integrity": "sha512-/yYMvcdYpwfQOumLNFccOnQvWfJoXN4Ny5a8m9OO/syGqeMtgpLy+Pn8be3nq4/pxdU6Qz0VnGVzU9OaHXxw4A==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "48.3.1", + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-enter": "48.3.1", + "@ckeditor/ckeditor5-select-all": "48.3.1", + "@ckeditor/ckeditor5-typing": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-undo": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-find-and-replace": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-find-and-replace/-/ckeditor5-find-and-replace-48.3.1.tgz", + "integrity": "sha512-R4DPKGC5XmN68W2HuUBRo8VJJ1KCB5Y8gr6JbNDNGQ7aJWrgAjQU7H7d9EEwxU6wjxzNAT7MJqPOEX/WixUBhA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-font": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-font/-/ckeditor5-font-48.3.1.tgz", + "integrity": "sha512-Q8EVEjVix3HOleJ6XtJpXDqD7WGMU3WZR/9eK1hMvOHnvRWkHwXJve5IoaBseJXSSQopG98D1rvWuNvE+qsZNg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-fullscreen": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-fullscreen/-/ckeditor5-fullscreen-48.3.1.tgz", + "integrity": "sha512-/UQzJFhOMJrDBB+02jSNJVD6E0cjNZ43KgsR8croTDEPLQ2pRH50aUkBZTZn7TfSYx7X2aeaYbyegvSB+pfw6g==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-editor-classic": "48.3.1", + "@ckeditor/ckeditor5-editor-decoupled": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-heading": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-heading/-/ckeditor5-heading-48.3.1.tgz", + "integrity": "sha512-CPlf4wSatQLqbgsj7DWPaOtjTgqchQX4Mv57NbV/2ZvDnqDOpjMi06d49yQngiPFv/6zqszr8U1raUX1OUhfYA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-enter": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-paragraph": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-highlight": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-highlight/-/ckeditor5-highlight-48.3.1.tgz", + "integrity": "sha512-wlqHoOuHeA2qrE9rBeVHgVK75puzTFWn/V8IhAcVBCPB2AApLYNTO7fkfhOEpxqodE2It1lPpN4JfVRWUkoCIw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-horizontal-line": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-horizontal-line/-/ckeditor5-horizontal-line-48.3.1.tgz", + "integrity": "sha512-yl25cVmB5T+fsC2VqJvDLnmS9ymn+4LLaGSnue2D8Tg1WK/YTyWxKg3n0anPzMWBBLr1XcKAyVmnGFI66AKwvA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "@ckeditor/ckeditor5-widget": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-html-embed": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-html-embed/-/ckeditor5-html-embed-48.3.1.tgz", + "integrity": "sha512-AuXaTHSnxR6chWIz9Z9UY2x4ANHN6Uf0jkfiAFcP6POWaD7YLsviTEYk+YGYw/kcXExMw3CQriDK5KdsjZtnkA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "@ckeditor/ckeditor5-widget": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-html-support": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-html-support/-/ckeditor5-html-support-48.3.1.tgz", + "integrity": "sha512-aQ0ZHvvOATsUBykQJcGCFOTJjBDX3OULfeJWV1bqdVWAQt0BHC5xkq6d1IpbyKDaW9IkcB19GOS9nQgs1FasNg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "48.3.1", + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-enter": "48.3.1", + "@ckeditor/ckeditor5-heading": "48.3.1", + "@ckeditor/ckeditor5-image": "48.3.1", + "@ckeditor/ckeditor5-list": "48.3.1", + "@ckeditor/ckeditor5-remove-format": "48.3.1", + "@ckeditor/ckeditor5-table": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "@ckeditor/ckeditor5-widget": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-icons": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-icons/-/ckeditor5-icons-48.3.1.tgz", + "integrity": "sha512-a8mE5oTQ8TKf/325UmDixhhqGbDyzd749kcrANyzxwc89PtTMdUayl+D0Sj92Xp5fqMHgDEqipUcoohH9OSFBg==", + "license": "SEE LICENSE IN LICENSE.md" + }, + "node_modules/@ckeditor/ckeditor5-image": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-image/-/ckeditor5-image-48.3.1.tgz", + "integrity": "sha512-3lQY0LEpUNle2nUrONOtOXax62JQE9ZYltMDkOhMy9hcto0BoIngBebBsBBKZmpPugvMTf4qPYh1VkorOAdH0g==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "48.3.1", + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-typing": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-undo": "48.3.1", + "@ckeditor/ckeditor5-upload": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "@ckeditor/ckeditor5-widget": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-indent": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-indent/-/ckeditor5-indent-48.3.1.tgz", + "integrity": "sha512-hRuAPVDex46Ky86wuWNoPLpkPYQt0jGECKPnSYsjavm/il6rm57J/Y/jk7CKGJJVIovVLFftvWsI0yHLcsAP1w==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-heading": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-list": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-integrations-common": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-integrations-common/-/ckeditor5-integrations-common-2.4.1.tgz", + "integrity": "sha512-DKBMAoKuNWebCFQurX748FP/D8QzfQs7cW5BCJVYRjz0bgbhV33Wj15zKVtByaNLLLOy/fKcYPGHv06KVc4k/Q==", + "license": "SEE LICENSE IN LICENSE.md", + "peerDependencies": { + "ckeditor5": ">=42.0.0 || ^0.0.0-nightly" + } + }, + "node_modules/@ckeditor/ckeditor5-language": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-language/-/ckeditor5-language-48.3.1.tgz", + "integrity": "sha512-DGOxZyTvrXisV+3FhFEcagfSlTC7m2tdhD2CJJmBRkKX+2ZOdeP1D0QCD6ybFlO3D/wT57Po2QX2nCVI6U79dQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-link": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-link/-/ckeditor5-link-48.3.1.tgz", + "integrity": "sha512-E3pAhuNy77F55JUnX40apemSbsOC3RbUPIgPclNlh/1PXs1uN/bjkkyDVYQ/rT5MrophY3tw/bLZ/cRrbEuKBg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "48.3.1", + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-enter": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-image": "48.3.1", + "@ckeditor/ckeditor5-typing": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "@ckeditor/ckeditor5-widget": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-list": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-list/-/ckeditor5-list-48.3.1.tgz", + "integrity": "sha512-KyuXH0aAiiQCOB+yJAru/C5GJqT7a+OoHoanjZMsFq5jjK9r5Tjrh6hUfqXpw+okU1x7Ow+/J4QLVI3QqSnluQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "48.3.1", + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-enter": "48.3.1", + "@ckeditor/ckeditor5-font": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-typing": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-markdown-gfm": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-markdown-gfm/-/ckeditor5-markdown-gfm-48.3.1.tgz", + "integrity": "sha512-y+aa3uPNwaTKxSXRcW3DNP4CfXBmXDmQk24mDVE4q7rl6lWkdnAUeEvUsaGYTRjs4szvY+HIaCts8OcSgKfvhA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "48.3.1", + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@types/hast": "3.0.4", + "hast-util-from-dom": "5.0.1", + "hast-util-to-html": "9.0.5", + "hast-util-to-mdast": "10.1.2", + "hastscript": "9.0.1", + "rehype-dom-parse": "5.0.2", + "rehype-dom-stringify": "4.0.2", + "rehype-remark": "10.0.1", + "remark-breaks": "4.0.0", + "remark-gfm": "4.0.1", + "remark-parse": "11.0.0", + "remark-rehype": "11.1.2", + "remark-stringify": "11.0.0", + "unified": "11.0.5", + "unist-util-visit": "5.0.0" + } + }, + "node_modules/@ckeditor/ckeditor5-media-embed": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-media-embed/-/ckeditor5-media-embed-48.3.1.tgz", + "integrity": "sha512-Asq6B/nuhhHCka4mtjbIM4RrHAOWwCAYmR4kaX8xh6G81FfXKI/X26rJ8TjLno5J7lNgacdB6OznNu/UcJO0gQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "48.3.1", + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-typing": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-undo": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "@ckeditor/ckeditor5-widget": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-mention": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-mention/-/ckeditor5-mention-48.3.1.tgz", + "integrity": "sha512-TdmXZ+NnBXdbMtbA6Yo93mZpTs0oz9HfN8jY9yEJXsECOC0XPC1+nghQQrxJ3a9+eq6qba/JNLLM+ZV0W6DOaA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-typing": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-minimap": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-minimap/-/ckeditor5-minimap-48.3.1.tgz", + "integrity": "sha512-kDJfTRv31WrFisXBKzHNtKhAseiSsJKw1oWIPqqLIN9rnYXnEpjqa5BRgypfFbS7LW0wgjKvCOH/yHMyPrv64A==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-page-break": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-page-break/-/ckeditor5-page-break-48.3.1.tgz", + "integrity": "sha512-Wbsq6ZEOQN+zmfhMx05Ey0mda6qCj74j34yEZrnJ5nfFAoody7q3hJ7yJ9Edu10zF8+LXfC8+ebpaXQr77IvLg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "@ckeditor/ckeditor5-widget": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-paragraph": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-paragraph/-/ckeditor5-paragraph-48.3.1.tgz", + "integrity": "sha512-TnfBhRiFMBbGoXaPOpUiG109IpUFjbdEQEtjlBfiB4bMG71J1pdVYBe+qN1kvr34cYgdR96P/wbUnqC4BZA6Jw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-paste-from-office": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-paste-from-office/-/ckeditor5-paste-from-office-48.3.1.tgz", + "integrity": "sha512-PAyMPmRRTY9MvvfhLdfPwgDOAlzm9u9C97aS7S/JbeKl2uys9uJ7nZRBC9v+9zf6eJMHx90ThKyVBtstKDC6LQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "48.3.1", + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-remove-format": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-remove-format/-/ckeditor5-remove-format-48.3.1.tgz", + "integrity": "sha512-ZVuLTxUupAnoilnhC6Sjev1+qdzzSJxZ0h1h41boaRHSoqXGtnvXosb4MYZ416LiLYDBkFdL1yLJhhPOsWNXMg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-restricted-editing": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-restricted-editing/-/ckeditor5-restricted-editing-48.3.1.tgz", + "integrity": "sha512-53u38P5fpz/8qwanJtGYtgmi5Szqg+6VhqnbSyPWOWK8dlJAUi0aMJy/ihseFkUFt2AfaUZHlUW7WzpwttbDCw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "48.3.1", + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-typing": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-select-all": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-select-all/-/ckeditor5-select-all-48.3.1.tgz", + "integrity": "sha512-bPXjCzqNeroJxnpW+dHXtBb5vigap7cwANJ6LS9lvTbQGKk3Ocq6jO0RlhmJ9RvcuHwAMH60a+23zW3PJAOkAQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-show-blocks": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-show-blocks/-/ckeditor5-show-blocks-48.3.1.tgz", + "integrity": "sha512-Frzw96nYEET7ymiEUvMHjkJ/QClUfsGTd+jJufXaT9hUx7t1nONVQVhzIEIruy8dgqt8prIRqVpczD3mQS8T4Q==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-source-editing": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-source-editing/-/ckeditor5-source-editing-48.3.1.tgz", + "integrity": "sha512-20rfbBZgZjEYnFCpS69U27TKZq9ZLMzKCBSXv8Kmv4qObHGgZMBY+R5UsG1VpIRG5DdUjP9lBWgmCVkzlof1EA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-special-characters": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-special-characters/-/ckeditor5-special-characters-48.3.1.tgz", + "integrity": "sha512-gqfxywMLASyjJV7AvhV3WCX3QYuizjWG0SxUy7zOils00UjIeImfQW07X5u/FSi2GLeUsOjSfttsAbtJXQXHzg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-typing": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-style": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-style/-/ckeditor5-style-48.3.1.tgz", + "integrity": "sha512-rYikWptU+1Kd4UbzZ0s04tO6CqlEoPzfX09jfdwWSNYLbgB9+CfrxnkbnFvfg3SCAgtFDactc07TBtxsH9+Eag==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-html-support": "48.3.1", + "@ckeditor/ckeditor5-list": "48.3.1", + "@ckeditor/ckeditor5-table": "48.3.1", + "@ckeditor/ckeditor5-typing": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-table": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-table/-/ckeditor5-table-48.3.1.tgz", + "integrity": "sha512-aoVzI5Srl5g0AP2XslxNugJFeogQGstk8JiCwPN1cRvtOb/e7pvMYBsAXhqdf3GFeq3E7HMw/fceXmDLlWJPgw==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-clipboard": "48.3.1", + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-typing": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "@ckeditor/ckeditor5-widget": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-typing": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-typing/-/ckeditor5-typing-48.3.1.tgz", + "integrity": "sha512-kBtgdIA9oWqrmTk24WRxaK/p91N00Mz0XE9/w7NDiOLAPBJNeBvt8Le4zOzyeLt8tn+DCP2h/BQg0t04oFveNQ==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-ui": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-ui/-/ckeditor5-ui-48.3.1.tgz", + "integrity": "sha512-bs0VgxH3xfs8B14it+5dNK9I5YIWDI27qxArJqmfDFbnVBVtxlaLtnntyykbrNWCck53LKZ2qCpDueVsCV2JmA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-editor-multi-root": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "@types/color-convert": "2.0.4", + "color-convert": "3.1.0", + "color-parse": "2.0.2", + "es-toolkit": "1.45.1", + "vanilla-colorful": "0.7.2" + } + }, + "node_modules/@ckeditor/ckeditor5-ui/node_modules/color-convert": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-3.1.0.tgz", + "integrity": "sha512-TVoqAq8ZDIpK5lsQY874DDnu65CSsc9vzq0wLpNQ6UMBq81GSZocVazPiBbYGzngzBOIRahpkTzCLVe2at4MfA==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + }, + "engines": { + "node": ">=14.6" + } + }, + "node_modules/@ckeditor/ckeditor5-ui/node_modules/color-name": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.1.tgz", + "integrity": "sha512-p2FdgwVx1a9yWBHP2wI0VgShkDpgN4kZISkxdNipGBJWpa5G6b04OINlVWCyJj0JmfvcPrgqt95E9k8yvaOJFg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/@ckeditor/ckeditor5-undo": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-undo/-/ckeditor5-undo-48.3.1.tgz", + "integrity": "sha512-psJd40k7knNqfbdCaBc6D6cC88exr4Y6AwQihUn/9DaQv8vBjJDNsifVsSgfBemT7QIchUDIoJwYkF8hmUDT9Q==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-upload": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-upload/-/ckeditor5-upload-48.3.1.tgz", + "integrity": "sha512-6hEOB4rAgtbDhntBw7Vw1wyn6BxR03mXNSGaKHyJ+OCZ1FzNAcCCkHI+6CI5INpAiAp8ISXfzblKec+EB0iA0Q==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1" + } + }, + "node_modules/@ckeditor/ckeditor5-utils": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-utils/-/ckeditor5-utils-48.3.1.tgz", + "integrity": "sha512-hLZLjgwWSQKB3/a7AULSB5066PujeqwQiiUwU71ObxLGAHJA5EWD102Jp093BmiFvf5k8/hXFqOtUuaihDCXtg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-ui": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-vue": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-vue/-/ckeditor5-vue-8.2.0.tgz", + "integrity": "sha512-XqcwSJupjGubflxL5/O7oAZ0/IwYgdATCfkxp+n8pfmgoIYP/hNUzlJczTq7+ilLCsSDyIdFzQxs2W/CM6Ea6w==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-integrations-common": "^2.4.0", + "lodash-es": "^4.18.1" + }, + "peerDependencies": { + "ckeditor5": ">=42.0.0 || ^0.0.0-nightly || ^0.0.0-internal", + "vue": "^3.4.0" + } + }, + "node_modules/@ckeditor/ckeditor5-watchdog": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-watchdog/-/ckeditor5-watchdog-48.3.1.tgz", + "integrity": "sha512-qv0D8GdaRdP9kM5LFYNNZTT3kN70jeEilZIA8wqLDf2+nIkz68xNZiwMbad9yrjGbMe2iQqrURTmML1/2+FRqA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-widget": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-widget/-/ckeditor5-widget-48.3.1.tgz", + "integrity": "sha512-2XrBEd0pz/aVmSlXYC/Q3vgGVLadtN2KK5T3euQv3MSWTZtA66ZQKeKLFwl5pNvv/ItnCHUDz/RMEAP1CqsfTg==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-enter": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-typing": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "es-toolkit": "1.45.1" + } + }, + "node_modules/@ckeditor/ckeditor5-word-count": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/@ckeditor/ckeditor5-word-count/-/ckeditor5-word-count-48.3.1.tgz", + "integrity": "sha512-ylAMJ0LNVJasul0cVbZFSIXK4tdZ/850NXPjtTiL63K//2bYfxRj3Xwh+mN14H4NX3ozkJhN2jaA9PU6Gmfs3Q==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "es-toolkit": "1.45.1" + } + }, "node_modules/@ctrl/tinycolor": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", @@ -545,6 +1434,30 @@ "tslib": "^2.4.0" } }, + "node_modules/@types/color-convert": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.4.tgz", + "integrity": "sha512-Ub1MmDdyZ7mX//g25uBAoH/mWGd9swVbt8BseymnaE18SU4po/PjmCrHxqIIRjBo3hV/vh1KGr0eMxUhp+t+dQ==", + "license": "MIT", + "dependencies": { + "@types/color-name": "^1.1.0" + } + }, + "node_modules/@types/color-name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.5.tgz", + "integrity": "sha512-j2K5UJqGTxeesj6oQuGpMgifpT5k9HprgQd8D1Y0lOFqKHl3PJu5GMeS4Y5EgjS55AE6OQxf8mPED9uaGbf4Cg==", + "license": "MIT" + }, + "node_modules/@types/debug": { + "version": "4.1.13", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", + "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -552,6 +1465,15 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/lodash": { "version": "4.17.24", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", @@ -567,6 +1489,21 @@ "@types/lodash": "*" } }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "24.13.3", "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", @@ -607,12 +1544,24 @@ "license": "MIT", "optional": true }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@types/web-bluetooth": { "version": "0.0.21", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", "license": "MIT" }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.3.tgz", + "integrity": "sha512-60YRaenCQcVjYEKOcG824+DRGGIQ3VKErcBoAEDJZz5bKIs2ZG+X/H9Nk+Q6EVkwJk5QNApxbrc5QtBSwtrXAg==", + "license": "ISC" + }, "node_modules/@vitejs/plugin-vue": { "version": "6.0.8", "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-6.0.8.tgz", @@ -940,6 +1889,16 @@ "proxy-from-env": "^2.1.0" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/base64-arraybuffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", @@ -959,6 +1918,12 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/blurhash": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/blurhash/-/blurhash-2.0.5.tgz", + "integrity": "sha512-cRygWd7kGBQO3VEhPiTgq4Wc43ctsM+o46urrmPOiuAe+07fzlSB9OJVdpgDL0jPqXUVQ9ht7aq7kxOeJHRK+w==", + "license": "MIT" + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -1001,6 +1966,46 @@ "node": ">=10.0.0" } }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chokidar": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", @@ -1017,6 +2022,74 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/ckeditor5": { + "version": "48.3.1", + "resolved": "https://registry.npmjs.org/ckeditor5/-/ckeditor5-48.3.1.tgz", + "integrity": "sha512-uuWdrM7mHVO0NsO3DTGDXp2zBkcuSYoS78+Ovpuci3B1pxzG7eHeLfeaNKIltWorhcWCxY4G+JTWOobcwflCTA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@ckeditor/ckeditor5-adapter-ckfinder": "48.3.1", + "@ckeditor/ckeditor5-alignment": "48.3.1", + "@ckeditor/ckeditor5-autoformat": "48.3.1", + "@ckeditor/ckeditor5-autosave": "48.3.1", + "@ckeditor/ckeditor5-basic-styles": "48.3.1", + "@ckeditor/ckeditor5-block-quote": "48.3.1", + "@ckeditor/ckeditor5-bookmark": "48.3.1", + "@ckeditor/ckeditor5-ckbox": "48.3.1", + "@ckeditor/ckeditor5-ckfinder": "48.3.1", + "@ckeditor/ckeditor5-clipboard": "48.3.1", + "@ckeditor/ckeditor5-cloud-services": "48.3.1", + "@ckeditor/ckeditor5-code-block": "48.3.1", + "@ckeditor/ckeditor5-core": "48.3.1", + "@ckeditor/ckeditor5-easy-image": "48.3.1", + "@ckeditor/ckeditor5-editor-balloon": "48.3.1", + "@ckeditor/ckeditor5-editor-classic": "48.3.1", + "@ckeditor/ckeditor5-editor-decoupled": "48.3.1", + "@ckeditor/ckeditor5-editor-inline": "48.3.1", + "@ckeditor/ckeditor5-editor-multi-root": "48.3.1", + "@ckeditor/ckeditor5-emoji": "48.3.1", + "@ckeditor/ckeditor5-engine": "48.3.1", + "@ckeditor/ckeditor5-enter": "48.3.1", + "@ckeditor/ckeditor5-essentials": "48.3.1", + "@ckeditor/ckeditor5-find-and-replace": "48.3.1", + "@ckeditor/ckeditor5-font": "48.3.1", + "@ckeditor/ckeditor5-fullscreen": "48.3.1", + "@ckeditor/ckeditor5-heading": "48.3.1", + "@ckeditor/ckeditor5-highlight": "48.3.1", + "@ckeditor/ckeditor5-horizontal-line": "48.3.1", + "@ckeditor/ckeditor5-html-embed": "48.3.1", + "@ckeditor/ckeditor5-html-support": "48.3.1", + "@ckeditor/ckeditor5-icons": "48.3.1", + "@ckeditor/ckeditor5-image": "48.3.1", + "@ckeditor/ckeditor5-indent": "48.3.1", + "@ckeditor/ckeditor5-language": "48.3.1", + "@ckeditor/ckeditor5-link": "48.3.1", + "@ckeditor/ckeditor5-list": "48.3.1", + "@ckeditor/ckeditor5-markdown-gfm": "48.3.1", + "@ckeditor/ckeditor5-media-embed": "48.3.1", + "@ckeditor/ckeditor5-mention": "48.3.1", + "@ckeditor/ckeditor5-minimap": "48.3.1", + "@ckeditor/ckeditor5-page-break": "48.3.1", + "@ckeditor/ckeditor5-paragraph": "48.3.1", + "@ckeditor/ckeditor5-paste-from-office": "48.3.1", + "@ckeditor/ckeditor5-remove-format": "48.3.1", + "@ckeditor/ckeditor5-restricted-editing": "48.3.1", + "@ckeditor/ckeditor5-select-all": "48.3.1", + "@ckeditor/ckeditor5-show-blocks": "48.3.1", + "@ckeditor/ckeditor5-source-editing": "48.3.1", + "@ckeditor/ckeditor5-special-characters": "48.3.1", + "@ckeditor/ckeditor5-style": "48.3.1", + "@ckeditor/ckeditor5-table": "48.3.1", + "@ckeditor/ckeditor5-typing": "48.3.1", + "@ckeditor/ckeditor5-ui": "48.3.1", + "@ckeditor/ckeditor5-undo": "48.3.1", + "@ckeditor/ckeditor5-upload": "48.3.1", + "@ckeditor/ckeditor5-utils": "48.3.1", + "@ckeditor/ckeditor5-watchdog": "48.3.1", + "@ckeditor/ckeditor5-widget": "48.3.1", + "@ckeditor/ckeditor5-word-count": "48.3.1" + } + }, "node_modules/cliui": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", @@ -1046,6 +2119,24 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-parse": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/color-parse/-/color-parse-2.0.2.tgz", + "integrity": "sha512-eCtOz5w5ttWIUcaKLiktF+DxZO1R9KLNY/xhbV6CkhM7sR3GhVghmt6X6yOnzeaM24po+Z9/S1apbXMwA3Iepw==", + "license": "MIT", + "dependencies": { + "color-name": "^2.0.0" + } + }, + "node_modules/color-parse/node_modules/color-name": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-2.1.1.tgz", + "integrity": "sha512-p2FdgwVx1a9yWBHP2wI0VgShkDpgN4kZISkxdNipGBJWpa5G6b04OINlVWCyJj0JmfvcPrgqt95E9k8yvaOJFg==", + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1058,6 +2149,16 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/confbox": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", @@ -1124,6 +2225,19 @@ "node": ">=0.10.0" } }, + "node_modules/decode-named-character-reference": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz", + "integrity": "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -1133,6 +2247,15 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -1143,6 +2266,19 @@ "node": ">=8" } }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dijkstrajs": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", @@ -1154,7 +2290,6 @@ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz", "integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==", "license": "(MPL-2.0 OR Apache-2.0)", - "optional": true, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -1278,11 +2413,20 @@ "node": ">= 0.4" } }, + "node_modules/es-toolkit": { + "version": "1.45.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz", + "integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/escape-string-regexp": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -1304,6 +2448,12 @@ "dev": true, "license": "MIT" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-png": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", @@ -1412,6 +2562,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/fuzzysort": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz", + "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==", + "license": "MIT" + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -1509,6 +2665,231 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-embedded": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-embedded/-/hast-util-embedded-3.0.0.tgz", + "integrity": "sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-dom": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", + "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==", + "license": "ISC", + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^9.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-has-property": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-has-property/-/hast-util-has-property-3.0.0.tgz", + "integrity": "sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-body-ok-link": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-is-body-ok-link/-/hast-util-is-body-ok-link-3.0.1.tgz", + "integrity": "sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-minify-whitespace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hast-util-minify-whitespace/-/hast-util-minify-whitespace-1.0.1.tgz", + "integrity": "sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-parse-selector": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", + "integrity": "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-phrasing": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-phrasing/-/hast-util-phrasing-3.0.1.tgz", + "integrity": "sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-embedded": "^3.0.0", + "hast-util-has-property": "^3.0.0", + "hast-util-is-body-ok-link": "^3.0.0", + "hast-util-is-element": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-dom": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-dom/-/hast-util-to-dom-4.0.1.tgz", + "integrity": "sha512-z1VE7sZ8uFzS2baF3LEflX1IPw2gSzrdo3QFEsyoi23MkCVY3FoE9x6nLgOgjwJu8VNWgo+07iaxtONhDzKrUQ==", + "license": "ISC", + "dependencies": { + "@types/hast": "^3.0.0", + "property-information": "^7.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-mdast": { + "version": "10.1.2", + "resolved": "https://registry.npmjs.org/hast-util-to-mdast/-/hast-util-to-mdast-10.1.2.tgz", + "integrity": "sha512-FiCRI7NmOvM4y+f5w32jPRzcxDIz+PUqDwEqn1A+1q2cdp3B8Gx7aVrXORdOKjMNDQsD1ogOr896+0jJHW1EFQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "hast-util-phrasing": "^3.0.0", + "hast-util-to-html": "^9.0.0", + "hast-util-to-text": "^4.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "mdast-util-to-string": "^4.0.0", + "rehype-minify-whitespace": "^6.0.0", + "trim-trailing-lines": "^2.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hastscript": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/hastscript/-/hastscript-9.0.1.tgz", + "integrity": "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-parse-selector": "^4.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hookable": { "version": "5.5.3", "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", @@ -1516,6 +2897,16 @@ "license": "MIT", "peer": true }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/html2canvas": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", @@ -1557,6 +2948,18 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/js-tokens": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", @@ -1907,6 +3310,16 @@ "lodash-es": "*" } }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -1916,6 +3329,16 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/markdown-table": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.4.tgz", + "integrity": "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -1925,12 +3348,799 @@ "node": ">= 0.4" } }, + "node_modules/mdast-util-find-and-replace": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz", + "integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "escape-string-regexp": "^5.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz", + "integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm/-/mdast-util-gfm-3.1.0.tgz", + "integrity": "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==", + "license": "MIT", + "dependencies": { + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-gfm-autolink-literal": "^2.0.0", + "mdast-util-gfm-footnote": "^2.0.0", + "mdast-util-gfm-strikethrough": "^2.0.0", + "mdast-util-gfm-table": "^2.0.0", + "mdast-util-gfm-task-list-item": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-autolink-literal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.1.tgz", + "integrity": "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "ccount": "^2.0.0", + "devlop": "^1.0.0", + "mdast-util-find-and-replace": "^3.0.0", + "micromark-util-character": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-strikethrough": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz", + "integrity": "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-table": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz", + "integrity": "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "markdown-table": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-gfm-task-list-item": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz", + "integrity": "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-newline-to-break": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz", + "integrity": "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-find-and-replace": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/memoize-one": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", "license": "MIT" }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-extension-gfm": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz", + "integrity": "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==", + "license": "MIT", + "dependencies": { + "micromark-extension-gfm-autolink-literal": "^2.0.0", + "micromark-extension-gfm-footnote": "^2.0.0", + "micromark-extension-gfm-strikethrough": "^2.0.0", + "micromark-extension-gfm-table": "^2.0.0", + "micromark-extension-gfm-tagfilter": "^2.0.0", + "micromark-extension-gfm-task-list-item": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-autolink-literal": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.1.0.tgz", + "integrity": "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==", + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-footnote": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.1.0.tgz", + "integrity": "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-strikethrough": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.1.0.tgz", + "integrity": "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-table": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.1.1.tgz", + "integrity": "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-tagfilter": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz", + "integrity": "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==", + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-gfm-task-list-item": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.1.0.tgz", + "integrity": "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==", + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -2223,6 +4433,16 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/property-information": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.2.0.tgz", + "integrity": "sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/proxy-from-env": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", @@ -2297,6 +4517,140 @@ "license": "MIT", "optional": true }, + "node_modules/rehype-dom-parse": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/rehype-dom-parse/-/rehype-dom-parse-5.0.2.tgz", + "integrity": "sha512-8CqP11KaqvtWsMqVEC2yM3cZWZsDNqqpr8nPvogjraLuh45stabgcpXadCAxu1n6JaUNJ/Xr3GIqXP7okbNqLg==", + "license": "ISC", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "unified": "^11.0.0" + } + }, + "node_modules/rehype-dom-stringify": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/rehype-dom-stringify/-/rehype-dom-stringify-4.0.2.tgz", + "integrity": "sha512-2HVFYbtmm5W3C2j8QsV9lcHdIMc2Yn/ytlPKcSC85/tRx2haZbU8V67Wxyh8STT38ZClvKlZ993Me/Hw8g88Aw==", + "license": "ISC", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-to-dom": "^4.0.0", + "unified": "^11.0.0" + } + }, + "node_modules/rehype-minify-whitespace": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/rehype-minify-whitespace/-/rehype-minify-whitespace-6.0.2.tgz", + "integrity": "sha512-Zk0pyQ06A3Lyxhe9vGtOtzz3Z0+qZ5+7icZ/PL/2x1SHPbKao5oB/g/rlc6BCTajqBb33JcOe71Ye1oFsuYbnw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-minify-whitespace": "^1.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-remark": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/rehype-remark/-/rehype-remark-10.0.1.tgz", + "integrity": "sha512-EmDndlb5NVwXGfUa4c9GPK+lXeItTilLhE6ADSaQuHr4JUlKw9MidzGzx4HpqZrNCt6vnHmEifXQiiA+CEnjYQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "hast-util-to-mdast": "^10.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-breaks": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz", + "integrity": "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-newline-to-break": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-gfm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", + "integrity": "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-gfm": "^3.0.0", + "micromark-extension-gfm": "^3.0.0", + "remark-parse": "^11.0.0", + "remark-stringify": "^11.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-stringify": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-stringify/-/remark-stringify-11.0.0.tgz", + "integrity": "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-to-markdown": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -2378,6 +4732,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stackblur-canvas": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", @@ -2402,6 +4766,20 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -2463,6 +4841,36 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trim-trailing-lines": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/trim-trailing-lines/-/trim-trailing-lines-2.1.0.tgz", + "integrity": "sha512-5UR5Biq4VlVOtzqkm2AZlgvSlDJtME46uV0br0gENbwN4l5+mMKT4b9gJKqWtuL2zAIqajGJGuvbCbcAJUZqBg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -2499,6 +4907,25 @@ "dev": true, "license": "MIT" }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unimport": { "version": "5.7.0", "resolved": "https://registry.npmjs.org/unimport/-/unimport-5.7.0.tgz", @@ -2535,6 +4962,88 @@ "@types/estree": "^1.0.0" } }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unplugin": { "version": "2.3.11", "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", @@ -2698,6 +5207,40 @@ "base64-arraybuffer": "^1.0.2" } }, + "node_modules/vanilla-colorful": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/vanilla-colorful/-/vanilla-colorful-0.7.2.tgz", + "integrity": "sha512-z2YZusTFC6KnLERx1cgoIRX2CjPRP0W75N+3CC6gbvdX5Ch47rZkEMGO2Xnf+IEmi3RiFLxS18gayMA27iU7Kg==", + "license": "MIT" + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/vite": { "version": "8.1.5", "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", @@ -2848,6 +5391,16 @@ "typescript": ">=5.0.0" } }, + "node_modules/web-namespaces": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/web-namespaces/-/web-namespaces-2.0.1.tgz", + "integrity": "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/webpack-virtual-modules": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", @@ -2930,6 +5483,16 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", "license": "0BSD" + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/web/package.json b/web/package.json index 07d2c68..33dc620 100644 --- a/web/package.json +++ b/web/package.json @@ -9,8 +9,11 @@ "preview": "vite preview" }, "dependencies": { + "@ckeditor/ckeditor5-vue": "^8.2.0", "@element-plus/icons-vue": "^2.3.2", "axios": "^1.18.1", + "ckeditor5": "^48.3.1", + "dompurify": "^3.4.12", "echarts": "^6.1.0", "element-plus": "^2.14.3", "html2canvas": "^1.4.1", diff --git a/web/src/components.d.ts b/web/src/components.d.ts index 2540142..e7c93d2 100644 --- a/web/src/components.d.ts +++ b/web/src/components.d.ts @@ -49,6 +49,7 @@ declare module 'vue' { ElTabs: typeof import('element-plus/es')['ElTabs'] ElTag: typeof import('element-plus/es')['ElTag'] ElTimeSelect: typeof import('element-plus/es')['ElTimeSelect'] + RichMessageContent: typeof import('./components/RichMessageContent.vue')['default'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] } diff --git a/web/src/components/RichMessageContent.vue b/web/src/components/RichMessageContent.vue new file mode 100644 index 0000000..8fed3c2 --- /dev/null +++ b/web/src/components/RichMessageContent.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/web/src/views/NotificationCenterView.vue b/web/src/views/NotificationCenterView.vue index fd3eb52..a90d17d 100644 --- a/web/src/views/NotificationCenterView.vue +++ b/web/src/views/NotificationCenterView.vue @@ -4,13 +4,30 @@ import { Bell, Check, EditPen, + Filter, Message, Promotion, Refresh, Search, + User, } from '@element-plus/icons-vue' +import { Ckeditor } from '@ckeditor/ckeditor5-vue' +import { + BlockQuote, + Bold, + ClassicEditor, + Essentials, + Heading, + Italic, + Link, + List, + Paragraph, + type EditorConfig, +} from 'ckeditor5' +import 'ckeditor5/ckeditor5.css' import { useRouter } from 'vue-router' import http, { apiErrorMessage } from '../api/http' +import RichMessageContent from '../components/RichMessageContent.vue' import { useAuthStore } from '../stores/auth' const router = useRouter() @@ -28,13 +45,21 @@ const sentTotal = ref(0) const loading = ref(false) const sending = ref(false) const composerLoading = ref(false) +const recipientLoading = ref(false) const page = ref(1) const sentPage = ref(1) +const recipientPage = ref(1) const pageSize = 20 +const recipientPageSize = 30 const unreadOnly = ref(false) const category = ref() const keyword = ref('') const composer = ref(null) +const recipientMode = ref<'Scope' | 'Filtered' | 'Selected'>('Scope') +const recipientItems = ref([]) +const recipientTotal = ref(0) +const selectedRecipients = ref([]) +const selectedNotification = ref(null) const messageForm = reactive({ teachingTaskId: undefined as string | undefined, @@ -42,6 +67,56 @@ const messageForm = reactive({ content: '', }) +const recipientFilters = reactive({ + collegeId: undefined as string | undefined, + role: undefined as string | undefined, + administrativeClassId: undefined as string | undefined, + teachingTaskId: undefined as string | undefined, + keyword: '', +}) + +const editorConfig: EditorConfig = { + licenseKey: import.meta.env.VITE_CKEDITOR_LICENSE_KEY || 'GPL', + plugins: [ + Essentials, + Paragraph, + Heading, + Bold, + Italic, + Link, + List, + BlockQuote, + ], + toolbar: { + items: [ + 'heading', + '|', + 'bold', + 'italic', + 'link', + '|', + 'bulletedList', + 'numberedList', + 'blockQuote', + '|', + 'undo', + 'redo', + ], + shouldNotGroupWhenFull: false, + }, + heading: { + options: [ + { model: 'paragraph', title: '正文', class: 'ck-heading_paragraph' }, + { model: 'heading2', view: 'h2', title: '标题', class: 'ck-heading_heading2' }, + { model: 'heading3', view: 'h3', title: '小标题', class: 'ck-heading_heading3' }, + ], + }, + link: { + addTargetToExternalLinks: true, + defaultProtocol: 'https://', + }, +} + const categories: Record = { General: { label: '一般通知', className: 'general' }, Approval: { label: '审核待办', className: 'approval' }, @@ -55,16 +130,44 @@ const categories: Record = { const selectedTask = computed(() => composer.value?.teachingTasks?.find( (task: any) => task.id === messageForm.teachingTaskId)) -const recipientCount = computed(() => - composer.value?.audienceType === 'TeachingTask' - ? selectedTask.value?.recipientCount ?? 0 - : composer.value?.recipientCount ?? 0) -const audienceName = computed(() => - composer.value?.audienceType === 'TeachingTask' - ? selectedTask.value +const isAdministratorComposer = computed(() => composer.value?.canFilterRecipients === true) +const recipientCount = computed(() => { + if (composer.value?.audienceType === 'TeachingTask') { + return selectedTask.value?.recipientCount ?? 0 + } + if (recipientMode.value === 'Filtered') return recipientTotal.value + if (recipientMode.value === 'Selected') return selectedRecipients.value.length + return composer.value?.recipientCount ?? 0 +}) +const audienceName = computed(() => { + if (composer.value?.audienceType === 'TeachingTask') { + return selectedTask.value ? `${selectedTask.value.taskNumber} · ${selectedTask.value.courseName}` : '请选择教学班' - : composer.value?.audienceName ?? '正在读取发信范围') + } + if (recipientMode.value === 'Filtered') return '当前筛选结果' + if (recipientMode.value === 'Selected') { + return selectedRecipients.value.length + ? `指定的 ${selectedRecipients.value.length} 名收件人` + : '尚未选择收件人' + } + return composer.value?.audienceName ?? '正在读取发信范围' +}) +const visibleClasses = computed(() => + (composer.value?.administrativeClasses ?? []).filter( + (item: any) => !recipientFilters.collegeId || + item.collegeId === recipientFilters.collegeId)) +const visibleTasks = computed(() => + (composer.value?.teachingTasks ?? []).filter( + (item: any) => !recipientFilters.collegeId || + item.collegeId === recipientFilters.collegeId)) +const selectedRecipientIds = computed(() => + new Set(selectedRecipients.value.map(item => item.id))) +const editorTextLength = computed(() => { + const holder = document.createElement('div') + holder.innerHTML = messageForm.content + return (holder.textContent ?? '').replace(/\u00a0/g, ' ').trim().length +}) function categoryLabel(value: string) { return categories[value]?.label ?? '系统消息' @@ -118,6 +221,9 @@ async function loadComposer() { composerLoading.value = true try { composer.value = (await http.get('/notifications/composer')).data + if (composer.value?.canFilterRecipients) { + await loadRecipients(true) + } } catch (error) { ElMessage.error(apiErrorMessage(error)) } finally { @@ -125,6 +231,69 @@ async function loadComposer() { } } +function recipientParams() { + return { + collegeId: recipientFilters.collegeId || undefined, + role: recipientFilters.role || undefined, + administrativeClassId: recipientFilters.administrativeClassId || undefined, + teachingTaskId: recipientFilters.teachingTaskId || undefined, + keyword: recipientFilters.keyword.trim() || undefined, + page: recipientPage.value, + pageSize: recipientPageSize, + } +} + +async function loadRecipients(reset = false) { + if (!isAdministratorComposer.value) return + if (reset) recipientPage.value = 1 + recipientLoading.value = true + try { + const { data } = await http.get('/notifications/recipients', { + params: recipientParams(), + }) + recipientItems.value = data.items + recipientTotal.value = data.total + } catch (error) { + ElMessage.error(apiErrorMessage(error)) + } finally { + recipientLoading.value = false + } +} + +async function changeRecipientMode(value: 'Scope' | 'Filtered' | 'Selected') { + recipientMode.value = value + if (value !== 'Scope') await loadRecipients(true) +} + +function onCollegeChange() { + recipientFilters.administrativeClassId = undefined + recipientFilters.teachingTaskId = undefined + loadRecipients(true) +} + +function isRecipientSelected(recipient: any) { + return selectedRecipientIds.value.has(recipient.id) +} + +function toggleRecipient(recipient: any) { + if (isRecipientSelected(recipient)) { + selectedRecipients.value = selectedRecipients.value.filter( + item => item.id !== recipient.id) + return + } + const maximum = composer.value?.maximumSelectedRecipients ?? 500 + if (selectedRecipients.value.length >= maximum) { + ElMessage.warning(`单次最多指定 ${maximum} 名收件人`) + return + } + selectedRecipients.value = [...selectedRecipients.value, recipient] +} + +function removeSelectedRecipient(recipientId: string) { + selectedRecipients.value = selectedRecipients.value.filter( + item => item.id !== recipientId) +} + async function changeTab(tab: 'inbox' | 'compose' | 'sent') { activeTab.value = tab if (tab === 'compose') await loadComposer() @@ -144,7 +313,11 @@ async function markRead(notification: any) { async function openNotification(notification: any) { await markRead(notification) - if (notification.linkUrl) await router.push(notification.linkUrl) + if (notification.linkUrl) { + await router.push(notification.linkUrl) + return + } + selectedNotification.value = notification } async function markAllRead() { @@ -165,7 +338,7 @@ async function sendMessage() { ElMessage.warning('请填写消息标题') return } - if (!messageForm.content.trim()) { + if (!editorTextLength.value) { ElMessage.warning('请填写消息正文') return } @@ -194,10 +367,27 @@ async function sendMessage() { title: messageForm.title, content: messageForm.content, teachingTaskId: messageForm.teachingTaskId || null, + recipientMode: isAdministratorComposer.value + ? recipientMode.value + : 'Scope', + recipientFilter: recipientMode.value === 'Filtered' + ? { + collegeId: recipientFilters.collegeId || null, + role: recipientFilters.role || null, + administrativeClassId: + recipientFilters.administrativeClassId || null, + teachingTaskId: recipientFilters.teachingTaskId || null, + keyword: recipientFilters.keyword.trim() || null, + } + : null, + recipientUserIds: recipientMode.value === 'Selected' + ? selectedRecipients.value.map(item => item.id) + : [], }) ElMessage.success(`已发送给 ${data.recipientCount} 个账号`) messageForm.title = '' messageForm.content = '' + selectedRecipients.value = [] await changeTab('sent') } catch (error: any) { if (error !== 'cancel' && error !== 'close') { @@ -302,11 +492,14 @@ onMounted(() => load()) v-for="notification in notifications" :key="notification.id" class="message-card" + role="button" + tabindex="0" :class="[ categoryClass(notification.category), { unread: !notification.isRead, linked: notification.linkUrl }, ]" @click="openNotification(notification)" + @keyup.enter="openNotification(notification)" >
@@ -319,7 +512,12 @@ onMounted(() => load())

{{ notification.title }}

-

{{ notification.content }}

+
+ +
来自 {{ notification.senderName }} @@ -349,92 +547,290 @@ onMounted(() => load())
- + - -
-
- NEW MESSAGE -

编写消息

+
+
+
+
+ + 01 +
+

选择收件人

- 发送后不可撤回 -
- - - -
- {{ task.taskNumber }} · {{ task.courseName }} - {{ task.termName }} · {{ task.recipientCount }} 人 + + + - 确认发送 - -
- + + +
+ {{ task.taskNumber }} · {{ task.courseName }} + {{ task.termName }} · {{ task.recipientCount }} 人 +
+
+
+ + 任课教师只能向本人所带教学班的有效学生账号发送消息。 + +
+
+ + +
+
+ + 02 +
+

编辑消息内容

+
+ +
+
+ 普通通知 + 成绩、选课等业务通知仍由对应流程自动发送 +
+
+ + + + + + + +
+ 支持标题、列表、引用和链接;不支持上传附件或图片。 + + {{ editorTextLength }} 字 · 源码 {{ messageForm.content.length }}/20000 + +
+
+ +
+ + 将发送给 {{ recipientCount }} 个账号 + {{ audienceName }} + + + 确认发送 + +
+
+
+ + + + +