From 80e9fd0307c0d015dbb2afbc9df3453d476b76d5 Mon Sep 17 00:00:00 2001 From: Chris Gregan Date: Fri, 12 Jun 2026 21:33:08 +0100 Subject: [PATCH 01/21] Replace package id with name; add package APIs Migrate package model and workshop integration: replace package 'id' semantics with 'name' and introduce PackageConstants and a PackageName validator. Add richer IPackageApiClient types and methods to represent remote packages, versions, aliases and publish/download flows (workshop-style versioning instead of single-file registry). Move and extend PackageDiscoveryReport (rename fields to PackageName/ReservedNamePrefix) and surface package discovery in ProjectLoadReporter output. Update PackageInfo fields (Id -> Name, Name -> Author/Title, RequiresTools -> PermittedTools) and update tooling: package_list/install/publish to use workshop semantics, default packages folder constant, and validation via PackageName. Update docs to reflect [permissions].tools manifest section and workshop terminology; remove package_create tool and associated docs. Misc: adjust ToolAllowlist/ContributionToolsHandler messages, remove .gitignore entry for PackageApiCredentials.private.cs, and add tests and other supporting changes. --- .gitignore | 3 - .../Resources/Strings/en-US/Resources.resw | 4 +- .../Packages/IPackageApiClient.cs | 93 +++- .../Packages/PackageConstants.cs | 42 ++ .../Packages}/PackageDiscoveryReport.cs | 25 +- .../Packages/PackageId.cs | 54 -- .../Packages/PackageInfo.cs | 18 +- .../Packages/PackageName.cs | 83 +++ .../Projects/IProjectLoadReporter.cs | 7 + .../Services/ContributionToolsHandler.cs | 6 +- .../Celbridge.Host/Services/ToolAllowlist.cs | 2 +- .../Services/ProjectLoadReporter.cs | 46 ++ .../Guides/Concepts/agent_instructions.md | 10 +- .../Concepts/document_editor_contributions.md | 11 +- .../Guides/Concepts/packages_overview.md | 31 +- .../Guides/Concepts/webview_devtools.md | 2 +- .../Guides/Namespaces/package.md | 12 +- .../Guides/Tools/package_create.md | 19 - .../Guides/Tools/package_install.md | 5 +- .../Guides/Tools/package_list.md | 13 +- .../Guides/Tools/package_publish.md | 11 +- .../Guides/Tools/webview_eval.md | 2 +- .../Tools/Package/PackageTools.Create.cs | 74 --- .../Tools/Package/PackageTools.Install.cs | 39 +- .../Tools/Package/PackageTools.List.cs | 24 +- .../Tools/Package/PackageTools.Publish.cs | 52 +- .../Tools/Package/PackageTools.cs | 14 +- .../Services/WebViewService.cs | 2 +- .../Web/celbridge-client/api/tools-api.js | 8 +- .../Web/celbridge-client/celbridge.js | 16 +- .../celbridge-client/tests/tools-api.test.js | 22 +- .../Editors/CodeEditor/package.toml | 5 +- .../Editors/FileViewer/package.toml | 5 +- .../Editors/Notes/package.toml | 5 +- .../Editors/SceneViewer/package.toml | 5 +- .../Package/package.toml | 5 +- Source/Tests/Archive/PackageArchiveTests.cs | 34 -- .../Tests/Packages/FileTypeProviderTests.cs | 13 +- .../Tests/Packages/LocalizationHelperTests.cs | 4 +- Source/Tests/Packages/ManifestTests.cs | 242 +++++---- .../Tests/Packages/PackageApiClientTests.cs | 370 ++++++++++++++ Source/Tests/Packages/PackageNameTests.cs | 91 ++++ Source/Tests/Packages/PackageRegistryTests.cs | 104 ++-- .../Projects/ProjectLoadReporterTests.cs | 78 ++- .../WebHost/WebViewServiceSupportTests.cs | 4 +- .../Services/CustomDocumentViewFactory.cs | 2 +- .../Views/ContributionDocumentView.xaml.cs | 14 +- .../Services/PackageApiClient.cs | 482 +++++++++++++----- .../Services/PackageApiCredentials.cs | 14 - .../Services/PackageManifestLoader.cs | 46 +- .../Services/PackageRegistry.cs | 109 ++-- .../Services/PackageService.cs | 11 +- .../Helpers/ArchiveHelper.cs | 15 - 53 files changed, 1614 insertions(+), 794 deletions(-) create mode 100644 Source/Core/Celbridge.Foundation/Packages/PackageConstants.cs rename Source/{Workspace/Celbridge.Packages/Services => Core/Celbridge.Foundation/Packages}/PackageDiscoveryReport.cs (64%) delete mode 100644 Source/Core/Celbridge.Foundation/Packages/PackageId.cs create mode 100644 Source/Core/Celbridge.Foundation/Packages/PackageName.cs delete mode 100644 Source/Core/Celbridge.Tools/Guides/Tools/package_create.md delete mode 100644 Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Create.cs create mode 100644 Source/Tests/Packages/PackageApiClientTests.cs create mode 100644 Source/Tests/Packages/PackageNameTests.cs delete mode 100644 Source/Workspace/Celbridge.Packages/Services/PackageApiCredentials.cs diff --git a/.gitignore b/.gitignore index 72a8d7e91..d58b78799 100644 --- a/.gitignore +++ b/.gitignore @@ -418,9 +418,6 @@ solution-config.props **/Celbridge.Spreadsheet/Package/lib/ **/SpreadsheetLicenseKeys.private.cs -# Package API credentials -**/PackageApiCredentials.private.cs - # Celbridge ephemeral data .cache/ .trash/ diff --git a/Source/Celbridge/Resources/Strings/en-US/Resources.resw b/Source/Celbridge/Resources/Strings/en-US/Resources.resw index d14697848..0efa53c55 100644 --- a/Source/Celbridge/Resources/Strings/en-US/Resources.resw +++ b/Source/Celbridge/Resources/Strings/en-US/Resources.resw @@ -639,7 +639,7 @@ Do you wish to continue? Package Load Error - One or more packages failed to load. See the log for details. + One or more packages failed to load. See the project load report for details. Project Check Findings @@ -1093,4 +1093,4 @@ Do you wish to continue? Reopen with... - + \ No newline at end of file diff --git a/Source/Core/Celbridge.Foundation/Packages/IPackageApiClient.cs b/Source/Core/Celbridge.Foundation/Packages/IPackageApiClient.cs index f544458f5..48f049613 100644 --- a/Source/Core/Celbridge.Foundation/Packages/IPackageApiClient.cs +++ b/Source/Core/Celbridge.Foundation/Packages/IPackageApiClient.cs @@ -1,27 +1,102 @@ namespace Celbridge.Packages; /// -/// An entry returned by the package API representing a file on the server. +/// Summary of a package version, as returned in package listings. /// -public partial record PackageApiEntry(int Id, string FileName, long FileSize, DateTime UploadedAt); +public record RemoteVersionSummary(int Version, string Author, DateTime Date); /// -/// Client for the Celbridge package registry REST API. +/// A package listed by the workshop. LatestVersion is null when the package +/// has no live versions. +/// +public record RemotePackageSummary( + string Name, + DateTime CreatedAt, + RemoteVersionSummary? LatestVersion, + int VersionsCount); + +/// +/// A single immutable version of a workshop package. Version numbers are +/// assigned by the server in publish order. +/// +public record RemotePackageVersion( + int Version, + string Author, + DateTime Date, + bool Tombstoned, + string ContentHash, + string Summary); + +/// +/// A named pointer at a package version (e.g. "latest", "stable"). +/// +public record RemotePackageAlias(string Alias, int Version); + +/// +/// Full metadata for a workshop package, including its versions and aliases. +/// +public record RemotePackageDetails( + string Name, + DateTime CreatedAt, + IReadOnlyList Versions, + IReadOnlyList Aliases); + +/// +/// Server receipt for a published version, carrying the assigned version number. +/// +public record RemotePublishReceipt( + string PackageName, + int Version, + string Author, + string ContentHash); + +/// +/// Client for the workshop server's package REST API. The Workshop URL and +/// Application Key are read from the credential store at request time; +/// credential values never appear in parameters, results, or error messages. +/// Destructive administrative operations (tombstoning a version, deleting a +/// package) are deliberately not part of this surface. /// public interface IPackageApiClient { /// - /// Lists all packages available on the remote registry. + /// Lists the packages available on the workshop. + /// + Task>> ListPackagesAsync(); + + /// + /// Gets a package's full metadata, including its versions and aliases. + /// + Task> GetPackageAsync(string packageName); + + /// + /// Publishes a new package version from ZIP data, with an optional change + /// summary. The first publish of a new name registers the package implicitly. + /// + Task> PublishVersionAsync(string packageName, byte[] zipData, string? summary = null); + + /// + /// Downloads the ZIP data for a specific package version. + /// + Task> DownloadVersionAsync(string packageName, int version); + + /// + /// Downloads the ZIP data for the latest live version of a package. + /// + Task> DownloadLatestAsync(string packageName); + + /// + /// Creates an alias pointing at a version, or moves an existing alias. /// - Task>> ListPackagesAsync(); + Task SetAliasAsync(string packageName, string alias, int version); /// - /// Downloads a package zip by file name from the remote registry. + /// Removes an alias. The version the alias pointed at is unaffected. /// - Task> DownloadPackageAsync(int fileId); + Task RemoveAliasAsync(string packageName, string alias); /// - /// Uploads a package zip to the remote registry. + /// Gets the plain text publish history of a package as of the given version. /// - Task UploadPackageAsync(string fileName, byte[] zipData); + Task> GetVersionHistoryAsync(string packageName, int version); } diff --git a/Source/Core/Celbridge.Foundation/Packages/PackageConstants.cs b/Source/Core/Celbridge.Foundation/Packages/PackageConstants.cs new file mode 100644 index 000000000..65aab997d --- /dev/null +++ b/Source/Core/Celbridge.Foundation/Packages/PackageConstants.cs @@ -0,0 +1,42 @@ +namespace Celbridge.Packages; + +/// +/// Constants shared across the package system: well-known file and folder +/// names, the reserved name prefix, and validation limits. +/// +public static class PackageConstants +{ + /// + /// File name of the package manifest at the root of every package folder. + /// + public const string ManifestFileName = "package.toml"; + + /// + /// Default install folder for packages, relative to the project root. + /// This is a convention, not a constraint: packages may install elsewhere. + /// + public const string DefaultPackagesFolder = "packages"; + + /// + /// File name of the generated version history written beside the manifest + /// when a package is installed. Excluded (case-insensitively) on publish. + /// + public const string HistoryFileName = "HISTORY.md"; + + /// + /// Name prefix reserved for first-party packages shipped inside Celbridge + /// module DLLs. Project packages may not claim names under this prefix. + /// + public const string ReservedNamePrefix = "celbridge."; + + /// + /// Maximum length of a package name, enforced by the PackageName validator. + /// + public const int MaxNameLength = 64; + + /// + /// Maximum length of the change summary accompanying a published version. + /// Over-long summaries are rejected, never truncated. + /// + public const int MaxSummaryLength = 512; +} diff --git a/Source/Workspace/Celbridge.Packages/Services/PackageDiscoveryReport.cs b/Source/Core/Celbridge.Foundation/Packages/PackageDiscoveryReport.cs similarity index 64% rename from Source/Workspace/Celbridge.Packages/Services/PackageDiscoveryReport.cs rename to Source/Core/Celbridge.Foundation/Packages/PackageDiscoveryReport.cs index 709a89308..9d4734a2f 100644 --- a/Source/Workspace/Celbridge.Packages/Services/PackageDiscoveryReport.cs +++ b/Source/Core/Celbridge.Foundation/Packages/PackageDiscoveryReport.cs @@ -7,28 +7,28 @@ public enum PackageLoadFailureReason { /// /// The package manifest is missing, cannot be parsed, is missing required - /// fields, or has an invalid id format. + /// fields, or has an invalid name format. /// InvalidManifest, /// - /// A project package tried to claim a package id under the reserved - /// "celbridge." namespace that is restricted to first-party bundled packages. + /// A project package tried to claim a name under the reserved "celbridge." + /// namespace that is restricted to first-party bundled packages. /// - ReservedIdPrefix, + ReservedNamePrefix, /// - /// A project package used a dotted id but no namespace registry exists yet - /// to validate the prefix. Only flat ids are currently permitted for + /// A project package used a dotted name but no namespace registry exists yet + /// to validate the prefix. Only flat names are currently permitted for /// project packages. /// UnregisteredNamespace, /// - /// The package id collides with another loaded package. Covers bundled vs + /// The package name collides with another loaded package. Covers bundled vs /// bundled, project vs bundled, and project vs project conflicts. /// - DuplicateId, + DuplicateName, /// /// A package's document-type registration declares a file extension that @@ -43,8 +43,15 @@ public enum PackageLoadFailureReason public sealed record PackageLoadFailure { public string Folder { get; init; } = string.Empty; - public string? PackageId { get; init; } + public string? PackageName { get; init; } public PackageLoadFailureReason Reason { get; init; } + + /// + /// Optional error detail explaining the failure, carried so diagnostic + /// surfaces can show the cause without consulting the application log. + /// Null when the reason alone describes the failure. + /// + public string? Detail { get; init; } } /// diff --git a/Source/Core/Celbridge.Foundation/Packages/PackageId.cs b/Source/Core/Celbridge.Foundation/Packages/PackageId.cs deleted file mode 100644 index e14fbde99..000000000 --- a/Source/Core/Celbridge.Foundation/Packages/PackageId.cs +++ /dev/null @@ -1,54 +0,0 @@ -namespace Celbridge.Packages; - -/// -/// Validation rules for package ids. -/// Package ids are lowercase kebab-case strings that may optionally use -/// dot-separated namespace segments (e.g. "my-mod" or "celbridge.notes"). -/// -public static class PackageId -{ - /// - /// Returns true if the string is a well-formed package id. - /// A valid id is non-empty, uses only lowercase ASCII letters, digits, - /// dots, and hyphens, and has no leading, trailing, or consecutive dots. - /// The ASCII-only character set is deliberate: it blocks Unicode homograph - /// attacks where a lookalike (e.g. Cyrillic 'o') masquerades as its ASCII - /// counterpart. Do not relax this to char.IsLetter or similar. - /// - public static bool IsValid(string id) - { - if (string.IsNullOrEmpty(id)) - { - return false; - } - - // Leading or trailing dot would produce an empty namespace or name segment. - if (id[0] == '.' || id[^1] == '.') - { - return false; - } - - char previousCharacter = '\0'; - foreach (var character in id) - { - if (character == '.') - { - if (previousCharacter == '.') - { - // Consecutive dots produce an empty segment. - return false; - } - } - else if (!char.IsAsciiLetterLower(character) && - !char.IsAsciiDigit(character) && - character != '-') - { - return false; - } - - previousCharacter = character; - } - - return true; - } -} diff --git a/Source/Core/Celbridge.Foundation/Packages/PackageInfo.cs b/Source/Core/Celbridge.Foundation/Packages/PackageInfo.cs index 3820a3bef..2a7f98b81 100644 --- a/Source/Core/Celbridge.Foundation/Packages/PackageInfo.cs +++ b/Source/Core/Celbridge.Foundation/Packages/PackageInfo.cs @@ -29,16 +29,22 @@ public enum PackageOrigin public partial record PackageInfo { /// - /// Unique identifier for the package (e.g., "celbridge.notes"). Ids that begin + /// Unique name identifying the package (e.g., "my-widget"). Names that begin /// with the "celbridge." prefix are reserved for first-party packages shipped /// inside Celbridge module DLLs. /// - public string Id { get; init; } = string.Empty; + public string Name { get; init; } = string.Empty; /// - /// Display name of the package (from package.toml). + /// Optional author of the package (from the manifest's 'author' key). The + /// workshop server reads this when a version is published. /// - public string Name { get; init; } = string.Empty; + public string Author { get; init; } = string.Empty; + + /// + /// Optional display name of the package (from the manifest's 'title' key). + /// + public string Title { get; init; } = string.Empty; /// /// Optional feature flag. When set, all contributions are disabled if this feature is off. @@ -46,9 +52,9 @@ public partial record PackageInfo public string? FeatureFlag { get; init; } /// - /// Tool allowlist declared under [mod].requires_tools. + /// Tool allowlist declared under [permissions].tools. /// - public IReadOnlyList RequiresTools { get; init; } = Array.Empty(); + public IReadOnlyList PermittedTools { get; init; } = Array.Empty(); /// /// Named secrets supplied by the module that bundles this package. Populated diff --git a/Source/Core/Celbridge.Foundation/Packages/PackageName.cs b/Source/Core/Celbridge.Foundation/Packages/PackageName.cs new file mode 100644 index 000000000..5dd7ecddd --- /dev/null +++ b/Source/Core/Celbridge.Foundation/Packages/PackageName.cs @@ -0,0 +1,83 @@ +namespace Celbridge.Packages; + +/// +/// Validation rules for package names. The package name is the package's +/// unique identifier and matches the name the workshop server knows it by +/// (e.g. "my-widget"). +/// +public static class PackageName +{ + /// + /// Returns true if the string is a well-formed package name. + /// A valid name is lowercase ASCII letters and digits with single interior + /// hyphens as the only separator, at most PackageConstants.MaxNameLength + /// characters. The ASCII-only character set is deliberate: it blocks + /// Unicode homograph attacks where a lookalike (e.g. Cyrillic 'o') + /// masquerades as its ASCII counterpart. Do not relax this to + /// char.IsLetter or similar. + /// + public static bool IsValid(string name) + { + if (string.IsNullOrEmpty(name) || + name.Length > PackageConstants.MaxNameLength) + { + return false; + } + + // A leading or trailing hyphen produces an empty word on one side. + if (name[0] == '-' || name[^1] == '-') + { + return false; + } + + char previousCharacter = '\0'; + foreach (var character in name) + { + if (character == '-') + { + if (previousCharacter == '-') + { + // Consecutive hyphens produce an empty word. + return false; + } + } + else if (!char.IsAsciiLetterLower(character) && + !char.IsAsciiDigit(character)) + { + return false; + } + + previousCharacter = character; + } + + return true; + } + + /// + /// Returns true if the string is a well-formed bundled package name: one + /// or more valid package name segments separated by single dots (e.g. + /// "celbridge.notes"). Dotted names are internal to first-party bundled + /// packages and are never published to a workshop. + /// + public static bool IsValidBundledName(string name) + { + if (string.IsNullOrEmpty(name) || + name.Length > PackageConstants.MaxNameLength) + { + return false; + } + + // An empty segment (leading, trailing, or consecutive dots) fails the + // per-segment check. + var segments = name.Split('.'); + foreach (var segment in segments) + { + if (!IsValid(segment)) + { + return false; + } + } + + return true; + } +} diff --git a/Source/Core/Celbridge.Foundation/Projects/IProjectLoadReporter.cs b/Source/Core/Celbridge.Foundation/Projects/IProjectLoadReporter.cs index e5d377f34..2c89f5b67 100644 --- a/Source/Core/Celbridge.Foundation/Projects/IProjectLoadReporter.cs +++ b/Source/Core/Celbridge.Foundation/Projects/IProjectLoadReporter.cs @@ -1,3 +1,5 @@ +using Celbridge.Packages; + namespace Celbridge.Projects; /// @@ -20,6 +22,11 @@ public interface IProjectLoadReporter /// void RecordLoadOutcome(bool loadSucceeded, Result? loadResult); + /// + /// Records the package discovery outcome, including any load failures. + /// + void RecordPackageReport(PackageDiscoveryReport report); + /// /// Records the consistency-check findings. /// diff --git a/Source/Core/Celbridge.Host/Services/ContributionToolsHandler.cs b/Source/Core/Celbridge.Host/Services/ContributionToolsHandler.cs index 06feab5ee..4aec6b67c 100644 --- a/Source/Core/Celbridge.Host/Services/ContributionToolsHandler.cs +++ b/Source/Core/Celbridge.Host/Services/ContributionToolsHandler.cs @@ -23,7 +23,7 @@ public static class ToolRpcErrorCodes /// /// Per-WebView RPC target for tools/list and tools/call, gated by a package's -/// requires_tools allowlist. +/// [permissions] tools allowlist. /// public sealed class ContributionToolsHandler { @@ -72,7 +72,7 @@ public async Task CallToolAsync(string name, JsonElement? argume } // The webview_* namespace is reserved for the MCP path. Blocking it here - // (regardless of the package's requires_tools entries) closes the + // (regardless of the package's permitted tools) closes the // cross-document attack vector where a contribution editor's JS could // call webview.eval against another open document. if (IsContributionRestricted(name)) @@ -85,7 +85,7 @@ public async Task CallToolAsync(string name, JsonElement? argume if (!ToolAllowlist.IsAllowed(name, _allowedPatterns)) { - throw new LocalRpcException($"Tool '{name}' is not in this package's requires_tools allowlist") + throw new LocalRpcException($"Tool '{name}' is not declared under [permissions] tools in the package manifest") { ErrorCode = ToolRpcErrorCodes.ToolDenied }; diff --git a/Source/Core/Celbridge.Host/Services/ToolAllowlist.cs b/Source/Core/Celbridge.Host/Services/ToolAllowlist.cs index 54523773a..38391b88a 100644 --- a/Source/Core/Celbridge.Host/Services/ToolAllowlist.cs +++ b/Source/Core/Celbridge.Host/Services/ToolAllowlist.cs @@ -1,7 +1,7 @@ namespace Celbridge.Host; /// -/// Matches tool aliases against requires_tools glob patterns. +/// Matches tool aliases against [permissions] tools glob patterns. /// Supports literal aliases, namespace wildcards ("foo.*"), and "*". /// public static class ToolAllowlist diff --git a/Source/Core/Celbridge.Projects/Services/ProjectLoadReporter.cs b/Source/Core/Celbridge.Projects/Services/ProjectLoadReporter.cs index e97f642a2..8865f4c84 100644 --- a/Source/Core/Celbridge.Projects/Services/ProjectLoadReporter.cs +++ b/Source/Core/Celbridge.Projects/Services/ProjectLoadReporter.cs @@ -1,6 +1,7 @@ using System.Globalization; using System.Text; using Celbridge.Logging; +using Celbridge.Packages; using Celbridge.Resources; namespace Celbridge.Projects.Services; @@ -24,6 +25,7 @@ public sealed class ProjectLoadReporter : IProjectLoadReporter private bool _userCancelledUpgrade; private bool _loadSucceeded; private Result? _loadResult; + private PackageDiscoveryReport? _packageReport; private ProjectCheckReport? _checkReport; private DateTimeOffset? _checkCompletedAt; @@ -45,6 +47,7 @@ public void BeginLoad(string projectFilePath) _userCancelledUpgrade = false; _loadSucceeded = false; _loadResult = null; + _packageReport = null; _checkReport = null; _checkCompletedAt = null; } @@ -63,6 +66,11 @@ public void RecordLoadOutcome(bool loadSucceeded, Result? loadResult) _loadCompletedAt = DateTimeOffset.UtcNow; } + public void RecordPackageReport(PackageDiscoveryReport report) + { + _packageReport = report; + } + public void RecordCheckReport(ProjectCheckReport report) { _checkReport = report; @@ -137,6 +145,11 @@ private string FormatReport() AppendLoadSection(builder); + if (_packageReport is not null) + { + AppendPackagesSection(builder); + } + if (_checkReport is not null) { AppendCheckSection(builder); @@ -187,6 +200,39 @@ private void AppendLoadSection(StringBuilder builder) } } + private void AppendPackagesSection(StringBuilder builder) + { + builder.AppendLine("## Packages"); + builder.AppendLine(); + + var report = _packageReport!; + builder.AppendLine($"- Bundled packages loaded: {report.BundledPackageCount}"); + builder.AppendLine($"- Project packages loaded: {report.ProjectPackageCount}"); + + if (report.Failures.Count == 0) + { + builder.AppendLine("- No load failures."); + builder.AppendLine(); + return; + } + + builder.AppendLine(); + builder.AppendLine($"### Load failures ({report.Failures.Count})"); + builder.AppendLine(); + foreach (var failure in report.Failures) + { + var packageLabel = string.IsNullOrEmpty(failure.PackageName) + ? $"`{failure.Folder}`" + : $"`{failure.PackageName}` in `{failure.Folder}`"; + builder.AppendLine($"- {packageLabel}: `{failure.Reason}`"); + if (!string.IsNullOrEmpty(failure.Detail)) + { + builder.AppendLine($" - {NormaliseNewlines(failure.Detail).Replace("\n", " ")}"); + } + } + builder.AppendLine(); + } + private void AppendCheckSection(StringBuilder builder) { builder.AppendLine("## Consistency check"); diff --git a/Source/Core/Celbridge.Tools/Guides/Concepts/agent_instructions.md b/Source/Core/Celbridge.Tools/Guides/Concepts/agent_instructions.md index b5f936c2e..0514b02e7 100644 --- a/Source/Core/Celbridge.Tools/Guides/Concepts/agent_instructions.md +++ b/Source/Core/Celbridge.Tools/Guides/Concepts/agent_instructions.md @@ -42,7 +42,7 @@ A single tool method is exposed under three names — the MCP form, the Python f | MCP tool name (in `tools/list`) | `_` | `file_replace` | | Python REPL proxy (`cel.*`) | `cel..(...)` | `cel.file.replace(...)` | | JavaScript call site (in a package) | `cel..(...)` | `cel.file.replace(...)` | -| `requires_tools` manifest entry | `.` | `"file.replace"` | +| `[permissions] tools` manifest entry | `.` | `"file.replace"` | The dot-form alias used in manifests matches the MCP tool name after swapping the first underscore for a dot. The JavaScript proxy converts the method portion to camelCase at the call site automatically; the manifest does **not**. @@ -69,11 +69,11 @@ Type `help(cel)` to list the namespaces, or `help(cel.file)` to see the methods ## JavaScript proxy conventions -Package extensions run inside a WebView hosted by a document editor contribution (declared in `package.toml` under `[contributes].document_editors`). Before writing any JS that calls `cel.*`, declare the tools your package needs in `package.toml` under `[mod].requires_tools`: +Package extensions run inside a WebView hosted by a document editor contribution (declared in `package.toml` under `[contributes].document_editors`). Before writing any JS that calls `cel.*`, declare the tools your package needs in `package.toml` under `[permissions].tools`: ```toml -[mod] -requires_tools = ["document.*", "file.*", "app.get_state"] +[permissions] +tools = ["document.*", "file.*", "app.get_state"] ``` The manifest uses the **alias form** — `namespace.snake_case_method`. The JS proxy converts the method portion to camelCase at the call site; the manifest does **not**. @@ -86,7 +86,7 @@ const tree = await cel.file.getTree(""); - **Arguments are positional and camelCase.** Extra arguments throw `CEL_TOOL_INVALID_ARGS`. - **Errors throw `CelToolError`** with `{ code, tool, message }`. -- **Calling a namespace not covered by `requires_tools`** throws `TypeError: Cannot read properties of undefined`. Fix the manifest, not the call site. +- **Calling a namespace not covered by `[permissions] tools`** throws `TypeError: Cannot read properties of undefined`. Fix the manifest, not the call site. ## Domain prep — namespace guides diff --git a/Source/Core/Celbridge.Tools/Guides/Concepts/document_editor_contributions.md b/Source/Core/Celbridge.Tools/Guides/Concepts/document_editor_contributions.md index fb2df3133..d9d764d2f 100644 --- a/Source/Core/Celbridge.Tools/Guides/Concepts/document_editor_contributions.md +++ b/Source/Core/Celbridge.Tools/Guides/Concepts/document_editor_contributions.md @@ -8,15 +8,14 @@ A package contribution that takes over a file extension in the documents panel. ```toml [package] -id = "my-editor" -name = "My Editor" -version = "1.0.0" +name = "my-editor" +title = "My Editor" [contributes] document_editors = ["my-editor.document.toml"] -[mod] -requires_tools = ["document.*", "file.*"] +[permissions] +tools = ["document.*", "file.*"] ``` `packages/my-editor/my-editor.document.toml`: @@ -130,7 +129,7 @@ Apply at every framework-driven `setContent` site. - **Localization** — `t('MyEditor_Editor_Name')` after `await client.initialize()`; strings live in `localization/.json` next to `index.html`. - **Secrets** — bundled-package descriptors can inject `client.secrets.`. Non-bundled packages see an empty map. -- **`requires_tools`** — every `cel.*` call must be declared under `[mod].requires_tools` in alias form (`"document.save"`). See `agent_instructions`. +- **`[permissions] tools`** — every `cel.*` call must be declared under `[permissions].tools` in alias form (`"document.save"`). See `agent_instructions`. ## Reference contributions diff --git a/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md b/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md index cd4848d80..a1391331f 100644 --- a/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md +++ b/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md @@ -4,41 +4,38 @@ Packages extend Celbridge with custom document editors and other contributions. ## Creating a package -```python -package.create("my-widget") -``` - -Creates `packages/my-widget/` with a stub `package.toml` manifest. +There is no scaffolding tool — a package is a folder with a manifest. Write `packages/my-widget/package.toml` with the file tools using the manifest shape below, and the package is discovered on the next project load. ## Manifest (`package.toml`) -Every package folder must contain a `package.toml` at its root with at minimum a `[package]` section containing `id` and `name`: +Every package folder must contain a `package.toml` at its root with at minimum a `[package]` section containing `name`: ```toml [package] -id = "my-widget" -name = "My Widget" -version = "1.0.0" +name = "my-widget" # identifier; matches the workshop's package name +author = "Acme" # read by the workshop when a version is published +title = "My Widget" # display name [contributes] document_editors = ["my-editor.document.toml"] ``` -**Required:** `id`, `name`. **Optional:** `version`, `feature_flag`. The `[contributes]` section lists document editor manifests provided by the package. +**Required:** `name`. **Optional:** `author`, `title`, `feature_flag`. The `[contributes]` section lists document editor manifests provided by the package. If your package contributes a document editor, also read `document_editor_contributions` for the manifest, handler, and read-only contract. + +A package name is lowercase ASCII alphanumeric with single interior hyphens as the only separator, 1-64 characters. There is no version field: version numbers are assigned by the workshop when a version is published. -## Registry workflow +## Workshop workflow | Tool | What it does | |---|---| -| `package_create("name")` | Create a new package with a stub manifest | -| `package_publish("packages/name", "name")` | Validate and upload to the registry | -| `package_install("name")` | Download and extract from the registry | -| `package_list()` | List all packages available in the registry | +| `package_publish("packages/name", "name")` | Validate and publish a new version to the workshop | +| `package_install("name")` | Download and extract the latest version from the workshop | +| `package_list()` | List all packages available in the workshop | -To publish, the package must live under `packages/`, the folder name must match the package id, and the manifest must be valid. +To publish, the package must live under `packages/`, the folder name must match the package name, and the manifest must be valid with a `name` equal to the published name. ## Confirmation prompts `package_publish` and `package_install` are destructive. Both accept `confirmWithUser` (default `true`). Pass `false` only when the user has explicitly asked for unattended operation. -For the JS proxy conventions and `requires_tools` declarations packages need at runtime, see `agent_instructions`. +For the JS proxy conventions and `[permissions] tools` declarations packages need at runtime, see `agent_instructions`. diff --git a/Source/Core/Celbridge.Tools/Guides/Concepts/webview_devtools.md b/Source/Core/Celbridge.Tools/Guides/Concepts/webview_devtools.md index 0ecfb829f..a25b26383 100644 --- a/Source/Core/Celbridge.Tools/Guides/Concepts/webview_devtools.md +++ b/Source/Core/Celbridge.Tools/Guides/Concepts/webview_devtools.md @@ -59,4 +59,4 @@ Both `webview-dev-tools` and `webview-dev-tools-eval` must be on, because `webvi ## Available from Python and MCP, not from package JS -The Python proxy runs on behalf of the user (the trust root). The JavaScript proxy runs inside third-party package code, so the host denies any `webview.*` call arriving from a contribution editor regardless of `requires_tools`. This blocks a cross-document attack vector. Do not declare `webview.*` in a package's `requires_tools`. +The Python proxy runs on behalf of the user (the trust root). The JavaScript proxy runs inside third-party package code, so the host denies any `webview.*` call arriving from a contribution editor regardless of its permitted tools. This blocks a cross-document attack vector. Do not declare `webview.*` in a package's `[permissions] tools`. diff --git a/Source/Core/Celbridge.Tools/Guides/Namespaces/package.md b/Source/Core/Celbridge.Tools/Guides/Namespaces/package.md index 36bff343c..73900d495 100644 --- a/Source/Core/Celbridge.Tools/Guides/Namespaces/package.md +++ b/Source/Core/Celbridge.Tools/Guides/Namespaces/package.md @@ -1,19 +1,19 @@ # package -The `package` namespace builds, installs, archives, and publishes Celbridge packages. A package is the unit of distributable functionality (a custom document editor, an asset library, a reusable Python module). Each package follows a folder layout with a manifest at the root. +The `package` namespace installs, archives, and publishes Celbridge packages. A package is the unit of distributable functionality (a custom document editor, an asset library, a reusable Python module). Each package follows a folder layout with a manifest at the root. ## Must-knows -- **Publishing and installing are interactive by default.** `package_publish` and `package_install` confirm with the user before mutating the registry or the project. Pass `confirmWithUser: false` only for unattended flows the user has consented to. See `silent_vs_interactive`. +- **Publishing and installing are interactive by default.** `package_publish` and `package_install` confirm with the user before mutating the workshop or the project. Pass `confirmWithUser: false` only for unattended flows the user has consented to. See `silent_vs_interactive`. - **`package_install` requires a loaded project.** Installing without a project loaded fails fast. - **Archives are produced under the project's content folder.** `package_archive` writes a `.celpkg` next to the source folder unless an explicit destination is given. `package_unarchive` is the inverse. - **Packages are not Python packages.** Despite some tooling overlap, this namespace is for Celbridge's own package format. To see the project's Python dependencies, read the `.celbridge` project file (`[project].dependencies`). +- **There is no create tool.** A package is a folder with a `package.toml` manifest; scaffold one by writing the manifest with the file tools. See `packages_overview` for the manifest schema. ## Tools -- `package_create` — scaffold a new package from a template at a chosen folder. -- `package_list` — list installed packages in the current project. -- `package_install` — install a package from a `.celpkg` archive into the current project. +- `package_list` — list the packages published to the connected workshop. +- `package_install` — download and extract the latest version of a workshop package. Interactive by default. - `package_archive` — archive a package folder into a `.celpkg` file. - `package_unarchive` — extract a `.celpkg` archive into a folder. -- `package_publish` — publish a package to a configured remote registry. Interactive by default. +- `package_publish` — publish a new package version to the connected workshop. Interactive by default. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_create.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_create.md deleted file mode 100644 index 315091614..000000000 --- a/Source/Core/Celbridge.Tools/Guides/Tools/package_create.md +++ /dev/null @@ -1,19 +0,0 @@ -# package_create - -Creates a new package in the project's `packages/` folder. The resulting folder is `packages/{packageName}/` with a stub `package.toml` containing `[package]` (id, name, version) and an empty `[contributes]` section. Use this as the first step when authoring a new package. - -The call fails if `packages/{packageName}` already exists — there is no overwrite option, by design. Delete or rename the existing folder first if you really mean to start over. - -## packageName - -Lowercase alphanumeric and hyphens, 1-214 characters (e.g. `"my-widget"`). Uppercase letters, underscores, dots, and other characters are rejected. - -## Returns - -A JSON object: - -- `packageName` (string) — echoed package name. -- `resource` (string) — resource key of the new package folder, e.g. `"packages/my-widget"`. -- `manifestPath` (string) — resource key of the created manifest, e.g. `"packages/my-widget/package.toml"`. - -If you're contributing a document editor, also read `document_editor_contributions` for the manifest, handler, and read-only contract. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_install.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_install.md index 31de72955..150eb5dda 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/package_install.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_install.md @@ -1,6 +1,6 @@ # package_install -Downloads a package from the remote registry and extracts it into `packages/{packageName}/`. The package must already exist on the registry — use `package_list` to discover what is published. By default surfaces a confirmation dialog before installing; pass `confirmWithUser: false` only when the user has explicitly asked for unattended operation. +Downloads the latest version of a package from the workshop and extracts it into `packages/{packageName}/`. The package must already exist on the workshop — use `package_list` to discover what is published. By default surfaces a confirmation dialog before installing; pass `confirmWithUser: false` only when the user has explicitly asked for unattended operation. If a package folder of the same name already exists, the install fails (the underlying unarchive runs with `overwrite: false`). Remove or rename the existing folder first if you intend to replace it. @@ -8,7 +8,7 @@ If a package folder of the same name already exists, the install fails (the unde ### packageName -The name as published on the registry (lowercase alphanumeric and hyphens, 1-214 characters). The corresponding registry file is `{packageName}.zip`. +The name as published on the workshop (lowercase alphanumeric with single hyphen separators, 1-64 characters). ### confirmWithUser @@ -26,3 +26,4 @@ A JSON object: - The downloaded zip is staged briefly under `temp:` and removed after extraction. A failure mid-extract still cleans up the temp file. - An existing `packages/{packageName}` folder causes the call to fail — decide whether to remove it explicitly rather than relying on a flag. +- A package whose versions have all been tombstoned has no live version and cannot be installed. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_list.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_list.md index e0b90bd0f..3fc451c87 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/package_list.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_list.md @@ -1,15 +1,14 @@ # package_list -Returns the packages currently published to the remote package registry. Use this to discover what is available before calling `package_install`, or to check whether a package you intend to publish would collide with an existing entry. - -Only registry entries that look like packages (`.zip` extension, valid package name) are returned; any other files in the registry are filtered out. +Returns the packages currently published to the connected workshop. Use this to discover what is available before calling `package_install`, or to check whether a package you intend to publish would collide with an existing entry. ## Returns A JSON array of objects, one per package: -- `packageName` (string) — derived from the registry file name (without the `.zip` extension). -- `size` (long) — file size in bytes. -- `uploadedAt` (datetime) — UTC timestamp of when the entry was uploaded. +- `packageName` (string) — the package's unique name on the workshop. +- `latestVersion` (int or null) — the number of the latest live version, or null when the package has no live versions. +- `publishedAt` (datetime or null) — UTC timestamp of when the latest live version was published. +- `versionsCount` (int) — total number of versions the package has. -The array is in the order returned by the registry; it is not sorted alphabetically. +The array is in the order returned by the workshop; it is not sorted alphabetically. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md index de998a61b..c4bda3d63 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md @@ -1,8 +1,8 @@ # package_publish -Zips the contents of `packages/{packageName}/` and uploads the result to the remote package registry as `{packageName}.zip`. Validates the package layout and manifest before uploading; an existing entry with the same name on the registry is overwritten by the upload. +Zips the contents of `packages/{packageName}/` and publishes the result to the workshop as a new version of the package. Versions are immutable and numbered by the workshop in publish order; publishing never overwrites an earlier version. The first publish of a new name registers the package on the workshop. -By default surfaces a confirmation dialog before publishing; pass `confirmWithUser: false` only when the user has explicitly asked for unattended operation. +Validates the package layout and manifest before uploading. By default surfaces a confirmation dialog before publishing; pass `confirmWithUser: false` only when the user has explicitly asked for unattended operation. ## Parameters @@ -12,7 +12,7 @@ Resource key of the package folder. Must start with `packages/` and the folder n ### packageName -Lowercase alphanumeric and hyphens, 1-214 characters. Must match the folder name segment of `resource`. +Lowercase alphanumeric with single hyphen separators, 1-64 characters. Must match the folder name segment of `resource` and the manifest's `name` field. ### confirmWithUser @@ -25,7 +25,7 @@ Before uploading, the tool verifies that: - `resource` is inside `packages/` and the folder segment equals `packageName`. - The folder exists on disk. - A `package.toml` file is present at the folder root. -- The manifest is valid TOML and contains a `[package]` section with non-empty `id` and `name` fields. +- The manifest is valid TOML and contains a `[package]` section whose `name` field equals `packageName`. If any check fails, no upload is attempted. @@ -34,10 +34,11 @@ If any check fails, no upload is attempted. A JSON object: - `packageName` (string) — echoed package name. +- `version` (int) — the version number the workshop assigned to this publish. - `entries` (int) — number of files included in the uploaded zip. - `size` (long) — uploaded zip size in bytes. ## Gotchas - Symlinks and other reparse points inside the package folder are skipped, not followed. -- Publishing replaces any existing registry entry with the same file name; there is no version check on the registry side. +- Publishing always creates a new version; there is no way to replace or delete an existing version through the tools. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/webview_eval.md b/Source/Core/Celbridge.Tools/Guides/Tools/webview_eval.md index 554b06de7..6adbfc923 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/webview_eval.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/webview_eval.md @@ -24,4 +24,4 @@ The JSON-serialised result of the expression. `null` is returned when the expres - The DevTools-only `getEventListeners()` helper does not exist in this context. Calling it raises a `ReferenceError`. - The expression body may contain sensitive output (cookies, storage values). The host logs only the resource and the expression length at info level. Treat the contents as you would any other arbitrary code execution path. -- Only available from Python and from the MCP transport. The JavaScript proxy refuses `webview.*` calls from package code regardless of `requires_tools`. Do not declare `webview.*` in a package manifest. +- Only available from Python and from the MCP transport. The JavaScript proxy refuses `webview.*` calls from package code regardless of `[permissions] tools`. Do not declare `webview.*` in a package manifest. diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Create.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Create.cs deleted file mode 100644 index d848d9928..000000000 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Create.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Text; -using System.Text.Json; -using ModelContextProtocol.Protocol; -using ModelContextProtocol.Server; - -namespace Celbridge.Tools; - -/// -/// Result returned by package_create with the created package details. -/// -public record class PackageCreateResult(string PackageName, string Resource, string ManifestPath); - -public partial class PackageTools -{ - /// Create a new package skeleton at packages/{packageName}/ with stub manifest. - [McpServerTool(Name = "package_create", Destructive = true)] - [ToolAlias("package.create")] - [RelatedGuides("packages_overview", "document_editor_contributions")] - public async partial Task Create(string packageName) - { - if (!IsValidPackageName(packageName)) - { - return ToolResponse.Error( - $"Invalid package name: '{packageName}'. " + - "Package names must be lowercase alphanumeric with hyphens, 1-214 characters."); - } - - var workspaceWrapper = GetRequiredService(); - var workspaceService = workspaceWrapper.WorkspaceService; - var resourceRegistry = workspaceService.ResourceService.Registry; - var resourceFileSystem = workspaceService.ResourceService.FileSystem; - var fileSystem = GetRequiredService(); - - var packageResource = ResourceKey.Create($"packages/{packageName}"); - var resolveResult = resourceRegistry.ResolveResourcePath(packageResource); - if (resolveResult.IsFailure) - { - var failure = Result.Fail("Failed to resolve path for package") - .WithErrors(resolveResult); - return ToolResponse.Error(failure); - } - var packageFolderPath = resolveResult.Value; - - var packageInfoResult = await fileSystem.GetInfoAsync(packageFolderPath); - if (packageInfoResult.IsSuccess - && packageInfoResult.Value.Kind == StorageItemKind.Folder) - { - return ToolResponse.Error($"Package already exists: 'packages/{packageName}'"); - } - - var manifestContent = new StringBuilder(); - manifestContent.AppendLine("[package]"); - manifestContent.AppendLine($"id = \"{packageName}\""); - manifestContent.AppendLine($"name = \"{packageName}\""); - manifestContent.AppendLine("version = \"1.0.0\""); - manifestContent.AppendLine(); - manifestContent.AppendLine("[contributes]"); - - var manifestResource = ResourceKey.Create($"packages/{packageName}/{ManifestFileName}"); - var writeManifestResult = await resourceFileSystem.WriteAllTextAsync(manifestResource, manifestContent.ToString()); - if (writeManifestResult.IsFailure) - { - return ToolResponse.Error($"Failed to create package: {writeManifestResult.FirstErrorMessage}"); - } - - var result = new PackageCreateResult( - packageName, - packageResource.ToString(), - $"packages/{packageName}/{ManifestFileName}"); - - var json = JsonSerializer.Serialize(result, JsonOptions); - return ToolResponse.Success(json); - } -} diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs index 767307bf7..9b86a30b1 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs @@ -11,42 +11,15 @@ public record class PackageInstallResult(string PackageName, int Entries, string public partial class PackageTools { - /// Install a package from the remote registry into packages/{packageName}/. + /// Install the latest version of a workshop package into packages/{packageName}/. [McpServerTool(Name = "package_install", Destructive = true)] [ToolAlias("package.install")] [RelatedGuides("packages_overview", "silent_vs_interactive")] public async partial Task Install(string packageName, bool confirmWithUser = true) { - if (!IsValidPackageName(packageName)) + if (!PackageName.IsValid(packageName)) { - return ToolResponse.Error( - $"Invalid package name: '{packageName}'. " + - "Package names must be lowercase alphanumeric with hyphens, 1-214 characters."); - } - - // Find the package in the remote registry - var packageApiClient = GetRequiredService(); - var listResult = await packageApiClient.ListPackagesAsync(); - - if (listResult.IsFailure) - { - return ToolResponse.Error(listResult); - } - - var expectedFileName = $"{packageName}.zip"; - PackageApiEntry? matchingEntry = null; - foreach (var entry in listResult.Value) - { - if (string.Equals(entry.FileName, expectedFileName, StringComparison.OrdinalIgnoreCase)) - { - matchingEntry = entry; - break; - } - } - - if (matchingEntry is null) - { - return ToolResponse.Error($"Package not found in registry: '{packageName}'"); + return ToolResponse.Error(InvalidPackageNameError(packageName)); } if (confirmWithUser) @@ -62,8 +35,8 @@ public async partial Task Install(string packageName, bool confi } } - // Download the package zip - var downloadResult = await packageApiClient.DownloadPackageAsync(matchingEntry.Id); + var packageApiClient = GetRequiredService(); + var downloadResult = await packageApiClient.DownloadLatestAsync(packageName); if (downloadResult.IsFailure) { return ToolResponse.Error(downloadResult); @@ -82,7 +55,7 @@ public async partial Task Install(string packageName, bool confi return ToolResponse.Error($"Failed to write downloaded package: {writeArchiveResult.FirstErrorMessage}"); } - var destinationResource = ResourceKey.Create($"packages/{packageName}"); + var destinationResource = ResourceKey.Create($"{PackageConstants.DefaultPackagesFolder}/{packageName}"); try { diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.List.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.List.cs index 5958efe8d..a522543e9 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.List.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.List.cs @@ -1,18 +1,18 @@ using System.Text.Json; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; -using Path = System.IO.Path; namespace Celbridge.Tools; /// -/// A package entry in the package_list result. +/// A package entry in the package_list result. LatestVersion and PublishedAt +/// are null when the package has no live versions. /// -public record class PackageListEntry(string PackageName, long Size, DateTime UploadedAt); +public record class PackageListEntry(string PackageName, int? LatestVersion, DateTime? PublishedAt, int VersionsCount); public partial class PackageTools { - /// List all packages available in the remote package registry. + /// List all packages available in the connected workshop. [McpServerTool(Name = "package_list", ReadOnly = true)] [ToolAlias("package.list")] [RelatedGuides("packages_overview")] @@ -27,16 +27,14 @@ public async partial Task List() } var packages = new List(); - foreach (var entry in listResult.Value) + foreach (var package in listResult.Value) { - if (entry.FileName.EndsWith(".zip", StringComparison.OrdinalIgnoreCase)) - { - var packageName = Path.GetFileNameWithoutExtension(entry.FileName); - if (IsValidPackageName(packageName)) - { - packages.Add(new PackageListEntry(packageName, entry.FileSize, entry.UploadedAt)); - } - } + var entry = new PackageListEntry( + package.Name, + package.LatestVersion?.Version, + package.LatestVersion?.Date, + package.VersionsCount); + packages.Add(entry); } var json = JsonSerializer.Serialize(packages, JsonOptions); diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs index 0fbe5167e..c65b8eb8e 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs @@ -12,19 +12,19 @@ namespace Celbridge.Tools; /// -/// Result returned by package_publish with the published package details. +/// Result returned by package_publish with the published package details, +/// including the version number assigned by the workshop. /// -public record class PackagePublishResult(string PackageName, int Entries, long Size); +public record class PackagePublishResult(string PackageName, int Version, int Entries, long Size); public partial class PackageTools { - private const string PackagesFolderPrefix = "packages/"; - private const string ManifestFileName = "package.toml"; + private const string PackagesFolderPrefix = $"{PackageConstants.DefaultPackagesFolder}/"; - /// Publish packages/{packageName}/ to the remote registry (visible to other users). + /// Publish packages/{packageName}/ to the workshop as a new package version. [McpServerTool(Name = "package_publish", Destructive = true)] [ToolAlias("package.publish")] - [RelatedGuides("resource_keys", "packages_overview", "silent_vs_interactive")] + [RelatedGuides("resource_keys", "packages_overview", "silent_vs_interactive", "document_editor_contributions")] [AllowDirectFileSystemAccess] public async partial Task Publish(string resource, string packageName, bool confirmWithUser = true) { @@ -33,11 +33,9 @@ public async partial Task Publish(string resource, string packag return ToolResponse.InvalidResourceKey(resource); } - if (!IsValidPackageName(packageName)) + if (!PackageName.IsValid(packageName)) { - return ToolResponse.Error( - $"Invalid package name: '{packageName}'. " + - "Package names must be lowercase alphanumeric with hyphens, 1-214 characters."); + return ToolResponse.Error(InvalidPackageNameError(packageName)); } // Validate the resource is inside the packages folder @@ -77,8 +75,8 @@ public async partial Task Publish(string resource, string packag } // Validate that the package manifest exists and is valid - var manifestPath = Path.Combine(sourcePath, ManifestFileName); - var validateResult = await ValidatePackageManifestAsync(fileSystem, manifestPath); + var manifestPath = Path.Combine(sourcePath, PackageConstants.ManifestFileName); + var validateResult = await ValidatePackageManifestAsync(fileSystem, manifestPath, packageName); if (validateResult.IsFailure) { return ToolResponse.Error(validateResult); @@ -149,27 +147,27 @@ public async partial Task Publish(string resource, string packag } var packageApiClient = GetRequiredService(); - var fileName = $"{packageName}.zip"; - var uploadResult = await packageApiClient.UploadPackageAsync(fileName, zipData); + var publishResult = await packageApiClient.PublishVersionAsync(packageName, zipData); - if (uploadResult.IsFailure) + if (publishResult.IsFailure) { - return ToolResponse.Error(uploadResult); + return ToolResponse.Error(publishResult); } + var receipt = publishResult.Value; - var result = new PackagePublishResult(packageName, entryCount, zipData.Length); + var result = new PackagePublishResult(packageName, receipt.Version, entryCount, zipData.Length); var json = JsonSerializer.Serialize(result, JsonOptions); return ToolResponse.Success(json); } - private static async Task ValidatePackageManifestAsync(ILocalFileSystem fileSystem, string manifestPath) + private static async Task ValidatePackageManifestAsync(ILocalFileSystem fileSystem, string manifestPath, string packageName) { var manifestInfoResult = await fileSystem.GetInfoAsync(manifestPath); if (manifestInfoResult.IsFailure || manifestInfoResult.Value.Kind != StorageItemKind.File) { return Result.Fail( - $"Package manifest not found. Expected '{ManifestFileName}' in the package folder."); + $"Package manifest not found. Expected '{PackageConstants.ManifestFileName}' in the package folder."); } var readResult = await fileSystem.ReadAllTextAsync(manifestPath); @@ -195,13 +193,6 @@ private static async Task ValidatePackageManifestAsync(ILocalFileSystem return Result.Fail("Package manifest is missing the required [package] section."); } - if (!packageTable.TryGetValue("id", out var idValue) || - idValue is not string idString || - string.IsNullOrWhiteSpace(idString)) - { - return Result.Fail("Package manifest is missing a required 'id' field in the [package] section."); - } - if (!packageTable.TryGetValue("name", out var nameValue) || nameValue is not string nameString || string.IsNullOrWhiteSpace(nameString)) @@ -209,6 +200,15 @@ nameValue is not string nameString || return Result.Fail("Package manifest is missing a required 'name' field in the [package] section."); } + // The workshop registers the package under the published name, so a + // manifest that declares a different name would install under an + // identity that disagrees with its own metadata. + if (!string.Equals(nameString, packageName, StringComparison.Ordinal)) + { + return Result.Fail( + $"Manifest 'name' value '{nameString}' does not match the package name '{packageName}'."); + } + return Result.Ok(); } } diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs index 5213facac..810835712 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs @@ -1,25 +1,19 @@ -using System.Text.RegularExpressions; using ModelContextProtocol.Server; namespace Celbridge.Tools; /// -/// MCP tools for package operations: archiving, unarchiving, and package registry management. +/// MCP tools for package operations: archiving, unarchiving, and workshop package management. /// [McpServerToolType] public partial class PackageTools : AgentToolBase { public PackageTools(IApplicationServiceProvider services) : base(services) { } - private static bool IsValidPackageName(string name) + private static string InvalidPackageNameError(string packageName) { - if (string.IsNullOrEmpty(name) || name.Length > 214) - { - return false; - } - - return Regex.IsMatch(name, @"^[a-z0-9]([a-z0-9\-]*[a-z0-9])?$") && - !name.Contains("--"); + return $"Invalid package name: '{packageName}'. " + + $"Package names must be lowercase alphanumeric with single hyphen separators, 1-{PackageConstants.MaxNameLength} characters."; } private async Task ConfirmActionAsync(string title, string message) diff --git a/Source/Core/Celbridge.WebHost/Services/WebViewService.cs b/Source/Core/Celbridge.WebHost/Services/WebViewService.cs index 9a927f67d..8a2a3e938 100644 --- a/Source/Core/Celbridge.WebHost/Services/WebViewService.cs +++ b/Source/Core/Celbridge.WebHost/Services/WebViewService.cs @@ -62,7 +62,7 @@ public WebViewToolSupport GetWebViewToolSupport(ResourceKey resource) if (contributingPackage is not null && contributingPackage.Info.DevToolsBlocked) { return NotSupported( - $"Resource '{resource}' is open with the '{match.EditorId}' editor, but the contributing package '{contributingPackage.Info.Id}' has set DevToolsBlocked = true. The webview_* tools are not available for this editor by package policy."); + $"Resource '{resource}' is open with the '{match.EditorId}' editor, but the contributing package '{contributingPackage.Info.Name}' has set DevToolsBlocked = true. The webview_* tools are not available for this editor by package policy."); } return Supported; diff --git a/Source/Core/Celbridge.WebHost/Web/celbridge-client/api/tools-api.js b/Source/Core/Celbridge.WebHost/Web/celbridge-client/api/tools-api.js index 8b5fe8219..d0dcf232d 100644 --- a/Source/Core/Celbridge.WebHost/Web/celbridge-client/api/tools-api.js +++ b/Source/Core/Celbridge.WebHost/Web/celbridge-client/api/tools-api.js @@ -1,7 +1,7 @@ // Tools API: MCP tool dispatch wrapper and dynamic `cel.*` proxy for contribution editors. // -// Contribution packages declare the tools they need via `requires_tools` in package.toml. -// The host injects the resolved allowlist as `window.__celbridgeContext.allowedTools` +// Contribution packages declare the tools they need via `[permissions] tools` in package.toml. +// The host injects the resolved allowlist as `window.__celbridgeContext.permittedTools` // before navigation. This module builds a dynamic proxy that exposes only the allowed // tools as `celbridge.cel..(...)`. // @@ -360,7 +360,7 @@ export class ToolsAPI { * * When the allowlist is empty the fetch is skipped — no tool can pass the * gate, so there is nothing to discover. Packages that do not declare - * `requires_tools` therefore pay no startup round-trip. + * `[permissions] tools` therefore pay no startup round-trip. * * @returns {Promise} */ @@ -427,7 +427,7 @@ export class ToolsAPI { throw new CelToolError( CelToolErrorCode.Denied, alias, - `Tool '${alias}' is not in this package's requires_tools allowlist` + `Tool '${alias}' is not declared under [permissions] tools in the package manifest` ); } diff --git a/Source/Core/Celbridge.WebHost/Web/celbridge-client/celbridge.js b/Source/Core/Celbridge.WebHost/Web/celbridge-client/celbridge.js index ab6cbfce5..b411a6884 100644 --- a/Source/Core/Celbridge.WebHost/Web/celbridge-client/celbridge.js +++ b/Source/Core/Celbridge.WebHost/Web/celbridge-client/celbridge.js @@ -54,8 +54,8 @@ export class Celbridge { /** * Host capability proxy (`cel.*`) and raw tool dispatch (`list`, `call`). - * Populated from the package's `requires_tools` allowlist, which the host - * injects as `window.__celbridgeContext.allowedTools` before navigation. + * Populated from the package's `[permissions] tools` allowlist, which the + * host injects as `window.__celbridgeContext.permittedTools` before navigation. * @type {ToolsAPI} */ tools; @@ -108,7 +108,7 @@ export class Celbridge { this.input = new InputAPI(this.#transport); this.theme = new ThemeAPI(); this.localization = new LocalizationAPI(this.#transport); - this.tools = new ToolsAPI(this.#transport, context.allowedTools); + this.tools = new ToolsAPI(this.#transport, context.permittedTools); this.secrets = context.secrets; this.options = context.options; this.#exposeCelGlobal = options.exposeCelGlobal !== false; @@ -266,7 +266,7 @@ export class Celbridge { * Called once during Celbridge construction. * * Contract: - * - `allowedTools` is an array of glob patterns from the package's `requires_tools`. + * - `permittedTools` is an array of glob patterns from the package's `[permissions] tools`. * A missing or empty value means the editor gets no tool access (default-deny). * - `secrets` is a map of secret name to resolved value, supplied by the bundled * package's C# descriptor. @@ -274,7 +274,7 @@ export class Celbridge { * Used by editors to configure themselves (e.g., which preview renderer to load). * * @param {Object} [providedContext] - Context passed via constructor options (testing). - * @returns {{ allowedTools: ReadonlyArray, secrets: Readonly>, options: Readonly> }} + * @returns {{ permittedTools: ReadonlyArray, secrets: Readonly>, options: Readonly> }} */ function readAndScrubContext(providedContext) { const fromArg = providedContext ?? null; @@ -290,15 +290,15 @@ function readAndScrubContext(providedContext) { } } - const allowedTools = Array.isArray(raw?.allowedTools) - ? Object.freeze([...raw.allowedTools]) + const permittedTools = Array.isArray(raw?.permittedTools) + ? Object.freeze([...raw.permittedTools]) : Object.freeze([]); const secrets = readStringMap(raw?.secrets); const options = readStringMap(raw?.options); return { - allowedTools, + permittedTools, secrets: Object.freeze(secrets), options: Object.freeze(options) }; diff --git a/Source/Core/Celbridge.WebHost/Web/celbridge-client/tests/tools-api.test.js b/Source/Core/Celbridge.WebHost/Web/celbridge-client/tests/tools-api.test.js index b5a3550c6..486945051 100644 --- a/Source/Core/Celbridge.WebHost/Web/celbridge-client/tests/tools-api.test.js +++ b/Source/Core/Celbridge.WebHost/Web/celbridge-client/tests/tools-api.test.js @@ -415,9 +415,9 @@ describe('Celbridge.tools integration', () => { expect(client.secrets).toEqual({}); }); - it('reads allowedTools from constructor context', () => { + it('reads permittedTools from constructor context', () => { const { client } = createTestClient({ - context: { allowedTools: ['app.get_state'], secrets: {} } + context: { permittedTools: ['app.get_state'], secrets: {} } }); expect(client.tools.allowedPatterns).toEqual(['app.get_state']); }); @@ -425,21 +425,21 @@ describe('Celbridge.tools integration', () => { it('reads and exposes secrets', () => { const secrets = { spreadjs_license: 'abc123' }; const { client } = createTestClient({ - context: { allowedTools: [], secrets } + context: { permittedTools: [], secrets } }); expect(client.secrets.spreadjs_license).toBe('abc123'); }); it('cel accessor throws before initialize() completes', () => { const { client } = createTestClient({ - context: { allowedTools: ['*'], secrets: {} } + context: { permittedTools: ['*'], secrets: {} } }); expect(() => client.cel).toThrow(/not initialized/); }); it('cel proxy dispatches via tools/call with positional arguments after initialize()', async () => { const { client, sentMessages, simulateResponse } = createTestClient({ - context: { allowedTools: ['app.get_state'], secrets: {} } + context: { permittedTools: ['app.get_state'], secrets: {} } }); // Kick off initialize() — it sends document/initialize, then tools/list, @@ -480,7 +480,7 @@ describe('Celbridge.tools integration', () => { it('denied tool calls reject without hitting the transport', async () => { const { client, sentMessages } = createTestClient({ - context: { allowedTools: ['app.*'], secrets: {} } + context: { permittedTools: ['app.*'], secrets: {} } }); await expect(client.tools.call('file.read', {})).rejects.toMatchObject({ @@ -497,7 +497,7 @@ describe('cel globalThis exposure', () => { delete globalThis.cel; const { client, sentMessages, simulateResponse } = createTestClient({ - context: { allowedTools: ['app.get_state'], secrets: {} } + context: { permittedTools: ['app.get_state'], secrets: {} } }); const initPromise = client.initialize(); @@ -527,7 +527,7 @@ describe('cel globalThis exposure', () => { it('accessing cel global before initialize() throws via client.cel', () => { delete globalThis.cel; const { client } = createTestClient({ - context: { allowedTools: ['*'], secrets: {} } + context: { permittedTools: ['*'], secrets: {} } }); expect(() => client.cel).toThrow(/not initialized/); @@ -544,7 +544,7 @@ describe('cel globalThis exposure', () => { onMessage: (handler) => { messageHandler = handler; }, timeout: 1000, exposeCelGlobal: false, - context: { allowedTools: [], secrets: {} } + context: { permittedTools: [], secrets: {} } }); const initPromise = client.initialize(); @@ -557,7 +557,7 @@ describe('cel globalThis exposure', () => { await initPromise; - // With allowedTools=[], no tools/list is sent, so init completes with only the one message. + // With permittedTools=[], no tools/list is sent, so init completes with only the one message. expect(globalThis.cel).toBeUndefined(); expect(client.tools.isReady).toBe(true); }); @@ -566,7 +566,7 @@ describe('cel globalThis exposure', () => { describe('Celbridge global context scrubbing', () => { it('reads globalThis.__celbridgeContext and deletes it', () => { globalThis.__celbridgeContext = { - allowedTools: ['app.*'], + permittedTools: ['app.*'], secrets: { key: 'value' } }; diff --git a/Source/Modules/Celbridge.DocumentEditors/Editors/CodeEditor/package.toml b/Source/Modules/Celbridge.DocumentEditors/Editors/CodeEditor/package.toml index c4f2b24b3..376ff3b8c 100644 --- a/Source/Modules/Celbridge.DocumentEditors/Editors/CodeEditor/package.toml +++ b/Source/Modules/Celbridge.DocumentEditors/Editors/CodeEditor/package.toml @@ -1,7 +1,6 @@ [package] -id = "celbridge.code-editor" -name = "CodeEditor_Package_Name" -version = "1.0.0" +name = "celbridge.code-editor" +title = "CodeEditor_Package_Name" [contributes] document_editors = ["code.document.toml", "markdown.document.toml"] diff --git a/Source/Modules/Celbridge.DocumentEditors/Editors/FileViewer/package.toml b/Source/Modules/Celbridge.DocumentEditors/Editors/FileViewer/package.toml index 5f7db6c1c..dca84e0bf 100644 --- a/Source/Modules/Celbridge.DocumentEditors/Editors/FileViewer/package.toml +++ b/Source/Modules/Celbridge.DocumentEditors/Editors/FileViewer/package.toml @@ -1,7 +1,6 @@ [package] -id = "celbridge.file-viewer" -name = "FileViewer_Package_Name" -version = "1.0.0" +name = "celbridge.file-viewer" +title = "FileViewer_Package_Name" [contributes] document_editors = ["fileviewer.document.toml"] diff --git a/Source/Modules/Celbridge.DocumentEditors/Editors/Notes/package.toml b/Source/Modules/Celbridge.DocumentEditors/Editors/Notes/package.toml index c8c073426..8e440835f 100644 --- a/Source/Modules/Celbridge.DocumentEditors/Editors/Notes/package.toml +++ b/Source/Modules/Celbridge.DocumentEditors/Editors/Notes/package.toml @@ -1,7 +1,6 @@ [package] -id = "celbridge.notes" -name = "Extension_Notes_Name" -version = "1.0.0" +name = "celbridge.notes" +title = "Extension_Notes_Name" feature_flag = "note-editor" [contributes] diff --git a/Source/Modules/Celbridge.DocumentEditors/Editors/SceneViewer/package.toml b/Source/Modules/Celbridge.DocumentEditors/Editors/SceneViewer/package.toml index 9935895f4..d624f48ca 100644 --- a/Source/Modules/Celbridge.DocumentEditors/Editors/SceneViewer/package.toml +++ b/Source/Modules/Celbridge.DocumentEditors/Editors/SceneViewer/package.toml @@ -1,7 +1,6 @@ [package] -id = "celbridge.scene-viewer" -name = "SceneViewer_Package_Name" -version = "1.0.0" +name = "celbridge.scene-viewer" +title = "SceneViewer_Package_Name" [contributes] document_editors = ["scene.document.toml"] diff --git a/Source/Modules/Celbridge.Spreadsheet/Package/package.toml b/Source/Modules/Celbridge.Spreadsheet/Package/package.toml index 298c31305..c9d2beee6 100644 --- a/Source/Modules/Celbridge.Spreadsheet/Package/package.toml +++ b/Source/Modules/Celbridge.Spreadsheet/Package/package.toml @@ -1,7 +1,6 @@ [package] -id = "celbridge.spreadsheet" -name = "Spreadsheet_Package_Name" -version = "1.0.0" +name = "celbridge.spreadsheet" +title = "Spreadsheet_Package_Name" [contributes] document_editors = ["spreadsheet.document.toml"] diff --git a/Source/Tests/Archive/PackageArchiveTests.cs b/Source/Tests/Archive/PackageArchiveTests.cs index d9356d4bf..73460e62d 100644 --- a/Source/Tests/Archive/PackageArchiveTests.cs +++ b/Source/Tests/Archive/PackageArchiveTests.cs @@ -619,40 +619,6 @@ public void CollectFolderHierarchyCollectsAllLevels() foldersToCreate.Should().Contain(Path.GetFullPath(Path.Combine(destinationPath, "a", "b", "c"))); } - [Test] - public void ValidPackageNamesAreAccepted() - { - ArchiveHelper.IsValidPackageName("my-widget").Should().BeTrue(); - ArchiveHelper.IsValidPackageName("widget").Should().BeTrue(); - ArchiveHelper.IsValidPackageName("a").Should().BeTrue(); - ArchiveHelper.IsValidPackageName("my-cool-package").Should().BeTrue(); - ArchiveHelper.IsValidPackageName("package123").Should().BeTrue(); - ArchiveHelper.IsValidPackageName("123package").Should().BeTrue(); - ArchiveHelper.IsValidPackageName("a1b2c3").Should().BeTrue(); - } - - [Test] - public void InvalidPackageNamesAreRejected() - { - ArchiveHelper.IsValidPackageName("").Should().BeFalse("empty name"); - ArchiveHelper.IsValidPackageName("My-Widget").Should().BeFalse("uppercase letters"); - ArchiveHelper.IsValidPackageName("-widget").Should().BeFalse("leading hyphen"); - ArchiveHelper.IsValidPackageName("widget-").Should().BeFalse("trailing hyphen"); - ArchiveHelper.IsValidPackageName("my--widget").Should().BeFalse("consecutive hyphens"); - ArchiveHelper.IsValidPackageName("my widget").Should().BeFalse("spaces"); - ArchiveHelper.IsValidPackageName("my_widget").Should().BeFalse("underscores"); - ArchiveHelper.IsValidPackageName("my.widget").Should().BeFalse("dots"); - ArchiveHelper.IsValidPackageName("my/widget").Should().BeFalse("slashes"); - ArchiveHelper.IsValidPackageName(new string('a', 215)).Should().BeFalse("exceeds max length"); - } - - [Test] - public void PackageNameAtMaxLengthIsAccepted() - { - var maxName = new string('a', 214); - ArchiveHelper.IsValidPackageName(maxName).Should().BeTrue(); - } - [Test] public void CollectFolderHierarchyStopsAtDestination() { diff --git a/Source/Tests/Packages/FileTypeProviderTests.cs b/Source/Tests/Packages/FileTypeProviderTests.cs index e3ec16349..174477947 100644 --- a/Source/Tests/Packages/FileTypeProviderTests.cs +++ b/Source/Tests/Packages/FileTypeProviderTests.cs @@ -2,6 +2,7 @@ using Celbridge.Packages; using Celbridge.Messaging; using Celbridge.Modules; +using Celbridge.Projects; using Celbridge.Resources; using Celbridge.Resources.Services; using Celbridge.Settings; @@ -80,7 +81,8 @@ public void Setup() var localizationService = new PackageLocalizationService(localizationLogger, workspaceWrapper, fileSystem); var registry = new PackageRegistry(logger, _moduleService, _featureFlags, localizationService, workspaceWrapper, fileSystem); - _service = new PackageService(messengerService, registry); + var loadReporter = Substitute.For(); + _service = new PackageService(messengerService, loadReporter, registry); } [TearDown] @@ -319,15 +321,14 @@ private async Task CreateBundledPackage( var packageDir = Path.Combine(_tempProjectFolder, "bundled", dirName); Directory.CreateDirectory(packageDir); - var packageId = $"test.{dirName}"; + var bundledName = $"test.{dirName}"; var featureFlagLine = featureFlag is not null ? $"\nfeature_flag = \"{featureFlag}\"" : ""; // Write package.toml File.WriteAllText(Path.Combine(packageDir, "package.toml"), $""" [package] - id = "{packageId}" - name = "{packageName}" - version = "1.0.0"{featureFlagLine} + name = "{bundledName}" + title = "{packageName}"{featureFlagLine} [contributes] document_editors = ["editor.document.toml"] @@ -353,7 +354,7 @@ private async Task CreateBundledPackage( File.WriteAllText(Path.Combine(packageDir, "editor.document.toml"), $""" [document] - id = "{packageId}-doc" + id = "{bundledName}-doc" type = "custom" entry_point = "index.html" display_name = "{packageName}" diff --git a/Source/Tests/Packages/LocalizationHelperTests.cs b/Source/Tests/Packages/LocalizationHelperTests.cs index 15ca4bfae..4d0eba6bc 100644 --- a/Source/Tests/Packages/LocalizationHelperTests.cs +++ b/Source/Tests/Packages/LocalizationHelperTests.cs @@ -29,8 +29,8 @@ public void Setup() _bundledPackage = new PackageInfo { - Id = "test.package", - Name = "Test Package", + Name = "test.package", + Title = "Test Package", PackageFolder = _tempFolder, Origin = PackageOrigin.Bundled }; diff --git a/Source/Tests/Packages/ManifestTests.cs b/Source/Tests/Packages/ManifestTests.cs index f546d56a4..2ec640e09 100644 --- a/Source/Tests/Packages/ManifestTests.cs +++ b/Source/Tests/Packages/ManifestTests.cs @@ -28,9 +28,8 @@ public void LoadPackage_ValidCustomDocument_ReturnsContribution() { WritePackageToml(""" [package] - id = "test.my-editor" - name = "My Editor" - version = "1.0.0" + name = "test.my-editor" + title = "My Editor" [contributes] document_editors = ["editor.document.toml"] @@ -53,7 +52,7 @@ public void LoadPackage_ValidCustomDocument_ReturnsContribution() result.IsSuccess.Should().BeTrue(); var package = result.Value; - package.Info.Name.Should().Be("My Editor"); + package.Info.Title.Should().Be("My Editor"); package.Info.PackageFolder.Should().Be(_tempFolder); package.Info.HostName.Should().Be("pkg-test-my-editor.celbridge"); package.DocumentEditors.Should().ContainSingle(); @@ -67,14 +66,51 @@ public void LoadPackage_ValidCustomDocument_ReturnsContribution() contribution.Package.HostName.Should().Be("pkg-test-my-editor.celbridge"); } + [Test] + public void LoadPackage_NameAuthorAndTitle_PopulateInfo() + { + WritePackageToml(""" + [package] + name = "my-widget" + author = "Acme" + title = "My Widget" + + [contributes] + """); + + var result = PackageManifestLoader.LoadPackage(Path.Combine(_tempFolder, "package.toml")); + + result.IsSuccess.Should().BeTrue(); + result.Value.Info.Name.Should().Be("my-widget"); + result.Value.Info.Author.Should().Be("Acme"); + result.Value.Info.Title.Should().Be("My Widget"); + } + + [Test] + public void LoadPackage_AuthorAndTitleOmitted_DefaultToEmpty() + { + WritePackageToml(""" + [package] + name = "my-widget" + + [contributes] + """); + + var result = PackageManifestLoader.LoadPackage(Path.Combine(_tempFolder, "package.toml")); + + result.IsSuccess.Should().BeTrue(); + result.Value.Info.Name.Should().Be("my-widget"); + result.Value.Info.Author.Should().BeEmpty(); + result.Value.Info.Title.Should().BeEmpty(); + } + [Test] public void LoadPackage_ValidCodeDocument_WithPreview_ReturnsContribution() { WritePackageToml(""" [package] - id = "test.code-preview" - name = "Code Preview" - version = "1.0.0" + name = "test.code-preview" + title = "Code Preview" [contributes] document_editors = ["cpv.document.toml"] @@ -115,9 +151,8 @@ public void LoadPackage_HostNameOverride_ReplacesDefault() { WritePackageToml(""" [package] - id = "test.my-editor" - name = "My Editor" - version = "1.0.0" + name = "test.my-editor" + title = "My Editor" [contributes] document_editors = ["editor.document.toml"] @@ -144,12 +179,11 @@ public void LoadPackage_HostNameOverride_ReplacesDefault() } [Test] - public void LoadPackage_MissingPackageId_ReturnsFailure() + public void LoadPackage_MissingPackageName_ReturnsFailure() { WritePackageToml(""" [package] - name = "No Id" - version = "1.0.0" + title = "No Name" [contributes] document_editors = ["doc.document.toml"] @@ -168,13 +202,34 @@ public void LoadPackage_MissingPackageId_ReturnsFailure() [TestCase("trailing-dot.", Description = "trailing dot rejected")] [TestCase("double..dot", Description = "consecutive dots rejected")] [TestCase(".", Description = "bare dot rejected")] - public void LoadPackage_InvalidIdFormat_ReturnsFailure(string invalidId) + [TestCase("-leading-hyphen", Description = "leading hyphen rejected")] + [TestCase("trailing-hyphen-", Description = "trailing hyphen rejected")] + [TestCase("double--hyphen", Description = "consecutive hyphens rejected")] + public void LoadPackage_InvalidNameFormat_ReturnsFailure(string invalidName) + { + WritePackageToml($""" + [package] + name = "{invalidName}" + title = "Test" + + [contributes] + document_editors = ["doc.document.toml"] + """); + + var result = PackageManifestLoader.LoadPackage(Path.Combine(_tempFolder, "package.toml")); + + result.IsFailure.Should().BeTrue(); + result.FirstErrorMessage.Should().Contain("invalid"); + } + + [Test] + public void LoadPackage_NameOverMaxLength_ReturnsFailure() { + var overLongName = new string('a', PackageConstants.MaxNameLength + 1); WritePackageToml($""" [package] - id = "{invalidId}" - name = "Test" - version = "1.0.0" + name = "{overLongName}" + title = "Test" [contributes] document_editors = ["doc.document.toml"] @@ -192,16 +247,15 @@ public void LoadPackage_InvalidIdFormat_ReturnsFailure(string invalidId) [TestCase("a.b.c.d", Description = "deeply nested")] [TestCase("digits123.allowed", Description = "digits in namespace")] [TestCase("hyphens-are-fine.here", Description = "hyphens in namespace")] - [TestCase("flat-name", Description = "flat global namespace id")] - [TestCase("simple", Description = "single-word flat id")] + [TestCase("flat-name", Description = "flat global namespace name")] + [TestCase("simple", Description = "single-word flat name")] [TestCase("a", Description = "single character")] - public void LoadPackage_ValidIdFormats_Accepted(string validId) + public void LoadPackage_ValidNameFormats_Accepted(string validName) { WritePackageToml($""" [package] - id = "{validId}" - name = "Test" - version = "1.0.0" + name = "{validName}" + title = "Test" [contributes] document_editors = ["doc.document.toml"] @@ -241,9 +295,8 @@ public void LoadPackage_EmptyFileTypes_ReturnsNoContributions() { WritePackageToml(""" [package] - id = "test.empty" - name = "Empty" - version = "1.0.0" + name = "test.empty" + title = "Empty" [contributes] document_editors = ["doc.document.toml"] @@ -268,9 +321,8 @@ public void LoadPackage_FileTypeMissingDisplayName_SkipsDocument() { WritePackageToml(""" [package] - id = "test.no-file-type-display" - name = "NoFileTypeDisplay" - version = "1.0.0" + name = "test.no-file-type-display" + title = "NoFileTypeDisplay" [contributes] document_editors = ["doc.document.toml"] @@ -319,9 +371,8 @@ public void LoadPackage_DefaultPriority_IsDefault() { WritePackageToml(""" [package] - id = "test.basic" - name = "Basic" - version = "1.0.0" + name = "test.basic" + title = "Basic" [contributes] document_editors = ["doc.document.toml"] @@ -349,9 +400,8 @@ public void LoadPackage_WithGeneralPriority_UsesGeneral() { WritePackageToml(""" [package] - id = "test.priority" - name = "Priority" - version = "1.0.0" + name = "test.priority" + title = "Priority" [contributes] document_editors = ["doc.document.toml"] @@ -380,9 +430,8 @@ public void LoadPackage_WithFeatureFlag_PropagatedToInfo() { WritePackageToml(""" [package] - id = "test.flagged" - name = "Flagged" - version = "1.0.0" + name = "test.flagged" + title = "Flagged" feature_flag = "my-feature" [contributes] @@ -412,9 +461,8 @@ public void LoadPackage_WithoutFeatureFlag_ReturnsNull() { WritePackageToml(""" [package] - id = "test.noflag" - name = "NoFlag" - version = "1.0.0" + name = "test.noflag" + title = "NoFlag" [contributes] document_editors = ["doc.document.toml"] @@ -443,9 +491,8 @@ public void LoadPackage_WithTemplates_ReturnsTemplates() { WritePackageToml(""" [package] - id = "test.templated" - name = "Templated" - version = "1.0.0" + name = "test.templated" + title = "Templated" [contributes] document_editors = ["doc.document.toml"] @@ -495,9 +542,8 @@ public void LoadPackage_WithoutTemplates_ReturnsEmptyList() { WritePackageToml(""" [package] - id = "test.notemplates" - name = "NoTemplates" - version = "1.0.0" + name = "test.notemplates" + title = "NoTemplates" [contributes] document_editors = ["doc.document.toml"] @@ -525,9 +571,8 @@ public void LoadPackage_FullDocument_AllFieldsPopulated() { WritePackageToml(""" [package] - id = "test.full-editor" - name = "Full Editor" - version = "2.0.0" + name = "test.full-editor" + title = "Full Editor" feature_flag = "full-pkg" [contributes] @@ -558,7 +603,7 @@ public void LoadPackage_FullDocument_AllFieldsPopulated() result.IsSuccess.Should().BeTrue(); var package = result.Value; - package.Info.Name.Should().Be("Full Editor"); + package.Info.Title.Should().Be("Full Editor"); package.Info.FeatureFlag.Should().Be("full-pkg"); var contribution = package.DocumentEditors[0]; @@ -575,9 +620,8 @@ public void LoadPackage_MultipleDocuments_ReturnsAll() { WritePackageToml(""" [package] - id = "test.multi" - name = "Multi" - version = "1.0.0" + name = "test.multi" + title = "Multi" [contributes] document_editors = ["a.document.toml", "b.document.toml"] @@ -620,9 +664,8 @@ public void LoadPackage_CodeEditorOptions_Parsed() { WritePackageToml(""" [package] - id = "test.code-editor" - name = "CodeEditor" - version = "1.0.0" + name = "test.code-editor" + title = "CodeEditor" [contributes] document_editors = ["doc.document.toml"] @@ -662,9 +705,8 @@ public void LoadPackage_MissingDocumentFile_SkipsDocument() { WritePackageToml(""" [package] - id = "test.missing-doc" - name = "MissingDoc" - version = "1.0.0" + name = "test.missing-doc" + title = "MissingDoc" [contributes] document_editors = ["nonexistent.document.toml"] @@ -681,9 +723,8 @@ public void LoadPackage_MultipleDocuments_SharePackageInfo() { WritePackageToml(""" [package] - id = "test.shared" - name = "Shared" - version = "1.0.0" + name = "test.shared" + title = "Shared" feature_flag = "shared-flag" [contributes] @@ -718,10 +759,10 @@ public void LoadPackage_MultipleDocuments_SharePackageInfo() result.Value.DocumentEditors.Should().HaveCount(2); // Both contributions share the same PackageInfo - result.Value.Info.Name.Should().Be("Shared"); + result.Value.Info.Title.Should().Be("Shared"); result.Value.Info.FeatureFlag.Should().Be("shared-flag"); - result.Value.DocumentEditors[0].Package.Name.Should().Be("Shared"); - result.Value.DocumentEditors[1].Package.Name.Should().Be("Shared"); + result.Value.DocumentEditors[0].Package.Title.Should().Be("Shared"); + result.Value.DocumentEditors[1].Package.Title.Should().Be("Shared"); } [Test] @@ -729,9 +770,8 @@ public void LoadPackage_CustomDocumentWithoutEntryPoint_DefaultsToIndexHtml() { WritePackageToml(""" [package] - id = "test.no-entry" - name = "NoEntry" - version = "1.0.0" + name = "test.no-entry" + title = "NoEntry" [contributes] document_editors = ["doc.document.toml"] @@ -757,16 +797,15 @@ public void LoadPackage_CustomDocumentWithoutEntryPoint_DefaultsToIndexHtml() } [Test] - public void LoadPackage_WithModSection_ParsesRequiresTools() + public void LoadPackage_WithPermissionsSection_ParsesPermittedTools() { WritePackageToml(""" [package] - id = "test.mod-section" - name = "ModSection" - version = "1.0.0" + name = "test.permissions-section" + title = "PermissionsSection" - [mod] - requires_tools = ["app.*", "document.open"] + [permissions] + tools = ["app.*", "document.open"] [contributes] document_editors = ["doc.document.toml"] @@ -774,7 +813,7 @@ public void LoadPackage_WithModSection_ParsesRequiresTools() WriteDocumentToml("doc.document.toml", """ [document] - id = "mod-section-doc" + id = "permissions-section-doc" type = "custom" display_name = "TestEditor" @@ -787,7 +826,7 @@ public void LoadPackage_WithModSection_ParsesRequiresTools() result.IsSuccess.Should().BeTrue(); var info = result.Value.Info; - info.RequiresTools.Should().Equal("app.*", "document.open"); + info.PermittedTools.Should().Equal("app.*", "document.open"); } [Test] @@ -795,9 +834,8 @@ public void LoadPackage_SecretsParameter_PopulatesPackageInfo() { WritePackageToml(""" [package] - id = "test.secrets" - name = "WithSecrets" - version = "1.0.0" + name = "test.secrets" + title = "WithSecrets" [contributes] document_editors = ["doc.document.toml"] @@ -836,9 +874,8 @@ public void LoadPackage_NoSecretsParameter_LeavesSecretsEmpty() { WritePackageToml(""" [package] - id = "test.no-secrets" - name = "NoSecrets" - version = "1.0.0" + name = "test.no-secrets" + title = "NoSecrets" [contributes] document_editors = ["doc.document.toml"] @@ -862,13 +899,12 @@ public void LoadPackage_NoSecretsParameter_LeavesSecretsEmpty() } [Test] - public void LoadPackage_WithoutModSection_DefaultsToEmptyRequirements() + public void LoadPackage_WithoutPermissionsSection_DefaultsToEmpty() { WritePackageToml(""" [package] - id = "test.no-mod" - name = "NoMod" - version = "1.0.0" + name = "test.no-permissions" + title = "NoPermissions" [contributes] document_editors = ["doc.document.toml"] @@ -876,7 +912,7 @@ public void LoadPackage_WithoutModSection_DefaultsToEmptyRequirements() WriteDocumentToml("doc.document.toml", """ [document] - id = "no-mod-doc" + id = "no-permissions-doc" type = "custom" display_name = "TestEditor" @@ -888,20 +924,19 @@ public void LoadPackage_WithoutModSection_DefaultsToEmptyRequirements() var result = PackageManifestLoader.LoadPackage(Path.Combine(_tempFolder, "package.toml")); result.IsSuccess.Should().BeTrue(); - result.Value.Info.RequiresTools.Should().BeEmpty(); + result.Value.Info.PermittedTools.Should().BeEmpty(); } [Test] - public void LoadPackage_ModSectionWithNonStringEntries_SkipsInvalid() + public void LoadPackage_PermissionsSectionWithNonStringEntries_SkipsInvalid() { WritePackageToml(""" [package] - id = "test.mixed" - name = "Mixed" - version = "1.0.0" + name = "test.mixed" + title = "Mixed" - [mod] - requires_tools = ["app.*", 42, "", "file.read"] + [permissions] + tools = ["app.*", 42, "", "file.read"] [contributes] document_editors = ["doc.document.toml"] @@ -921,7 +956,7 @@ public void LoadPackage_ModSectionWithNonStringEntries_SkipsInvalid() var result = PackageManifestLoader.LoadPackage(Path.Combine(_tempFolder, "package.toml")); result.IsSuccess.Should().BeTrue(); - result.Value.Info.RequiresTools.Should().Equal("app.*", "file.read"); + result.Value.Info.PermittedTools.Should().Equal("app.*", "file.read"); } [Test] @@ -929,9 +964,8 @@ public void LoadPackage_ExtensionsFile_ExpandsToEachJsonKey() { WritePackageToml(""" [package] - id = "test.code" - name = "Code" - version = "1.0.0" + name = "test.code" + title = "Code" [contributes] document_editors = ["code.document.toml"] @@ -967,9 +1001,8 @@ public void LoadPackage_ExtensionsFile_MissingFile_DocumentSkipped() { WritePackageToml(""" [package] - id = "test.missing-ext" - name = "Missing" - version = "1.0.0" + name = "test.missing-ext" + title = "Missing" [contributes] document_editors = ["doc.document.toml"] @@ -999,9 +1032,8 @@ public void LoadPackage_ExtensionsFile_CombinedWithExtension_DocumentSkipped() { WritePackageToml(""" [package] - id = "test.conflict" - name = "Conflict" - version = "1.0.0" + name = "test.conflict" + title = "Conflict" [contributes] document_editors = ["doc.document.toml"] diff --git a/Source/Tests/Packages/PackageApiClientTests.cs b/Source/Tests/Packages/PackageApiClientTests.cs new file mode 100644 index 000000000..876cf0f64 --- /dev/null +++ b/Source/Tests/Packages/PackageApiClientTests.cs @@ -0,0 +1,370 @@ +using System.Net; +using System.Text; +using Celbridge.Credentials; +using Celbridge.Packages; + +namespace Celbridge.Tests.Packages; + +[TestFixture] +public class PackageApiClientTests +{ + private const string TestApplicationKey = "kpf_testkey_supersecret"; + + private StubMessageHandler _messageHandler = null!; + private ICredentialService _credentialService = null!; + private PackageApiClient _client = null!; + + [SetUp] + public void Setup() + { + _messageHandler = new StubMessageHandler(); + _credentialService = Substitute.For(); + SetStoredConnection("https://workshop.example.com", TestApplicationKey); + _client = new PackageApiClient(_credentialService, _messageHandler); + } + + [TearDown] + public void TearDown() + { + _client.Dispose(); + } + + [Test] + public async Task ListPackages_SendsApiKeyHeaderAndParsesSummaries() + { + _messageHandler.Responder = _ => JsonResponse(""" + [ + { + "name": "my-widget", + "created_at": "2026-01-10T12:00:00Z", + "latest_version": { "version": 3, "author": "alice", "date": "2026-02-01T09:30:00Z" }, + "versions_count": 3 + }, + { + "name": "empty-package", + "created_at": "2026-01-12T08:00:00Z", + "latest_version": null, + "versions_count": 0 + } + ] + """); + + var result = await _client.ListPackagesAsync(); + + result.IsSuccess.Should().BeTrue(); + + var request = _messageHandler.Requests.Single(); + request.Method.Should().Be(HttpMethod.Get); + request.RequestUri.Should().Be(new Uri("https://workshop.example.com/api/packages/")); + request.Headers.Authorization.Should().NotBeNull(); + request.Headers.Authorization!.Scheme.Should().Be("Api-Key"); + request.Headers.Authorization.Parameter.Should().Be(TestApplicationKey); + + var packages = result.Value; + packages.Should().HaveCount(2); + packages[0].Name.Should().Be("my-widget"); + packages[0].VersionsCount.Should().Be(3); + packages[0].LatestVersion.Should().NotBeNull(); + packages[0].LatestVersion!.Version.Should().Be(3); + packages[0].LatestVersion!.Author.Should().Be("alice"); + packages[1].Name.Should().Be("empty-package"); + packages[1].LatestVersion.Should().BeNull(); + } + + [Test] + public async Task GetPackage_ParsesVersionsAndAliases() + { + _messageHandler.Responder = _ => JsonResponse(""" + { + "name": "my-widget", + "created_at": "2026-01-10T12:00:00Z", + "versions": [ + { "version": 1, "author": "alice", "date": "2026-01-10T12:00:00Z", "tombstoned": true, "content_hash": "aaa111", "summary": "Initial release" }, + { "version": 2, "author": "bob", "date": "2026-02-01T09:30:00Z", "tombstoned": false, "content_hash": "bbb222", "summary": "Bug fixes" } + ], + "aliases": [ + { "alias": "latest", "version": 2 }, + { "alias": "stable", "version": 2 } + ] + } + """); + + var result = await _client.GetPackageAsync("my-widget"); + + result.IsSuccess.Should().BeTrue(); + + var request = _messageHandler.Requests.Single(); + request.RequestUri.Should().Be(new Uri("https://workshop.example.com/api/packages/my-widget/")); + + var details = result.Value; + details.Name.Should().Be("my-widget"); + details.Versions.Should().HaveCount(2); + details.Versions[0].Version.Should().Be(1); + details.Versions[0].Tombstoned.Should().BeTrue(); + details.Versions[0].Summary.Should().Be("Initial release"); + details.Versions[1].Author.Should().Be("bob"); + details.Versions[1].ContentHash.Should().Be("bbb222"); + details.Aliases.Should().HaveCount(2); + details.Aliases[0].Alias.Should().Be("latest"); + details.Aliases[0].Version.Should().Be(2); + } + + [Test] + public async Task GetPackage_UnknownPackage_Fails() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.NotFound); + + var result = await _client.GetPackageAsync("missing-package"); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("missing-package"); + result.MessageChain.Should().Contain("not found"); + } + + [Test] + public async Task PublishVersion_SendsMultipartAndParsesReceipt() + { + _messageHandler.Responder = _ => JsonResponse(""" + { + "package": "my-widget", + "version": 4, + "author": "alice", + "download_url": "https://workshop.example.com/api/packages/my-widget/versions/4/download/", + "content_hash": "ccc333" + } + """, HttpStatusCode.Created); + + var zipData = new byte[] { 0x50, 0x4B, 0x03, 0x04 }; + var result = await _client.PublishVersionAsync("my-widget", zipData, "Fixed the frobnicator"); + + result.IsSuccess.Should().BeTrue(); + + var request = _messageHandler.Requests.Single(); + request.Method.Should().Be(HttpMethod.Post); + request.RequestUri.Should().Be(new Uri("https://workshop.example.com/api/packages/my-widget/versions/")); + + // Quote characters are stripped because the framework only quotes + // multipart names and file names when they are not valid tokens. + var requestBody = _messageHandler.RequestBodies.Single().Replace("\"", ""); + requestBody.Should().Contain("name=file; filename=my-widget.zip"); + requestBody.Should().Contain("name=summary"); + requestBody.Should().Contain("Fixed the frobnicator"); + + var receipt = result.Value; + receipt.PackageName.Should().Be("my-widget"); + receipt.Version.Should().Be(4); + receipt.Author.Should().Be("alice"); + receipt.ContentHash.Should().Be("ccc333"); + } + + [Test] + public async Task PublishVersion_WithoutSummary_OmitsSummaryPart() + { + _messageHandler.Responder = _ => JsonResponse(""" + { "package": "my-widget", "version": 1, "author": "alice", "download_url": "", "content_hash": "ddd444" } + """, HttpStatusCode.Created); + + var result = await _client.PublishVersionAsync("my-widget", [0x50]); + + result.IsSuccess.Should().BeTrue(); + _messageHandler.RequestBodies.Single().Replace("\"", "").Should().NotContain("name=summary"); + } + + [Test] + public async Task DownloadVersion_ReturnsZipBytes() + { + var zipData = new byte[] { 0x50, 0x4B, 0x03, 0x04, 0x42 }; + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new ByteArrayContent(zipData) + }; + + var result = await _client.DownloadVersionAsync("my-widget", 2); + + result.IsSuccess.Should().BeTrue(); + result.Value.Should().Equal(zipData); + + var request = _messageHandler.Requests.Single(); + request.RequestUri.Should().Be(new Uri("https://workshop.example.com/api/packages/my-widget/versions/2/download/")); + } + + [Test] + public async Task DownloadVersion_TombstonedVersion_Fails() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.Gone); + + var result = await _client.DownloadVersionAsync("my-widget", 1); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("tombstoned"); + } + + [Test] + public async Task DownloadLatest_NoLiveVersion_Fails() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.NotFound); + + var result = await _client.DownloadLatestAsync("my-widget"); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("no live version"); + + var request = _messageHandler.Requests.Single(); + request.RequestUri.Should().Be(new Uri("https://workshop.example.com/api/packages/my-widget/latest/")); + } + + [Test] + public async Task SetAlias_SendsPutWithVersionBody() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.OK); + + var result = await _client.SetAliasAsync("my-widget", "stable", 2); + + result.IsSuccess.Should().BeTrue(); + + var request = _messageHandler.Requests.Single(); + request.Method.Should().Be(HttpMethod.Put); + request.RequestUri.Should().Be(new Uri("https://workshop.example.com/api/packages/my-widget/aliases/stable/")); + _messageHandler.RequestBodies.Single().Should().Be("""{"version":2}"""); + } + + [Test] + public async Task RemoveAlias_SendsDelete() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.NoContent); + + var result = await _client.RemoveAliasAsync("my-widget", "stable"); + + result.IsSuccess.Should().BeTrue(); + + var request = _messageHandler.Requests.Single(); + request.Method.Should().Be(HttpMethod.Delete); + request.RequestUri.Should().Be(new Uri("https://workshop.example.com/api/packages/my-widget/aliases/stable/")); + } + + [Test] + public async Task GetVersionHistory_ReturnsPlainText() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent("Version 2 published by bob\nVersion 1 published by alice", Encoding.UTF8, "text/plain") + }; + + var result = await _client.GetVersionHistoryAsync("my-widget", 2); + + result.IsSuccess.Should().BeTrue(); + result.Value.Should().Contain("Version 2 published by bob"); + + var request = _messageHandler.Requests.Single(); + request.RequestUri.Should().Be(new Uri("https://workshop.example.com/api/packages/my-widget/versions/2/history/")); + } + + [Test] + public async Task Request_Unauthorized_FailsWithoutEchoingKey() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.Unauthorized); + + var result = await _client.ListPackagesAsync(); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("Application Key"); + result.MessageChain.Should().Contain("Settings"); + result.MessageChain.Should().NotContain(TestApplicationKey); + result.DiagnosticReport.Should().NotContain(TestApplicationKey); + } + + [Test] + public async Task HttpUrl_NonLoopbackHost_RejectedBeforeSending() + { + SetStoredConnection("http://workshop.example.com", TestApplicationKey); + + var result = await _client.ListPackagesAsync(); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("HTTPS"); + result.MessageChain.Should().NotContain(TestApplicationKey); + _messageHandler.Requests.Should().BeEmpty(); + } + + [Test] + public async Task HttpUrl_Localhost_Allowed() + { + SetStoredConnection("http://localhost:8000", TestApplicationKey); + _messageHandler.Responder = _ => JsonResponse("[]"); + + var result = await _client.ListPackagesAsync(); + + result.IsSuccess.Should().BeTrue(); + _messageHandler.Requests.Single().RequestUri + .Should().Be(new Uri("http://localhost:8000/api/packages/")); + } + + [Test] + public async Task BaseUrlWithPathSegment_KeepsThePathWhenBuildingEndpoints() + { + SetStoredConnection("https://example.com/workshop", TestApplicationKey); + _messageHandler.Responder = _ => JsonResponse("[]"); + + var result = await _client.ListPackagesAsync(); + + result.IsSuccess.Should().BeTrue(); + _messageHandler.Requests.Single().RequestUri + .Should().Be(new Uri("https://example.com/workshop/api/packages/")); + } + + [Test] + public async Task NoStoredConnection_FailsWithCredentialError() + { + _credentialService.GetWorkshopConnectionAsync() + .Returns(Result.Fail("No Workshop connection is stored")); + + var result = await _client.ListPackagesAsync(); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("No Workshop connection is stored"); + _messageHandler.Requests.Should().BeEmpty(); + } + + private void SetStoredConnection(string workshopUrl, string applicationKey) + { + var connection = new WorkshopConnection(workshopUrl, applicationKey); + _credentialService.GetWorkshopConnectionAsync() + .Returns(Result.Ok(connection)); + } + + private static HttpResponseMessage JsonResponse(string json, HttpStatusCode statusCode = HttpStatusCode.OK) + { + return new HttpResponseMessage(statusCode) + { + Content = new StringContent(json, Encoding.UTF8, "application/json") + }; + } + + // Records every request (and its body, captured before the request is + // disposed) and serves the canned response from Responder. + private sealed class StubMessageHandler : HttpMessageHandler + { + public List Requests { get; } = new(); + public List RequestBodies { get; } = new(); + + public Func Responder { get; set; } = + _ => new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(string.Empty) + }; + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + Requests.Add(request); + + var body = string.Empty; + if (request.Content is not null) + { + body = await request.Content.ReadAsStringAsync(cancellationToken); + } + RequestBodies.Add(body); + + return Responder(request); + } + } +} diff --git a/Source/Tests/Packages/PackageNameTests.cs b/Source/Tests/Packages/PackageNameTests.cs new file mode 100644 index 000000000..cc23496a4 --- /dev/null +++ b/Source/Tests/Packages/PackageNameTests.cs @@ -0,0 +1,91 @@ +using Celbridge.Packages; + +namespace Celbridge.Tests.Packages; + +[TestFixture] +public class PackageNameTests +{ + [TestCase("a", Description = "single character")] + [TestCase("simple", Description = "single word")] + [TestCase("my-widget", Description = "hyphen separator")] + [TestCase("my-cool-package", Description = "multiple hyphen separators")] + [TestCase("package123", Description = "trailing digits")] + [TestCase("123package", Description = "leading digits")] + [TestCase("a1b2c3", Description = "mixed letters and digits")] + public void IsValid_WellFormedNames_Accepted(string name) + { + PackageName.IsValid(name).Should().BeTrue(); + } + + [TestCase("", Description = "empty")] + [TestCase("My-Widget", Description = "uppercase rejected")] + [TestCase("-widget", Description = "leading hyphen rejected")] + [TestCase("widget-", Description = "trailing hyphen rejected")] + [TestCase("my--widget", Description = "consecutive hyphens rejected")] + [TestCase("my widget", Description = "whitespace rejected")] + [TestCase("my_widget", Description = "underscore rejected")] + [TestCase("my.widget", Description = "dot rejected")] + [TestCase("my/widget", Description = "slash rejected")] + public void IsValid_MalformedNames_Rejected(string name) + { + PackageName.IsValid(name).Should().BeFalse(); + } + + [Test] + public void IsValid_HomographLookalike_Rejected() + { + // Cyrillic 'о' (U+043E) masquerading as ASCII 'o'. + PackageName.IsValid("my-widgоt").Should().BeFalse(); + } + + [Test] + public void IsValid_NameAtMaxLength_Accepted() + { + var maxName = new string('a', PackageConstants.MaxNameLength); + + PackageName.IsValid(maxName).Should().BeTrue(); + } + + [Test] + public void IsValid_NameOverMaxLength_Rejected() + { + var overLongName = new string('a', PackageConstants.MaxNameLength + 1); + + PackageName.IsValid(overLongName).Should().BeFalse(); + } + + [TestCase("celbridge.notes", Description = "reserved bundled namespace")] + [TestCase("a.b", Description = "minimal dotted")] + [TestCase("a.b.c.d", Description = "deeply nested")] + [TestCase("digits123.allowed", Description = "digits in namespace")] + [TestCase("hyphens-are-fine.here", Description = "hyphens in segments")] + [TestCase("flat-name", Description = "flat names are also valid bundled names")] + public void IsValidBundledName_WellFormedNames_Accepted(string name) + { + PackageName.IsValidBundledName(name).Should().BeTrue(); + } + + [TestCase("", Description = "empty")] + [TestCase(".", Description = "bare dot rejected")] + [TestCase(".leading-dot", Description = "leading dot rejected")] + [TestCase("trailing-dot.", Description = "trailing dot rejected")] + [TestCase("double..dot", Description = "consecutive dots rejected")] + [TestCase("Celbridge.Notes", Description = "uppercase rejected")] + [TestCase("has_underscore.notes", Description = "underscore rejected")] + [TestCase("has spaces.notes", Description = "whitespace rejected")] + [TestCase("celbridge.-notes", Description = "segment with leading hyphen rejected")] + [TestCase("celbridge.no--tes", Description = "segment with consecutive hyphens rejected")] + public void IsValidBundledName_MalformedNames_Rejected(string name) + { + PackageName.IsValidBundledName(name).Should().BeFalse(); + } + + [Test] + public void IsValidBundledName_NameOverMaxLength_Rejected() + { + var segment = new string('a', PackageConstants.MaxNameLength); + var overLongName = $"celbridge.{segment}"; + + PackageName.IsValidBundledName(overLongName).Should().BeFalse(); + } +} diff --git a/Source/Tests/Packages/PackageRegistryTests.cs b/Source/Tests/Packages/PackageRegistryTests.cs index 542c73a71..f830275b8 100644 --- a/Source/Tests/Packages/PackageRegistryTests.cs +++ b/Source/Tests/Packages/PackageRegistryTests.cs @@ -3,6 +3,7 @@ using Celbridge.Messaging; using Celbridge.Modules; using Celbridge.Packages; +using Celbridge.Projects; using Celbridge.Resources; using Celbridge.Resources.Services; using Celbridge.Settings; @@ -19,6 +20,7 @@ public class PackageServiceTests private PackageService _service = null!; private IModuleService _moduleService = null!; private IMessengerService _messengerService = null!; + private IProjectLoadReporter _loadReporter = null!; private IResourceRegistry _resourceRegistry = null!; [SetUp] @@ -88,7 +90,8 @@ public void Setup() var localizationService = new PackageLocalizationService(localizationLogger, workspaceWrapper, fileSystem); var registry = new PackageRegistry(logger, _moduleService, featureFlags, localizationService, workspaceWrapper, fileSystem); - _service = new PackageService(_messengerService, registry); + _loadReporter = Substitute.For(); + _service = new PackageService(_messengerService, _loadReporter, registry); } [TearDown] @@ -127,7 +130,7 @@ public async Task RegisterPackages_ValidManifest_ReturnsManifest() var contributions = _service.GetAllDocumentEditors(); contributions.Should().HaveCount(1); - contributions[0].Package.Name.Should().Be("My Editor"); + contributions[0].Package.Title.Should().Be("My Editor"); contributions[0].Should().BeOfType(); } @@ -141,7 +144,7 @@ public async Task RegisterPackages_MultiplePackages_ReturnsAll() var contributions = _service.GetAllDocumentEditors(); contributions.Should().HaveCount(2); - var names = contributions.Select(m => m.Package.Name).ToList(); + var names = contributions.Select(m => m.Package.Title).ToList(); names.Should().Contain("Editor A"); names.Should().Contain("Editor B"); } @@ -160,7 +163,7 @@ public async Task RegisterPackages_InvalidManifest_SkipsAndContinues() var contributions = _service.GetAllDocumentEditors(); contributions.Should().HaveCount(1); - contributions[0].Package.Name.Should().Be("Good"); + contributions[0].Package.Title.Should().Be("Good"); } [Test] @@ -176,7 +179,7 @@ public async Task RegisterPackages_FolderWithoutManifest_IsSkipped() var contributions = _service.GetAllDocumentEditors(); contributions.Should().HaveCount(1); - contributions[0].Package.Name.Should().Be("Found"); + contributions[0].Package.Title.Should().Be("Found"); } [Test] @@ -190,7 +193,7 @@ public async Task RegisterPackages_IncludesModulePackages() var contributions = _service.GetAllDocumentEditors(); contributions.Should().HaveCount(1); - contributions[0].Package.Name.Should().Be("Bundled"); + contributions[0].Package.Title.Should().Be("Bundled"); } [Test] @@ -205,15 +208,15 @@ public async Task RegisterPackages_CombinesProjectAndBundled() var contributions = _service.GetAllDocumentEditors(); contributions.Should().HaveCount(2); - var names = contributions.Select(m => m.Package.Name).ToList(); + var names = contributions.Select(m => m.Package.Title).ToList(); names.Should().Contain("Project"); names.Should().Contain("Bundled"); } [Test] - public async Task RegisterPackages_ProjectPackageWithReservedIdPrefix_Skipped() + public async Task RegisterPackages_ProjectPackageWithReservedNamePrefix_Skipped() { - // Project packages may not claim an id under the reserved "celbridge." namespace. + // Project packages may not claim a name under the reserved "celbridge." namespace. CreateProjectPackage("impostor", "celbridge.notes", "Impostor Notes", "custom", ".imp"); CreateProjectPackage("legit", "legit", "Legit", "custom", ".legit"); @@ -221,13 +224,13 @@ public async Task RegisterPackages_ProjectPackageWithReservedIdPrefix_Skipped() var contributions = _service.GetAllDocumentEditors(); contributions.Should().HaveCount(1); - contributions[0].Package.Id.Should().Be("legit"); + contributions[0].Package.Name.Should().Be("legit"); } [Test] - public async Task RegisterPackages_ProjectPackageWithMixedCaseId_RejectedByFormatValidation() + public async Task RegisterPackages_ProjectPackageWithMixedCaseName_RejectedByFormatValidation() { - // Package ids are lowercase-only. A mixed-case id fails manifest validation + // Package names are lowercase-only. A mixed-case name fails manifest validation // before the reserved-prefix check runs, so "Celbridge.Something" cannot be // used as a workaround for the prefix block. CreateProjectPackage("mixed-case", "Celbridge.Something", "Mixed Case", "custom", ".mc"); @@ -238,11 +241,11 @@ public async Task RegisterPackages_ProjectPackageWithMixedCaseId_RejectedByForma } [Test] - public async Task RegisterPackages_ProjectPackageWithDottedId_Skipped() + public async Task RegisterPackages_ProjectPackageWithDottedName_Skipped() { // Until a namespace registry exists, project packages cannot claim a - // dotted id because there is no way to validate namespace ownership. - // Only flat global-namespace ids are permitted for project packages. + // dotted name because there is no way to validate namespace ownership. + // Only flat global-namespace names are permitted for project packages. CreateProjectPackage("dotted", "acme.tool", "Dotted", "custom", ".dot"); CreateProjectPackage("flat", "legit-tool", "Flat", "custom", ".flat"); @@ -250,7 +253,7 @@ public async Task RegisterPackages_ProjectPackageWithDottedId_Skipped() var contributions = _service.GetAllDocumentEditors(); contributions.Should().HaveCount(1); - contributions[0].Package.Id.Should().Be("legit-tool"); + contributions[0].Package.Name.Should().Be("legit-tool"); } [TestCase(".cel")] @@ -267,7 +270,7 @@ public async Task RegisterPackages_ProjectPackageWithCelExtension_Skipped(string var contributions = _service.GetAllDocumentEditors(); contributions.Should().HaveCount(1); - contributions[0].Package.Id.Should().Be("legit"); + contributions[0].Package.Name.Should().Be("legit"); } [Test] @@ -282,7 +285,7 @@ public async Task RegisterPackages_PackageWithCelInMiddleSegment_Accepted() var contributions = _service.GetAllDocumentEditors(); contributions.Should().HaveCount(1); - contributions[0].Package.Id.Should().Be("mid-cel"); + contributions[0].Package.Name.Should().Be("mid-cel"); } [Test] @@ -300,7 +303,7 @@ public async Task RegisterPackages_BundledPackageWithCelExtension_Skipped() } [Test] - public async Task RegisterPackages_BundledPackageWithReservedIdPrefix_Allowed() + public async Task RegisterPackages_BundledPackageWithReservedNamePrefix_Allowed() { // Bundled packages are the intended owners of the "celbridge." namespace. var bundledDir = CreateBundledPackage("bundled-official", "celbridge.notes", "Official Notes", "custom", ".note"); @@ -311,13 +314,13 @@ public async Task RegisterPackages_BundledPackageWithReservedIdPrefix_Allowed() var contributions = _service.GetAllDocumentEditors(); contributions.Should().HaveCount(1); - contributions[0].Package.Id.Should().Be("celbridge.notes"); + contributions[0].Package.Name.Should().Be("celbridge.notes"); } [Test] - public async Task RegisterPackages_TwoBundledPackagesSameId_BothSkipped() + public async Task RegisterPackages_TwoBundledPackagesSameName_BothSkipped() { - // Two bundled packages with the same id is a first-party build bug. + // Two bundled packages with the same name is a first-party build bug. // Both are skipped rather than silently picking a winner. var dirA = CreateBundledPackage("bundled-a", "celbridge.conflict", "Conflict A", "custom", ".a"); var dirB = CreateBundledPackage("bundled-b", "celbridge.conflict", "Conflict B", "custom", ".b"); @@ -336,7 +339,7 @@ public async Task RegisterPackages_TwoBundledPackagesSameId_BothSkipped() [Test] public async Task RegisterPackages_ProjectPackageConflictsWithBundled_ProjectSkipped() { - // Bundled wins over project when ids collide. Both use flat ids here + // Bundled wins over project when names collide. Both use flat names here // so the collision check is what rejects the project package, not the // reserved-prefix or unregistered-namespace rules. var bundledDir = CreateBundledPackage("bundled", "shared-id", "Bundled", "custom", ".bnd"); @@ -348,13 +351,13 @@ public async Task RegisterPackages_ProjectPackageConflictsWithBundled_ProjectSki var contributions = _service.GetAllDocumentEditors(); contributions.Should().HaveCount(1); - contributions[0].Package.Name.Should().Be("Bundled"); + contributions[0].Package.Title.Should().Be("Bundled"); } [Test] - public async Task RegisterPackages_TwoProjectPackagesSameId_BothSkipped() + public async Task RegisterPackages_TwoProjectPackagesSameName_BothSkipped() { - // Two project packages with the same id cannot be distinguished so + // Two project packages with the same name cannot be distinguished so // both are skipped. A non-colliding sibling continues to load. CreateProjectPackage("dup-a", "dup-tool", "Dup A", "custom", ".a"); CreateProjectPackage("dup-b", "dup-tool", "Dup B", "custom", ".b"); @@ -364,7 +367,7 @@ public async Task RegisterPackages_TwoProjectPackagesSameId_BothSkipped() var contributions = _service.GetAllDocumentEditors(); contributions.Should().HaveCount(1); - contributions[0].Package.Id.Should().Be("other-tool"); + contributions[0].Package.Name.Should().Be("other-tool"); } [Test] @@ -378,6 +381,26 @@ public async Task RegisterPackages_LoadFailures_SendPackageLoadErrorMessage() _messengerService.Received(1).Send(Arg.Is(m => m.ErrorType == ConsoleErrorType.PackageLoadError)); } + [Test] + public async Task RegisterPackages_RecordsDiscoveryInProjectLoadReport() + { + // The error banner points users at the project load report, so the + // discovery outcome must be recorded and flushed during registration. + CreateProjectPackage("good", "good", "Good", "custom", ".good"); + var badDir = Path.Combine(_tempProjectFolder, "packages", "bad"); + Directory.CreateDirectory(badDir); + File.WriteAllText(Path.Combine(badDir, "package.toml"), "{ invalid toml }"); + + await _service.RegisterPackagesAsync(_tempProjectFolder); + + _loadReporter.Received(1).RecordPackageReport(Arg.Is(r => + r.ProjectPackageCount == 1 && + r.Failures.Count == 1 && + r.Failures[0].Reason == PackageLoadFailureReason.InvalidManifest && + !string.IsNullOrEmpty(r.Failures[0].Detail))); + await _loadReporter.Received(1).FlushAsync(); + } + [Test] public async Task RegisterPackages_NoFailures_DoesNotSendPackageLoadErrorMessage() { @@ -459,7 +482,7 @@ public async Task GetContributingPackage_KnownEditorId_ReturnsThePackage() await _service.RegisterPackagesAsync(_tempProjectFolder); - // CustomDocumentViewFactory builds editor IDs as "{packageId}.{contributionId}". + // CustomDocumentViewFactory builds editor IDs as "{packageName}.{contributionId}". // The contributionId comes from the [document] table key in package.toml, // which CreateBundledPackage sets to the docType argument. var editorId = new DocumentEditorId("celbridge.notes.custom"); @@ -467,7 +490,7 @@ public async Task GetContributingPackage_KnownEditorId_ReturnsThePackage() var package = _service.GetContributingPackage(editorId); package.Should().NotBeNull(); - package!.Info.Id.Should().Be("celbridge.notes"); + package!.Info.Name.Should().Be("celbridge.notes"); } [Test] @@ -482,11 +505,11 @@ public async Task GetContributingPackage_UnknownEditorId_ReturnsNull() } [Test] - public async Task GetContributingPackage_DistinguishesPackagesWithDottedIdPrefixes() + public async Task GetContributingPackage_DistinguishesPackagesWithDottedNamePrefixes() { // A naive split-on-first-dot would mismatch "celbridge.notes.custom" against - // a package whose id is just "celbridge". The lookup must match the longest - // package-id prefix, not split heuristically. + // a package whose name is just "celbridge". The lookup must match the longest + // package-name prefix, not split heuristically. var notesDir = CreateBundledPackage("notes-pkg", "celbridge.notes", "Notes", "custom", ".note"); _moduleService.GetBundledPackages().Returns(new List { new() { Folder = notesDir } }); @@ -495,38 +518,37 @@ public async Task GetContributingPackage_DistinguishesPackagesWithDottedIdPrefix var package = _service.GetContributingPackage(new DocumentEditorId("celbridge.notes.custom")); package.Should().NotBeNull(); - package!.Info.Id.Should().Be("celbridge.notes"); + package!.Info.Name.Should().Be("celbridge.notes"); } /// /// Creates a package in the project's packages/ folder with a standard structure. /// - private string CreateProjectPackage(string dirName, string packageId, string packageName, string docType, string fileExt) + private string CreateProjectPackage(string dirName, string packageName, string packageTitle, string docType, string fileExt) { var packageDir = Path.Combine(_tempProjectFolder, "packages", dirName); Directory.CreateDirectory(packageDir); - WritePackageFiles(packageDir, packageId, packageName, docType, fileExt); + WritePackageFiles(packageDir, packageName, packageTitle, docType, fileExt); return packageDir; } /// /// Creates a bundled package in a standalone folder. /// - private string CreateBundledPackage(string dirName, string packageId, string packageName, string docType, string fileExt) + private string CreateBundledPackage(string dirName, string packageName, string packageTitle, string docType, string fileExt) { var packageDir = Path.Combine(_tempProjectFolder, dirName); Directory.CreateDirectory(packageDir); - WritePackageFiles(packageDir, packageId, packageName, docType, fileExt); + WritePackageFiles(packageDir, packageName, packageTitle, docType, fileExt); return packageDir; } - private static void WritePackageFiles(string packageDir, string packageId, string packageName, string docType, string fileExt) + private static void WritePackageFiles(string packageDir, string packageName, string packageTitle, string docType, string fileExt) { File.WriteAllText(Path.Combine(packageDir, "package.toml"), $""" [package] - id = "{packageId}" name = "{packageName}" - version = "1.0.0" + title = "{packageTitle}" [contributes] document_editors = ["editor.document.toml"] @@ -534,7 +556,7 @@ private static void WritePackageFiles(string packageDir, string packageId, strin File.WriteAllText(Path.Combine(packageDir, "editor.document.toml"), $""" [document] - id = "{packageId}-doc" + id = "{packageName}-doc" type = "{docType}" display_name = "TestEditor" diff --git a/Source/Tests/Projects/ProjectLoadReporterTests.cs b/Source/Tests/Projects/ProjectLoadReporterTests.cs index c7cd573d1..f6e404eec 100644 --- a/Source/Tests/Projects/ProjectLoadReporterTests.cs +++ b/Source/Tests/Projects/ProjectLoadReporterTests.cs @@ -1,4 +1,5 @@ using Celbridge.FileSystem.Services; +using Celbridge.Packages; using Celbridge.Projects; using Celbridge.Projects.Services; using Celbridge.Resources; @@ -204,6 +205,74 @@ public async Task FlushAsync_CleanCheck_ReportsNoFindings() content.Should().Contain("No findings"); } + [Test] + public async Task FlushAsync_AfterRecordPackageReport_IncludesPackagesSection() + { + // Mirrors the runtime flow: PackageService records the discovery + // outcome during workspace load, after the load section is written. + _reporter.BeginLoad(_projectFilePath); + _reporter.RecordLoadOutcome(loadSucceeded: true, loadResult: Result.Ok()); + + var report = new PackageDiscoveryReport + { + BundledPackageCount = 5, + ProjectPackageCount = 1, + Failures = new[] + { + new PackageLoadFailure + { + Folder = @"C:\projects\demo\packages\excel-art", + PackageName = null, + Reason = PackageLoadFailureReason.InvalidManifest, + Detail = "Package has invalid 'name' value 'Excel Art'" + }, + new PackageLoadFailure + { + Folder = @"C:\projects\demo\packages\impostor", + PackageName = "celbridge.notes", + Reason = PackageLoadFailureReason.ReservedNamePrefix + } + } + }; + _reporter.RecordPackageReport(report); + + var reportPath = await _reporter.FlushAsync(); + + reportPath.Should().NotBeNull(); + var content = await File.ReadAllTextAsync(reportPath!); + + content.Should().Contain("## Packages"); + content.Should().Contain("- Bundled packages loaded: 5"); + content.Should().Contain("- Project packages loaded: 1"); + content.Should().Contain("### Load failures (2)"); + content.Should().Contain(@"- `C:\projects\demo\packages\excel-art`: `InvalidManifest`"); + content.Should().Contain("Package has invalid 'name' value 'Excel Art'"); + content.Should().Contain(@"- `celbridge.notes` in `C:\projects\demo\packages\impostor`: `ReservedNamePrefix`"); + } + + [Test] + public async Task FlushAsync_CleanPackageDiscovery_ReportsNoLoadFailures() + { + _reporter.BeginLoad(_projectFilePath); + _reporter.RecordLoadOutcome(loadSucceeded: true, loadResult: Result.Ok()); + + var report = new PackageDiscoveryReport + { + BundledPackageCount = 5, + ProjectPackageCount = 2, + Failures = Array.Empty() + }; + _reporter.RecordPackageReport(report); + + var reportPath = await _reporter.FlushAsync(); + + reportPath.Should().NotBeNull(); + var content = await File.ReadAllTextAsync(reportPath!); + content.Should().Contain("## Packages"); + content.Should().Contain("No load failures"); + content.Should().NotContain("### Load failures"); + } + [Test] public async Task BeginLoad_ClearsPriorCheckSection() { @@ -216,11 +285,17 @@ public async Task BeginLoad_ClearsPriorCheckSection() BrokenReferences: Array.Empty(), OrphanCelFiles: new[] { new ResourceKey("stale.png.cel") }, BrokenCelFiles: Array.Empty())); + _reporter.RecordPackageReport(new PackageDiscoveryReport + { + BundledPackageCount = 5, + ProjectPackageCount = 0, + Failures = Array.Empty() + }); _reporter.RecordLoadOutcome(loadSucceeded: true, loadResult: Result.Ok()); await _reporter.FlushAsync(); // Second load picks up where the first left off — the prior check - // findings should not bleed through. + // and package findings should not bleed through. _reporter.BeginLoad(_projectFilePath); _reporter.RecordMigrationResult( MigrationResult.WithVersions(MigrationStatus.Complete, Result.Ok(), "1.0.0", "1.0.0"), @@ -234,6 +309,7 @@ public async Task BeginLoad_ClearsPriorCheckSection() var content = await File.ReadAllTextAsync(reportPath!); content.Should().NotContain("## Consistency check"); content.Should().NotContain("stale.png.cel"); + content.Should().NotContain("## Packages"); } [Test] diff --git a/Source/Tests/WebHost/WebViewServiceSupportTests.cs b/Source/Tests/WebHost/WebViewServiceSupportTests.cs index ff809a298..44dba8d26 100644 --- a/Source/Tests/WebHost/WebViewServiceSupportTests.cs +++ b/Source/Tests/WebHost/WebViewServiceSupportTests.cs @@ -56,7 +56,7 @@ public void GetWebViewToolSupport_ContributionEditorWithDevToolsBlocked_NamesThe { Info = new PackageInfo { - Id = "celbridge.spreadsheet", + Name = "celbridge.spreadsheet", DevToolsBlocked = true } }; @@ -95,7 +95,7 @@ public void GetWebViewToolSupport_OpenInOtherwiseSupportedEditor_ReportsSupporte { Info = new PackageInfo { - Id = "celbridge.notes", + Name = "celbridge.notes", DevToolsBlocked = false } }; diff --git a/Source/Workspace/Celbridge.Documents/Services/CustomDocumentViewFactory.cs b/Source/Workspace/Celbridge.Documents/Services/CustomDocumentViewFactory.cs index 0f6ed8d3c..824efce11 100644 --- a/Source/Workspace/Celbridge.Documents/Services/CustomDocumentViewFactory.cs +++ b/Source/Workspace/Celbridge.Documents/Services/CustomDocumentViewFactory.cs @@ -15,7 +15,7 @@ public class CustomDocumentViewFactory : DocumentEditorFactoryBase private readonly IFeatureFlags _featureFlags; private readonly string _resolvedDisplayName; - public override DocumentEditorId EditorId => new($"{_contribution.Package.Id}.{_contribution.Id}"); + public override DocumentEditorId EditorId => new($"{_contribution.Package.Name}.{_contribution.Id}"); public override string DisplayName => _resolvedDisplayName; diff --git a/Source/Workspace/Celbridge.Documents/Views/ContributionDocumentView.xaml.cs b/Source/Workspace/Celbridge.Documents/Views/ContributionDocumentView.xaml.cs index 9e53a583f..b96c33fa2 100644 --- a/Source/Workspace/Celbridge.Documents/Views/ContributionDocumentView.xaml.cs +++ b/Source/Workspace/Celbridge.Documents/Views/ContributionDocumentView.xaml.cs @@ -303,7 +303,7 @@ private async Task InitContributionViewAsync() var mcpToolBridge = _serviceProvider.GetService(); if (mcpToolBridge is not null) { - _toolsHandler = new ContributionToolsHandler(mcpToolBridge, Contribution.Package.RequiresTools); + _toolsHandler = new ContributionToolsHandler(mcpToolBridge, Contribution.Package.PermittedTools); Host.AddLocalRpcTarget(_toolsHandler); } @@ -326,7 +326,7 @@ private async Task InitContributionViewAsync() { _logger.LogError(ex, $"Failed to initialize contribution view: {Contribution.Package.Name}"); TeardownWebViewState(); - var failure = Result.Fail($"Failed to initialize contribution view: {Contribution.Package.Id}") + var failure = Result.Fail($"Failed to initialize contribution view: {Contribution.Package.Name}") .WithException(ex); _initTcs!.TrySetResult(failure); } @@ -809,7 +809,7 @@ private async Task InjectCelbridgeContextAsync( PackageInfo package, IReadOnlyDictionary options) { - var allowedTools = package.RequiresTools; + var permittedTools = package.PermittedTools; var secrets = package.Secrets; // An empty secret value almost certainly indicates a missing private license @@ -820,13 +820,13 @@ private async Task InjectCelbridgeContextAsync( if (string.IsNullOrEmpty(pair.Value)) { _logger.LogWarning( - "Secret '{SecretName}' for package '{PackageId}' is empty; the editor will likely fail to activate.", - pair.Key, package.Id); + "Secret '{SecretName}' for package '{PackageName}' is empty; the editor will likely fail to activate.", + pair.Key, package.Name); } } var contextJson = JsonSerializer.Serialize( - new CelbridgeContext(allowedTools, secrets, options), + new CelbridgeContext(permittedTools, secrets, options), ContextSerializerOptions); var coreWebView2 = WebView?.CoreWebView2; @@ -846,7 +846,7 @@ private async Task InjectCelbridgeContextAsync( }; private sealed record CelbridgeContext( - IReadOnlyList AllowedTools, + IReadOnlyList PermittedTools, IReadOnlyDictionary Secrets, IReadOnlyDictionary Options); } diff --git a/Source/Workspace/Celbridge.Packages/Services/PackageApiClient.cs b/Source/Workspace/Celbridge.Packages/Services/PackageApiClient.cs index 48d909e32..355556dc0 100644 --- a/Source/Workspace/Celbridge.Packages/Services/PackageApiClient.cs +++ b/Source/Workspace/Celbridge.Packages/Services/PackageApiClient.cs @@ -3,235 +3,435 @@ using System.Text; using System.Text.Json; using System.Text.Json.Serialization; +using Celbridge.Credentials; namespace Celbridge.Packages; /// -/// Communicates with the Django-based package registry REST API. -/// Credentials are read from the PackageApiCredentials partial class. +/// Communicates with the workshop server's package REST API. The Workshop URL +/// and Application Key are read from the credential store on every request, so +/// a connection change in Settings takes effect without a restart. The key is +/// never included in results or error messages. /// -public class PackageApiClient : IPackageApiClient +public class PackageApiClient : IPackageApiClient, IDisposable { - private HttpClient? _httpClient; - private CookieContainer? _cookieContainer; + private const string ApiKeyScheme = "Api-Key"; - public async Task>> ListPackagesAsync() + private readonly ICredentialService _credentialService; + private readonly HttpClient _httpClient; + + public PackageApiClient(ICredentialService credentialService) + : this(credentialService, new HttpClientHandler()) + { + } + + /// + /// Creates a client over an explicit message handler so tests can serve + /// canned responses without a network. + /// + public PackageApiClient(ICredentialService credentialService, HttpMessageHandler messageHandler) { - var loginResult = await EnsureLoggedInAsync(); - if (loginResult.IsFailure) + _credentialService = credentialService; + _httpClient = new HttpClient(messageHandler); + } + + public async Task>> ListPackagesAsync() + { + var sendResult = await SendAsync(HttpMethod.Get, "api/packages/"); + if (sendResult.IsFailure) { - return Result>.Fail(loginResult.DiagnosticReport); + return Result.Fail("Failed to list workshop packages").WithErrors(sendResult); } - try + using var response = sendResult.Value; + if (!response.IsSuccessStatusCode) { - var response = await _httpClient!.GetAsync("api/files/"); - if (!response.IsSuccessStatusCode) - { - return Result>.Fail( - $"Failed to list packages (HTTP {(int)response.StatusCode})"); - } + return Result.Fail($"Failed to list workshop packages (HTTP {(int)response.StatusCode})"); + } - var json = await response.Content.ReadAsStringAsync(); - var entries = JsonSerializer.Deserialize>(json, JsonOptions); - if (entries is null) - { - return Result>.Fail("Failed to parse package list response"); - } + var parseResult = await ParseJsonAsync>(response, "package list"); + if (parseResult.IsFailure) + { + return Result.Fail(parseResult); + } + var entries = parseResult.Value; - var packages = new List(); - foreach (var entry in entries) - { - var fileName = Path.GetFileName(entry.File); - packages.Add(new PackageApiEntry(entry.FileId, fileName, entry.FileSize, entry.UploadedAt)); - } + var summaries = new List(entries.Count); + foreach (var entry in entries) + { + summaries.Add(ToPackageSummary(entry)); + } - return Result>.Ok(packages); + return summaries.OkResult>(); + } + + public async Task> GetPackageAsync(string packageName) + { + var sendResult = await SendAsync(HttpMethod.Get, $"api/packages/{Uri.EscapeDataString(packageName)}/"); + if (sendResult.IsFailure) + { + return Result.Fail($"Failed to get workshop package '{packageName}'").WithErrors(sendResult); } - catch (HttpRequestException exception) + + using var response = sendResult.Value; + if (response.StatusCode == HttpStatusCode.NotFound) + { + return Result.Fail($"Package '{packageName}' was not found on the workshop."); + } + + if (!response.IsSuccessStatusCode) + { + return Result.Fail($"Failed to get workshop package '{packageName}' (HTTP {(int)response.StatusCode})"); + } + + var parseResult = await ParseJsonAsync(response, "package metadata"); + if (parseResult.IsFailure) + { + return Result.Fail(parseResult); + } + var details = parseResult.Value; + + var versions = new List(); + foreach (var version in details.Versions ?? []) { - return Result>.Fail($"Network error listing packages: {exception.Message}"); + versions.Add(new RemotePackageVersion( + version.Version, + version.Author ?? string.Empty, + version.Date, + version.Tombstoned, + version.ContentHash ?? string.Empty, + version.Summary ?? string.Empty)); } + + var aliases = new List(); + foreach (var alias in details.Aliases ?? []) + { + aliases.Add(new RemotePackageAlias(alias.Alias ?? string.Empty, alias.Version)); + } + + return new RemotePackageDetails( + details.Name ?? packageName, + details.CreatedAt, + versions.AsReadOnly(), + aliases.AsReadOnly()); } - public async Task> DownloadPackageAsync(int fileId) + public async Task> PublishVersionAsync(string packageName, byte[] zipData, string? summary = null) { - var loginResult = await EnsureLoggedInAsync(); - if (loginResult.IsFailure) + using var content = new MultipartFormDataContent(); + + var fileContent = new ByteArrayContent(zipData); + fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); + content.Add(fileContent, "file", $"{packageName}.zip"); + + if (!string.IsNullOrEmpty(summary)) { - return Result.Fail(loginResult.DiagnosticReport); + content.Add(new StringContent(summary, Encoding.UTF8), "summary"); } - try + var sendResult = await SendAsync(HttpMethod.Post, $"api/packages/{Uri.EscapeDataString(packageName)}/versions/", content); + if (sendResult.IsFailure) { - var response = await _httpClient!.GetAsync($"api/files/{fileId}/"); - if (!response.IsSuccessStatusCode) - { - return Result.Fail( - $"Failed to download package (HTTP {(int)response.StatusCode})"); - } + return Result.Fail($"Failed to publish package '{packageName}'").WithErrors(sendResult); + } - var data = await response.Content.ReadAsByteArrayAsync(); - return Result.Ok(data); + using var response = sendResult.Value; + if (!response.IsSuccessStatusCode) + { + // The error body carries the server's validation message (e.g. a bad + // bundle); it never contains credential material. + var errorBody = await response.Content.ReadAsStringAsync(); + return Result.Fail($"Failed to publish package '{packageName}' (HTTP {(int)response.StatusCode}): {errorBody}"); } - catch (HttpRequestException exception) + + var parseResult = await ParseJsonAsync(response, "publish receipt"); + if (parseResult.IsFailure) { - return Result.Fail($"Network error downloading package: {exception.Message}"); + return Result.Fail(parseResult); } + var receipt = parseResult.Value; + + return new RemotePublishReceipt( + receipt.Package ?? packageName, + receipt.Version, + receipt.Author ?? string.Empty, + receipt.ContentHash ?? string.Empty); } - public async Task UploadPackageAsync(string fileName, byte[] zipData) + public async Task> DownloadVersionAsync(string packageName, int version) { - var loginResult = await EnsureLoggedInAsync(); - if (loginResult.IsFailure) + var sendResult = await SendAsync(HttpMethod.Get, $"api/packages/{Uri.EscapeDataString(packageName)}/versions/{version}/download/"); + if (sendResult.IsFailure) { - return Result.Fail(loginResult); + return Result.Fail($"Failed to download version {version} of package '{packageName}'").WithErrors(sendResult); } - try + using var response = sendResult.Value; + if (response.StatusCode == HttpStatusCode.Gone) { - var csrfToken = GetCsrfToken(); + return Result.Fail($"Version {version} of package '{packageName}' has been tombstoned and can no longer be downloaded."); + } - using var content = new MultipartFormDataContent(); - var fileContent = new ByteArrayContent(zipData); - fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); - content.Add(fileContent, "file", fileName); + if (response.StatusCode == HttpStatusCode.NotFound) + { + return Result.Fail($"Version {version} of package '{packageName}' was not found on the workshop."); + } - using var request = new HttpRequestMessage(HttpMethod.Post, "api/upload/"); - request.Content = content; - if (!string.IsNullOrEmpty(csrfToken)) - { - request.Headers.Add("X-CSRFToken", csrfToken); - } + if (!response.IsSuccessStatusCode) + { + return Result.Fail($"Failed to download version {version} of package '{packageName}' (HTTP {(int)response.StatusCode})"); + } - var response = await _httpClient!.SendAsync(request); - if (!response.IsSuccessStatusCode) - { - var errorBody = await response.Content.ReadAsStringAsync(); - return Result.Fail( - $"Failed to upload package (HTTP {(int)response.StatusCode}): {errorBody}"); - } + var data = await response.Content.ReadAsByteArrayAsync(); + return data; + } - return Result.Ok(); + public async Task> DownloadLatestAsync(string packageName) + { + var sendResult = await SendAsync(HttpMethod.Get, $"api/packages/{Uri.EscapeDataString(packageName)}/latest/"); + if (sendResult.IsFailure) + { + return Result.Fail($"Failed to download the latest version of package '{packageName}'").WithErrors(sendResult); } - catch (HttpRequestException exception) + + using var response = sendResult.Value; + if (response.StatusCode == HttpStatusCode.NotFound) { - return Result.Fail($"Network error uploading package: {exception.Message}"); + return Result.Fail($"Package '{packageName}' has no live version to download. It may not exist on the workshop, or every version may have been tombstoned."); } + + if (!response.IsSuccessStatusCode) + { + return Result.Fail($"Failed to download the latest version of package '{packageName}' (HTTP {(int)response.StatusCode})"); + } + + var data = await response.Content.ReadAsByteArrayAsync(); + return data; } - private async Task EnsureLoggedInAsync() + public async Task SetAliasAsync(string packageName, string alias, int version) { - if (_httpClient is not null) + var body = JsonSerializer.Serialize(new AliasVersionBodyDto(version)); + using var content = new StringContent(body, Encoding.UTF8, "application/json"); + + var sendResult = await SendAsync(HttpMethod.Put, $"api/packages/{Uri.EscapeDataString(packageName)}/aliases/{Uri.EscapeDataString(alias)}/", content); + if (sendResult.IsFailure) + { + return Result.Fail($"Failed to set alias '{alias}' on package '{packageName}'").WithErrors(sendResult); + } + + using var response = sendResult.Value; + if (!response.IsSuccessStatusCode) { - return Result.Ok(); + return Result.Fail($"Failed to set alias '{alias}' to version {version} of package '{packageName}' (HTTP {(int)response.StatusCode})"); } - var baseUrl = PackageApiCredentials.BaseUrl; - var username = PackageApiCredentials.Username; - var password = PackageApiCredentials.Password; + return Result.Ok(); + } - if (string.IsNullOrEmpty(baseUrl) || string.IsNullOrEmpty(username) || string.IsNullOrEmpty(password)) + public async Task RemoveAliasAsync(string packageName, string alias) + { + var sendResult = await SendAsync(HttpMethod.Delete, $"api/packages/{Uri.EscapeDataString(packageName)}/aliases/{Uri.EscapeDataString(alias)}/"); + if (sendResult.IsFailure) { - return Result.Fail( - "Package API credentials are not configured. " + - "Add a PackageApiCredentials.private.cs file with the BaseUrl, Username, and Password values."); + return Result.Fail($"Failed to remove alias '{alias}' from package '{packageName}'").WithErrors(sendResult); } - if (!baseUrl.EndsWith('/')) + using var response = sendResult.Value; + if (response.StatusCode == HttpStatusCode.NotFound) { - baseUrl += '/'; + return Result.Fail($"Alias '{alias}' was not found on package '{packageName}'."); } - try + if (!response.IsSuccessStatusCode) { - _cookieContainer = new CookieContainer(); - var handler = new HttpClientHandler - { - CookieContainer = _cookieContainer, - UseCookies = true - }; + return Result.Fail($"Failed to remove alias '{alias}' from package '{packageName}' (HTTP {(int)response.StatusCode})"); + } - var httpClient = new HttpClient(handler) - { - BaseAddress = new Uri(baseUrl) - }; + return Result.Ok(); + } - // Set Basic Auth on all requests, matching the Django API's authentication - var basicAuthValue = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}")); - httpClient.DefaultRequestHeaders.Authorization = - new AuthenticationHeaderValue("Basic", basicAuthValue); + public async Task> GetVersionHistoryAsync(string packageName, int version) + { + var sendResult = await SendAsync(HttpMethod.Get, $"api/packages/{Uri.EscapeDataString(packageName)}/versions/{version}/history/"); + if (sendResult.IsFailure) + { + return Result.Fail($"Failed to get the history of package '{packageName}'").WithErrors(sendResult); + } - // GET the login page to obtain the CSRF token - var loginPageResponse = await httpClient.GetAsync("admin/login/"); - loginPageResponse.EnsureSuccessStatusCode(); + using var response = sendResult.Value; + if (!response.IsSuccessStatusCode) + { + return Result.Fail($"Failed to get the history of package '{packageName}' as of version {version} (HTTP {(int)response.StatusCode})"); + } - var csrfToken = GetCsrfTokenFromContainer(baseUrl); + var history = await response.Content.ReadAsStringAsync(); + return history; + } - // POST credentials with the CSRF token - var loginContent = new FormUrlEncodedContent(new Dictionary - { - ["username"] = username, - ["password"] = password, - ["csrfmiddlewaretoken"] = csrfToken ?? "", - ["next"] = "/admin/" - }); + // Builds and sends one authenticated request. Fails without sending when no + // connection is stored or the stored URL is unusable, and maps 401 to a + // single actionable message here so no call site can leak the key. + private async Task> SendAsync(HttpMethod method, string relativePath, HttpContent? content = null) + { + var connectionResult = await _credentialService.GetWorkshopConnectionAsync(); + if (connectionResult.IsFailure) + { + return Result.Fail("Failed to read the Workshop connection from the credential store") + .WithErrors(connectionResult); + } + var connection = connectionResult.Value; - var loginResponse = await httpClient.PostAsync("admin/login/", loginContent); - var loginBody = await loginResponse.Content.ReadAsStringAsync(); + var baseUriResult = ValidateWorkshopUrl(connection.WorkshopUrl); + if (baseUriResult.IsFailure) + { + return Result.Fail(baseUriResult); + } + var baseUri = baseUriResult.Value; - if (loginBody.Contains("Log in")) + var request = new HttpRequestMessage(method, new Uri(baseUri, relativePath)); + request.Headers.Authorization = new AuthenticationHeaderValue(ApiKeyScheme, connection.ApplicationKey); + if (content is not null) + { + request.Content = content; + } + + try + { + var response = await _httpClient.SendAsync(request); + if (response.StatusCode == HttpStatusCode.Unauthorized) { - return Result.Fail("Package API login failed. Check credentials in PackageApiCredentials.private.cs."); + response.Dispose(); + return Result.Fail( + "The workshop rejected the stored Application Key (HTTP 401). " + + "The key may be invalid, revoked, or no longer linked to the workshop. " + + "Update the Workshop connection on the Settings page."); } - _httpClient = httpClient; - return Result.Ok(); + return response; } catch (HttpRequestException exception) { - _cookieContainer = null; - return Result.Fail($"Failed to connect to package API: {exception.Message}"); + return Result.Fail("A network error occurred while contacting the workshop") + .WithException(exception); + } + catch (TaskCanceledException exception) + { + return Result.Fail("The workshop request timed out") + .WithException(exception); } } - private string? GetCsrfToken() + // The Application Key is a bearer credential, so sending it over plain HTTP + // would fully compromise it to any network observer. Loopback hosts are + // exempt to support local development servers. + private static Result ValidateWorkshopUrl(string workshopUrl) { - if (_cookieContainer is null || _httpClient?.BaseAddress is null) + var urlText = workshopUrl.Trim(); + if (!urlText.EndsWith('/')) + { + // Ensure the trailing path segment is kept when combining with + // relative endpoint paths. + urlText += '/'; + } + + if (!Uri.TryCreate(urlText, UriKind.Absolute, out var uri)) + { + return Result.Fail("The stored Workshop URL is not a valid absolute URL. Update the Workshop connection on the Settings page."); + } + + if (uri.Scheme == Uri.UriSchemeHttps) { - return null; + return uri; } - return GetCsrfTokenFromContainer(_httpClient.BaseAddress.ToString()); + if (uri.Scheme == Uri.UriSchemeHttp && uri.IsLoopback) + { + return uri; + } + + return Result.Fail("The Workshop URL must use HTTPS. Plain HTTP would expose the Application Key to network observers and is only permitted for localhost development servers."); } - private string? GetCsrfTokenFromContainer(string baseUrl) + private static async Task> ParseJsonAsync(HttpResponseMessage response, string payloadDescription) where T : notnull { - if (_cookieContainer is null) + try { - return null; - } + var json = await response.Content.ReadAsStringAsync(); + var parsed = JsonSerializer.Deserialize(json); + if (parsed is null) + { + return Result.Fail($"Failed to parse the workshop {payloadDescription} response"); + } - var cookies = _cookieContainer.GetCookies(new Uri(baseUrl)); - var csrfCookie = cookies["csrftoken"]; - return csrfCookie?.Value; + return parsed; + } + catch (JsonException exception) + { + return Result.Fail($"Failed to parse the workshop {payloadDescription} response") + .WithException(exception); + } } - private static readonly JsonSerializerOptions JsonOptions = new() + private static RemotePackageSummary ToPackageSummary(PackageSummaryDto entry) { - PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower - }; + RemoteVersionSummary? latestVersion = null; + if (entry.LatestVersion is not null) + { + latestVersion = new RemoteVersionSummary( + entry.LatestVersion.Version, + entry.LatestVersion.Author ?? string.Empty, + entry.LatestVersion.Date); + } + + return new RemotePackageSummary( + entry.Name ?? string.Empty, + entry.CreatedAt, + latestVersion, + entry.VersionsCount); + } - private record ApiFileEntry( - [property: JsonPropertyName("id")] int FileId, - [property: JsonPropertyName("file")] string File, - [property: JsonPropertyName("file_size")] long FileSize, - [property: JsonPropertyName("uploaded_at")] DateTime UploadedAt); + private record VersionSummaryDto( + [property: JsonPropertyName("version")] int Version, + [property: JsonPropertyName("author")] string? Author, + [property: JsonPropertyName("date")] DateTime Date); + + private record PackageSummaryDto( + [property: JsonPropertyName("name")] string? Name, + [property: JsonPropertyName("created_at")] DateTime CreatedAt, + [property: JsonPropertyName("latest_version")] VersionSummaryDto? LatestVersion, + [property: JsonPropertyName("versions_count")] int VersionsCount); + + private record VersionDetailDto( + [property: JsonPropertyName("version")] int Version, + [property: JsonPropertyName("author")] string? Author, + [property: JsonPropertyName("date")] DateTime Date, + [property: JsonPropertyName("tombstoned")] bool Tombstoned, + [property: JsonPropertyName("content_hash")] string? ContentHash, + [property: JsonPropertyName("summary")] string? Summary); + + private record AliasDto( + [property: JsonPropertyName("alias")] string? Alias, + [property: JsonPropertyName("version")] int Version); + + private record PackageDetailsDto( + [property: JsonPropertyName("name")] string? Name, + [property: JsonPropertyName("created_at")] DateTime CreatedAt, + [property: JsonPropertyName("versions")] List? Versions, + [property: JsonPropertyName("aliases")] List? Aliases); + + private record PublishReceiptDto( + [property: JsonPropertyName("package")] string? Package, + [property: JsonPropertyName("version")] int Version, + [property: JsonPropertyName("author")] string? Author, + [property: JsonPropertyName("download_url")] string? DownloadUrl, + [property: JsonPropertyName("content_hash")] string? ContentHash); + + private record AliasVersionBodyDto( + [property: JsonPropertyName("version")] int Version); public void Dispose() { - _httpClient?.Dispose(); - _httpClient = null; - _cookieContainer = null; + _httpClient.Dispose(); } } diff --git a/Source/Workspace/Celbridge.Packages/Services/PackageApiCredentials.cs b/Source/Workspace/Celbridge.Packages/Services/PackageApiCredentials.cs deleted file mode 100644 index ff97c5a7b..000000000 --- a/Source/Workspace/Celbridge.Packages/Services/PackageApiCredentials.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Celbridge.Packages; - -/// -/// Holds credentials for the package registry API. -/// The default values are empty strings. To provide real credentials, create a -/// PackageApiCredentials.private.cs file (gitignored) with a static constructor -/// that sets these fields. -/// -internal static partial class PackageApiCredentials -{ - internal static string BaseUrl = ""; - internal static string Username = ""; - internal static string Password = ""; -} diff --git a/Source/Workspace/Celbridge.Packages/Services/PackageManifestLoader.cs b/Source/Workspace/Celbridge.Packages/Services/PackageManifestLoader.cs index 1692ed472..e4b349ed1 100644 --- a/Source/Workspace/Celbridge.Packages/Services/PackageManifestLoader.cs +++ b/Source/Workspace/Celbridge.Packages/Services/PackageManifestLoader.cs @@ -13,7 +13,7 @@ public static class PackageManifestLoader { private const string PackageSection = "package"; private const string ContributesSection = "contributes"; - private const string ModSection = "mod"; + private const string PermissionsSection = "permissions"; private const string DocumentSection = "document"; private const string DocumentEditorsKey = "document_editors"; private const string DocumentFileTypesSection = "document_file_types"; @@ -21,10 +21,12 @@ public static class PackageManifestLoader private const string CodeEditorSection = "code_editor"; private const string CodePreviewSection = "code_preview"; private const string OptionsSection = "options"; - private const string RequiresToolsKey = "requires_tools"; + private const string ToolsKey = "tools"; private const string IdKey = "id"; private const string NameKey = "name"; + private const string AuthorKey = "author"; + private const string TitleKey = "title"; private const string FeatureFlagKey = "feature_flag"; private const string TypeKey = "type"; private const string PriorityKey = "priority"; @@ -51,7 +53,7 @@ public static class PackageManifestLoader /// /// Loads a package from a package.toml file, including all referenced document editor contributions. - /// hostNameOverride, when non-null, replaces the default package-id-derived virtual host name. + /// hostNameOverride, when non-null, replaces the default package-name-derived virtual host name. /// secrets, when non-empty, populates PackageInfo.Secrets for WebView injection. /// devToolsBlocked, when true, permanently disables DevTools on WebViews hosting this package. /// origin tags the resulting PackageInfo so downstream read sites can pick the right IO path. @@ -94,21 +96,26 @@ public static Result LoadPackage( return Result.Fail($"Missing [{PackageSection}] section: {packageTomlPath}"); } - var packageId = GetString(packageTable, IdKey); - if (string.IsNullOrEmpty(packageId)) + var packageName = GetString(packageTable, NameKey); + if (string.IsNullOrEmpty(packageName)) { - return Result.Fail($"Package missing required '{IdKey}' field: {packageTomlPath}"); + return Result.Fail($"Package missing required '{NameKey}' field: {packageTomlPath}"); } - if (!PackageId.IsValid(packageId)) + // Bundled first-party packages use dotted names (e.g. "celbridge.notes"), + // so structural validation accepts the dotted form for every origin. + // Project discovery rejects dotted names downstream with a specific + // failure reason. + if (!PackageName.IsValidBundledName(packageName)) { - return Result.Fail($"Package has invalid '{IdKey}' value '{packageId}': {packageTomlPath}. Package ids must use only lowercase ASCII letters, digits, dots, and hyphens, with no leading, trailing, or consecutive dots."); + return Result.Fail($"Package has invalid '{NameKey}' value '{packageName}': {packageTomlPath}. Package names must be lowercase ASCII letters and digits with single interior hyphens, at most {PackageConstants.MaxNameLength} characters."); } - var packageName = GetString(packageTable, NameKey); + var packageAuthor = GetString(packageTable, AuthorKey); + var packageTitle = GetString(packageTable, TitleKey); var featureFlag = GetStringOrNull(packageTable, FeatureFlagKey); - var safeName = packageId.Replace('.', '-').ToLowerInvariant(); + var safeName = packageName.Replace('.', '-'); var defaultHostName = $"{PackageHostPrefix}{safeName}{HostSuffix}"; // Bundled packages may pin the virtual host name via the C#-side @@ -117,23 +124,24 @@ public static Result LoadPackage( // packages cannot impersonate a bundled host. var hostName = !string.IsNullOrEmpty(hostNameOverride) ? hostNameOverride : defaultHostName; - var requiresTools = Array.Empty() as IReadOnlyList; - if (root.TryGetValue(ModSection, out var modObject) && - modObject is TomlTable modTable) + var permittedTools = Array.Empty() as IReadOnlyList; + if (root.TryGetValue(PermissionsSection, out var permissionsObject) && + permissionsObject is TomlTable permissionsTable) { - requiresTools = GetStringArray(modTable, RequiresToolsKey); + permittedTools = GetStringArray(permissionsTable, ToolsKey); } var packageSecrets = secrets ?? EmptySecrets; var packageInfo = new PackageInfo { - Id = packageId, Name = packageName, + Author = packageAuthor, + Title = packageTitle, FeatureFlag = featureFlag, PackageFolder = packageFolder, HostName = hostName, - RequiresTools = requiresTools, + PermittedTools = permittedTools, Secrets = packageSecrets, DevToolsBlocked = devToolsBlocked, Origin = origin @@ -313,18 +321,18 @@ private static Result LoadDocument( $"Templates cannot be used with external content."); } - // Custom contributions have their editor id composed as "{packageId}.{documentId}" at + // Custom contributions have their editor id composed as "{packageName}.{documentId}" at // factory-construction time. Validate the composed id here so plugin authors fail fast // at manifest parse with a clear message, rather than hitting a DocumentEditorId // constructor throw later when someone tries to open a file of this type. if (documentType != CodeDocumentType) { - var composedEditorId = $"{packageInfo.Id}.{documentId}"; + var composedEditorId = $"{packageInfo.Name}.{documentId}"; if (!DocumentEditorId.IsValid(composedEditorId)) { return Result.Fail( $"Invalid document editor id '{composedEditorId}' in manifest: {documentTomlPath}. " + - $"Package id and document id must combine to form a DocumentEditorId using only lowercase letters, digits, dots, and hyphens."); + $"Package name and document id must combine to form a DocumentEditorId using only lowercase letters, digits, dots, and hyphens."); } } diff --git a/Source/Workspace/Celbridge.Packages/Services/PackageRegistry.cs b/Source/Workspace/Celbridge.Packages/Services/PackageRegistry.cs index 7c988bc34..ffc490cc3 100644 --- a/Source/Workspace/Celbridge.Packages/Services/PackageRegistry.cs +++ b/Source/Workspace/Celbridge.Packages/Services/PackageRegistry.cs @@ -11,10 +11,6 @@ namespace Celbridge.Packages; /// public class PackageRegistry { - private const string PackagesFolderName = "packages"; - private const string ManifestFileName = "package.toml"; - private const string ReservedIdPrefix = "celbridge."; - // Editors like the code editor can handle 150+ extensions; listing them all // makes the discovery log unreadable. Above this count we elide to a count. private const int MaxInlineExtensionsInLog = 20; @@ -96,22 +92,22 @@ public IReadOnlyList GetAllDocumentEditors() public Package? GetContributingPackage(DocumentEditorId editorId) { - // Custom contribution editor IDs are formatted as "{packageId}.{contributionId}" - // by CustomDocumentViewFactory. Package IDs themselves contain dots - // (e.g. "celbridge.notes"), so match by full-id prefix rather than + // Custom contribution editor IDs are formatted as "{packageName}.{contributionId}" + // by CustomDocumentViewFactory. Bundled package names themselves contain + // dots (e.g. "celbridge.notes"), so match by full-name prefix rather than // splitting on the first separator. var editorIdString = editorId.ToString(); foreach (var package in GetAllPackages()) { - var packageId = package.Info.Id; - if (packageId.Length == 0) + var packageName = package.Info.Name; + if (packageName.Length == 0) { continue; } - if (editorIdString.Length > packageId.Length - && editorIdString.StartsWith(packageId, StringComparison.Ordinal) - && editorIdString[packageId.Length] == '.') + if (editorIdString.Length > packageName.Length + && editorIdString.StartsWith(packageName, StringComparison.Ordinal) + && editorIdString[packageName.Length] == '.') { return package; } @@ -223,7 +219,7 @@ private Result CheckReservedExtensions(Package package) if (sidecarService.IsSidecarFileName(fileType.FileExtension)) { return Result.Fail( - $"Package '{package.Info.Id}' declares document-file-type extension '{fileType.FileExtension}'. " + $"Package '{package.Info.Name}' declares document-file-type extension '{fileType.FileExtension}'. " + $"The .cel namespace is reserved for project metadata sidecars."); } } @@ -256,7 +252,7 @@ private List DiscoverBundledPackages() foreach (var descriptor in descriptors) { - var manifestPath = Path.Combine(descriptor.Folder, ManifestFileName); + var manifestPath = Path.Combine(descriptor.Folder, PackageConstants.ManifestFileName); if (!_bundledReader.Exists(manifestPath)) { // A bundled package with no manifest is a build-time error. @@ -266,8 +262,9 @@ private List DiscoverBundledPackages() failures.Add(new PackageLoadFailure { Folder = descriptor.Folder, - PackageId = null, - Reason = PackageLoadFailureReason.InvalidManifest + PackageName = null, + Reason = PackageLoadFailureReason.InvalidManifest, + Detail = $"The package manifest file is missing: {manifestPath}" }); continue; } @@ -285,8 +282,9 @@ private List DiscoverBundledPackages() failures.Add(new PackageLoadFailure { Folder = descriptor.Folder, - PackageId = null, - Reason = PackageLoadFailureReason.InvalidManifest + PackageName = null, + Reason = PackageLoadFailureReason.InvalidManifest, + Detail = loadResult.FirstErrorMessage }); continue; } @@ -300,8 +298,9 @@ private List DiscoverBundledPackages() failures.Add(new PackageLoadFailure { Folder = descriptor.Folder, - PackageId = package.Info.Id, - Reason = PackageLoadFailureReason.ReservedExtension + PackageName = package.Info.Name, + Reason = PackageLoadFailureReason.ReservedExtension, + Detail = reservedExtensionCheck.FirstErrorMessage }); continue; } @@ -309,23 +308,23 @@ private List DiscoverBundledPackages() candidates.Add(package); } - // Any group of bundled packages that share an id is a first-party build bug. + // Any group of bundled packages that share a name is a first-party build bug. // Skip every colliding package so the conflict is visible rather than silently // picking a winner, and log at Error level so CI and developers notice. - foreach (var group in candidates.GroupBy(p => p.Info.Id, StringComparer.Ordinal)) + foreach (var group in candidates.GroupBy(p => p.Info.Name, StringComparer.Ordinal)) { var members = group.ToList(); if (members.Count > 1) { _logger.LogError( - $"Multiple bundled packages declare id '{group.Key}'. All {members.Count} instances skipped."); + $"Multiple bundled packages declare name '{group.Key}'. All {members.Count} instances skipped."); foreach (var member in members) { failures.Add(new PackageLoadFailure { Folder = member.Info.PackageFolder, - PackageId = group.Key, - Reason = PackageLoadFailureReason.DuplicateId + PackageName = group.Key, + Reason = PackageLoadFailureReason.DuplicateName }); } continue; @@ -346,7 +345,7 @@ private async Task> DiscoverProjectPackagesAsync(string return failures; } - var packagesResource = new ResourceKey(PackagesFolderName); + var packagesResource = new ResourceKey(PackageConstants.DefaultPackagesFolder); var resourceFileSystem = _workspaceWrapper.WorkspaceService.ResourceService.FileSystem; var packagesInfoResult = await resourceFileSystem.GetInfoAsync(packagesResource); @@ -373,7 +372,7 @@ private async Task> DiscoverProjectPackagesAsync(string continue; } - var manifestResource = item.Resource.Combine(ManifestFileName); + var manifestResource = item.Resource.Combine(PackageConstants.ManifestFileName); var manifestInfoResult = await resourceFileSystem.GetInfoAsync(manifestResource); if (manifestInfoResult.IsFailure || manifestInfoResult.Value.Kind != StorageItemKind.File) @@ -403,8 +402,9 @@ private async Task> DiscoverProjectPackagesAsync(string failures.Add(new PackageLoadFailure { Folder = packageFolder, - PackageId = null, - Reason = PackageLoadFailureReason.InvalidManifest + PackageName = null, + Reason = PackageLoadFailureReason.InvalidManifest, + Detail = loadResult.FirstErrorMessage }); continue; } @@ -418,56 +418,57 @@ private async Task> DiscoverProjectPackagesAsync(string failures.Add(new PackageLoadFailure { Folder = packageFolder, - PackageId = package.Info.Id, - Reason = PackageLoadFailureReason.ReservedExtension + PackageName = package.Info.Name, + Reason = PackageLoadFailureReason.ReservedExtension, + Detail = reservedExtensionCheck.FirstErrorMessage }); continue; } - // The "celbridge." id namespace is reserved for first-party packages + // The "celbridge." name namespace is reserved for first-party packages // shipped inside Celbridge module DLLs. Project packages that try to - // claim a reserved id are rejected so they cannot impersonate a + // claim a reserved name are rejected so they cannot impersonate a // bundled package in logs, diagnostics, or resource lookups. - if (package.Info.Id.StartsWith(ReservedIdPrefix, StringComparison.Ordinal)) + if (package.Info.Name.StartsWith(PackageConstants.ReservedNamePrefix, StringComparison.Ordinal)) { _logger.LogWarning( - $"Skipping project package with reserved '{ReservedIdPrefix}' id prefix: {package.Info.Id}"); + $"Skipping project package with reserved '{PackageConstants.ReservedNamePrefix}' name prefix: {package.Info.Name}"); failures.Add(new PackageLoadFailure { Folder = packageFolder, - PackageId = package.Info.Id, - Reason = PackageLoadFailureReason.ReservedIdPrefix + PackageName = package.Info.Name, + Reason = PackageLoadFailureReason.ReservedNamePrefix }); continue; } - // Any other dotted id claims a namespace whose ownership a registry + // Any other dotted name claims a namespace whose ownership a registry // would need to validate. Until such a registry exists, project - // packages must use flat global-namespace ids. Allowing arbitrary - // dotted ids now would let them collide with future registered + // packages must use flat global-namespace names. Allowing arbitrary + // dotted names now would let them collide with future registered // namespaces once the registry is introduced. - if (package.Info.Id.Contains('.')) + if (package.Info.Name.Contains('.')) { _logger.LogWarning( - $"Skipping project package '{package.Info.Id}' with dotted id: no namespace registry is available to validate the prefix."); + $"Skipping project package '{package.Info.Name}' with dotted name: no namespace registry is available to validate the prefix."); failures.Add(new PackageLoadFailure { Folder = packageFolder, - PackageId = package.Info.Id, + PackageName = package.Info.Name, Reason = PackageLoadFailureReason.UnregisteredNamespace }); continue; } - if (_bundledPackages.Any(b => b.Info.Id.Equals(package.Info.Id, StringComparison.Ordinal))) + if (_bundledPackages.Any(b => b.Info.Name.Equals(package.Info.Name, StringComparison.Ordinal))) { _logger.LogWarning( - $"Skipping project package '{package.Info.Id}' because its id conflicts with a bundled package."); + $"Skipping project package '{package.Info.Name}' because its name conflicts with a bundled package."); failures.Add(new PackageLoadFailure { Folder = packageFolder, - PackageId = package.Info.Id, - Reason = PackageLoadFailureReason.DuplicateId + PackageName = package.Info.Name, + Reason = PackageLoadFailureReason.DuplicateName }); continue; } @@ -475,24 +476,24 @@ private async Task> DiscoverProjectPackagesAsync(string candidates.Add(package); } - // When two project packages share an id we cannot tell the legitimate + // When two project packages share a name we cannot tell the legitimate // one from an impostor, so skip every colliding package rather than pick // a winner based on filesystem ordering. The user sees a missing editor, // investigates, and resolves the conflict. - foreach (var group in candidates.GroupBy(p => p.Info.Id, StringComparer.Ordinal)) + foreach (var group in candidates.GroupBy(p => p.Info.Name, StringComparer.Ordinal)) { var members = group.ToList(); if (members.Count > 1) { _logger.LogWarning( - $"Multiple project packages declare id '{group.Key}'. All {members.Count} instances skipped to avoid ambiguity."); + $"Multiple project packages declare name '{group.Key}'. All {members.Count} instances skipped to avoid ambiguity."); foreach (var member in members) { failures.Add(new PackageLoadFailure { Folder = member.Info.PackageFolder, - PackageId = group.Key, - Reason = PackageLoadFailureReason.DuplicateId + PackageName = group.Key, + Reason = PackageLoadFailureReason.DuplicateName }); } continue; @@ -529,7 +530,7 @@ private void LogDiscoveredPackage(Package package, string source) if (editorCount == 0) { _logger.LogInformation( - $"Discovered {source} package '{package.Info.Id}' (no document editors)"); + $"Discovered {source} package '{package.Info.Name}' (no document editors)"); return; } @@ -553,6 +554,6 @@ private void LogDiscoveredPackage(Package package, string source) var editorList = string.Join("; ", editorDescriptions); _logger.LogInformation( - $"Discovered {source} package '{package.Info.Id}' ({editorCount} {editorLabel}: {editorList})"); + $"Discovered {source} package '{package.Info.Name}' ({editorCount} {editorLabel}: {editorList})"); } } diff --git a/Source/Workspace/Celbridge.Packages/Services/PackageService.cs b/Source/Workspace/Celbridge.Packages/Services/PackageService.cs index 206a12651..520166e19 100644 --- a/Source/Workspace/Celbridge.Packages/Services/PackageService.cs +++ b/Source/Workspace/Celbridge.Packages/Services/PackageService.cs @@ -1,6 +1,7 @@ using Celbridge.Console; using Celbridge.Documents; using Celbridge.Messaging; +using Celbridge.Projects; namespace Celbridge.Packages; @@ -10,13 +11,16 @@ namespace Celbridge.Packages; public class PackageService : IPackageService { private readonly IMessengerService _messengerService; + private readonly IProjectLoadReporter _loadReporter; private readonly PackageRegistry _registry; public PackageService( IMessengerService messengerService, + IProjectLoadReporter loadReporter, PackageRegistry registry) { _messengerService = messengerService; + _loadReporter = loadReporter; _registry = registry; } @@ -24,10 +28,15 @@ public async Task RegisterPackagesAsync(string projectFolderPath) { var report = await _registry.DiscoverPackagesAsync(projectFolderPath); + // Record the outcome in the project load report before raising the + // error banner, so the details the banner points at are already on + // disk when the user goes looking. + _loadReporter.RecordPackageReport(report); + await _loadReporter.FlushAsync(); + if (report.Failures.Count > 0) { // Surface the failures via the console panel error banner. - // Individual failures are already logged by the registry. var projectName = Path.GetFileName(projectFolderPath) ?? string.Empty; var message = new ConsoleErrorMessage(ConsoleErrorType.PackageLoadError, projectName); _messengerService.Send(message); diff --git a/Source/Workspace/Celbridge.Resources/Helpers/ArchiveHelper.cs b/Source/Workspace/Celbridge.Resources/Helpers/ArchiveHelper.cs index 97824d8e8..0351427b3 100644 --- a/Source/Workspace/Celbridge.Resources/Helpers/ArchiveHelper.cs +++ b/Source/Workspace/Celbridge.Resources/Helpers/ArchiveHelper.cs @@ -131,21 +131,6 @@ public static string GlobToRegex(string glob) return $"^{regexPattern}$"; } - /// - /// Returns true if the name follows npm package naming conventions: lowercase alphanumeric - /// and hyphens, 1-214 characters, must start and end with a letter or digit, no consecutive hyphens. - /// - public static bool IsValidPackageName(string name) - { - if (string.IsNullOrEmpty(name) || name.Length > 214) - { - return false; - } - - return Regex.IsMatch(name, @"^[a-z0-9]([a-z0-9\-]*[a-z0-9])?$") && - !name.Contains("--"); - } - /// /// Returns the path to the local package registry folder in AppData. /// From 217e882ca51eed0f308c8f703e194ccaf3a5a256 Mon Sep 17 00:00:00 2001 From: Chris Gregan Date: Sat, 13 Jun 2026 09:40:39 +0100 Subject: [PATCH 02/21] Add package tools, history and install/publish Introduce richer package management features and supporting helpers. Adds package tools: package_info, enhanced package_install (version/alias, destination, replace semantics and confirmations), package_publish (reads manifest, summary, writes HISTORY.md), and alias management docs/tools (set/remove). Adds PackageHistoryFile to format/parse HISTORY.md and writes history metadata beside manifests; install now generates HISTORY.md and publish refreshes it. Resource/FS updates: add FileSystemAttributes.ReparsePoint and map native reparse points in LocalFileSystem; add ResourceKey.CombinePath helper. Update guides and tests to reflect the new behaviors and parameters. --- .../Resources/Strings/en-US/Resources.resw | 13 +- .../Services/LocalFileSystem.cs | 5 + .../Celbridge.Foundation/Core/ResourceKey.cs | 21 +- .../FileSystem/ILocalFileSystem.cs | 6 + .../Guides/Concepts/packages_overview.md | 21 +- .../Guides/Namespaces/package.md | 17 +- .../Guides/Tools/package_info.md | 29 ++ .../Guides/Tools/package_install.md | 26 +- .../Guides/Tools/package_publish.md | 24 +- .../Guides/Tools/package_remove_alias.md | 22 ++ .../Guides/Tools/package_set_alias.md | 28 ++ .../Helpers/PackageHistoryFile.cs | 116 ++++++ .../Tools/Package/PackageTools.Info.cs | 76 ++++ .../Tools/Package/PackageTools.Install.cs | 350 ++++++++++++++++-- .../Tools/Package/PackageTools.Publish.cs | 281 +++++++++----- .../Tools/Package/PackageTools.RemoveAlias.cs | 42 +++ .../Tools/Package/PackageTools.SetAlias.cs | 48 +++ .../Tools/Package/PackageTools.cs | 23 ++ Source/Tests/Resources/ResourceKeyTests.cs | 27 ++ Source/Tests/Tools/PackageHistoryFileTests.cs | 98 +++++ .../Commands/UnarchiveResourceCommand.cs | 6 +- 21 files changed, 1119 insertions(+), 160 deletions(-) create mode 100644 Source/Core/Celbridge.Tools/Guides/Tools/package_info.md create mode 100644 Source/Core/Celbridge.Tools/Guides/Tools/package_remove_alias.md create mode 100644 Source/Core/Celbridge.Tools/Guides/Tools/package_set_alias.md create mode 100644 Source/Core/Celbridge.Tools/Helpers/PackageHistoryFile.cs create mode 100644 Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Info.cs create mode 100644 Source/Core/Celbridge.Tools/Tools/Package/PackageTools.RemoveAlias.cs create mode 100644 Source/Core/Celbridge.Tools/Tools/Package/PackageTools.SetAlias.cs create mode 100644 Source/Tests/Tools/PackageHistoryFileTests.cs diff --git a/Source/Celbridge/Resources/Strings/en-US/Resources.resw b/Source/Celbridge/Resources/Strings/en-US/Resources.resw index 0efa53c55..822749826 100644 --- a/Source/Celbridge/Resources/Strings/en-US/Resources.resw +++ b/Source/Celbridge/Resources/Strings/en-US/Resources.resw @@ -1023,13 +1023,22 @@ Do you wish to continue? Publish Package - Publish package '{0}' to the remote registry? + Publish package '{0}' to the workshop as a new version? Install Package - Install package '{0}' from the remote registry? + Install package '{0}' version {1} into '{2}'? + + + Replace Package + + + The folder '{0}' already contains package '{1}' (version {2}). Installing version {3} will replace its contents and discard any local changes. The replaced files are moved to the trash. Continue? + + + The folder '{0}' already contains package '{1}'. Installing version {2} will replace its contents and discard any local changes. The replaced files are moved to the trash. Continue? Spreadsheet Editor diff --git a/Source/Core/Celbridge.FileSystem/Services/LocalFileSystem.cs b/Source/Core/Celbridge.FileSystem/Services/LocalFileSystem.cs index e58fc3ab7..0b6ea9bfb 100644 --- a/Source/Core/Celbridge.FileSystem/Services/LocalFileSystem.cs +++ b/Source/Core/Celbridge.FileSystem/Services/LocalFileSystem.cs @@ -349,6 +349,11 @@ private static FileSystemAttributes MapToPortable(System.IO.FileAttributes nativ portable |= FileSystemAttributes.ReadOnly; } + if ((native & System.IO.FileAttributes.ReparsePoint) != 0) + { + portable |= FileSystemAttributes.ReparsePoint; + } + return portable; } } diff --git a/Source/Core/Celbridge.Foundation/Core/ResourceKey.cs b/Source/Core/Celbridge.Foundation/Core/ResourceKey.cs index d05f1653e..961a44e8e 100644 --- a/Source/Core/Celbridge.Foundation/Core/ResourceKey.cs +++ b/Source/Core/Celbridge.Foundation/Core/ResourceKey.cs @@ -240,7 +240,7 @@ public bool IsDescendantOf(ResourceKey folderKey) } /// - /// Returns a new ResourceKey that is the combination of the current key and the specified segment. + /// Returns a new ResourceKey that is the combination of the current key and exactly one segment. /// The root is preserved; the segment is appended to the path. /// public ResourceKey Combine(string segment) @@ -261,6 +261,25 @@ public ResourceKey Combine(string segment) return new ResourceKey(_root, combinedPath); } + /// + /// Returns a new ResourceKey formed by appending a relative path to the current key. + /// The path is forward-slash separated and may span multiple segments, each validated + /// as Combine validates a single segment. The root is preserved. + /// + public ResourceKey CombinePath(string relativePath) + { + ArgumentException.ThrowIfNullOrEmpty(relativePath); + + var key = this; + var segments = relativePath.Split('/', StringSplitOptions.RemoveEmptyEntries); + foreach (var segment in segments) + { + key = key.Combine(segment); + } + + return key; + } + /// /// Returns true if the string represents a valid resource key segment. /// diff --git a/Source/Core/Celbridge.Foundation/FileSystem/ILocalFileSystem.cs b/Source/Core/Celbridge.Foundation/FileSystem/ILocalFileSystem.cs index c460005f1..e5db0726a 100644 --- a/Source/Core/Celbridge.Foundation/FileSystem/ILocalFileSystem.cs +++ b/Source/Core/Celbridge.Foundation/FileSystem/ILocalFileSystem.cs @@ -17,6 +17,12 @@ public enum FileSystemAttributes /// backends, derived from write-access permission. /// ReadOnly = 1 << 0, + + /// + /// The item is a reparse point: a symbolic link or junction rather than a + /// regular file or folder. + /// + ReparsePoint = 1 << 1, } /// diff --git a/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md b/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md index a1391331f..b3a64801b 100644 --- a/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md +++ b/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md @@ -1,6 +1,6 @@ # Packages -Packages extend Celbridge with custom document editors and other contributions. Each package lives in its own kebab-case subfolder under `packages/` (e.g. `packages/my-widget`). Packages run inside a WebView2 control and communicate with the host via JSON-RPC. Web content (HTML, JavaScript, CSS) is typical but not required. +Packages extend Celbridge with custom document editors and other contributions. Each package lives in its own kebab-case subfolder (conventionally under `packages/`, e.g. `packages/my-widget`). Packages run inside a WebView2 control and communicate with the host via JSON-RPC. Web content (HTML, JavaScript, CSS) is typical but not required. ## Creating a package @@ -22,20 +22,29 @@ document_editors = ["my-editor.document.toml"] **Required:** `name`. **Optional:** `author`, `title`, `feature_flag`. The `[contributes]` section lists document editor manifests provided by the package. If your package contributes a document editor, also read `document_editor_contributions` for the manifest, handler, and read-only contract. -A package name is lowercase ASCII alphanumeric with single interior hyphens as the only separator, 1-64 characters. There is no version field: version numbers are assigned by the workshop when a version is published. +A package name is lowercase ASCII alphanumeric with single interior hyphens as the only separator, 1-64 characters. There is no version field in the manifest: version numbers are assigned by the workshop when a version is published. + +## Versions, aliases, and history + +The workshop models a package as a container of immutable, server-numbered versions (1, 2, 3, ...). Named **aliases** (`latest`, `stable`, ...) point at versions; `latest` is managed by the workshop, others are publisher-defined. `package_info` returns both lists. + +When you install a package, its workshop history is written to a generated `HISTORY.md` beside the manifest (newest first); `package_publish` writes the same file for the version it assigns. This is metadata about the workshop, not package content — it is excluded from uploads, and the workshop stays authoritative for publish history. The installed (or last-published) version is recorded in `HISTORY.md`, which is where `package_status` reads it from. ## Workshop workflow | Tool | What it does | |---|---| -| `package_publish("packages/name", "name")` | Validate and publish a new version to the workshop | -| `package_install("name")` | Download and extract the latest version from the workshop | | `package_list()` | List all packages available in the workshop | +| `package_info("name")` | Inspect a package's versions and aliases | +| `package_install("name", version, destination)` | Download and extract a version (or alias) into a destination folder | +| `package_publish("packages/name/package.toml", summary)` | Validate and publish a new version; name read from the manifest | +| `package_set_alias("name", "stable", 3)` | Point an alias at a version | +| `package_remove_alias("name", "stable")` | Remove an alias | -To publish, the package must live under `packages/`, the folder name must match the package name, and the manifest must be valid with a `name` equal to the published name. +`package_publish` reads the published name from the manifest's `[package].name`, so the source folder can have any name and live under any readable root, including a `temp:` staging area. ## Confirmation prompts -`package_publish` and `package_install` are destructive. Both accept `confirmWithUser` (default `true`). Pass `false` only when the user has explicitly asked for unattended operation. +`package_publish` and `package_install` are destructive and confirm by default. Both accept `confirmWithUser` (default `true`); pass `false` only when the user has explicitly asked for unattended operation. Reinstalling over an existing package folder replaces its contents — the replaced files route through the resource trash, so the change is recoverable. Alias curation is non-destructive and is not gated. For the JS proxy conventions and `[permissions] tools` declarations packages need at runtime, see `agent_instructions`. diff --git a/Source/Core/Celbridge.Tools/Guides/Namespaces/package.md b/Source/Core/Celbridge.Tools/Guides/Namespaces/package.md index 73900d495..6150bce34 100644 --- a/Source/Core/Celbridge.Tools/Guides/Namespaces/package.md +++ b/Source/Core/Celbridge.Tools/Guides/Namespaces/package.md @@ -1,19 +1,24 @@ # package -The `package` namespace installs, archives, and publishes Celbridge packages. A package is the unit of distributable functionality (a custom document editor, an asset library, a reusable Python module). Each package follows a folder layout with a manifest at the root. +The `package` namespace installs, inspects, publishes, archives, and curates Celbridge packages. A package is the unit of distributable functionality (a custom document editor, an asset library, a reusable Python module). Each package follows a folder layout with a `package.toml` manifest at the root. ## Must-knows - **Publishing and installing are interactive by default.** `package_publish` and `package_install` confirm with the user before mutating the workshop or the project. Pass `confirmWithUser: false` only for unattended flows the user has consented to. See `silent_vs_interactive`. - **`package_install` requires a loaded project.** Installing without a project loaded fails fast. -- **Archives are produced under the project's content folder.** `package_archive` writes a `.celpkg` next to the source folder unless an explicit destination is given. `package_unarchive` is the inverse. +- **Install anywhere, but only `project:` loads.** A package installs into a `{packageName}` subfolder of the destination you choose (default `packages/`). Copies installed to non-loading roots such as `temp:` are inert reference data for comparison and merge workflows. +- **The package name comes from the manifest.** `package_publish` reads the name from `[package].name`; there is no folder-name rule and no separate name argument. +- **Aliases are non-destructive.** `package_set_alias` and `package_remove_alias` only repoint or detach a label; they never touch version content. Destructive administration (tombstone, delete) is deliberately not exposed. - **Packages are not Python packages.** Despite some tooling overlap, this namespace is for Celbridge's own package format. To see the project's Python dependencies, read the `.celbridge` project file (`[project].dependencies`). - **There is no create tool.** A package is a folder with a `package.toml` manifest; scaffold one by writing the manifest with the file tools. See `packages_overview` for the manifest schema. ## Tools - `package_list` — list the packages published to the connected workshop. -- `package_install` — download and extract the latest version of a workshop package. Interactive by default. -- `package_archive` — archive a package folder into a `.celpkg` file. -- `package_unarchive` — extract a `.celpkg` archive into a folder. -- `package_publish` — publish a new package version to the connected workshop. Interactive by default. +- `package_info` — inspect one package's versions and aliases. +- `package_install` — download and extract a version (or alias) into a destination folder. Interactive by default. +- `package_publish` — publish a new version from a package folder; name read from the manifest. Interactive by default. +- `package_set_alias` — create or move an alias (e.g. `stable`) to a version. +- `package_remove_alias` — remove an alias; the version it pointed at is unaffected. +- `package_archive` — archive a folder into a zip file. +- `package_unarchive` — extract a zip archive into a folder. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_info.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_info.md new file mode 100644 index 000000000..8102c666d --- /dev/null +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_info.md @@ -0,0 +1,29 @@ +# package_info + +Returns a workshop package's full metadata — every version and every alias — in one call. Use it before installing to choose a version or alias, or before curating aliases with `package_set_alias` / `package_remove_alias`. For the catalogue of packages rather than one package's detail, use `package_list`. + +## Parameters + +### packageName + +The name as published on the workshop (lowercase alphanumeric with single hyphen separators, 1-64 characters). + +## Returns + +A JSON object: + +- `packageName` (string) — the package's name. +- `createdAt` (datetime) — when the package was first registered. +- `versions` (array) — one object per version, each with: + - `version` (int) — the version number. + - `author` (string) — who published it. + - `date` (datetime) — when it was published. + - `tombstoned` (bool) — true if the version's content has been removed; it cannot be installed. + - `contentHash` (string) — the uploaded content's hash. + - `summary` (string) — the publisher's change summary. +- `aliases` (array) — one object per alias, each with `alias` (string) and `version` (int). `latest` is managed by the workshop; others such as `stable` are publisher-defined. + +## Gotchas + +- A `404` from the workshop (no such package) surfaces as an error; check the name with `package_list`. +- Tombstoned versions still appear in the list with `tombstoned: true` so history reads correctly; filter them out when choosing what to install. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_install.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_install.md index 150eb5dda..daeae712c 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/package_install.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_install.md @@ -1,8 +1,6 @@ # package_install -Downloads the latest version of a package from the workshop and extracts it into `packages/{packageName}/`. The package must already exist on the workshop — use `package_list` to discover what is published. By default surfaces a confirmation dialog before installing; pass `confirmWithUser: false` only when the user has explicitly asked for unattended operation. - -If a package folder of the same name already exists, the install fails (the underlying unarchive runs with `overwrite: false`). Remove or rename the existing folder first if you intend to replace it. +Downloads a package from the connected workshop and extracts it into a folder named for the package under a destination of your choosing (default `packages/`). Use `package_list` to discover what is published and `package_info` to see the versions and aliases a package offers. By default a confirmation dialog is shown before installing; pass `confirmWithUser: false` only when the user has explicitly asked for unattended operation. ## Parameters @@ -10,20 +8,34 @@ If a package folder of the same name already exists, the install fails (the unde The name as published on the workshop (lowercase alphanumeric with single hyphen separators, 1-64 characters). +### version + +Which version to install. Accepts a version number (e.g. `3`), an alias name (e.g. `stable`), or `latest` (the default), which selects the highest live version. A tombstoned version cannot be installed. + +### destination + +Resource key of the folder the package is installed *into*. The package always lands in a `{packageName}` subfolder of this destination, so two packages never overlap. Defaults to `packages/` in the project root. Any writeable root works — `packages`, `project:lib`, or a staging area such as `temp:package-staging/review`. Only packages under the `project:` root load as features; copies under other roots are inert reference data for comparison and merge workflows. + ### confirmWithUser -When `true` (default), shows a confirmation dialog before downloading and extracting. When `false`, runs silently. Leave at the default unless the user has asked for an unattended run. +When `true` (default), shows a confirmation dialog before downloading and extracting. When the destination already holds the package, the prompt names the folder, states that local changes will be lost, and shows the installed and incoming versions. Leave at the default unless the user has asked for an unattended run. ## Returns A JSON object: - `packageName` (string) — echoed package name. +- `version` (int) — the version number that was installed. - `entries` (int) — number of files extracted. -- `destination` (string) — resource key of the destination folder. +- `destination` (string) — resource key of the package folder. + +## Reinstalling replaces + +Installing over an existing package folder completely replaces its contents — there is no merge. The replaced files are moved to the resource trash first, so even a silent reinstall is recoverable with undo. The installed version's workshop history is written to `HISTORY.md` beside the manifest (newest first); that file is how the installed version is later determined. ## Gotchas -- The downloaded zip is staged briefly under `temp:` and removed after extraction. A failure mid-extract still cleans up the temp file. -- An existing `packages/{packageName}` folder causes the call to fail — decide whether to remove it explicitly rather than relying on a flag. +- Installing into `project:` fails before downloading if another manifest already claims the same package name at a *different* path — move, rename, or remove it first, or reinstall over the existing folder to replace it. Use `package_status` to see what is installed where. Copies under non-loading roots (e.g. `temp:`) are exempt because they never load. +- The downloaded zip is staged briefly under `temp:` and removed after extraction, even if the extract fails partway. - A package whose versions have all been tombstoned has no live version and cannot be installed. +- `HISTORY.md` is generated metadata, not package content; `package_publish` never uploads it. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md index c4bda3d63..115af17bd 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md @@ -1,18 +1,18 @@ # package_publish -Zips the contents of `packages/{packageName}/` and publishes the result to the workshop as a new version of the package. Versions are immutable and numbered by the workshop in publish order; publishing never overwrites an earlier version. The first publish of a new name registers the package on the workshop. +Zips a package folder and publishes it to the connected workshop as a new version. The package name is read from the manifest, so the source can live anywhere and need not sit under `packages/`. Versions are immutable and numbered by the workshop in publish order; publishing never overwrites an earlier version. The first publish of a new name registers the package on the workshop. -Validates the package layout and manifest before uploading. By default surfaces a confirmation dialog before publishing; pass `confirmWithUser: false` only when the user has explicitly asked for unattended operation. +By default a confirmation dialog is shown before publishing; pass `confirmWithUser: false` only when the user has explicitly asked for unattended operation. ## Parameters ### resource -Resource key of the package folder. Must start with `packages/` and the folder name must equal `packageName`. Both checks are explicit so a typo cannot publish the wrong folder under a different name. +Resource key of the package's `package.toml` manifest (its containing folder is also accepted). The folder that holds the manifest is what gets zipped and uploaded. Because this is a resource key, any readable root works — including assembling a package under `temp:package-staging` and publishing it from there without ever installing it into `project:`. -### packageName +### summary -Lowercase alphanumeric with single hyphen separators, 1-64 characters. Must match the folder name segment of `resource` and the manifest's `name` field. +Optional. A concise paragraph describing the change, capped at 512 characters. It feeds the version metadata and the workshop history, so write it like a commit message — what changed and why, not an inventory of files. An over-long summary is rejected (never truncated) so you can rewrite it. ### confirmWithUser @@ -22,10 +22,9 @@ When `true` (default), shows a confirmation dialog before uploading. Leave at th Before uploading, the tool verifies that: -- `resource` is inside `packages/` and the folder segment equals `packageName`. -- The folder exists on disk. -- A `package.toml` file is present at the folder root. -- The manifest is valid TOML and contains a `[package]` section whose `name` field equals `packageName`. +- `resource` resolves to a `package.toml` manifest (or a folder containing one). +- The manifest is valid TOML with a `[package]` section whose `name` is a valid package name. +- The `summary`, if given, is within the 512-character cap. If any check fails, no upload is attempted. @@ -33,12 +32,17 @@ If any check fails, no upload is attempted. A JSON object: -- `packageName` (string) — echoed package name. +- `packageName` (string) — the name read from the manifest. - `version` (int) — the version number the workshop assigned to this publish. - `entries` (int) — number of files included in the uploaded zip. - `size` (long) — uploaded zip size in bytes. +## HISTORY.md + +After a successful publish, the tool writes a fresh `HISTORY.md` beside the manifest recording the version just assigned (one `# ` section per version, newest first). This makes the source folder match what a consumer who installs that version receives, and lets `package_status` report the right version for it. The file itself is excluded from the upload (matched case-insensitively) — the workshop stays authoritative for publish history. + ## Gotchas - Symlinks and other reparse points inside the package folder are skipped, not followed. - Publishing always creates a new version; there is no way to replace or delete an existing version through the tools. +- The workshop reads the publisher from the manifest's `author` field; set it before the first publish. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_remove_alias.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_remove_alias.md new file mode 100644 index 000000000..0da2cbbd7 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_remove_alias.md @@ -0,0 +1,22 @@ +# package_remove_alias + +Removes an alias from a workshop package. Only the named pointer is detached; the version it pointed at, and all version content, are untouched. This is non-destructive curation, the inverse of `package_set_alias`, and is not gated with a confirmation prompt. + +## Parameters + +### packageName + +The name as published on the workshop (lowercase alphanumeric with single hyphen separators, 1-64 characters). + +### alias + +The alias to remove (e.g. `stable`). The `latest` alias is managed by the workshop and is rejected here. + +## Returns + +A JSON object echoing `packageName` and `alias`, with `removed: true`. + +## Gotchas + +- Removing an alias does not delete or tombstone any version; installers pinned to a specific version number are unaffected. +- Removing an alias that does not exist surfaces as an error from the workshop. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_set_alias.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_set_alias.md new file mode 100644 index 000000000..7b1521909 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_set_alias.md @@ -0,0 +1,28 @@ +# package_set_alias + +Creates an alias pointing at a version, or moves an existing alias to a different version. Aliases are named pointers such as `stable` that let installers track a moving target without naming a fixed version number. This is publisher curation; to read a package's current aliases use `package_info`. + +Setting an alias never changes version content — it only repoints a label — so it is not gated with a confirmation prompt. + +## Parameters + +### packageName + +The name as published on the workshop (lowercase alphanumeric with single hyphen separators, 1-64 characters). + +### alias + +The alias to create or move (e.g. `stable`). Same character rule as a package name. The `latest` alias is managed by the workshop and is rejected here. + +### version + +The version number the alias should point at. A positive integer assigned by the workshop; see `package_info` for the available versions. + +## Returns + +A JSON object echoing `packageName`, `alias`, and `version`. + +## Gotchas + +- The workshop validates that the target version exists; a missing or tombstoned version surfaces as an error. +- Moving an alias is the same call as creating one — `set` always overwrites the alias's current target. diff --git a/Source/Core/Celbridge.Tools/Helpers/PackageHistoryFile.cs b/Source/Core/Celbridge.Tools/Helpers/PackageHistoryFile.cs new file mode 100644 index 000000000..5df1498b9 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Helpers/PackageHistoryFile.cs @@ -0,0 +1,116 @@ +using System.Globalization; +using System.IO; +using System.Text; + +namespace Celbridge.Tools; + +/// +/// Formats and parses the generated HISTORY.md changelog written beside a package manifest. +/// +internal static class PackageHistoryFile +{ + /// + /// Builds the HISTORY.md changelog from the package's versions, covering + /// every version up to and including the installed one, newest first. + /// + public static string Format(IReadOnlyList versions, int installedVersion) + { + var orderedVersions = versions + .Where(packageVersion => packageVersion.Version <= installedVersion) + .OrderByDescending(packageVersion => packageVersion.Version) + .ToList(); + + var builder = new StringBuilder(); + foreach (var packageVersion in orderedVersions) + { + if (builder.Length > 0) + { + builder.Append("\r\n"); + } + + builder.Append("# "); + builder.Append(packageVersion.Version.ToString(CultureInfo.InvariantCulture)); + if (packageVersion.Tombstoned) + { + builder.Append(" (tombstoned)"); + } + builder.Append("\r\n\r\n"); + + var date = packageVersion.Date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); + var author = packageVersion.Author?.Trim() ?? string.Empty; + if (author.Length > 0) + { + builder.Append($"Published by {author} on {date}.\r\n"); + } + else + { + builder.Append($"Published on {date}.\r\n"); + } + + // The content hash fingerprints the published version, letting a + // vendored copy be verified against the workshop's record. It is + // self-describing, so it stands alone without a label. + var contentHash = packageVersion.ContentHash?.Trim() ?? string.Empty; + if (contentHash.Length > 0) + { + builder.Append(contentHash); + builder.Append("\r\n"); + } + + var summary = packageVersion.Summary?.Trim() ?? string.Empty; + if (summary.Length > 0) + { + var normalizedSummary = summary.Replace("\r\n", "\n").Replace("\n", "\r\n"); + builder.Append("\r\n"); + builder.Append(normalizedSummary); + builder.Append("\r\n"); + } + } + + return builder.ToString(); + } + + /// + /// Reads the installed version from a HISTORY.md body: the first non-empty + /// line is the newest version's heading ("# 23"). Returns null when the + /// file has no parseable version heading (e.g. a hand-authored file). + /// + public static int? TryReadInstalledVersion(string historyMarkdown) + { + if (string.IsNullOrEmpty(historyMarkdown)) + { + return null; + } + + using var reader = new StringReader(historyMarkdown); + string? line; + while ((line = reader.ReadLine()) is not null) + { + var trimmed = line.Trim(); + if (trimmed.Length == 0) + { + continue; + } + + // The first non-empty line must be the newest version's heading. + var headingText = trimmed.TrimStart('#').Trim(); + + // Drop any trailing note such as "(tombstoned)". + var spaceIndex = headingText.IndexOf(' '); + if (spaceIndex > 0) + { + headingText = headingText.Substring(0, spaceIndex); + } + + if (int.TryParse(headingText, NumberStyles.Integer, CultureInfo.InvariantCulture, out var version) + && version > 0) + { + return version; + } + + return null; + } + + return null; + } +} diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Info.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Info.cs new file mode 100644 index 000000000..bbae4a2d4 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Info.cs @@ -0,0 +1,76 @@ +using System.Text.Json; +using ModelContextProtocol.Protocol; +using ModelContextProtocol.Server; + +namespace Celbridge.Tools; + +/// +/// A version entry in the package_info result. +/// +public record class PackageVersionEntry( + int Version, + string Author, + DateTime Date, + bool Tombstoned, + string ContentHash, + string Summary); + +/// +/// An alias entry in the package_info result. +/// +public record class PackageAliasEntry(string Alias, int Version); + +/// +/// Result returned by package_info: a package's versions and aliases. +/// +public record class PackageInfoResult( + string PackageName, + DateTime CreatedAt, + IReadOnlyList Versions, + IReadOnlyList Aliases); + +public partial class PackageTools +{ + /// Inspect a workshop package: its versions and aliases. + [McpServerTool(Name = "package_info", ReadOnly = true)] + [ToolAlias("package.info")] + [RelatedGuides("packages_overview")] + public async partial Task Info(string packageName) + { + if (!PackageName.IsValid(packageName)) + { + return ToolResponse.Error(InvalidPackageNameError(packageName)); + } + + var packageApiClient = GetRequiredService(); + var detailsResult = await packageApiClient.GetPackageAsync(packageName); + if (detailsResult.IsFailure) + { + return ToolResponse.Error(detailsResult); + } + var details = detailsResult.Value; + + var versions = new List(); + foreach (var packageVersion in details.Versions) + { + var entry = new PackageVersionEntry( + packageVersion.Version, + packageVersion.Author, + packageVersion.Date, + packageVersion.Tombstoned, + packageVersion.ContentHash, + packageVersion.Summary); + versions.Add(entry); + } + + var aliases = new List(); + foreach (var packageAlias in details.Aliases) + { + aliases.Add(new PackageAliasEntry(packageAlias.Alias, packageAlias.Version)); + } + + var result = new PackageInfoResult(details.Name, details.CreatedAt, versions, aliases); + var json = JsonSerializer.Serialize(result, JsonOptions); + return ToolResponse.Success(json); + } +} diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs index 9b86a30b1..975cc8fe8 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs @@ -1,68 +1,156 @@ using System.Text.Json; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; +using Path = System.IO.Path; namespace Celbridge.Tools; /// /// Result returned by package_install with the installed package details. /// -public record class PackageInstallResult(string PackageName, int Entries, string Destination); +public record class PackageInstallResult(string PackageName, int Version, int Entries, string Destination); public partial class PackageTools { - /// Install the latest version of a workshop package into packages/{packageName}/. + /// Install a workshop package version or alias into a destination folder (default packages/). [McpServerTool(Name = "package_install", Destructive = true)] [ToolAlias("package.install")] - [RelatedGuides("packages_overview", "silent_vs_interactive")] - public async partial Task Install(string packageName, bool confirmWithUser = true) + [RelatedGuides("packages_overview", "resource_keys", "silent_vs_interactive")] + public async partial Task Install( + string packageName, + string version = "latest", + string destination = "", + bool confirmWithUser = true) { if (!PackageName.IsValid(packageName)) { return ToolResponse.Error(InvalidPackageNameError(packageName)); } - if (confirmWithUser) + var workspaceWrapper = GetRequiredService(); + if (!workspaceWrapper.IsWorkspacePageLoaded) + { + return ToolResponse.Error("No project is loaded. Open a project before installing a package."); + } + + ResourceKey destinationFolder; + if (string.IsNullOrWhiteSpace(destination)) { - var localizerService = GetRequiredService(); - var title = localizerService.GetString("Package_InstallConfirm_Title"); - var message = localizerService.GetString("Package_InstallConfirm_Message", packageName); + destinationFolder = new ResourceKey(PackageConstants.DefaultPackagesFolder); + } + else if (!ResourceKey.TryCreate(destination, out destinationFolder)) + { + return ToolResponse.InvalidResourceKey(destination); + } + + // The package extracts into a subfolder named for the package under the + // chosen destination, so packages installed side by side never overlap. + var packageFolder = destinationFolder.Combine(packageName); - var confirmed = await ConfirmActionAsync(title, message); + var workspaceService = workspaceWrapper.WorkspaceService; + var resourceService = workspaceService.ResourceService; + var resourceRegistry = resourceService.Registry; + var resourceFileSystem = resourceService.FileSystem; + + var canCreateResult = resourceService.Operations.CanCreateResource(packageFolder, isFolder: true); + if (canCreateResult.IsFailure) + { + return ToolResponse.Error( + $"Cannot install into '{destinationFolder}': {canCreateResult.FirstErrorMessage}"); + } + + // A package installed under project: participates in discovery, so a + // second copy of the same name at a different path would fault both + // copies. Refuse before downloading and name the existing location. + if (packageFolder.Root == ResourceKey.DefaultRoot) + { + var duplicateCheck = CheckForDuplicateProjectPackage( + workspaceService.PackageService, + resourceRegistry, + packageName, + packageFolder); + if (duplicateCheck.IsFailure) + { + return ToolResponse.Error(duplicateCheck); + } + } + + var packageApiClient = GetRequiredService(); + + var detailsResult = await packageApiClient.GetPackageAsync(packageName); + if (detailsResult.IsFailure) + { + return ToolResponse.Error(detailsResult); + } + var packageDetails = detailsResult.Value; + + var requestedVersion = string.IsNullOrWhiteSpace(version) ? "latest" : version.Trim(); + var resolveVersionResult = ResolveInstallVersion(packageDetails, requestedVersion); + if (resolveVersionResult.IsFailure) + { + return ToolResponse.Error(resolveVersionResult); + } + var resolvedVersion = resolveVersionResult.Value; + + // Treat an existing folder at the destination as a replace: its contents + // are trashed and the package re-extracted. The installed version is read + // back from the existing HISTORY.md to inform the confirmation. + var existingFolderResult = await resourceFileSystem.GetInfoAsync(packageFolder); + var isReplace = existingFolderResult.IsSuccess + && existingFolderResult.Value.Kind == StorageItemKind.Folder; + + int? installedVersion = null; + if (isReplace) + { + installedVersion = await TryReadInstalledVersionAsync(resourceFileSystem, packageFolder); + } + + if (confirmWithUser) + { + var confirmed = await ConfirmInstallAsync( + packageName, + packageFolder, + resolvedVersion, + isReplace, + installedVersion); if (!confirmed) { return ToolResponse.Error("Install cancelled by user."); } } - var packageApiClient = GetRequiredService(); - var downloadResult = await packageApiClient.DownloadLatestAsync(packageName); + var downloadResult = await packageApiClient.DownloadVersionAsync(packageName, resolvedVersion); if (downloadResult.IsFailure) { return ToolResponse.Error(downloadResult); } + var packageBytes = downloadResult.Value; - var workspaceWrapper = GetRequiredService(); - var workspaceService = workspaceWrapper.WorkspaceService; - var resourceFileSystem = workspaceService.ResourceService.FileSystem; + if (isReplace) + { + var replaceResult = await ReplaceExistingFolderAsync(packageFolder); + if (replaceResult.IsFailure) + { + return ToolResponse.Error(replaceResult); + } + } // Stage the downloaded zip under temp: so it lives in .celbridge/temp/ // (created at workspace load) and is reachable through the gateway. - var tempArchiveResource = new ResourceKey($"temp:{packageName}.zip"); - var writeArchiveResult = await resourceFileSystem.WriteAllBytesAsync(tempArchiveResource, downloadResult.Value); + var stagedArchive = new ResourceKey($"temp:{packageName}.zip"); + var writeArchiveResult = await resourceFileSystem.WriteAllBytesAsync(stagedArchive, packageBytes); if (writeArchiveResult.IsFailure) { return ToolResponse.Error($"Failed to write downloaded package: {writeArchiveResult.FirstErrorMessage}"); } - var destinationResource = ResourceKey.Create($"{PackageConstants.DefaultPackagesFolder}/{packageName}"); - + int extractedEntries; try { var unarchiveResultWrapper = await ExecuteCommandAsync(command => { - command.ArchiveResource = tempArchiveResource; - command.DestinationResource = destinationResource; + command.ArchiveResource = stagedArchive; + command.DestinationResource = packageFolder; command.Overwrite = false; }); @@ -71,16 +159,228 @@ public async partial Task Install(string packageName, bool confi return ToolResponse.Error(unarchiveResultWrapper); } - var unarchiveResult = unarchiveResultWrapper.Value; - var result = new PackageInstallResult(packageName, unarchiveResult.Entries, destinationResource.ToString()); - var json = JsonSerializer.Serialize(result, JsonOptions); - return ToolResponse.Success(json); + extractedEntries = unarchiveResultWrapper.Value.Entries; } finally { // Best-effort cleanup of the staged archive; a failure here does // not change the install outcome the caller sees. - await resourceFileSystem.DeleteAsync(tempArchiveResource); + await resourceFileSystem.DeleteAsync(stagedArchive); + } + + var historyFile = packageFolder.Combine(PackageConstants.HistoryFileName); + var historyMarkdown = PackageHistoryFile.Format(packageDetails.Versions, resolvedVersion); + var writeHistoryResult = await resourceFileSystem.WriteAllTextAsync(historyFile, historyMarkdown); + if (writeHistoryResult.IsFailure) + { + Logger.LogWarning(writeHistoryResult, $"Failed to write {PackageConstants.HistoryFileName} for package '{packageName}'"); + } + + var result = new PackageInstallResult(packageName, resolvedVersion, extractedEntries, packageFolder.ToString()); + var json = JsonSerializer.Serialize(result, JsonOptions); + return ToolResponse.Success(json); + } + + // Resolves the requested version string to a concrete live version number. + // "latest" selects the highest non-tombstoned version; a numeric string + // selects that exact version; anything else is treated as an alias name. + private static Result ResolveInstallVersion(RemotePackageDetails details, string requestedVersion) + { + if (string.Equals(requestedVersion, "latest", StringComparison.OrdinalIgnoreCase)) + { + var liveVersions = details.Versions + .Where(packageVersion => !packageVersion.Tombstoned) + .ToList(); + if (liveVersions.Count == 0) + { + return Result.Fail($"Package '{details.Name}' has no live version to install."); + } + + return liveVersions.Max(packageVersion => packageVersion.Version); + } + + if (int.TryParse(requestedVersion, out var explicitVersion)) + { + var match = details.Versions.FirstOrDefault(packageVersion => packageVersion.Version == explicitVersion); + if (match is null) + { + return Result.Fail($"Version {explicitVersion} not found for package '{details.Name}'."); + } + if (match.Tombstoned) + { + return Result.Fail($"Version {explicitVersion} of package '{details.Name}' has been tombstoned and cannot be installed."); + } + + return explicitVersion; } + + var alias = details.Aliases.FirstOrDefault(packageAlias => + string.Equals(packageAlias.Alias, requestedVersion, StringComparison.Ordinal)); + if (alias is null) + { + return Result.Fail($"'{requestedVersion}' is not a version number or a known alias for package '{details.Name}'."); + } + + var aliasTarget = details.Versions.FirstOrDefault(packageVersion => packageVersion.Version == alias.Version); + if (aliasTarget is null) + { + return Result.Fail($"Alias '{requestedVersion}' points at version {alias.Version}, which does not exist."); + } + if (aliasTarget.Tombstoned) + { + return Result.Fail($"Alias '{requestedVersion}' points at version {alias.Version}, which has been tombstoned."); + } + + return alias.Version; + } + + private static Result CheckForDuplicateProjectPackage( + IPackageService packageService, + IResourceRegistry resourceRegistry, + string packageName, + ResourceKey packageFolder) + { + var resolveTargetResult = resourceRegistry.ResolveResourcePath(packageFolder, validateCase: false); + if (resolveTargetResult.IsFailure) + { + return Result.Fail($"Cannot resolve install destination '{packageFolder}': {resolveTargetResult.FirstErrorMessage}"); + } + var targetPath = NormalizeFolderPath(resolveTargetResult.Value); + + foreach (var package in packageService.GetAllPackages()) + { + if (package.Info.Origin != PackageOrigin.Project) + { + continue; + } + if (!string.Equals(package.Info.Name, packageName, StringComparison.Ordinal)) + { + continue; + } + + var existingPath = NormalizeFolderPath(package.Info.PackageFolder); + if (string.Equals(existingPath, targetPath, StringComparison.OrdinalIgnoreCase)) + { + // Same path: this is the replace case, not a duplicate fault. + continue; + } + + var existingLocation = DescribeFolder(resourceRegistry, package.Info.PackageFolder); + return Result.Fail( + $"Package '{packageName}' is already installed in the project at '{existingLocation}'. " + + "Move, rename, or remove it before installing to a different location, or reinstall over the existing folder to replace it."); + } + + return Result.Ok(); + } + + private static string NormalizeFolderPath(string path) + { + return Path.TrimEndingDirectorySeparator(Path.GetFullPath(path)); + } + + private static string DescribeFolder(IResourceRegistry resourceRegistry, string folderPath) + { + var keyResult = resourceRegistry.GetResourceKey(folderPath); + if (keyResult.IsSuccess) + { + return keyResult.Value.ToString(); + } + + return folderPath; + } + + private static async Task TryReadInstalledVersionAsync( + IResourceFileSystem resourceFileSystem, + ResourceKey packageFolder) + { + var historyFile = packageFolder.Combine(PackageConstants.HistoryFileName); + var infoResult = await resourceFileSystem.GetInfoAsync(historyFile); + if (infoResult.IsFailure + || infoResult.Value.Kind != StorageItemKind.File) + { + return null; + } + + var readResult = await resourceFileSystem.ReadAllTextAsync(historyFile); + if (readResult.IsFailure) + { + return null; + } + + return PackageHistoryFile.TryReadInstalledVersion(readResult.Value); + } + + private async Task ConfirmInstallAsync( + string packageName, + ResourceKey packageFolder, + int incomingVersion, + bool isReplace, + int? installedVersion) + { + var localizerService = GetRequiredService(); + + string title; + string message; + if (isReplace) + { + title = localizerService.GetString("Package_ReplaceConfirm_Title"); + if (installedVersion.HasValue) + { + message = localizerService.GetString( + "Package_ReplaceConfirm_Message", + packageFolder.ToString(), + packageName, + installedVersion.Value, + incomingVersion); + } + else + { + message = localizerService.GetString( + "Package_ReplaceConfirm_MessageUnknownVersion", + packageFolder.ToString(), + packageName, + incomingVersion); + } + } + else + { + title = localizerService.GetString("Package_InstallConfirm_Title"); + message = localizerService.GetString( + "Package_InstallConfirm_Message", + packageName, + incomingVersion, + packageFolder.ToString()); + } + + return await ConfirmActionAsync(title, message); + } + + private async Task ReplaceExistingFolderAsync(ResourceKey packageFolder) + { + // BreakReferences avoids a second confirmation prompt: the install + // already confirmed the replace, and the re-extracted package recreates + // the same resource keys, so references resolve again afterwards. + var deleteResultWrapper = await ExecuteCommandAsync(command => + { + command.Resources = new List { packageFolder }; + command.ReferencePolicy = DeleteReferencePolicy.BreakReferences; + }); + + if (deleteResultWrapper.IsFailure) + { + return Result.Fail($"Failed to replace existing package folder '{packageFolder}'.") + .WithErrors(deleteResultWrapper); + } + + var deleteResult = deleteResultWrapper.Value; + if (deleteResult.BatchOutcome != DeleteBatchOutcome.DeletedAll) + { + return Result.Fail( + $"Could not fully remove the existing package folder '{packageFolder}' before reinstalling. " + + "Close any open files under it and try again."); + } + + return Result.Ok(); } } diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs index c65b8eb8e..9af886cda 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs @@ -4,8 +4,6 @@ using ModelContextProtocol.Server; using Tomlyn; using Tomlyn.Model; -using File = System.IO.File; -using FileAttributes = System.IO.FileAttributes; using MemoryStream = System.IO.MemoryStream; using Path = System.IO.Path; @@ -19,68 +17,54 @@ public record class PackagePublishResult(string PackageName, int Version, int En public partial class PackageTools { - private const string PackagesFolderPrefix = $"{PackageConstants.DefaultPackagesFolder}/"; - - /// Publish packages/{packageName}/ to the workshop as a new package version. + /// Publish a package folder to the workshop as a new version, named from its manifest. [McpServerTool(Name = "package_publish", Destructive = true)] [ToolAlias("package.publish")] [RelatedGuides("resource_keys", "packages_overview", "silent_vs_interactive", "document_editor_contributions")] - [AllowDirectFileSystemAccess] - public async partial Task Publish(string resource, string packageName, bool confirmWithUser = true) + public async partial Task Publish(string resource, string summary = "", bool confirmWithUser = true) { if (!ResourceKey.TryCreate(resource, out var resourceKey)) { return ToolResponse.InvalidResourceKey(resource); } - if (!PackageName.IsValid(packageName)) - { - return ToolResponse.Error(InvalidPackageNameError(packageName)); - } - - // Validate the resource is inside the packages folder - var resourceString = resourceKey.ToString(); - if (!resourceString.StartsWith(PackagesFolderPrefix, StringComparison.OrdinalIgnoreCase)) + if (summary.Length > PackageConstants.MaxSummaryLength) { return ToolResponse.Error( - $"Package must be inside the '{PackagesFolderPrefix}' folder. " + - $"Expected: '{PackagesFolderPrefix}{packageName}'"); + $"The summary is {summary.Length} characters, but the maximum is {PackageConstants.MaxSummaryLength}. " + + "Shorten the summary and try again; it is not truncated automatically."); } - // Validate the folder name matches the package name - var folderName = resourceString.Substring(PackagesFolderPrefix.Length).TrimEnd('/'); - if (!string.Equals(folderName, packageName, StringComparison.Ordinal)) + var workspaceWrapper = GetRequiredService(); + if (!workspaceWrapper.IsWorkspacePageLoaded) { - return ToolResponse.Error( - $"Folder name '{folderName}' does not match package name '{packageName}'. " + - $"The package folder must be '{PackagesFolderPrefix}{packageName}'."); + return ToolResponse.Error("No project is loaded. Open a project before publishing a package."); } - var workspaceWrapper = GetRequiredService(); - var resourceRegistry = workspaceWrapper.WorkspaceService.ResourceService.Registry; + var resourceService = workspaceWrapper.WorkspaceService.ResourceService; + var resourceRegistry = resourceService.Registry; var fileSystem = GetRequiredService(); - var resolveSourceResult = resourceRegistry.ResolveResourcePath(resourceKey); - if (resolveSourceResult.IsFailure) + var resolveResult = resourceRegistry.ResolveResourcePath(resourceKey); + if (resolveResult.IsFailure) { - return ToolResponse.Error(resolveSourceResult.FirstErrorMessage); + return ToolResponse.Error(resolveResult.FirstErrorMessage); } - var sourcePath = resolveSourceResult.Value; + var resolvedPath = resolveResult.Value; - var sourceInfoResult = await fileSystem.GetInfoAsync(sourcePath); - if (sourceInfoResult.IsFailure - || sourceInfoResult.Value.Kind != StorageItemKind.Folder) + var locateResult = await LocatePackageFolderAsync(fileSystem, resourceKey, resolvedPath); + if (locateResult.IsFailure) { - return ToolResponse.Error($"Folder not found: '{resourceKey}'"); + return ToolResponse.Error(locateResult); } + var packageSource = locateResult.Value; - // Validate that the package manifest exists and is valid - var manifestPath = Path.Combine(sourcePath, PackageConstants.ManifestFileName); - var validateResult = await ValidatePackageManifestAsync(fileSystem, manifestPath, packageName); - if (validateResult.IsFailure) + var nameResult = await ReadManifestPackageNameAsync(fileSystem, packageSource.ManifestPath); + if (nameResult.IsFailure) { - return ToolResponse.Error(validateResult); + return ToolResponse.Error(nameResult); } + var packageName = nameResult.Value; if (confirmWithUser) { @@ -95,73 +79,100 @@ public async partial Task Publish(string resource, string packag } } - int entryCount = 0; - byte[] zipData; - - try + var buildResult = await BuildPackageArchiveAsync(fileSystem, packageSource.FolderPath); + if (buildResult.IsFailure) { - using var memoryStream = new MemoryStream(); - using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, leaveOpen: true)) - { - var enumerateResult = await fileSystem.EnumerateAsync(sourcePath, "*", recursive: true); - if (enumerateResult.IsFailure) - { - return ToolResponse.Error($"Failed to enumerate package files: {enumerateResult.FirstErrorMessage}"); - } - var filePaths = enumerateResult.Value - .Where(entry => !entry.IsFolder) - .Select(entry => entry.FullPath) - .ToList(); - - foreach (var filePath in filePaths) - { - // Reparse-point check still uses System.IO directly: file - // attribute introspection is outside the ILocalFileSystem gateway. - var fileAttributes = File.GetAttributes(filePath); - if (fileAttributes.HasFlag(FileAttributes.ReparsePoint)) - { - continue; - } - - var relativePath = Path.GetRelativePath(sourcePath, filePath); - var entryName = relativePath.Replace('\\', '/'); - - var entry = zipArchive.CreateEntry(entryName, CompressionLevel.Optimal); - using var entryStream = entry.Open(); - var openResult = await fileSystem.OpenReadAsync(filePath); - if (openResult.IsFailure) - { - return ToolResponse.Error($"Failed to open file for packaging '{filePath}': {openResult.FirstErrorMessage}"); - } - using var sourceStream = openResult.Value; - await sourceStream.CopyToAsync(entryStream); - entryCount++; - } - } - - zipData = memoryStream.ToArray(); - } - catch (System.IO.IOException exception) - { - return ToolResponse.Error($"Failed to create package archive: {exception.Message}"); + return ToolResponse.Error(buildResult); } + var archive = buildResult.Value; var packageApiClient = GetRequiredService(); - var publishResult = await packageApiClient.PublishVersionAsync(packageName, zipData); - + var publishSummary = string.IsNullOrEmpty(summary) ? null : summary; + var publishResult = await packageApiClient.PublishVersionAsync(packageName, archive.ZipData, publishSummary); if (publishResult.IsFailure) { return ToolResponse.Error(publishResult); } var receipt = publishResult.Value; - var result = new PackagePublishResult(packageName, receipt.Version, entryCount, zipData.Length); + // Refresh the local HISTORY.md to the version just assigned, so the + // source folder matches what a consumer who installs this version + // receives. Best effort: the publish has already succeeded. + await RefreshPublishedHistoryAsync( + packageApiClient, + resourceService.FileSystem, + packageSource.FolderResource, + packageName, + receipt.Version); + + var result = new PackagePublishResult(packageName, receipt.Version, archive.EntryCount, archive.ZipData.Length); var json = JsonSerializer.Serialize(result, JsonOptions); return ToolResponse.Success(json); } - private static async Task ValidatePackageManifestAsync(ILocalFileSystem fileSystem, string manifestPath, string packageName) + private async Task RefreshPublishedHistoryAsync( + IPackageApiClient packageApiClient, + IResourceFileSystem resourceFileSystem, + ResourceKey folderResource, + string packageName, + int publishedVersion) + { + var detailsResult = await packageApiClient.GetPackageAsync(packageName); + if (detailsResult.IsFailure) + { + Logger.LogWarning(detailsResult, + $"Published '{packageName}' version {publishedVersion} but could not read back its history to refresh {PackageConstants.HistoryFileName}"); + return; + } + + var historyFile = folderResource.Combine(PackageConstants.HistoryFileName); + var historyMarkdown = PackageHistoryFile.Format(detailsResult.Value.Versions, publishedVersion); + var writeResult = await resourceFileSystem.WriteAllTextAsync(historyFile, historyMarkdown); + if (writeResult.IsFailure) + { + Logger.LogWarning(writeResult, + $"Published '{packageName}' version {publishedVersion} but could not write {PackageConstants.HistoryFileName}"); + } + } + + // The publish source is the package's package.toml; its folder is what gets + // zipped. Accepts either the manifest's own resource key or the folder that + // contains it, so an agent can name whichever it has to hand. + private static async Task> LocatePackageFolderAsync( + ILocalFileSystem fileSystem, + ResourceKey resourceKey, + string resolvedPath) { + var infoResult = await fileSystem.GetInfoAsync(resolvedPath); + if (infoResult.IsFailure + || infoResult.Value.Kind == StorageItemKind.NotFound) + { + return Result.Fail($"Resource not found: '{resourceKey}'."); + } + + ResourceKey folderResource; + string folderPath; + string manifestPath; + if (infoResult.Value.Kind == StorageItemKind.Folder) + { + folderResource = resourceKey; + folderPath = resolvedPath; + manifestPath = Path.Combine(resolvedPath, PackageConstants.ManifestFileName); + } + else + { + var fileName = Path.GetFileName(resolvedPath); + if (!string.Equals(fileName, PackageConstants.ManifestFileName, StringComparison.OrdinalIgnoreCase)) + { + return Result.Fail( + $"Expected the package's '{PackageConstants.ManifestFileName}' manifest or its folder, " + + $"but '{resourceKey}' is a different file."); + } + folderResource = resourceKey.GetParent(); + manifestPath = resolvedPath; + folderPath = Path.GetDirectoryName(resolvedPath)!; + } + var manifestInfoResult = await fileSystem.GetInfoAsync(manifestPath); if (manifestInfoResult.IsFailure || manifestInfoResult.Value.Kind != StorageItemKind.File) @@ -170,6 +181,11 @@ private static async Task ValidatePackageManifestAsync(ILocalFileSystem $"Package manifest not found. Expected '{PackageConstants.ManifestFileName}' in the package folder."); } + return new PackageSource(folderResource, folderPath, manifestPath); + } + + private static async Task> ReadManifestPackageNameAsync(ILocalFileSystem fileSystem, string manifestPath) + { var readResult = await fileSystem.ReadAllTextAsync(manifestPath); if (readResult.IsFailure) { @@ -187,28 +203,91 @@ private static async Task ValidatePackageManifestAsync(ILocalFileSystem return Result.Fail($"Invalid TOML in package manifest: {exception.Message}"); } - if (!tomlTable.TryGetValue("package", out var packageSection) || - packageSection is not TomlTable packageTable) + if (!tomlTable.TryGetValue("package", out var packageSection) + || packageSection is not TomlTable packageTable) { return Result.Fail("Package manifest is missing the required [package] section."); } - if (!packageTable.TryGetValue("name", out var nameValue) || - nameValue is not string nameString || - string.IsNullOrWhiteSpace(nameString)) + if (!packageTable.TryGetValue("name", out var nameValue) + || nameValue is not string nameString + || string.IsNullOrWhiteSpace(nameString)) { return Result.Fail("Package manifest is missing a required 'name' field in the [package] section."); } - // The workshop registers the package under the published name, so a - // manifest that declares a different name would install under an - // identity that disagrees with its own metadata. - if (!string.Equals(nameString, packageName, StringComparison.Ordinal)) + if (!PackageName.IsValid(nameString)) { return Result.Fail( - $"Manifest 'name' value '{nameString}' does not match the package name '{packageName}'."); + $"Package manifest declares an invalid name '{nameString}'. " + + $"Package names must be lowercase alphanumeric with single hyphen separators, 1-{PackageConstants.MaxNameLength} characters."); } - return Result.Ok(); + return nameString; } + + private static async Task> BuildPackageArchiveAsync(ILocalFileSystem fileSystem, string folderPath) + { + int entryCount = 0; + byte[] zipData; + + try + { + using var memoryStream = new MemoryStream(); + using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, leaveOpen: true)) + { + var enumerateResult = await fileSystem.EnumerateAsync(folderPath, "*", recursive: true); + if (enumerateResult.IsFailure) + { + return Result.Fail($"Failed to enumerate package files: {enumerateResult.FirstErrorMessage}"); + } + var fileEntries = enumerateResult.Value + .Where(entry => !entry.IsFolder) + .ToList(); + + foreach (var fileEntry in fileEntries) + { + // Skip symlinks and other reparse points rather than following them. + if (fileEntry.Attributes.HasFlag(FileSystemAttributes.ReparsePoint)) + { + continue; + } + + var filePath = fileEntry.FullPath; + var relativePath = Path.GetRelativePath(folderPath, filePath); + var entryName = relativePath.Replace('\\', '/'); + + // The generated HISTORY.md is a snapshot of the workshop's + // own history, not package content, so it is never published. + if (string.Equals(entryName, PackageConstants.HistoryFileName, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + var entry = zipArchive.CreateEntry(entryName, CompressionLevel.Optimal); + using var entryStream = entry.Open(); + var openResult = await fileSystem.OpenReadAsync(filePath); + if (openResult.IsFailure) + { + return Result.Fail($"Failed to open file for packaging '{filePath}': {openResult.FirstErrorMessage}"); + } + using var sourceStream = openResult.Value; + await sourceStream.CopyToAsync(entryStream); + entryCount++; + } + } + + zipData = memoryStream.ToArray(); + } + catch (System.IO.IOException exception) + { + return Result.Fail($"Failed to create package archive: {exception.Message}"); + } + + return new PackageArchive(zipData, entryCount); + } + + private record class PackageSource(ResourceKey FolderResource, string FolderPath, string ManifestPath); + + private record class PackageArchive(byte[] ZipData, int EntryCount); } diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.RemoveAlias.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.RemoveAlias.cs new file mode 100644 index 000000000..6926b48d3 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.RemoveAlias.cs @@ -0,0 +1,42 @@ +using System.Text.Json; +using ModelContextProtocol.Protocol; +using ModelContextProtocol.Server; + +namespace Celbridge.Tools; + +/// +/// Result returned by package_remove_alias confirming the alias was removed. +/// +public record class PackageRemoveAliasResult(string PackageName, string Alias, bool Removed); + +public partial class PackageTools +{ + /// Remove a workshop package alias; the version it pointed at is unaffected. + [McpServerTool(Name = "package_remove_alias")] + [ToolAlias("package.remove_alias")] + [RelatedGuides("packages_overview")] + public async partial Task RemoveAlias(string packageName, string alias) + { + if (!PackageName.IsValid(packageName)) + { + return ToolResponse.Error(InvalidPackageNameError(packageName)); + } + + var aliasCheck = ValidateAlias(alias); + if (aliasCheck.IsFailure) + { + return ToolResponse.Error(aliasCheck); + } + + var packageApiClient = GetRequiredService(); + var removeResult = await packageApiClient.RemoveAliasAsync(packageName, alias); + if (removeResult.IsFailure) + { + return ToolResponse.Error(removeResult); + } + + var result = new PackageRemoveAliasResult(packageName, alias, true); + var json = JsonSerializer.Serialize(result, JsonOptions); + return ToolResponse.Success(json); + } +} diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.SetAlias.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.SetAlias.cs new file mode 100644 index 000000000..61a61fdcb --- /dev/null +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.SetAlias.cs @@ -0,0 +1,48 @@ +using System.Text.Json; +using ModelContextProtocol.Protocol; +using ModelContextProtocol.Server; + +namespace Celbridge.Tools; + +/// +/// Result returned by package_set_alias with the alias and the version it now +/// points at. +/// +public record class PackageSetAliasResult(string PackageName, string Alias, int Version); + +public partial class PackageTools +{ + /// Create or move a workshop package alias (e.g. stable) to a version. + [McpServerTool(Name = "package_set_alias")] + [ToolAlias("package.set_alias")] + [RelatedGuides("packages_overview")] + public async partial Task SetAlias(string packageName, string alias, int version) + { + if (!PackageName.IsValid(packageName)) + { + return ToolResponse.Error(InvalidPackageNameError(packageName)); + } + + var aliasCheck = ValidateAlias(alias); + if (aliasCheck.IsFailure) + { + return ToolResponse.Error(aliasCheck); + } + + if (version < 1) + { + return ToolResponse.Error($"Invalid version: {version}. Versions are positive integers assigned by the workshop."); + } + + var packageApiClient = GetRequiredService(); + var setResult = await packageApiClient.SetAliasAsync(packageName, alias, version); + if (setResult.IsFailure) + { + return ToolResponse.Error(setResult); + } + + var result = new PackageSetAliasResult(packageName, alias, version); + var json = JsonSerializer.Serialize(result, JsonOptions); + return ToolResponse.Success(json); + } +} diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs index 810835712..919da1761 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs @@ -8,14 +8,37 @@ namespace Celbridge.Tools; [McpServerToolType] public partial class PackageTools : AgentToolBase { + private ILogger? _logger; + public PackageTools(IApplicationServiceProvider services) : base(services) { } + private ILogger Logger => _logger ??= GetRequiredService>(); + private static string InvalidPackageNameError(string packageName) { return $"Invalid package name: '{packageName}'. " + $"Package names must be lowercase alphanumeric with single hyphen separators, 1-{PackageConstants.MaxNameLength} characters."; } + // The 'latest' alias is server-managed, so the curation tools refuse to set + // or remove it. Other aliases follow the conservative package-name rule. + private static Result ValidateAlias(string alias) + { + if (string.Equals(alias, "latest", StringComparison.OrdinalIgnoreCase)) + { + return Result.Fail("The 'latest' alias is managed by the workshop and cannot be set or removed manually."); + } + + if (!PackageName.IsValid(alias)) + { + return Result.Fail( + $"Invalid alias: '{alias}'. " + + $"Aliases must be lowercase alphanumeric with single hyphen separators, 1-{PackageConstants.MaxNameLength} characters."); + } + + return Result.Ok(); + } + private async Task ConfirmActionAsync(string title, string message) { var confirmResultWrapper = await ExecuteCommandAsync(command => diff --git a/Source/Tests/Resources/ResourceKeyTests.cs b/Source/Tests/Resources/ResourceKeyTests.cs index 16e3ed4db..785fd2db3 100644 --- a/Source/Tests/Resources/ResourceKeyTests.cs +++ b/Source/Tests/Resources/ResourceKeyTests.cs @@ -178,6 +178,33 @@ public void CombineValidatesSegments() act3.Should().Throw(); } + [Test] + public void CombinePathAppendsMultipleSegments() + { + var baseKey = new ResourceKey("packages/king-fury"); + + // A multi-segment entry path (the king-fury install regression) combines + // segment by segment rather than being rejected as one bad segment. + baseKey.CombinePath("audio/music.mp3").ToString() + .Should().Be("project:packages/king-fury/audio/music.mp3"); + + // A single segment behaves like Combine. + baseKey.CombinePath("index.html").ToString() + .Should().Be("project:packages/king-fury/index.html"); + + // The root is preserved, and a root-only base key works. + new ResourceKey("temp:").CombinePath("a/b/c.txt").ToString() + .Should().Be("temp:a/b/c.txt"); + + // An invalid segment anywhere in the path is rejected, as Combine rejects it. + var act1 = () => baseKey.CombinePath("audio/bad\0file"); + act1.Should().Throw(); + + // An empty path throws. + var act2 = () => baseKey.CombinePath(""); + act2.Should().Throw(); + } + [Test] public void EmptyKeyIsValid() { diff --git a/Source/Tests/Tools/PackageHistoryFileTests.cs b/Source/Tests/Tools/PackageHistoryFileTests.cs new file mode 100644 index 000000000..ace81704e --- /dev/null +++ b/Source/Tests/Tools/PackageHistoryFileTests.cs @@ -0,0 +1,98 @@ +using Celbridge.Packages; +using Celbridge.Tools; + +namespace Celbridge.Tests.Tools; + +/// +/// Tests for PackageHistoryFile — the HISTORY.md changelog rendered on install +/// and publish, and the installed-version read-back that package_status and the +/// replace confirmation rely on. +/// +[TestFixture] +public class PackageHistoryFileTests +{ + private static RemotePackageVersion MakeVersion( + int version, + string author = "Acme", + string contentHash = "sha256:abc", + string summary = "Change summary.") + { + var date = new DateTime(2026, 6, 13, 0, 0, 0, DateTimeKind.Utc); + return new RemotePackageVersion(version, author, date, Tombstoned: false, contentHash, summary); + } + + [Test] + public void Format_OrdersNewestFirst_SoInstalledVersionIsTheFirstHeading() + { + var versions = new List + { + MakeVersion(1), + MakeVersion(2), + MakeVersion(3), + }; + + var markdown = PackageHistoryFile.Format(versions, installedVersion: 3); + + markdown.Should().StartWith("# 3"); + PackageHistoryFile.TryReadInstalledVersion(markdown).Should().Be(3); + } + + [Test] + public void Format_ExcludesVersionsNewerThanTheInstalledOne() + { + var versions = new List + { + MakeVersion(1), + MakeVersion(2), + MakeVersion(3), + }; + + var markdown = PackageHistoryFile.Format(versions, installedVersion: 2); + + markdown.Should().Contain("# 2"); + markdown.Should().Contain("# 1"); + markdown.Should().NotContain("# 3"); + PackageHistoryFile.TryReadInstalledVersion(markdown).Should().Be(2); + } + + [Test] + public void Format_IncludesAuthorDateContentHashAndSummary() + { + var versions = new List + { + MakeVersion(1, author: "Acme", contentHash: "sha256:abc123", summary: "Initial release."), + }; + + var markdown = PackageHistoryFile.Format(versions, installedVersion: 1); + + markdown.Should().Contain("Published by Acme on 2026-06-13."); + markdown.Should().Contain("sha256:abc123"); + markdown.Should().Contain("Initial release."); + } + + [Test] + public void Format_OmitsContentHashLine_WhenHashIsBlank() + { + var versions = new List + { + MakeVersion(1, contentHash: string.Empty), + }; + + var markdown = PackageHistoryFile.Format(versions, installedVersion: 1); + + markdown.Should().Contain("Published by Acme on 2026-06-13."); + markdown.Should().NotContain("sha256"); + } + + [Test] + public void TryReadInstalledVersion_ReturnsNull_WhenFirstLineIsNotAVersionHeading() + { + PackageHistoryFile.TryReadInstalledVersion("Some hand-authored notes.\r\n").Should().BeNull(); + } + + [Test] + public void TryReadInstalledVersion_ReturnsNull_ForEmptyContent() + { + PackageHistoryFile.TryReadInstalledVersion(string.Empty).Should().BeNull(); + } +} diff --git a/Source/Workspace/Celbridge.Resources/Commands/UnarchiveResourceCommand.cs b/Source/Workspace/Celbridge.Resources/Commands/UnarchiveResourceCommand.cs index b509caca8..28fa68164 100644 --- a/Source/Workspace/Celbridge.Resources/Commands/UnarchiveResourceCommand.cs +++ b/Source/Workspace/Celbridge.Resources/Commands/UnarchiveResourceCommand.cs @@ -148,7 +148,9 @@ private async Task ExecuteExtractAsync() if (!Overwrite) { - var entryResource = DestinationResource.Combine(entryName); + // Combine segment by segment: entry names like "audio/music.mp3" + // are multi-segment relative paths, which Combine alone rejects. + var entryResource = DestinationResource.CombinePath(entryName); var existingInfoResult = await resourceFileSystem.GetInfoAsync(entryResource); if (existingInfoResult.IsSuccess && existingInfoResult.Value.Kind == StorageItemKind.File) @@ -252,7 +254,7 @@ private async Task ExecuteExtractAsync() // Extract files foreach (var entry in validEntries) { - var entryResource = DestinationResource.Combine(entry.FullName); + var entryResource = DestinationResource.CombinePath(entry.FullName); // If overwriting, delete existing file first so it's preserved in trash for undo if (Overwrite) From 48c38576ef65e0f8f9d5d212d4c388ffd1d3f307 Mon Sep 17 00:00:00 2001 From: Chris Gregan Date: Sun, 14 Jun 2026 08:22:08 +0100 Subject: [PATCH 03/21] Add package delete/unpublish/status features Introduce destructive package administration and status tooling plus related plumbing and docs. - Add IPackageApiClient.DeleteVersionAsync and DeletePackageAsync and switch RemotePackageVersion.Tombstoned -> Deleted. API clients now model deleted versions (bytes removed) and server tombstones are not tracked by Celbridge. - Implement package_delete, package_unpublish and package_status tools (with confirmations, irreversible behavior) and wire up JSON result types. - Add PackageConstants.LatestAlias and use it where appropriate. - Update PackageHistoryFile to render a [package_deleted] marker for deleted versions while preserving version metadata. - Extend FileTools.Grep to search non-default roots (temp:, logs:) by walking the gateway, support explicit file lists, and add filename glob filtering for scoped walks. - Update docs and guides (packages_overview, package namespace, tool pages) to document deletion, unpublish, and status behavior and confirmation semantics. - Add and update unit tests to cover new APIs, tools, HISTORY formatting, grep behavior, and package load-failure reporting. These changes add irreversible delete/unpublish operations (always prompt) and expose package_status for local project inspection; they also improve grep scope handling and ensure deleted versions remain verifiable via retained metadata. --- .../Resources/Strings/en-US/Resources.resw | 19 ++- .../Packages/IPackageApiClient.cs | 23 ++- .../Packages/IPackageService.cs | 7 + .../Packages/PackageConstants.cs | 20 +-- .../Guides/Concepts/packages_overview.md | 6 + .../Guides/Namespaces/package.md | 7 +- .../Celbridge.Tools/Guides/Tools/file_grep.md | 2 + .../Guides/Tools/package_delete.md | 27 ++++ .../Guides/Tools/package_status.md | 22 +++ .../Guides/Tools/package_unpublish.md | 21 +++ .../Helpers/PackageHistoryFile.cs | 30 ++-- .../Tools/File/FileTools.Grep.cs | 139 +++++++++++++++++- .../Tools/Package/PackageTools.Delete.cs | 89 +++++++++++ .../Tools/Package/PackageTools.Info.cs | 4 +- .../Tools/Package/PackageTools.Install.cs | 59 +------- .../Tools/Package/PackageTools.Status.cs | 95 ++++++++++++ .../Tools/Package/PackageTools.Unpublish.cs | 51 +++++++ .../Tools/Package/PackageTools.cs | 2 +- .../Tests/Packages/FileTypeProviderTests.cs | 7 + .../Tests/Packages/PackageApiClientTests.cs | 68 ++++++++- Source/Tests/Packages/PackageRegistryTests.cs | 43 ++++++ Source/Tests/Tools/FileToolTests.cs | 72 +++++++++ Source/Tests/Tools/PackageHistoryFileTests.cs | 41 +++++- .../Tools/PackageVersionResolverTests.cs | 127 ++++++++++++++++ .../Services/PackageApiClient.cs | 74 +++++++++- .../Services/PackageRegistry.cs | 90 ++++++++---- .../Services/PackageService.cs | 5 + 27 files changed, 1018 insertions(+), 132 deletions(-) create mode 100644 Source/Core/Celbridge.Tools/Guides/Tools/package_delete.md create mode 100644 Source/Core/Celbridge.Tools/Guides/Tools/package_status.md create mode 100644 Source/Core/Celbridge.Tools/Guides/Tools/package_unpublish.md create mode 100644 Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Delete.cs create mode 100644 Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Status.cs create mode 100644 Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Unpublish.cs create mode 100644 Source/Tests/Tools/PackageVersionResolverTests.cs diff --git a/Source/Celbridge/Resources/Strings/en-US/Resources.resw b/Source/Celbridge/Resources/Strings/en-US/Resources.resw index 822749826..4953bb608 100644 --- a/Source/Celbridge/Resources/Strings/en-US/Resources.resw +++ b/Source/Celbridge/Resources/Strings/en-US/Resources.resw @@ -1035,10 +1035,25 @@ Do you wish to continue? Replace Package - The folder '{0}' already contains package '{1}' (version {2}). Installing version {3} will replace its contents and discard any local changes. The replaced files are moved to the trash. Continue? + '{0}' already contains package '{1}' (version {2}). Installing version {3} replaces its contents, moving the current files to the trash. Continue? - The folder '{0}' already contains package '{1}'. Installing version {2} will replace its contents and discard any local changes. The replaced files are moved to the trash. Continue? + '{0}' already contains package '{1}'. Installing version {2} replaces its contents, moving the current files to the trash. Continue? + + + Delete Package Version + + + Delete version {0} of package '{1}' from the workshop? Its content is removed permanently and cannot be recovered. + + + Delete version {0} of package '{1}' from the workshop? Its content is removed permanently and cannot be recovered. Aliases pointing at it will be left dangling or repointed: {2}. + + + Unpublish Package + + + Unpublish package '{0}' from the workshop? The package and all its versions are removed permanently and cannot be recovered. Spreadsheet Editor diff --git a/Source/Core/Celbridge.Foundation/Packages/IPackageApiClient.cs b/Source/Core/Celbridge.Foundation/Packages/IPackageApiClient.cs index 48f049613..8646ad352 100644 --- a/Source/Core/Celbridge.Foundation/Packages/IPackageApiClient.cs +++ b/Source/Core/Celbridge.Foundation/Packages/IPackageApiClient.cs @@ -17,13 +17,15 @@ public record RemotePackageSummary( /// /// A single immutable version of a workshop package. Version numbers are -/// assigned by the server in publish order. +/// assigned by the server in publish order. Deleted is true once the version's +/// bytes have been removed; the version record, number, and content hash are +/// retained so vendored copies stay verifiable. /// public record RemotePackageVersion( int Version, string Author, DateTime Date, - bool Tombstoned, + bool Deleted, string ContentHash, string Summary); @@ -54,8 +56,8 @@ public record RemotePublishReceipt( /// Client for the workshop server's package REST API. The Workshop URL and /// Application Key are read from the credential store at request time; /// credential values never appear in parameters, results, or error messages. -/// Destructive administrative operations (tombstoning a version, deleting a -/// package) are deliberately not part of this surface. +/// Destructive operations (deleting a version, deleting a package) remove +/// content outright; Celbridge does not model the server's tombstone state. /// public interface IPackageApiClient { @@ -95,6 +97,19 @@ public interface IPackageApiClient /// Task RemoveAliasAsync(string packageName, string alias); + /// + /// Deletes a published version, removing its bytes. The version record and + /// content hash are retained so the number is never reused and vendored + /// copies stay verifiable. Irreversible from the client. + /// + Task DeleteVersionAsync(string packageName, int version); + + /// + /// Deletes a whole package and all its versions from the workshop. + /// Irreversible from the client. + /// + Task DeletePackageAsync(string packageName); + /// /// Gets the plain text publish history of a package as of the given version. /// diff --git a/Source/Core/Celbridge.Foundation/Packages/IPackageService.cs b/Source/Core/Celbridge.Foundation/Packages/IPackageService.cs index 4d411c875..e9e06e80d 100644 --- a/Source/Core/Celbridge.Foundation/Packages/IPackageService.cs +++ b/Source/Core/Celbridge.Foundation/Packages/IPackageService.cs @@ -31,6 +31,13 @@ public interface IPackageService /// IReadOnlyList GetAllPackages(); + /// + /// Returns the package load failures from the most recent discovery pass, + /// so a status query can surface them after the load-time error banner has + /// fired. Empty before the first discovery. + /// + IReadOnlyList GetLoadFailures(); + /// /// Returns all document editor contributions from all discovered packages. /// diff --git a/Source/Core/Celbridge.Foundation/Packages/PackageConstants.cs b/Source/Core/Celbridge.Foundation/Packages/PackageConstants.cs index 65aab997d..bc08ac9eb 100644 --- a/Source/Core/Celbridge.Foundation/Packages/PackageConstants.cs +++ b/Source/Core/Celbridge.Foundation/Packages/PackageConstants.cs @@ -1,42 +1,42 @@ namespace Celbridge.Packages; /// -/// Constants shared across the package system: well-known file and folder -/// names, the reserved name prefix, and validation limits. +/// Well-known names and validation limits shared across the package system. /// public static class PackageConstants { /// - /// File name of the package manifest at the root of every package folder. + /// File name of the package manifest at the root of a package folder. /// public const string ManifestFileName = "package.toml"; /// /// Default install folder for packages, relative to the project root. - /// This is a convention, not a constraint: packages may install elsewhere. /// public const string DefaultPackagesFolder = "packages"; /// - /// File name of the generated version history written beside the manifest - /// when a package is installed. Excluded (case-insensitively) on publish. + /// File name of the generated version-history changelog beside a package's manifest. /// public const string HistoryFileName = "HISTORY.md"; /// - /// Name prefix reserved for first-party packages shipped inside Celbridge - /// module DLLs. Project packages may not claim names under this prefix. + /// Name prefix reserved for first-party packages shipped inside Celbridge module DLLs. /// public const string ReservedNamePrefix = "celbridge."; /// - /// Maximum length of a package name, enforced by the PackageName validator. + /// Name of the server-managed alias that points at a package's highest live version. + /// + public const string LatestAlias = "latest"; + + /// + /// Maximum length of a package name. /// public const int MaxNameLength = 64; /// /// Maximum length of the change summary accompanying a published version. - /// Over-long summaries are rejected, never truncated. /// public const int MaxSummaryLength = 512; } diff --git a/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md b/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md index b3a64801b..0b84ad033 100644 --- a/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md +++ b/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md @@ -28,6 +28,8 @@ A package name is lowercase ASCII alphanumeric with single interior hyphens as t The workshop models a package as a container of immutable, server-numbered versions (1, 2, 3, ...). Named **aliases** (`latest`, `stable`, ...) point at versions; `latest` is managed by the workshop, others are publisher-defined. `package_info` returns both lists. +A version can be **deleted** (`package_delete`), which removes its content bytes permanently. The version number, date, and content hash are retained — the number is never reused, and a vendored copy stays verifiable — but the bytes are gone. Celbridge does not model the server's hidden tombstone state. A deleted version still appears in `HISTORY.md` with its heading and metadata, rendering `[package_deleted]` in place of its summary, so the gap in the numbering is explained rather than silent. Durability rests on consumers vendoring what they depend on, not on the workshop promising eternal availability. + When you install a package, its workshop history is written to a generated `HISTORY.md` beside the manifest (newest first); `package_publish` writes the same file for the version it assigns. This is metadata about the workshop, not package content — it is excluded from uploads, and the workshop stays authoritative for publish history. The installed (or last-published) version is recorded in `HISTORY.md`, which is where `package_status` reads it from. ## Workshop workflow @@ -40,6 +42,8 @@ When you install a package, its workshop history is written to a generated `HIST | `package_publish("packages/name/package.toml", summary)` | Validate and publish a new version; name read from the manifest | | `package_set_alias("name", "stable", 3)` | Point an alias at a version | | `package_remove_alias("name", "stable")` | Remove an alias | +| `package_delete("name", "3")` | Delete one version permanently (always confirms) | +| `package_unpublish("name")` | Remove a whole package and every version (always confirms) | `package_publish` reads the published name from the manifest's `[package].name`, so the source folder can have any name and live under any readable root, including a `temp:` staging area. @@ -47,4 +51,6 @@ When you install a package, its workshop history is written to a generated `HIST `package_publish` and `package_install` are destructive and confirm by default. Both accept `confirmWithUser` (default `true`); pass `false` only when the user has explicitly asked for unattended operation. Reinstalling over an existing package folder replaces its contents — the replaced files route through the resource trash, so the change is recoverable. Alias curation is non-destructive and is not gated. +`package_delete` and `package_unpublish` remove workshop content permanently and **always confirm** — they have no `confirmWithUser` opt-out, because the bytes cannot be recovered through the workshop. They are held to a firmer bar than `package_install`/`package_publish` (which are reversible via trash or re-install) for that reason. + For the JS proxy conventions and `[permissions] tools` declarations packages need at runtime, see `agent_instructions`. diff --git a/Source/Core/Celbridge.Tools/Guides/Namespaces/package.md b/Source/Core/Celbridge.Tools/Guides/Namespaces/package.md index 6150bce34..4c2714921 100644 --- a/Source/Core/Celbridge.Tools/Guides/Namespaces/package.md +++ b/Source/Core/Celbridge.Tools/Guides/Namespaces/package.md @@ -7,8 +7,10 @@ The `package` namespace installs, inspects, publishes, archives, and curates Cel - **Publishing and installing are interactive by default.** `package_publish` and `package_install` confirm with the user before mutating the workshop or the project. Pass `confirmWithUser: false` only for unattended flows the user has consented to. See `silent_vs_interactive`. - **`package_install` requires a loaded project.** Installing without a project loaded fails fast. - **Install anywhere, but only `project:` loads.** A package installs into a `{packageName}` subfolder of the destination you choose (default `packages/`). Copies installed to non-loading roots such as `temp:` are inert reference data for comparison and merge workflows. +- **`package_status` is the local installed-package map.** It reports each project package's name, version, and folder, plus any load failures (such as a duplicate-name fault). Use it to decide install locations and to diagnose why a package is not loading; it reads no workshop. - **The package name comes from the manifest.** `package_publish` reads the name from `[package].name`; there is no folder-name rule and no separate name argument. -- **Aliases are non-destructive.** `package_set_alias` and `package_remove_alias` only repoint or detach a label; they never touch version content. Destructive administration (tombstone, delete) is deliberately not exposed. +- **Aliases are non-destructive.** `package_set_alias` and `package_remove_alias` only repoint or detach a label; they never touch version content. +- **Delete and unpublish are destructive and always confirm.** `package_delete` (one version) and `package_unpublish` (the whole package) remove content permanently and prompt every time — there is no `confirmWithUser` opt-out. Celbridge does not model the server's hidden tombstone state; deleted bytes are gone, though the version's history entry and content hash are kept. Durability rests on consumers vendoring, not on the workshop promising eternal availability. - **Packages are not Python packages.** Despite some tooling overlap, this namespace is for Celbridge's own package format. To see the project's Python dependencies, read the `.celbridge` project file (`[project].dependencies`). - **There is no create tool.** A package is a folder with a `package.toml` manifest; scaffold one by writing the manifest with the file tools. See `packages_overview` for the manifest schema. @@ -20,5 +22,8 @@ The `package` namespace installs, inspects, publishes, archives, and curates Cel - `package_publish` — publish a new version from a package folder; name read from the manifest. Interactive by default. - `package_set_alias` — create or move an alias (e.g. `stable`) to a version. - `package_remove_alias` — remove an alias; the version it pointed at is unaffected. +- `package_delete` — delete one published version; its content is removed permanently. Always confirms. +- `package_unpublish` — remove a whole package and all its versions. Always confirms. +- `package_status` — report the project's installed packages (name, version, folder) and any load failures. Local; reads no workshop. - `package_archive` — archive a folder into a zip file. - `package_unarchive` — extract a zip archive into a folder. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/file_grep.md b/Source/Core/Celbridge.Tools/Guides/Tools/file_grep.md index e081cda88..85d5f48d5 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/file_grep.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/file_grep.md @@ -35,6 +35,8 @@ Wraps the search term in `\b` boundaries. Ignored when `useRegex` is true — wr A folder resource key. Only files within this folder (and its descendants) are searched. Empty string searches the whole project. +A scope under a non-default root (e.g. `temp:staging/my-pkg`, `logs:`) is searched too — the tool walks that root's tree directly. This is what makes a staging-folder comparison work (install a package version to `temp:` and grep it against the local copy). `include`/`exclude` still apply. Note that non-default roots are not part of the loading project, so they are not covered by an empty-scope whole-project search; name the root explicitly to search it. + ### include / exclude Comma-separated glob lists matched against file names: `"*.cs,*.xaml"`, `"*.generated.cs,*.g.cs"`. `exclude` wins when a file matches both. Globs follow the project-wide convention — see `file_search` for `**` semantics. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_delete.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_delete.md new file mode 100644 index 000000000..ad2ca0787 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_delete.md @@ -0,0 +1,27 @@ +# package_delete + +Deletes a single published version of a package from the workshop. The version's content (its ZIP bytes) is removed permanently and cannot be downloaded again. The version's history entry and content hash are retained, so the number is never reused and a vendored copy stays verifiable, but the bytes are gone — this is a deletion, not the server's hidden tombstone state. + +This is destructive administration and **always prompts for confirmation**: there is no `confirmWithUser` opt-out, unlike `package_install` and `package_publish`. The bar is deliberately firmer than `page_unpublish` because deleted version bytes are not recoverable through the workshop, whereas a page is re-publishable static content. + +## Parameters + +### packageName + +The name as published on the workshop (lowercase alphanumeric with single hyphen separators, 1-64 characters). + +### version + +The version to delete, **required** — there is no default, because a destructive call must name its target. Accepts the same selectors as `package_install`: a version number (`"3"`), an alias (`"stable"`), or `"latest"` (the highest live version). The resolved version is named in the confirmation prompt. + +## Returns + +A JSON object echoing `packageName`, the resolved `version`, and `deleted: true`. + +## Gotchas + +- **No default target.** Calling without a version is an error; name the number or alias explicitly. +- **Aliases that point at the deleted version are surfaced in the confirmation.** `latest` is server-managed and repoints to the highest remaining version; whether a publisher alias such as `stable` is detached or repointed is a server behaviour. The prompt lists the affected aliases so the consequence is clear before you confirm. +- **Deleting an already-deleted version reports that state** rather than failing silently. +- **Durability is the consumer's responsibility.** The workshop does not promise eternal availability; a consumer who needs a version permanently should vendor it. See `packages_overview`. +- To remove the entire package and every version at once, use `package_unpublish`. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_status.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_status.md new file mode 100644 index 000000000..71a2c0e6a --- /dev/null +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_status.md @@ -0,0 +1,22 @@ +# package_status + +Reports the project's package state, read locally — it does not contact the workshop. For each discovered project package it returns the name, the installed version, and the folder it lives in; it also lists any packages that failed to load, with the reason. With packages installable anywhere under the project (not just `packages/`), this is how an agent learns what is installed where before choosing an install destination or repairing a duplicate-name fault. + +Only project packages are reported. Bundled packages that ship inside the application are not part of the project's state and are omitted, as are copies installed to non-loading roots such as `temp:` (those never load). + +## Parameters + +None. + +## Returns + +A JSON object with two arrays: + +- `packages` — each loaded project package: `name`, `version` (read from the package's `HISTORY.md`; `null` when the package was hand-authored and never installed from a workshop, so no history exists), and `folder` (the resource key of the package folder, e.g. `project:packages/my-widget`). +- `failures` — each manifest that failed to load: `name` (may be `null` when the manifest could not be parsed), `folder` (resource key), `reason` (e.g. `DuplicateName`, `InvalidManifest`, `ReservedNamePrefix`, `UnregisteredNamespace`, `ReservedExtension`), and an optional `detail`. + +## Gotchas + +- **A `DuplicateName` failure means two manifests claim the same name, and all of them are skipped** — none loads until the conflict is resolved. Move, rename, or remove one of the colliding folders, then reload the project. +- **`version` reflects the installed/published version recorded in `HISTORY.md`**, not a manifest field — the manifest carries no authoritative version under the workshop model. A `null` version is normal for a package authored in place. +- The reported state is from the last project load; install or publish actions taken in this session are reflected after the project re-scans. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_unpublish.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_unpublish.md new file mode 100644 index 000000000..b36e9a171 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_unpublish.md @@ -0,0 +1,21 @@ +# package_unpublish + +Removes a whole package and all its versions from the workshop, permanently. This is the package counterpart of `page_unpublish`: where `package_delete` removes one version, `package_unpublish` removes the package itself and every version it holds. + +This is destructive administration and **always prompts for confirmation**: there is no `confirmWithUser` opt-out. The content bytes are not recoverable through the workshop afterward. + +## Parameters + +### packageName + +The name as published on the workshop (lowercase alphanumeric with single hyphen separators, 1-64 characters). + +## Returns + +A JSON object echoing `packageName` and `unpublished: true`. + +## Gotchas + +- **This removes every version, not just the latest.** To remove a single version and keep the rest, use `package_delete`. +- **Irreversible through the workshop.** Durability rests on consumers vendoring the content they depend on, not on the workshop retaining it. See `packages_overview`. +- Unpublishing a package does not touch any page; pages are a separate, decoupled subsystem. diff --git a/Source/Core/Celbridge.Tools/Helpers/PackageHistoryFile.cs b/Source/Core/Celbridge.Tools/Helpers/PackageHistoryFile.cs index 5df1498b9..996a1e35a 100644 --- a/Source/Core/Celbridge.Tools/Helpers/PackageHistoryFile.cs +++ b/Source/Core/Celbridge.Tools/Helpers/PackageHistoryFile.cs @@ -9,6 +9,9 @@ namespace Celbridge.Tools; /// internal static class PackageHistoryFile { + // Marker text rendered in place of a deleted version's summary. + private const string DeletedVersionSummary = "[package_deleted]"; + /// /// Builds the HISTORY.md changelog from the package's versions, covering /// every version up to and including the installed one, newest first. @@ -30,10 +33,6 @@ public static string Format(IReadOnlyList versions, int in builder.Append("# "); builder.Append(packageVersion.Version.ToString(CultureInfo.InvariantCulture)); - if (packageVersion.Tombstoned) - { - builder.Append(" (tombstoned)"); - } builder.Append("\r\n\r\n"); var date = packageVersion.Date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); @@ -57,14 +56,27 @@ public static string Format(IReadOnlyList versions, int in builder.Append("\r\n"); } - var summary = packageVersion.Summary?.Trim() ?? string.Empty; - if (summary.Length > 0) + if (packageVersion.Deleted) { - var normalizedSummary = summary.Replace("\r\n", "\n").Replace("\n", "\r\n"); + // A deleted version has lost its bytes and publisher summary, so + // the marker stands in for the summary. Its heading, date, and + // content hash (emitted above) remain, so the version reads as + // removed rather than as a gap in the numbering. builder.Append("\r\n"); - builder.Append(normalizedSummary); + builder.Append(DeletedVersionSummary); builder.Append("\r\n"); } + else + { + var summary = packageVersion.Summary?.Trim() ?? string.Empty; + if (summary.Length > 0) + { + var normalizedSummary = summary.Replace("\r\n", "\n").Replace("\n", "\r\n"); + builder.Append("\r\n"); + builder.Append(normalizedSummary); + builder.Append("\r\n"); + } + } } return builder.ToString(); @@ -95,7 +107,7 @@ public static string Format(IReadOnlyList versions, int in // The first non-empty line must be the newest version's heading. var headingText = trimmed.TrimStart('#').Trim(); - // Drop any trailing note such as "(tombstoned)". + // Drop any trailing note after the version number. var spaceIndex = headingText.IndexOf(' '); if (spaceIndex > 0) { diff --git a/Source/Core/Celbridge.Tools/Tools/File/FileTools.Grep.cs b/Source/Core/Celbridge.Tools/Tools/File/FileTools.Grep.cs index 529f1ae38..25853d061 100644 --- a/Source/Core/Celbridge.Tools/Tools/File/FileTools.Grep.cs +++ b/Source/Core/Celbridge.Tools/Tools/File/FileTools.Grep.cs @@ -62,13 +62,32 @@ public async partial Task Grep(string searchTerm, bool useRegex } var workspaceWrapper = GetRequiredService(); - var resourceFileSystem = workspaceWrapper.WorkspaceService.ResourceService.FileSystem; + var resourceService = workspaceWrapper.WorkspaceService.ResourceService; + var resourceFileSystem = resourceService.FileSystem; if (!string.IsNullOrEmpty(files)) { return await GrepTargetedFiles(files, searchTerm, useRegex, matchCase, wholeWord, maxResults, contextLines, includeContent, summaryOnly, resourceFileSystem); } + // File enumeration has two sources, picked by root. The project root has + // a pre-built registry index (already ignore-filtered and sorted, rebuilt + // on resource changes rather than per search), which SearchService walks + // below and the live Search panel shares. Other roots (temp:, logs:) are + // not indexed, so an explicitly-named non-default-root scope is walked + // live through the gateway here and matched the same way the files= path + // is. Bare and project: scopes fall through to the indexed walk. + var rootHandlerRegistry = resourceService.RootHandlers; + if (!string.IsNullOrEmpty(resource) + && ResourceKey.TryCreate(resource, out var scopeKey) + && scopeKey.Root != ResourceKey.DefaultRoot + && rootHandlerRegistry.RootHandlers.ContainsKey(scopeKey.Root)) + { + return await GrepNonDefaultRootScopeAsync( + scopeKey, searchTerm, useRegex, matchCase, wholeWord, include, exclude, + maxResults, contextLines, includeContent, summaryOnly, resourceFileSystem); + } + var searchService = workspaceWrapper.WorkspaceService.SearchService; var results = await searchService.SearchAsync( @@ -247,6 +266,85 @@ private async Task GrepTargetedFiles(string filesJson, string se return ToolResponse.Error("No resource keys provided in files parameter."); } + // The result's Resource field echoes the caller's exact key string, so a + // round-trip through ResourceKey does not canonicalize what the agent passed. + var targets = new List<(ResourceKey Key, string Display)>(); + foreach (var fileKeyString in fileKeyStrings) + { + if (ResourceKey.TryCreate(fileKeyString, out var fileResourceKey)) + { + targets.Add((fileResourceKey, fileKeyString)); + } + } + + return await GrepFileListAsync(targets, searchTerm, useRegex, matchCase, wholeWord, maxResults, contextLines, includeContent, summaryOnly, resourceFileSystem); + } + + // Enumerates a non-default root (temp:, logs:) live through the gateway, + // since only the project root carries the registry index SearchService uses. + // The files found are matched the same way the files= path matches, with + // include/exclude applied to the file name. + private async Task GrepNonDefaultRootScopeAsync( + ResourceKey scopeFolder, + string searchTerm, + bool useRegex, + bool matchCase, + bool wholeWord, + string include, + string exclude, + int maxResults, + int contextLines, + bool includeContent, + bool summaryOnly, + IResourceFileSystem resourceFileSystem) + { + var entries = new List(); + await CollectRecursiveAsync(resourceFileSystem, scopeFolder, entries); + + var includeRegex = BuildFileNameGlobRegex(include); + var excludeRegex = BuildFileNameGlobRegex(exclude); + + var targets = new List<(ResourceKey Key, string Display)>(); + foreach (var entry in entries) + { + if (entry.IsFolder) + { + continue; + } + + var fileName = entry.Resource.ResourceName; + if (includeRegex is not null + && !includeRegex.IsMatch(fileName)) + { + continue; + } + if (excludeRegex is not null + && excludeRegex.IsMatch(fileName)) + { + continue; + } + + targets.Add((entry.Resource, entry.Resource.ToString())); + } + + return await GrepFileListAsync(targets, searchTerm, useRegex, matchCase, wholeWord, maxResults, contextLines, includeContent, summaryOnly, resourceFileSystem); + } + + // Greps an explicit list of files, accumulating matches up to maxResults. + // Shared by the targeted files= path and the non-default-root scope walk; + // each target carries the display string used for its result Resource field. + private async Task GrepFileListAsync( + List<(ResourceKey Key, string Display)> targets, + string searchTerm, + bool useRegex, + bool matchCase, + bool wholeWord, + int maxResults, + int contextLines, + bool includeContent, + bool summaryOnly, + IResourceFileSystem resourceFileSystem) + { var searchPattern = useRegex ? searchTerm : Regex.Escape(searchTerm); if (wholeWord && !useRegex) { @@ -260,17 +358,14 @@ private async Task GrepTargetedFiles(string filesJson, string se int totalMatches = 0; bool truncated = false; - foreach (var fileKeyString in fileKeyStrings) + foreach (var target in targets) { if (truncated) { break; } - if (!ResourceKey.TryCreate(fileKeyString, out var fileResourceKey)) - { - continue; - } + var fileResourceKey = target.Key; var infoResult = await resourceFileSystem.GetInfoAsync(fileResourceKey); if (infoResult.IsFailure @@ -345,7 +440,7 @@ private async Task GrepTargetedFiles(string filesJson, string se } fileResults.Add(new GrepFileResult( - fileKeyString, + target.Display, fileResourceKey.ResourceName, fileMatchCount, matchList, @@ -356,4 +451,34 @@ private async Task GrepTargetedFiles(string filesJson, string se var grepResult = new GrepResult(totalMatches, fileResults.Count, truncated, fileResults); return BuildGrepResponse(grepResult); } + + // Builds a case-insensitive regex from a comma-separated glob list matched + // against a file name (e.g. "*.js,*.css"), or null when the list is empty. + // Mirrors the project-walk's include/exclude semantics so scoped grep over a + // non-default root filters the same way. + private static Regex? BuildFileNameGlobRegex(string globList) + { + if (string.IsNullOrEmpty(globList)) + { + return null; + } + + var patterns = globList.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + if (patterns.Length == 0) + { + return null; + } + + var alternatives = new List(); + foreach (var pattern in patterns) + { + var escaped = Regex.Escape(pattern) + .Replace("\\*", ".*") + .Replace("\\?", "."); + alternatives.Add($"(?:^{escaped}$)"); + } + + var combined = string.Join("|", alternatives); + return new Regex(combined, RegexOptions.IgnoreCase); + } } diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Delete.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Delete.cs new file mode 100644 index 000000000..57db31430 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Delete.cs @@ -0,0 +1,89 @@ +using System.Text.Json; +using ModelContextProtocol.Protocol; +using ModelContextProtocol.Server; + +namespace Celbridge.Tools; + +/// +/// Result returned by package_delete confirming the version was deleted. +/// +public record class PackageDeleteResult(string PackageName, int Version, bool Deleted); + +public partial class PackageTools +{ + /// Delete a published package version from the workshop, removing its content permanently. + [McpServerTool(Name = "package_delete", Destructive = true)] + [ToolAlias("package.delete")] + [RelatedGuides("packages_overview")] + public async partial Task Delete(string packageName, string version) + { + if (!PackageName.IsValid(packageName)) + { + return ToolResponse.Error(InvalidPackageNameError(packageName)); + } + + if (string.IsNullOrWhiteSpace(version)) + { + return ToolResponse.Error("A version number or alias is required: package_delete has no default target."); + } + + var packageApiClient = GetRequiredService(); + + var detailsResult = await packageApiClient.GetPackageAsync(packageName); + if (detailsResult.IsFailure) + { + return ToolResponse.Error(detailsResult); + } + var packageDetails = detailsResult.Value; + + var resolveResult = PackageVersionResolver.ResolveForDelete(packageDetails, version.Trim()); + if (resolveResult.IsFailure) + { + return ToolResponse.Error(resolveResult); + } + var resolvedVersion = resolveResult.Value; + + // Aliases pointing at the deleted version are left dangling (or repointed + // by the server, depending on the alias), so the confirmation names them. + var danglingAliases = packageDetails.Aliases + .Where(packageAlias => packageAlias.Version == resolvedVersion) + .Select(packageAlias => packageAlias.Alias) + .ToList(); + + var confirmed = await ConfirmDeleteVersionAsync(packageName, resolvedVersion, danglingAliases); + if (!confirmed) + { + return ToolResponse.Error("Delete cancelled by user."); + } + + var deleteResult = await packageApiClient.DeleteVersionAsync(packageName, resolvedVersion); + if (deleteResult.IsFailure) + { + return ToolResponse.Error(deleteResult); + } + + var result = new PackageDeleteResult(packageName, resolvedVersion, true); + var json = JsonSerializer.Serialize(result, JsonOptions); + return ToolResponse.Success(json); + } + + private async Task ConfirmDeleteVersionAsync(string packageName, int version, IReadOnlyList danglingAliases) + { + var localizerService = GetRequiredService(); + + var title = localizerService.GetString("Package_DeleteConfirm_Title"); + + string message; + if (danglingAliases.Count > 0) + { + var aliasList = string.Join(", ", danglingAliases); + message = localizerService.GetString("Package_DeleteConfirm_MessageWithAliases", version, packageName, aliasList); + } + else + { + message = localizerService.GetString("Package_DeleteConfirm_Message", version, packageName); + } + + return await ConfirmActionAsync(title, message); + } +} diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Info.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Info.cs index bbae4a2d4..c21f371f5 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Info.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Info.cs @@ -11,7 +11,7 @@ public record class PackageVersionEntry( int Version, string Author, DateTime Date, - bool Tombstoned, + bool Deleted, string ContentHash, string Summary); @@ -57,7 +57,7 @@ public async partial Task Info(string packageName) packageVersion.Version, packageVersion.Author, packageVersion.Date, - packageVersion.Tombstoned, + packageVersion.Deleted, packageVersion.ContentHash, packageVersion.Summary); versions.Add(entry); diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs index 975cc8fe8..9be0b64c0 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs @@ -18,7 +18,7 @@ public partial class PackageTools [RelatedGuides("packages_overview", "resource_keys", "silent_vs_interactive")] public async partial Task Install( string packageName, - string version = "latest", + string version = PackageConstants.LatestAlias, string destination = "", bool confirmWithUser = true) { @@ -84,8 +84,8 @@ public async partial Task Install( } var packageDetails = detailsResult.Value; - var requestedVersion = string.IsNullOrWhiteSpace(version) ? "latest" : version.Trim(); - var resolveVersionResult = ResolveInstallVersion(packageDetails, requestedVersion); + var requestedVersion = string.IsNullOrWhiteSpace(version) ? PackageConstants.LatestAlias : version.Trim(); + var resolveVersionResult = PackageVersionResolver.ResolveForInstall(packageDetails, requestedVersion); if (resolveVersionResult.IsFailure) { return ToolResponse.Error(resolveVersionResult); @@ -181,59 +181,6 @@ public async partial Task Install( return ToolResponse.Success(json); } - // Resolves the requested version string to a concrete live version number. - // "latest" selects the highest non-tombstoned version; a numeric string - // selects that exact version; anything else is treated as an alias name. - private static Result ResolveInstallVersion(RemotePackageDetails details, string requestedVersion) - { - if (string.Equals(requestedVersion, "latest", StringComparison.OrdinalIgnoreCase)) - { - var liveVersions = details.Versions - .Where(packageVersion => !packageVersion.Tombstoned) - .ToList(); - if (liveVersions.Count == 0) - { - return Result.Fail($"Package '{details.Name}' has no live version to install."); - } - - return liveVersions.Max(packageVersion => packageVersion.Version); - } - - if (int.TryParse(requestedVersion, out var explicitVersion)) - { - var match = details.Versions.FirstOrDefault(packageVersion => packageVersion.Version == explicitVersion); - if (match is null) - { - return Result.Fail($"Version {explicitVersion} not found for package '{details.Name}'."); - } - if (match.Tombstoned) - { - return Result.Fail($"Version {explicitVersion} of package '{details.Name}' has been tombstoned and cannot be installed."); - } - - return explicitVersion; - } - - var alias = details.Aliases.FirstOrDefault(packageAlias => - string.Equals(packageAlias.Alias, requestedVersion, StringComparison.Ordinal)); - if (alias is null) - { - return Result.Fail($"'{requestedVersion}' is not a version number or a known alias for package '{details.Name}'."); - } - - var aliasTarget = details.Versions.FirstOrDefault(packageVersion => packageVersion.Version == alias.Version); - if (aliasTarget is null) - { - return Result.Fail($"Alias '{requestedVersion}' points at version {alias.Version}, which does not exist."); - } - if (aliasTarget.Tombstoned) - { - return Result.Fail($"Alias '{requestedVersion}' points at version {alias.Version}, which has been tombstoned."); - } - - return alias.Version; - } - private static Result CheckForDuplicateProjectPackage( IPackageService packageService, IResourceRegistry resourceRegistry, diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Status.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Status.cs new file mode 100644 index 000000000..cf91712a9 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Status.cs @@ -0,0 +1,95 @@ +using System.Text.Json; +using Celbridge.Packages; +using ModelContextProtocol.Protocol; +using ModelContextProtocol.Server; + +namespace Celbridge.Tools; + +/// +/// A loaded project package in the package_status result. Version is null when +/// it cannot be read from the package's HISTORY.md (e.g. a hand-authored package +/// that was never installed from a workshop). +/// +public record class PackageStatusEntry(string Name, int? Version, string Folder); + +/// +/// A package that failed to load in the package_status result, with the folder +/// the manifest lives in and the reason it was rejected. +/// +public record class PackageStatusFailure(string? Name, string Folder, string Reason, string? Detail); + +/// +/// Result returned by package_status: the discovered project packages and any +/// load failures (including duplicate-name faults). +/// +public record class PackageStatusResult( + IReadOnlyList Packages, + IReadOnlyList Failures); + +public partial class PackageTools +{ + /// Report the project's installed packages, their versions and folders, and any load failures. + [McpServerTool(Name = "package_status", ReadOnly = true)] + [ToolAlias("package.status")] + [RelatedGuides("packages_overview")] + public async partial Task Status() + { + var workspaceWrapper = GetRequiredService(); + if (!workspaceWrapper.IsWorkspacePageLoaded) + { + return ToolResponse.Error("No project is loaded. Open a project before checking package status."); + } + + var workspaceService = workspaceWrapper.WorkspaceService; + var packageService = workspaceService.PackageService; + var resourceService = workspaceService.ResourceService; + var resourceRegistry = resourceService.Registry; + var resourceFileSystem = resourceService.FileSystem; + + var packages = new List(); + foreach (var package in packageService.GetAllPackages()) + { + // Only project packages participate in discovery. Bundled packages + // ship inside the app and are not part of the project's state. + if (package.Info.Origin != PackageOrigin.Project) + { + continue; + } + + var folderKeyResult = resourceRegistry.GetResourceKey(package.Info.PackageFolder); + if (folderKeyResult.IsFailure) + { + continue; + } + var folderKey = folderKeyResult.Value; + + var version = await TryReadInstalledVersionAsync(resourceFileSystem, folderKey); + packages.Add(new PackageStatusEntry(package.Info.Name, version, folderKey.ToString())); + } + + packages.Sort((left, right) => string.Compare(left.Name, right.Name, StringComparison.Ordinal)); + + var failures = new List(); + foreach (var failure in packageService.GetLoadFailures()) + { + // Only project-tree failures are actionable for the user. A bundled + // package failure is a first-party build issue, and its folder is not + // a project resource, so it is skipped here. + var failureKeyResult = resourceRegistry.GetResourceKey(failure.Folder); + if (failureKeyResult.IsFailure) + { + continue; + } + + failures.Add(new PackageStatusFailure( + failure.PackageName, + failureKeyResult.Value.ToString(), + failure.Reason.ToString(), + failure.Detail)); + } + + var result = new PackageStatusResult(packages.AsReadOnly(), failures.AsReadOnly()); + var json = JsonSerializer.Serialize(result, JsonOptions); + return ToolResponse.Success(json); + } +} diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Unpublish.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Unpublish.cs new file mode 100644 index 000000000..2ca92c32d --- /dev/null +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Unpublish.cs @@ -0,0 +1,51 @@ +using System.Text.Json; +using ModelContextProtocol.Protocol; +using ModelContextProtocol.Server; + +namespace Celbridge.Tools; + +/// +/// Result returned by package_unpublish confirming the package was removed. +/// +public record class PackageUnpublishResult(string PackageName, bool Unpublished); + +public partial class PackageTools +{ + /// Unpublish a whole package and all its versions from the workshop, permanently. + [McpServerTool(Name = "package_unpublish", Destructive = true)] + [ToolAlias("package.unpublish")] + [RelatedGuides("packages_overview")] + public async partial Task Unpublish(string packageName) + { + if (!PackageName.IsValid(packageName)) + { + return ToolResponse.Error(InvalidPackageNameError(packageName)); + } + + var confirmed = await ConfirmUnpublishAsync(packageName); + if (!confirmed) + { + return ToolResponse.Error("Unpublish cancelled by user."); + } + + var packageApiClient = GetRequiredService(); + var unpublishResult = await packageApiClient.DeletePackageAsync(packageName); + if (unpublishResult.IsFailure) + { + return ToolResponse.Error(unpublishResult); + } + + var result = new PackageUnpublishResult(packageName, true); + var json = JsonSerializer.Serialize(result, JsonOptions); + return ToolResponse.Success(json); + } + + private async Task ConfirmUnpublishAsync(string packageName) + { + var localizerService = GetRequiredService(); + var title = localizerService.GetString("Package_UnpublishConfirm_Title"); + var message = localizerService.GetString("Package_UnpublishConfirm_Message", packageName); + + return await ConfirmActionAsync(title, message); + } +} diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs index 919da1761..7d9aaddb3 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs @@ -24,7 +24,7 @@ private static string InvalidPackageNameError(string packageName) // or remove it. Other aliases follow the conservative package-name rule. private static Result ValidateAlias(string alias) { - if (string.Equals(alias, "latest", StringComparison.OrdinalIgnoreCase)) + if (string.Equals(alias, PackageConstants.LatestAlias, StringComparison.OrdinalIgnoreCase)) { return Result.Fail("The 'latest' alias is managed by the workshop and cannot be set or removed manually."); } diff --git a/Source/Tests/Packages/FileTypeProviderTests.cs b/Source/Tests/Packages/FileTypeProviderTests.cs index 174477947..14c515c3d 100644 --- a/Source/Tests/Packages/FileTypeProviderTests.cs +++ b/Source/Tests/Packages/FileTypeProviderTests.cs @@ -46,6 +46,13 @@ public void Setup() var key = callInfo.Arg(); return Result.Ok(Path.Combine(_tempProjectFolder, key.Path.Replace('/', Path.DirectorySeparatorChar))); }); + // The package walk enumerates the project tree through the gateway, which + // resolves with validateCase:false; stub the two-argument overload too. + resourceRegistry.ResolveResourcePath(Arg.Any(), Arg.Any()).Returns(callInfo => + { + var key = callInfo.Arg(); + return Result.Ok(Path.Combine(_tempProjectFolder, key.Path.Replace('/', Path.DirectorySeparatorChar))); + }); resourceRegistry.GetResourceKey(Arg.Any()).Returns(callInfo => { var path = callInfo.Arg(); diff --git a/Source/Tests/Packages/PackageApiClientTests.cs b/Source/Tests/Packages/PackageApiClientTests.cs index 876cf0f64..3aea68a50 100644 --- a/Source/Tests/Packages/PackageApiClientTests.cs +++ b/Source/Tests/Packages/PackageApiClientTests.cs @@ -100,7 +100,7 @@ public async Task GetPackage_ParsesVersionsAndAliases() details.Name.Should().Be("my-widget"); details.Versions.Should().HaveCount(2); details.Versions[0].Version.Should().Be(1); - details.Versions[0].Tombstoned.Should().BeTrue(); + details.Versions[0].Deleted.Should().BeTrue(); details.Versions[0].Summary.Should().Be("Initial release"); details.Versions[1].Author.Should().Be("bob"); details.Versions[1].ContentHash.Should().Be("bbb222"); @@ -189,14 +189,14 @@ public async Task DownloadVersion_ReturnsZipBytes() } [Test] - public async Task DownloadVersion_TombstonedVersion_Fails() + public async Task DownloadVersion_DeletedVersion_Fails() { _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.Gone); var result = await _client.DownloadVersionAsync("my-widget", 1); result.IsFailure.Should().BeTrue(); - result.MessageChain.Should().Contain("tombstoned"); + result.MessageChain.Should().Contain("deleted"); } [Test] @@ -242,6 +242,68 @@ public async Task RemoveAlias_SendsDelete() request.RequestUri.Should().Be(new Uri("https://workshop.example.com/api/packages/my-widget/aliases/stable/")); } + [Test] + public async Task DeleteVersion_SendsDeleteToVersionEndpoint() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.NoContent); + + var result = await _client.DeleteVersionAsync("my-widget", 2); + + result.IsSuccess.Should().BeTrue(); + + var request = _messageHandler.Requests.Single(); + request.Method.Should().Be(HttpMethod.Delete); + request.RequestUri.Should().Be(new Uri("https://workshop.example.com/api/packages/my-widget/versions/2/")); + } + + [Test] + public async Task DeleteVersion_NotFound_Fails() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.NotFound); + + var result = await _client.DeleteVersionAsync("my-widget", 9); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("not found"); + } + + [Test] + public async Task DeleteVersion_AlreadyDeleted_Fails() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.Gone); + + var result = await _client.DeleteVersionAsync("my-widget", 1); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("already been deleted"); + } + + [Test] + public async Task DeletePackage_SendsDeleteToPackageEndpoint() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.NoContent); + + var result = await _client.DeletePackageAsync("my-widget"); + + result.IsSuccess.Should().BeTrue(); + + var request = _messageHandler.Requests.Single(); + request.Method.Should().Be(HttpMethod.Delete); + request.RequestUri.Should().Be(new Uri("https://workshop.example.com/api/packages/my-widget/")); + } + + [Test] + public async Task DeletePackage_NotFound_Fails() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.NotFound); + + var result = await _client.DeletePackageAsync("missing-package"); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("missing-package"); + result.MessageChain.Should().Contain("not found"); + } + [Test] public async Task GetVersionHistory_ReturnsPlainText() { diff --git a/Source/Tests/Packages/PackageRegistryTests.cs b/Source/Tests/Packages/PackageRegistryTests.cs index f830275b8..8110049ba 100644 --- a/Source/Tests/Packages/PackageRegistryTests.cs +++ b/Source/Tests/Packages/PackageRegistryTests.cs @@ -370,6 +370,49 @@ public async Task RegisterPackages_TwoProjectPackagesSameName_BothSkipped() contributions[0].Package.Name.Should().Be("other-tool"); } + [Test] + public async Task GetLoadFailures_AfterDuplicateName_ReportsCollidingPackages() + { + // package_status reads load failures from here after the load-time error + // banner has already fired, so the failures must be retained. + CreateProjectPackage("dup-a", "dup-tool", "Dup A", "custom", ".a"); + CreateProjectPackage("dup-b", "dup-tool", "Dup B", "custom", ".b"); + + await _service.RegisterPackagesAsync(_tempProjectFolder); + + var duplicateFailures = _service.GetLoadFailures() + .Where(failure => failure.Reason == PackageLoadFailureReason.DuplicateName) + .ToList(); + duplicateFailures.Should().HaveCount(2); + duplicateFailures.Should().OnlyContain(failure => failure.PackageName == "dup-tool"); + } + + [Test] + public async Task GetLoadFailures_AllValid_IsEmpty() + { + CreateProjectPackage("legit", "legit", "Legit", "custom", ".legit"); + + await _service.RegisterPackagesAsync(_tempProjectFolder); + + _service.GetLoadFailures().Should().BeEmpty(); + } + + [Test] + public async Task RegisterPackages_NestedManifest_DiscoveredOutsidePackagesFolder() + { + // Discovery is no longer tied to packages/: a manifest anywhere under the + // project root is found, honouring the gateway's visibility rules. + var toolsDir = Path.Combine(_tempProjectFolder, "tools", "my-editor"); + Directory.CreateDirectory(toolsDir); + WritePackageFiles(toolsDir, "nested-tool", "Nested Tool", "custom", ".nst"); + + await _service.RegisterPackagesAsync(_tempProjectFolder); + + var contributions = _service.GetAllDocumentEditors(); + contributions.Should().HaveCount(1); + contributions[0].Package.Name.Should().Be("nested-tool"); + } + [Test] public async Task RegisterPackages_LoadFailures_SendPackageLoadErrorMessage() { diff --git a/Source/Tests/Tools/FileToolTests.cs b/Source/Tests/Tools/FileToolTests.cs index 7adf3c313..1509c9918 100644 --- a/Source/Tests/Tools/FileToolTests.cs +++ b/Source/Tests/Tools/FileToolTests.cs @@ -790,6 +790,78 @@ public async Task Write_DispatchesCommand_ForNonCelTarget() dispatched.Should().BeTrue(); } + [Test] + public async Task Grep_NonDefaultRootScope_WalksGatewayAndFindsMatches() + { + // Regression: a temp:-scoped project-walk returned a silent empty result + // because the search index covers only the project tree. The scope now + // descends the named root through the gateway and finds the matches. + var stagingDir = Path.Combine(_tempFolder, "staging", "sample-package", "src"); + Directory.CreateDirectory(stagingDir); + await File.WriteAllTextAsync(Path.Combine(stagingDir, "app.js"), "const alpha = 'beta';\n"); + + StubResolveAllKeysToTempFolder(); + RegisterNonDefaultRoot("temp"); + + var tools = new FileTools(_services); + var result = await tools.Grep(searchTerm: "alpha|beta", useRegex: true, resource: "temp:staging/sample-package"); + + result.IsError.Should().NotBe(true); + var root = ParseResult(result); + root.GetProperty("totalMatches").GetInt32().Should().Be(2); + root.GetProperty("totalFiles").GetInt32().Should().Be(1); + root.GetProperty("files")[0].GetProperty("resource").GetString() + .Should().Be("temp:staging/sample-package/src/app.js"); + } + + [Test] + public async Task Grep_NonDefaultRootScope_HonoursIncludeFilter() + { + var stagingDir = Path.Combine(_tempFolder, "staging", "sample-package", "src"); + Directory.CreateDirectory(stagingDir); + await File.WriteAllTextAsync(Path.Combine(stagingDir, "app.js"), "const alpha = 'beta';\n"); + + StubResolveAllKeysToTempFolder(); + RegisterNonDefaultRoot("temp"); + + var tools = new FileTools(_services); + // The only matching file is a .js, so an include of *.css must exclude it. + var result = await tools.Grep(searchTerm: "alpha", resource: "temp:staging/sample-package", include: "*.css"); + + result.IsError.Should().NotBe(true); + var root = ParseResult(result); + root.GetProperty("totalMatches").GetInt32().Should().Be(0); + root.GetProperty("totalFiles").GetInt32().Should().Be(0); + } + + private string KeyToTempPath(ResourceKey key) + { + return Path.Combine(_tempFolder, key.Path.Replace('/', Path.DirectorySeparatorChar)); + } + + // This harness treats every root as the same on-disk temp tree, so map any + // resource key to a path under the temp folder for both resolve overloads + // (the gateway enumerates with validateCase:false). + private void StubResolveAllKeysToTempFolder() + { + _resourceRegistry.ResolveResourcePath(Arg.Any()) + .Returns(callInfo => Result.Ok(KeyToTempPath(callInfo.Arg()))); + _resourceRegistry.ResolveResourcePath(Arg.Any(), Arg.Any()) + .Returns(callInfo => Result.Ok(KeyToTempPath(callInfo.Arg()))); + } + + private void RegisterNonDefaultRoot(string root) + { + var resourceService = _services.GetRequiredService().WorkspaceService.ResourceService; + var rootHandlerRegistry = Substitute.For(); + var handlers = new Dictionary + { + [root] = Substitute.For() + }; + rootHandlerRegistry.RootHandlers.Returns(handlers); + resourceService.RootHandlers.Returns(rootHandlerRegistry); + } + private static JsonElement ParseResult(CallToolResult result) { var json = result.Content.OfType().Single().Text; diff --git a/Source/Tests/Tools/PackageHistoryFileTests.cs b/Source/Tests/Tools/PackageHistoryFileTests.cs index ace81704e..fd1416b09 100644 --- a/Source/Tests/Tools/PackageHistoryFileTests.cs +++ b/Source/Tests/Tools/PackageHistoryFileTests.cs @@ -15,10 +15,11 @@ private static RemotePackageVersion MakeVersion( int version, string author = "Acme", string contentHash = "sha256:abc", - string summary = "Change summary.") + string summary = "Change summary.", + bool deleted = false) { var date = new DateTime(2026, 6, 13, 0, 0, 0, DateTimeKind.Utc); - return new RemotePackageVersion(version, author, date, Tombstoned: false, contentHash, summary); + return new RemotePackageVersion(version, author, date, deleted, contentHash, summary); } [Test] @@ -84,6 +85,42 @@ public void Format_OmitsContentHashLine_WhenHashIsBlank() markdown.Should().NotContain("sha256"); } + [Test] + public void Format_DeletedVersion_RendersSentinelInPlaceOfSummary_AndKeepsMetadata() + { + var versions = new List + { + MakeVersion(1, contentHash: "sha256:keep", summary: "Original summary.", deleted: true), + MakeVersion(2, summary: "Live summary."), + }; + + var markdown = PackageHistoryFile.Format(versions, installedVersion: 2); + + // The deleted version keeps its heading and content hash for provenance, + // but its publisher summary is replaced by the sentinel. + markdown.Should().Contain("# 1"); + markdown.Should().Contain("sha256:keep"); + markdown.Should().Contain("[package_deleted]"); + markdown.Should().NotContain("Original summary."); + // The live version still renders its real summary. + markdown.Should().Contain("Live summary."); + } + + [Test] + public void Format_DeletedVersion_HeadingHasNoSuffix() + { + var versions = new List + { + MakeVersion(5, deleted: true), + }; + + var markdown = PackageHistoryFile.Format(versions, installedVersion: 5); + + markdown.Should().StartWith("# 5\r\n"); + markdown.Should().NotContain("tombstoned"); + PackageHistoryFile.TryReadInstalledVersion(markdown).Should().Be(5); + } + [Test] public void TryReadInstalledVersion_ReturnsNull_WhenFirstLineIsNotAVersionHeading() { diff --git a/Source/Tests/Tools/PackageVersionResolverTests.cs b/Source/Tests/Tools/PackageVersionResolverTests.cs new file mode 100644 index 000000000..85c78a085 --- /dev/null +++ b/Source/Tests/Tools/PackageVersionResolverTests.cs @@ -0,0 +1,127 @@ +using Celbridge.Packages; +using Celbridge.Tools; + +namespace Celbridge.Tests.Tools; + +/// +/// Tests for PackageVersionResolver — the version-or-alias selection shared by +/// package_install (rejects deleted targets) and package_delete (allows them). +/// +[TestFixture] +public class PackageVersionResolverTests +{ + private static RemotePackageVersion Version(int version, bool deleted = false) + { + var date = new DateTime(2026, 6, 13, 0, 0, 0, DateTimeKind.Utc); + return new RemotePackageVersion(version, "Acme", date, deleted, "sha256:abc", "Summary."); + } + + private static RemotePackageDetails Details( + IReadOnlyList versions, + IReadOnlyList? aliases = null) + { + var createdAt = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc); + return new RemotePackageDetails("my-widget", createdAt, versions, aliases ?? Array.Empty()); + } + + [Test] + public void ResolveForInstall_Latest_SelectsHighestNonDeletedVersion() + { + var details = Details(new List + { + Version(1), + Version(2), + Version(3, deleted: true), + }); + + var result = PackageVersionResolver.ResolveForInstall(details, "latest"); + + result.IsSuccess.Should().BeTrue(); + result.Value.Should().Be(2); + } + + [Test] + public void ResolveForInstall_ExplicitDeletedVersion_Fails() + { + var details = Details(new List + { + Version(1, deleted: true), + Version(2), + }); + + var result = PackageVersionResolver.ResolveForInstall(details, "1"); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("deleted"); + } + + [Test] + public void ResolveForInstall_AliasToDeletedVersion_Fails() + { + var details = Details( + new List { Version(1, deleted: true), Version(2) }, + new List { new("stable", 1) }); + + var result = PackageVersionResolver.ResolveForInstall(details, "stable"); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("deleted"); + } + + [Test] + public void ResolveForInstall_UnknownSelector_Fails() + { + var details = Details(new List { Version(1) }); + + var result = PackageVersionResolver.ResolveForInstall(details, "nope"); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("not a version number or a known alias"); + } + + [Test] + public void ResolveForDelete_Alias_ResolvesToTargetVersion() + { + var details = Details( + new List { Version(1), Version(2) }, + new List { new("stable", 2) }); + + var result = PackageVersionResolver.ResolveForDelete(details, "stable"); + + result.IsSuccess.Should().BeTrue(); + result.Value.Should().Be(2); + } + + [Test] + public void ResolveForDelete_ExplicitDeletedVersion_ResolvesCleanly() + { + // Delete does not pre-reject a deleted target; the client reports the + // already-deleted state instead. + var details = Details(new List + { + Version(1, deleted: true), + Version(2), + }); + + var result = PackageVersionResolver.ResolveForDelete(details, "1"); + + result.IsSuccess.Should().BeTrue(); + result.Value.Should().Be(1); + } + + [Test] + public void ResolveForDelete_Latest_SelectsHighestNonDeletedVersion() + { + var details = Details(new List + { + Version(1), + Version(2), + Version(3, deleted: true), + }); + + var result = PackageVersionResolver.ResolveForDelete(details, "latest"); + + result.IsSuccess.Should().BeTrue(); + result.Value.Should().Be(2); + } +} diff --git a/Source/Workspace/Celbridge.Packages/Services/PackageApiClient.cs b/Source/Workspace/Celbridge.Packages/Services/PackageApiClient.cs index 355556dc0..9a2e5e49a 100644 --- a/Source/Workspace/Celbridge.Packages/Services/PackageApiClient.cs +++ b/Source/Workspace/Celbridge.Packages/Services/PackageApiClient.cs @@ -98,7 +98,7 @@ public async Task> GetPackageAsync(string packageNa version.Version, version.Author ?? string.Empty, version.Date, - version.Tombstoned, + version.Deleted, version.ContentHash ?? string.Empty, version.Summary ?? string.Empty)); } @@ -169,7 +169,7 @@ public async Task> DownloadVersionAsync(string packageName, int v using var response = sendResult.Value; if (response.StatusCode == HttpStatusCode.Gone) { - return Result.Fail($"Version {version} of package '{packageName}' has been tombstoned and can no longer be downloaded."); + return Result.Fail($"Version {version} of package '{packageName}' has been deleted and can no longer be downloaded."); } if (response.StatusCode == HttpStatusCode.NotFound) @@ -197,7 +197,7 @@ public async Task> DownloadLatestAsync(string packageName) using var response = sendResult.Value; if (response.StatusCode == HttpStatusCode.NotFound) { - return Result.Fail($"Package '{packageName}' has no live version to download. It may not exist on the workshop, or every version may have been tombstoned."); + return Result.Fail($"Package '{packageName}' has no live version to download. It may not exist on the workshop, or every version may have been deleted."); } if (!response.IsSuccessStatusCode) @@ -251,6 +251,67 @@ public async Task RemoveAliasAsync(string packageName, string alias) return Result.Ok(); } + public async Task DeleteVersionAsync(string packageName, int version) + { + using var content = BuildDeleteReasonContent(); + + var sendResult = await SendAsync(HttpMethod.Delete, $"api/packages/{Uri.EscapeDataString(packageName)}/versions/{version}/", content); + if (sendResult.IsFailure) + { + return Result.Fail($"Failed to delete version {version} of package '{packageName}'").WithErrors(sendResult); + } + + using var response = sendResult.Value; + if (response.StatusCode == HttpStatusCode.NotFound) + { + return Result.Fail($"Version {version} of package '{packageName}' was not found on the workshop."); + } + + if (response.StatusCode == HttpStatusCode.Gone) + { + return Result.Fail($"Version {version} of package '{packageName}' has already been deleted."); + } + + if (!response.IsSuccessStatusCode) + { + return Result.Fail($"Failed to delete version {version} of package '{packageName}' (HTTP {(int)response.StatusCode})"); + } + + return Result.Ok(); + } + + public async Task DeletePackageAsync(string packageName) + { + using var content = BuildDeleteReasonContent(); + + var sendResult = await SendAsync(HttpMethod.Delete, $"api/packages/{Uri.EscapeDataString(packageName)}/", content); + if (sendResult.IsFailure) + { + return Result.Fail($"Failed to delete package '{packageName}'").WithErrors(sendResult); + } + + using var response = sendResult.Value; + if (response.StatusCode == HttpStatusCode.NotFound) + { + return Result.Fail($"Package '{packageName}' was not found on the workshop."); + } + + if (!response.IsSuccessStatusCode) + { + return Result.Fail($"Failed to delete package '{packageName}' (HTTP {(int)response.StatusCode})"); + } + + return Result.Ok(); + } + + // The delete endpoints accept a {reason} audit note. The tools do not collect + // one, so a fixed note is sent. A server that ignores the field is unaffected. + private static StringContent BuildDeleteReasonContent() + { + var body = JsonSerializer.Serialize(new DeleteReasonBodyDto("Deleted via Celbridge.")); + return new StringContent(body, Encoding.UTF8, "application/json"); + } + public async Task> GetVersionHistoryAsync(string packageName, int version) { var sendResult = await SendAsync(HttpMethod.Get, $"api/packages/{Uri.EscapeDataString(packageName)}/versions/{version}/history/"); @@ -402,11 +463,13 @@ private record PackageSummaryDto( [property: JsonPropertyName("latest_version")] VersionSummaryDto? LatestVersion, [property: JsonPropertyName("versions_count")] int VersionsCount); + // The server's wire field is still "tombstoned". The client maps it to a + // Deleted flag because Celbridge does not model a dead-but-retained state. private record VersionDetailDto( [property: JsonPropertyName("version")] int Version, [property: JsonPropertyName("author")] string? Author, [property: JsonPropertyName("date")] DateTime Date, - [property: JsonPropertyName("tombstoned")] bool Tombstoned, + [property: JsonPropertyName("tombstoned")] bool Deleted, [property: JsonPropertyName("content_hash")] string? ContentHash, [property: JsonPropertyName("summary")] string? Summary); @@ -430,6 +493,9 @@ private record PublishReceiptDto( private record AliasVersionBodyDto( [property: JsonPropertyName("version")] int Version); + private record DeleteReasonBodyDto( + [property: JsonPropertyName("reason")] string Reason); + public void Dispose() { _httpClient.Dispose(); diff --git a/Source/Workspace/Celbridge.Packages/Services/PackageRegistry.cs b/Source/Workspace/Celbridge.Packages/Services/PackageRegistry.cs index ffc490cc3..750d25ffa 100644 --- a/Source/Workspace/Celbridge.Packages/Services/PackageRegistry.cs +++ b/Source/Workspace/Celbridge.Packages/Services/PackageRegistry.cs @@ -1,6 +1,7 @@ using Celbridge.Documents; using Celbridge.Logging; using Celbridge.Modules; +using Celbridge.Resources; using Celbridge.Settings; using Celbridge.Workspace; @@ -25,6 +26,10 @@ public class PackageRegistry private List _bundledPackages = []; private List _projectPackages = []; + // Failures from the most recent discovery pass, retained so package_status + // can report them after load (the error banner only fires once). + private IReadOnlyList _lastFailures = Array.Empty(); + // Bundled packages live outside any IResourceRegistry root so their reads // stay on direct File.* IO via the gateway. One reader is reused across // the discovery and template-fetch paths to keep the bundled branch cheap. @@ -66,6 +71,8 @@ public async Task DiscoverPackagesAsync(string projectFo Failures = failures.AsReadOnly() }; + _lastFailures = report.Failures; + LogDiscoveredPackages(); _logger.LogInformation( @@ -82,6 +89,11 @@ public IReadOnlyList GetAllPackages() return combined.AsReadOnly(); } + public IReadOnlyList GetLoadFailures() + { + return _lastFailures; + } + public IReadOnlyList GetAllDocumentEditors() { return GetAllPackages() @@ -345,43 +357,23 @@ private async Task> DiscoverProjectPackagesAsync(string return failures; } - var packagesResource = new ResourceKey(PackageConstants.DefaultPackagesFolder); var resourceFileSystem = _workspaceWrapper.WorkspaceService.ResourceService.FileSystem; - - var packagesInfoResult = await resourceFileSystem.GetInfoAsync(packagesResource); - if (packagesInfoResult.IsFailure - || packagesInfoResult.Value.Kind != StorageItemKind.Folder) - { - return failures; - } - - var enumerateResult = await resourceFileSystem.EnumerateFolderAsync(packagesResource); - if (enumerateResult.IsFailure) - { - return failures; - } - var resourceRegistry = _workspaceWrapper.WorkspaceService.ResourceService.Registry; var projectReader = new ResourceFileSystemPackageReader(resourceFileSystem, resourceRegistry); - var candidates = new List(); - foreach (var item in enumerateResult.Value) - { - if (!item.IsFolder) - { - continue; - } + // Walk the project's visible resource set for package.toml manifests. A + // manifest is a package wherever it lives under the project root. The + // file-system gateway applies the project's ignore rules, so excluded + // content (vendored assets, build output) is never scanned. Descent stops + // at each package root so a package's own content is not searched for + // nested manifests. + var manifestResources = new List(); + await CollectProjectManifestsAsync(resourceFileSystem, ResourceKey.Empty, manifestResources); - var manifestResource = item.Resource.Combine(PackageConstants.ManifestFileName); - var manifestInfoResult = await resourceFileSystem.GetInfoAsync(manifestResource); - if (manifestInfoResult.IsFailure - || manifestInfoResult.Value.Kind != StorageItemKind.File) - { - // A folder under packages/ with no manifest is not a package. - // Silently skip rather than report as a failure. - continue; - } + var candidates = new List(); + foreach (var manifestResource in manifestResources) + { var resolveResult = resourceRegistry.ResolveResourcePath(manifestResource); if (resolveResult.IsFailure) { @@ -505,6 +497,42 @@ private async Task> DiscoverProjectPackagesAsync(string return failures; } + // Recursively collects every package.toml manifest in the project's visible + // resource set. Enumeration goes through the file-system gateway, which + // honours the project's ignore rules, so excluded content is never visited. + // A folder that directly holds a manifest is a package root: its manifest is + // recorded and the walk does not descend into it, so a package's own files + // (which may legitimately vendor another package's folder) are not scanned. + private async Task CollectProjectManifestsAsync( + IResourceFileSystem resourceFileSystem, + ResourceKey folderResource, + List manifestResources) + { + var enumerateResult = await resourceFileSystem.EnumerateFolderAsync(folderResource); + if (enumerateResult.IsFailure) + { + return; + } + var items = enumerateResult.Value; + + var manifestItem = items.FirstOrDefault(item => + !item.IsFolder + && string.Equals(item.Resource.ResourceName, PackageConstants.ManifestFileName, StringComparison.OrdinalIgnoreCase)); + if (manifestItem is not null) + { + manifestResources.Add(manifestItem.Resource); + return; + } + + foreach (var item in items) + { + if (item.IsFolder) + { + await CollectProjectManifestsAsync(resourceFileSystem, item.Resource, manifestResources); + } + } + } + // Runs after dedup so that duplicate-id packages rejected by the group // passes do not appear in the log. Emits one Info-level line per accepted // package so a support reader can tell at a glance which packages loaded, diff --git a/Source/Workspace/Celbridge.Packages/Services/PackageService.cs b/Source/Workspace/Celbridge.Packages/Services/PackageService.cs index 520166e19..fdebec93e 100644 --- a/Source/Workspace/Celbridge.Packages/Services/PackageService.cs +++ b/Source/Workspace/Celbridge.Packages/Services/PackageService.cs @@ -50,6 +50,11 @@ public IReadOnlyList GetAllPackages() return _registry.GetAllPackages(); } + public IReadOnlyList GetLoadFailures() + { + return _registry.GetLoadFailures(); + } + public IReadOnlyList GetAllDocumentEditors() { return _registry.GetAllDocumentEditors(); From 33c895f0f62f25c8364264342d02b89b325251cb Mon Sep 17 00:00:00 2001 From: Chris Gregan Date: Sun, 14 Jun 2026 08:22:19 +0100 Subject: [PATCH 04/21] Create PackageVersionResolver.cs --- .../Helpers/PackageVersionResolver.cs | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 Source/Core/Celbridge.Tools/Helpers/PackageVersionResolver.cs diff --git a/Source/Core/Celbridge.Tools/Helpers/PackageVersionResolver.cs b/Source/Core/Celbridge.Tools/Helpers/PackageVersionResolver.cs new file mode 100644 index 000000000..ec4e8b31c --- /dev/null +++ b/Source/Core/Celbridge.Tools/Helpers/PackageVersionResolver.cs @@ -0,0 +1,82 @@ +using Celbridge.Packages; + +namespace Celbridge.Tools; + +/// +/// Resolves a requested version string to a concrete workshop version number. +/// The string is the latest alias (the highest live version), a version number, +/// or an alias name. +/// +internal static class PackageVersionResolver +{ + /// + /// Resolves a version for install. A deleted version cannot be downloaded, + /// so a deleted target is rejected with a clear error rather than resolved. + /// + public static Result ResolveForInstall(RemotePackageDetails details, string requestedVersion) + { + return Resolve(details, requestedVersion, rejectDeleted: true); + } + + /// + /// Resolves a version for delete. A deleted target is not pre-rejected here. + /// The client reports the already-deleted state instead. The latest alias + /// still selects the highest live version, never a dead one. + /// + public static Result ResolveForDelete(RemotePackageDetails details, string requestedVersion) + { + return Resolve(details, requestedVersion, rejectDeleted: false); + } + + private static Result Resolve(RemotePackageDetails details, string requestedVersion, bool rejectDeleted) + { + if (string.Equals(requestedVersion, PackageConstants.LatestAlias, StringComparison.OrdinalIgnoreCase)) + { + var liveVersions = details.Versions + .Where(packageVersion => !packageVersion.Deleted) + .ToList(); + if (liveVersions.Count == 0) + { + return Result.Fail($"Package '{details.Name}' has no live version available."); + } + + return liveVersions.Max(packageVersion => packageVersion.Version); + } + + if (int.TryParse(requestedVersion, out var explicitVersion)) + { + var match = details.Versions.FirstOrDefault(packageVersion => packageVersion.Version == explicitVersion); + if (match is null) + { + return Result.Fail($"Version {explicitVersion} not found for package '{details.Name}'."); + } + if (rejectDeleted + && match.Deleted) + { + return Result.Fail($"Version {explicitVersion} of package '{details.Name}' has been deleted and cannot be installed."); + } + + return explicitVersion; + } + + var alias = details.Aliases.FirstOrDefault(packageAlias => + string.Equals(packageAlias.Alias, requestedVersion, StringComparison.Ordinal)); + if (alias is null) + { + return Result.Fail($"'{requestedVersion}' is not a version number or a known alias for package '{details.Name}'."); + } + + var aliasTarget = details.Versions.FirstOrDefault(packageVersion => packageVersion.Version == alias.Version); + if (aliasTarget is null) + { + return Result.Fail($"Alias '{requestedVersion}' points at version {alias.Version}, which does not exist."); + } + if (rejectDeleted + && aliasTarget.Deleted) + { + return Result.Fail($"Alias '{requestedVersion}' points at version {alias.Version}, which has been deleted."); + } + + return alias.Version; + } +} From 50b3b44b65caf2d08ccc8d3054f008cd139ed549 Mon Sep 17 00:00:00 2001 From: Chris Gregan Date: Sun, 14 Jun 2026 09:43:16 +0100 Subject: [PATCH 05/21] Add HISTORY.md metadata, parse refs, and stale checks Introduce structured HISTORY.md entries: headings now use a name@version token and a compact bracketed metadata line (time, author, short hash, deleted). PackageHistoryFile.Format was changed to accept packageName, emits RFC3339 UTC timestamps and a 12-char short hash, and provides TryReadInstalledReference and IsStaleBase helpers. Package publish/install logic updated to read the installed reference, warn/confirm on stale or unreadable install records, and include an optional warning in publish results; tests and localization strings were updated accordingly to cover the new format and checks. --- .../Resources/Strings/en-US/Resources.resw | 6 + .../Guides/Concepts/packages_overview.md | 12 ++ .../Guides/Tools/package_publish.md | 18 +- .../Helpers/PackageHistoryFile.cs | 180 ++++++++++++++---- .../Tools/Package/PackageTools.Install.cs | 12 +- .../Tools/Package/PackageTools.Publish.cs | 141 +++++++++++++- Source/Tests/Tools/PackageHistoryFileTests.cs | 133 +++++++++---- 7 files changed, 408 insertions(+), 94 deletions(-) diff --git a/Source/Celbridge/Resources/Strings/en-US/Resources.resw b/Source/Celbridge/Resources/Strings/en-US/Resources.resw index 4953bb608..8a98ff78e 100644 --- a/Source/Celbridge/Resources/Strings/en-US/Resources.resw +++ b/Source/Celbridge/Resources/Strings/en-US/Resources.resw @@ -1025,6 +1025,12 @@ Do you wish to continue? Publish package '{0}' to the workshop as a new version? + + Publish '{0}'? Version {1} is installed but the latest is now {2} — publishing may overwrite newer work. + + + Publish '{0}'? Its install record (HISTORY.md) can't be read, so it may be behind the latest version. + Install Package diff --git a/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md b/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md index 0b84ad033..fb6c9adb4 100644 --- a/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md +++ b/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md @@ -32,6 +32,18 @@ A version can be **deleted** (`package_delete`), which removes its content bytes When you install a package, its workshop history is written to a generated `HISTORY.md` beside the manifest (newest first); `package_publish` writes the same file for the version it assigns. This is metadata about the workshop, not package content — it is excluded from uploads, and the workshop stays authoritative for publish history. The installed (or last-published) version is recorded in `HISTORY.md`, which is where `package_status` reads it from. +Each entry is shaped for grep/fragment reasoning: + +``` +# my-widget@5 + +[time: 2026-06-13T15:14:50Z, author: Acme, hash: eb1ddd1ce6a9] + +Merge the credits change into the rolled-back content. +``` + +The header is a `name@version` token (so a quoted entry is self-describing), followed by one compact bracketed metadata line — `time` (full UTC timestamp), `author`, and a 12-character `hash` fingerprint — then the free-text summary. A deleted version adds `deleted: true` to the line and renders `[package_deleted]` as its body. The full content hash stays authoritative in `package_info`; the short `hash` is for cheap reasoning and cross-checking a summary's claims against the actual bytes. + ## Workshop workflow | Tool | What it does | diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md index 115af17bd..d3ef48858 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md @@ -36,13 +36,25 @@ A JSON object: - `version` (int) — the version number the workshop assigned to this publish. - `entries` (int) — number of files included in the uploaded zip. - `size` (long) — uploaded zip size in bytes. +- `warning` (string or null) — an advisory note, or `null`. Currently set when this folder was published from a stale base (see Concurrent publishing). ## HISTORY.md -After a successful publish, the tool writes a fresh `HISTORY.md` beside the manifest recording the version just assigned (one `# ` section per version, newest first). This makes the source folder match what a consumer who installs that version receives, and lets `package_status` report the right version for it. The file itself is excluded from the upload (matched case-insensitively) — the workshop stays authoritative for publish history. +After a successful publish, the tool writes a fresh `HISTORY.md` beside the manifest recording the version just assigned (one `# name@version` section per version, newest first, each with a compact metadata line — see `packages_overview`). This makes the source folder match what a consumer who installs that version receives, and lets `package_status` report the right version for it. The file itself is excluded from the upload (matched case-insensitively) — the workshop stays authoritative for publish history. + +## Concurrent publishing + +The workshop is a shared rendezvous point with no concurrency guard, so two people starting from the same version and both publishing produce siblings that the linear history presents as a sequence. As a guardrail, if the source folder was installed from a version older than the workshop's current latest, another version landed after this folder was installed and this publish may overwrite or diverge from it. When this is detected: + +- With `confirmWithUser: true` (default), the confirmation prompt spells out the staleness — it names the installed and latest versions and asks you to continue — so you give informed consent rather than discovering the clash afterward. +- With `confirmWithUser: false`, the publish still proceeds (publishing is append-only — the other version is not destroyed), and the result's `warning` field reports the clash so an agent can react. + +Either way, consider reinstalling the latest version and re-applying your changes before publishing. The check only fires for same-package iteration; a folder installed from a different package (a rename or fork) is not flagged. + +The check needs the install record (`HISTORY.md`) to read which version the folder came from. A folder with **no** record — a package authored in place — is a normal case and is not flagged. But a record that is **present yet unreadable or malformed** means the check could not run, so it is surfaced the same way (confirmation note plus result `warning`): the publish still proceeds, but you are told the stale-base check was skipped. ## Gotchas - Symlinks and other reparse points inside the package folder are skipped, not followed. -- Publishing always creates a new version; there is no way to replace or delete an existing version through the tools. -- The workshop reads the publisher from the manifest's `author` field; set it before the first publish. +- Publishing always creates a new version and never overwrites an earlier one. To remove a version, use `package_delete`; to remove a whole package, `package_unpublish`. +- The workshop reads the publisher from the manifest's `author` field, so set it before the first publish. diff --git a/Source/Core/Celbridge.Tools/Helpers/PackageHistoryFile.cs b/Source/Core/Celbridge.Tools/Helpers/PackageHistoryFile.cs index 996a1e35a..3a04f2268 100644 --- a/Source/Core/Celbridge.Tools/Helpers/PackageHistoryFile.cs +++ b/Source/Core/Celbridge.Tools/Helpers/PackageHistoryFile.cs @@ -4,6 +4,12 @@ namespace Celbridge.Tools; +/// +/// The package and version named by the newest HISTORY.md entry, parsed from +/// its "name@version" heading. +/// +public sealed record InstalledPackageReference(string Name, int Version); + /// /// Formats and parses the generated HISTORY.md changelog written beside a package manifest. /// @@ -12,11 +18,15 @@ internal static class PackageHistoryFile // Marker text rendered in place of a deleted version's summary. private const string DeletedVersionSummary = "[package_deleted]"; + // Length of the truncated content fingerprint, matching the git short-hash + // convention. The full hash stays authoritative in package_info. + private const int ShortHashLength = 12; + /// /// Builds the HISTORY.md changelog from the package's versions, covering /// every version up to and including the installed one, newest first. /// - public static string Format(IReadOnlyList versions, int installedVersion) + public static string Format(string packageName, IReadOnlyList versions, int installedVersion) { var orderedVersions = versions .Where(packageVersion => packageVersion.Version <= installedVersion) @@ -31,63 +41,122 @@ public static string Format(IReadOnlyList versions, int in builder.Append("\r\n"); } + // The header carries the name@version token so a single entry is + // self-describing and survives a rename when read standalone. builder.Append("# "); + builder.Append(packageName); + builder.Append('@'); builder.Append(packageVersion.Version.ToString(CultureInfo.InvariantCulture)); builder.Append("\r\n\r\n"); - var date = packageVersion.Date.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture); - var author = packageVersion.Author?.Trim() ?? string.Empty; - if (author.Length > 0) + AppendMetadataLine(builder, packageVersion); + + // The body is the free-text summary, or the deleted marker. A deleted + // version keeps its heading and metadata but loses the summary, so the + // version reads as removed rather than as a gap in the numbering. + string body; + if (packageVersion.Deleted) { - builder.Append($"Published by {author} on {date}.\r\n"); + body = DeletedVersionSummary; } else { - builder.Append($"Published on {date}.\r\n"); - } - - // The content hash fingerprints the published version, letting a - // vendored copy be verified against the workshop's record. It is - // self-describing, so it stands alone without a label. - var contentHash = packageVersion.ContentHash?.Trim() ?? string.Empty; - if (contentHash.Length > 0) - { - builder.Append(contentHash); - builder.Append("\r\n"); + var summary = packageVersion.Summary?.Trim() ?? string.Empty; + body = summary.Replace("\r\n", "\n").Replace("\n", "\r\n"); } - if (packageVersion.Deleted) + if (body.Length > 0) { - // A deleted version has lost its bytes and publisher summary, so - // the marker stands in for the summary. Its heading, date, and - // content hash (emitted above) remain, so the version reads as - // removed rather than as a gap in the numbering. builder.Append("\r\n"); - builder.Append(DeletedVersionSummary); + builder.Append(body); builder.Append("\r\n"); } - else - { - var summary = packageVersion.Summary?.Trim() ?? string.Empty; - if (summary.Length > 0) - { - var normalizedSummary = summary.Replace("\r\n", "\n").Replace("\n", "\r\n"); - builder.Append("\r\n"); - builder.Append(normalizedSummary); - builder.Append("\r\n"); - } - } } return builder.ToString(); } + // One compact bracketed line carrying the entry's fixed metadata fields, so a + // grep hit or a quoted fragment returns the whole record in a single match. + private static void AppendMetadataLine(StringBuilder builder, RemotePackageVersion packageVersion) + { + var fields = new List(); + fields.Add($"time: {FormatTimestamp(packageVersion.Date)}"); + + var author = packageVersion.Author?.Trim() ?? string.Empty; + if (author.Length > 0) + { + fields.Add($"author: {author}"); + } + + var hash = TruncateHash(packageVersion.ContentHash); + if (hash.Length > 0) + { + fields.Add($"hash: {hash}"); + } + + if (packageVersion.Deleted) + { + fields.Add("deleted: true"); + } + + builder.Append('['); + builder.Append(string.Join(", ", fields)); + builder.Append("]\r\n"); + } + + // Renders the publish time as RFC 3339 / ISO 8601 in UTC with a Z suffix. + // Date-only would not distinguish versions published minutes apart on the + // same day, which must stay ordered. The workshop sends UTC timestamps, so + // an unspecified kind is taken as UTC rather than shifted as local. + private static string FormatTimestamp(DateTime date) + { + DateTime utc = date.Kind switch + { + DateTimeKind.Utc => date, + DateTimeKind.Local => date.ToUniversalTime(), + _ => DateTime.SpecifyKind(date, DateTimeKind.Utc) + }; + + return utc.ToString("yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture); + } + + // Truncates the content hash to a short fingerprint, dropping any algorithm + // prefix such as "sha256:". The short form is for cheap reasoning. The full + // hash stays authoritative in package_info. + private static string TruncateHash(string? contentHash) + { + var hash = contentHash?.Trim() ?? string.Empty; + if (hash.Length == 0) + { + return string.Empty; + } + + var colonIndex = hash.LastIndexOf(':'); + if (colonIndex >= 0 + && colonIndex + 1 < hash.Length) + { + hash = hash.Substring(colonIndex + 1); + } + + return hash.Length <= ShortHashLength ? hash : hash.Substring(0, ShortHashLength); + } + /// - /// Reads the installed version from a HISTORY.md body: the first non-empty - /// line is the newest version's heading ("# 23"). Returns null when the + /// Reads the installed version from a HISTORY.md body. Returns null when the /// file has no parseable version heading (e.g. a hand-authored file). /// public static int? TryReadInstalledVersion(string historyMarkdown) + { + return TryReadInstalledReference(historyMarkdown)?.Version; + } + + /// + /// Reads the package and version named by the newest HISTORY.md entry, parsed + /// from its "name@version" heading on the first non-empty line. Returns null + /// when there is no parseable heading. + /// + public static InstalledPackageReference? TryReadInstalledReference(string historyMarkdown) { if (string.IsNullOrEmpty(historyMarkdown)) { @@ -104,20 +173,30 @@ public static string Format(IReadOnlyList versions, int in continue; } - // The first non-empty line must be the newest version's heading. + // The first non-empty line must be the newest entry's "name@version" heading. var headingText = trimmed.TrimStart('#').Trim(); - // Drop any trailing note after the version number. + // Drop any trailing note after the token. var spaceIndex = headingText.IndexOf(' '); if (spaceIndex > 0) { headingText = headingText.Substring(0, spaceIndex); } - if (int.TryParse(headingText, NumberStyles.Integer, CultureInfo.InvariantCulture, out var version) + var atIndex = headingText.LastIndexOf('@'); + if (atIndex <= 0 + || atIndex + 1 >= headingText.Length) + { + return null; + } + + var name = headingText.Substring(0, atIndex); + var versionText = headingText.Substring(atIndex + 1); + + if (int.TryParse(versionText, NumberStyles.Integer, CultureInfo.InvariantCulture, out var version) && version > 0) { - return version; + return new InstalledPackageReference(name, version); } return null; @@ -125,4 +204,27 @@ public static string Format(IReadOnlyList versions, int in return null; } + + /// + /// Returns true when the source folder's install record is a stale base for + /// publishing: a same-package version older than the workshop's latest live + /// version, signalling another publish landed since this folder was installed. + /// + public static bool IsStaleBase(InstalledPackageReference? installed, string packageName, int latestLiveVersion) + { + if (installed is null) + { + return false; + } + + // Only same-package iteration is a lost-update risk. A different recorded + // name is a rename or fork, not a stale base. + var samePackage = string.Equals(installed.Name, packageName, StringComparison.Ordinal); + if (!samePackage) + { + return false; + } + + return installed.Version < latestLiveVersion; + } } diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs index 9be0b64c0..0108cdcb4 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs @@ -169,7 +169,7 @@ public async partial Task Install( } var historyFile = packageFolder.Combine(PackageConstants.HistoryFileName); - var historyMarkdown = PackageHistoryFile.Format(packageDetails.Versions, resolvedVersion); + var historyMarkdown = PackageHistoryFile.Format(packageName, packageDetails.Versions, resolvedVersion); var writeHistoryResult = await resourceFileSystem.WriteAllTextAsync(historyFile, historyMarkdown); if (writeHistoryResult.IsFailure) { @@ -240,6 +240,14 @@ private static string DescribeFolder(IResourceRegistry resourceRegistry, string private static async Task TryReadInstalledVersionAsync( IResourceFileSystem resourceFileSystem, ResourceKey packageFolder) + { + var reference = await TryReadInstalledReferenceAsync(resourceFileSystem, packageFolder); + return reference?.Version; + } + + private static async Task TryReadInstalledReferenceAsync( + IResourceFileSystem resourceFileSystem, + ResourceKey packageFolder) { var historyFile = packageFolder.Combine(PackageConstants.HistoryFileName); var infoResult = await resourceFileSystem.GetInfoAsync(historyFile); @@ -255,7 +263,7 @@ private static string DescribeFolder(IResourceRegistry resourceRegistry, string return null; } - return PackageHistoryFile.TryReadInstalledVersion(readResult.Value); + return PackageHistoryFile.TryReadInstalledReference(readResult.Value); } private async Task ConfirmInstallAsync( diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs index 9af886cda..07af4a149 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs @@ -11,9 +11,10 @@ namespace Celbridge.Tools; /// /// Result returned by package_publish with the published package details, -/// including the version number assigned by the workshop. +/// including the version number assigned by the workshop. Warning carries an +/// advisory note (e.g. a stale-base concurrent-publish warning) or is null. /// -public record class PackagePublishResult(string PackageName, int Version, int Entries, long Size); +public record class PackagePublishResult(string PackageName, int Version, int Entries, long Size, string? Warning = null); public partial class PackageTools { @@ -66,19 +67,37 @@ public async partial Task Publish(string resource, string summar } var packageName = nameResult.Value; + var packageApiClient = GetRequiredService(); + + // Guardrail against the concurrent-publish footgun: if this folder was + // installed from a version older than the workshop's current latest, + // another publish landed in between and this one may overwrite or diverge + // from it. The confirmation spells out the risk so the user gives informed + // consent. Publishing is append-only (the sibling version still exists), + // so an agent run (confirmWithUser false) proceeds with the warning in the + // result rather than being blocked. A present-but-unreadable install + // record is surfaced the same way, since the check could not run. + var baseCheck = await CheckBaseAsync( + packageApiClient, + resourceService.FileSystem, + packageSource.FolderResource, + packageName); + if (confirmWithUser) { - var localizerService = GetRequiredService(); - var title = localizerService.GetString("Package_PublishConfirm_Title"); - var message = localizerService.GetString("Package_PublishConfirm_Message", packageName); - - var confirmed = await ConfirmActionAsync(title, message); + var confirmed = await ConfirmPublishAsync(packageName, baseCheck); if (!confirmed) { return ToolResponse.Error("Publish cancelled by user."); } } + var publishWarning = BuildPublishWarning(packageName, baseCheck); + if (publishWarning is not null) + { + Logger.LogWarning(publishWarning); + } + var buildResult = await BuildPackageArchiveAsync(fileSystem, packageSource.FolderPath); if (buildResult.IsFailure) { @@ -86,7 +105,6 @@ public async partial Task Publish(string resource, string summar } var archive = buildResult.Value; - var packageApiClient = GetRequiredService(); var publishSummary = string.IsNullOrEmpty(summary) ? null : summary; var publishResult = await packageApiClient.PublishVersionAsync(packageName, archive.ZipData, publishSummary); if (publishResult.IsFailure) @@ -105,11 +123,114 @@ await RefreshPublishedHistoryAsync( packageName, receipt.Version); - var result = new PackagePublishResult(packageName, receipt.Version, archive.EntryCount, archive.ZipData.Length); + var result = new PackagePublishResult(packageName, receipt.Version, archive.EntryCount, archive.ZipData.Length, publishWarning); var json = JsonSerializer.Serialize(result, JsonOptions); return ToolResponse.Success(json); } + private async Task ConfirmPublishAsync(string packageName, PublishBaseCheck baseCheck) + { + var localizerService = GetRequiredService(); + var title = localizerService.GetString("Package_PublishConfirm_Title"); + + string message = baseCheck.Concern switch + { + PublishBaseConcern.Stale => localizerService.GetString( + "Package_PublishStaleConfirm_Message", packageName, baseCheck.InstalledVersion, baseCheck.LatestVersion), + PublishBaseConcern.RecordUnreadable => localizerService.GetString( + "Package_PublishUnreadableRecordConfirm_Message", packageName), + _ => localizerService.GetString("Package_PublishConfirm_Message", packageName) + }; + + return await ConfirmActionAsync(title, message); + } + + private static string? BuildPublishWarning(string packageName, PublishBaseCheck baseCheck) + { + switch (baseCheck.Concern) + { + case PublishBaseConcern.Stale: + return $"This folder was installed from {packageName}@{baseCheck.InstalledVersion}, " + + $"but the workshop's latest version is now {baseCheck.LatestVersion}. Another version was " + + "published after this folder was installed, so publishing may overwrite or diverge " + + "from that work. To build on the latest, reinstall it and re-apply your changes."; + + case PublishBaseConcern.RecordUnreadable: + return $"The install record ({PackageConstants.HistoryFileName}) for this folder could not be read, " + + "so the stale-base check was skipped. If this folder was installed from the workshop, verify it " + + "is not based on a superseded version before relying on this publish."; + + default: + return null; + } + } + + // Inspects the source folder's install record to decide whether publishing is + // building on an out-of-date base. The record is read here (rather than via + // the shared helper) so a present-but-unreadable record is told apart from an + // absent one: an absent record is the legitimate authored-in-place case, while + // an unreadable one means the check could not run and is surfaced as such. + private async Task CheckBaseAsync( + IPackageApiClient packageApiClient, + IResourceFileSystem resourceFileSystem, + ResourceKey folderResource, + string packageName) + { + var historyFile = folderResource.Combine(PackageConstants.HistoryFileName); + var infoResult = await resourceFileSystem.GetInfoAsync(historyFile); + if (infoResult.IsFailure + || infoResult.Value.Kind != StorageItemKind.File) + { + // No record: authored in place, or never installed. Nothing to check. + return new PublishBaseCheck(PublishBaseConcern.None); + } + + var readResult = await resourceFileSystem.ReadAllTextAsync(historyFile); + if (readResult.IsFailure) + { + return new PublishBaseCheck(PublishBaseConcern.RecordUnreadable); + } + + var installedReference = PackageHistoryFile.TryReadInstalledReference(readResult.Value); + if (installedReference is null) + { + // Present but no parseable heading: the base cannot be determined. + return new PublishBaseCheck(PublishBaseConcern.RecordUnreadable); + } + + var detailsResult = await packageApiClient.GetPackageAsync(packageName); + if (detailsResult.IsFailure) + { + // A brand-new package or an unreachable workshop has nothing to compare. + return new PublishBaseCheck(PublishBaseConcern.None); + } + + var liveVersions = detailsResult.Value.Versions + .Where(packageVersion => !packageVersion.Deleted) + .ToList(); + if (liveVersions.Count == 0) + { + return new PublishBaseCheck(PublishBaseConcern.None); + } + var latestLiveVersion = liveVersions.Max(packageVersion => packageVersion.Version); + + if (!PackageHistoryFile.IsStaleBase(installedReference, packageName, latestLiveVersion)) + { + return new PublishBaseCheck(PublishBaseConcern.None); + } + + return new PublishBaseCheck(PublishBaseConcern.Stale, installedReference.Version, latestLiveVersion); + } + + private enum PublishBaseConcern + { + None, + Stale, + RecordUnreadable + } + + private sealed record PublishBaseCheck(PublishBaseConcern Concern, int InstalledVersion = 0, int LatestVersion = 0); + private async Task RefreshPublishedHistoryAsync( IPackageApiClient packageApiClient, IResourceFileSystem resourceFileSystem, @@ -126,7 +247,7 @@ private async Task RefreshPublishedHistoryAsync( } var historyFile = folderResource.Combine(PackageConstants.HistoryFileName); - var historyMarkdown = PackageHistoryFile.Format(detailsResult.Value.Versions, publishedVersion); + var historyMarkdown = PackageHistoryFile.Format(packageName, detailsResult.Value.Versions, publishedVersion); var writeResult = await resourceFileSystem.WriteAllTextAsync(historyFile, historyMarkdown); if (writeResult.IsFailure) { diff --git a/Source/Tests/Tools/PackageHistoryFileTests.cs b/Source/Tests/Tools/PackageHistoryFileTests.cs index fd1416b09..585786a0f 100644 --- a/Source/Tests/Tools/PackageHistoryFileTests.cs +++ b/Source/Tests/Tools/PackageHistoryFileTests.cs @@ -5,25 +5,28 @@ namespace Celbridge.Tests.Tools; /// /// Tests for PackageHistoryFile — the HISTORY.md changelog rendered on install -/// and publish, and the installed-version read-back that package_status and the -/// replace confirmation rely on. +/// and publish, the installed-reference read-back that package_status and the +/// replace confirmation rely on, and the stale-base publish check. /// [TestFixture] public class PackageHistoryFileTests { + private const string PackageName = "sample-package"; + private static RemotePackageVersion MakeVersion( int version, string author = "Acme", - string contentHash = "sha256:abc", + string contentHash = "abc123abc123def", string summary = "Change summary.", - bool deleted = false) + bool deleted = false, + DateTime? date = null) { - var date = new DateTime(2026, 6, 13, 0, 0, 0, DateTimeKind.Utc); - return new RemotePackageVersion(version, author, date, deleted, contentHash, summary); + var versionDate = date ?? new DateTime(2026, 6, 13, 15, 14, 50, DateTimeKind.Utc); + return new RemotePackageVersion(version, author, versionDate, deleted, contentHash, summary); } [Test] - public void Format_OrdersNewestFirst_SoInstalledVersionIsTheFirstHeading() + public void Format_HeaderCarriesNameAtVersionToken_NewestFirst() { var versions = new List { @@ -32,9 +35,9 @@ public void Format_OrdersNewestFirst_SoInstalledVersionIsTheFirstHeading() MakeVersion(3), }; - var markdown = PackageHistoryFile.Format(versions, installedVersion: 3); + var markdown = PackageHistoryFile.Format(PackageName, versions, installedVersion: 3); - markdown.Should().StartWith("# 3"); + markdown.Should().StartWith("# sample-package@3"); PackageHistoryFile.TryReadInstalledVersion(markdown).Should().Be(3); } @@ -48,77 +51,96 @@ public void Format_ExcludesVersionsNewerThanTheInstalledOne() MakeVersion(3), }; - var markdown = PackageHistoryFile.Format(versions, installedVersion: 2); + var markdown = PackageHistoryFile.Format(PackageName, versions, installedVersion: 2); - markdown.Should().Contain("# 2"); - markdown.Should().Contain("# 1"); - markdown.Should().NotContain("# 3"); - PackageHistoryFile.TryReadInstalledVersion(markdown).Should().Be(2); + markdown.Should().Contain("# sample-package@2"); + markdown.Should().Contain("# sample-package@1"); + markdown.Should().NotContain("# sample-package@3"); } [Test] - public void Format_IncludesAuthorDateContentHashAndSummary() + public void Format_MetadataLine_CarriesFullUtcTimestampAuthorAndShortHash() { var versions = new List { - MakeVersion(1, author: "Acme", contentHash: "sha256:abc123", summary: "Initial release."), + MakeVersion(1, author: "Celbridge", contentHash: "eb1ddd1ce6a9bbbb", summary: "Initial release."), }; - var markdown = PackageHistoryFile.Format(versions, installedVersion: 1); + var markdown = PackageHistoryFile.Format(PackageName, versions, installedVersion: 1); - markdown.Should().Contain("Published by Acme on 2026-06-13."); - markdown.Should().Contain("sha256:abc123"); + // Full timestamp with a Z suffix, not date-only: versions published the + // same day must stay distinguishable and ordered. + markdown.Should().Contain("[time: 2026-06-13T15:14:50Z, author: Celbridge, hash: eb1ddd1ce6a9]"); markdown.Should().Contain("Initial release."); } [Test] - public void Format_OmitsContentHashLine_WhenHashIsBlank() + public void Format_ShortHash_StripsAlgorithmPrefixAndTruncatesTo12() + { + var versions = new List + { + MakeVersion(1, contentHash: "sha256:0123456789abcdef0123"), + }; + + var markdown = PackageHistoryFile.Format(PackageName, versions, installedVersion: 1); + + markdown.Should().Contain("hash: 0123456789ab"); + markdown.Should().NotContain("sha256:"); + } + + [Test] + public void Format_OmitsHashField_WhenHashIsBlank() { var versions = new List { MakeVersion(1, contentHash: string.Empty), }; - var markdown = PackageHistoryFile.Format(versions, installedVersion: 1); + var markdown = PackageHistoryFile.Format(PackageName, versions, installedVersion: 1); - markdown.Should().Contain("Published by Acme on 2026-06-13."); - markdown.Should().NotContain("sha256"); + markdown.Should().Contain("time: 2026-06-13T15:14:50Z"); + markdown.Should().NotContain("hash:"); } [Test] - public void Format_DeletedVersion_RendersSentinelInPlaceOfSummary_AndKeepsMetadata() + public void Format_DeletedVersion_RendersDeletedFlagAndSentinel() { var versions = new List { - MakeVersion(1, contentHash: "sha256:keep", summary: "Original summary.", deleted: true), + MakeVersion(1, contentHash: "keepkeepkeep11", summary: "Original summary.", deleted: true), MakeVersion(2, summary: "Live summary."), }; - var markdown = PackageHistoryFile.Format(versions, installedVersion: 2); + var markdown = PackageHistoryFile.Format(PackageName, versions, installedVersion: 2); - // The deleted version keeps its heading and content hash for provenance, - // but its publisher summary is replaced by the sentinel. - markdown.Should().Contain("# 1"); - markdown.Should().Contain("sha256:keep"); + // The deleted version keeps its heading, time, and hash for provenance, + // gains a deleted flag, and renders the sentinel instead of its summary. + markdown.Should().Contain("# sample-package@1"); + markdown.Should().Contain("hash: keepkeepkeep"); + markdown.Should().Contain("deleted: true"); markdown.Should().Contain("[package_deleted]"); markdown.Should().NotContain("Original summary."); - // The live version still renders its real summary. markdown.Should().Contain("Live summary."); } [Test] - public void Format_DeletedVersion_HeadingHasNoSuffix() + public void TryReadInstalledReference_ParsesNameAndVersion() { - var versions = new List - { - MakeVersion(5, deleted: true), - }; + var versions = new List { MakeVersion(7) }; + var markdown = PackageHistoryFile.Format(PackageName, versions, installedVersion: 7); - var markdown = PackageHistoryFile.Format(versions, installedVersion: 5); + var reference = PackageHistoryFile.TryReadInstalledReference(markdown); - markdown.Should().StartWith("# 5\r\n"); - markdown.Should().NotContain("tombstoned"); - PackageHistoryFile.TryReadInstalledVersion(markdown).Should().Be(5); + reference.Should().NotBeNull(); + reference!.Name.Should().Be(PackageName); + reference.Version.Should().Be(7); + } + + [Test] + public void TryReadInstalledReference_VersionOnlyHeading_ReturnsNull() + { + // A bare "# version" heading is not a valid entry; only "name@version" is. + PackageHistoryFile.TryReadInstalledReference("# 5\r\n\r\nSome notes.\r\n").Should().BeNull(); } [Test] @@ -132,4 +154,35 @@ public void TryReadInstalledVersion_ReturnsNull_ForEmptyContent() { PackageHistoryFile.TryReadInstalledVersion(string.Empty).Should().BeNull(); } + + [Test] + public void IsStaleBase_SamePackageOlderThanLatest_IsStale() + { + var installed = new InstalledPackageReference(PackageName, 4); + + PackageHistoryFile.IsStaleBase(installed, PackageName, latestLiveVersion: 6).Should().BeTrue(); + } + + [Test] + public void IsStaleBase_SamePackageAtLatest_IsNotStale() + { + var installed = new InstalledPackageReference(PackageName, 6); + + PackageHistoryFile.IsStaleBase(installed, PackageName, latestLiveVersion: 6).Should().BeFalse(); + } + + [Test] + public void IsStaleBase_DifferentPackage_IsNotStale() + { + // A different recorded name is a rename or fork, not a lost-update race. + var installed = new InstalledPackageReference("other-package", 1); + + PackageHistoryFile.IsStaleBase(installed, PackageName, latestLiveVersion: 6).Should().BeFalse(); + } + + [Test] + public void IsStaleBase_NoInstallRecord_IsNotStale() + { + PackageHistoryFile.IsStaleBase(null, PackageName, latestLiveVersion: 6).Should().BeFalse(); + } } From eba7bafdc9b37a528cd6d4bc524cc9311a7e669b Mon Sep 17 00:00:00 2001 From: Chris Gregan Date: Sun, 14 Jun 2026 21:01:22 +0100 Subject: [PATCH 06/21] Add pages subsystem and Author setting Introduce a pages subsystem (publish-only static sites) and add an Author field to workshop credentials and publishing flows. Changes include: new IPageApiClient, PageConstants, and PageTools (page_publish, page_list, page_info, page_unpublish) with archive building, manifest handling, and confirmation flows; PublishAuthor helper and AgentToolBase.ShowAlertAsync for user alerts; Require Author for package and page publishes and pass it to the API (IPackageApiClient.PublishVersionAsync updated); WorkshopConnection record extended with Author and CredentialService normalized for older entries; Settings UI and viewmodel updated to capture Author; many documentation/guide pages and localization strings added; tests adjusted to reflect removal of manifest-level package author. Several workspace service stubs added for page API integration. The commit enforces that an Author is configured in Settings before publishing and documents the publish-only nature of pages. --- .../Resources/Strings/en-US/Resources.resw | 30 ++ .../Credentials/ICredentialService.cs | 7 +- .../Packages/IPackageApiClient.cs | 6 +- .../Packages/IPageApiClient.cs | 40 +++ .../Packages/PackageInfo.cs | 6 - .../Packages/PageConstants.cs | 19 ++ .../Services/CredentialService.cs | 8 + .../Guides/Concepts/packages_overview.md | 5 +- .../Guides/Concepts/pages_overview.md | 37 +++ .../Celbridge.Tools/Guides/Namespaces/page.md | 18 ++ .../Guides/Tools/package_publish.md | 3 +- .../Celbridge.Tools/Guides/Tools/page_info.md | 18 ++ .../Celbridge.Tools/Guides/Tools/page_list.md | 22 ++ .../Guides/Tools/page_publish.md | 43 +++ .../Guides/Tools/page_unpublish.md | 25 ++ .../Celbridge.Tools/Helpers/PublishAuthor.cs | 33 ++ .../Celbridge.Tools/Tools/AgentToolBase.cs | 14 + .../Tools/Package/PackageTools.Publish.cs | 12 +- .../Tools/Package/PackageTools.cs | 23 ++ .../Tools/Page/PageTools.Info.cs | 47 +++ .../Tools/Page/PageTools.List.cs | 48 +++ .../Tools/Page/PageTools.Publish.cs | 301 ++++++++++++++++++ .../Tools/Page/PageTools.Unpublish.cs | 55 ++++ .../Celbridge.Tools/Tools/Page/PageTools.cs | 52 +++ .../ViewModels/Pages/SettingsPageViewModel.cs | 7 +- .../Views/Pages/SettingsPage.xaml | 20 +- .../Views/Pages/SettingsPage.xaml.cs | 5 + Source/Tests/Packages/ManifestTests.cs | 9 +- .../Tests/Packages/PackageApiClientTests.cs | 8 +- Source/Tests/Packages/PageApiClientTests.cs | 284 +++++++++++++++++ .../Tests/Settings/CredentialServiceTests.cs | 14 + .../SettingsPageViewModelTests.cs | 28 ++ .../ServiceConfiguration.cs | 1 + .../Services/PackageApiClient.cs | 147 ++------- .../Services/PackageManifestLoader.cs | 3 - .../Services/PageApiClient.cs | 219 +++++++++++++ .../Services/WorkshopApiSender.cs | 148 +++++++++ 37 files changed, 1611 insertions(+), 154 deletions(-) create mode 100644 Source/Core/Celbridge.Foundation/Packages/IPageApiClient.cs create mode 100644 Source/Core/Celbridge.Foundation/Packages/PageConstants.cs create mode 100644 Source/Core/Celbridge.Tools/Guides/Concepts/pages_overview.md create mode 100644 Source/Core/Celbridge.Tools/Guides/Namespaces/page.md create mode 100644 Source/Core/Celbridge.Tools/Guides/Tools/page_info.md create mode 100644 Source/Core/Celbridge.Tools/Guides/Tools/page_list.md create mode 100644 Source/Core/Celbridge.Tools/Guides/Tools/page_publish.md create mode 100644 Source/Core/Celbridge.Tools/Guides/Tools/page_unpublish.md create mode 100644 Source/Core/Celbridge.Tools/Helpers/PublishAuthor.cs create mode 100644 Source/Core/Celbridge.Tools/Tools/Page/PageTools.Info.cs create mode 100644 Source/Core/Celbridge.Tools/Tools/Page/PageTools.List.cs create mode 100644 Source/Core/Celbridge.Tools/Tools/Page/PageTools.Publish.cs create mode 100644 Source/Core/Celbridge.Tools/Tools/Page/PageTools.Unpublish.cs create mode 100644 Source/Core/Celbridge.Tools/Tools/Page/PageTools.cs create mode 100644 Source/Tests/Packages/PageApiClientTests.cs create mode 100644 Source/Workspace/Celbridge.Packages/Services/PageApiClient.cs create mode 100644 Source/Workspace/Celbridge.Packages/Services/WorkshopApiSender.cs diff --git a/Source/Celbridge/Resources/Strings/en-US/Resources.resw b/Source/Celbridge/Resources/Strings/en-US/Resources.resw index 8a98ff78e..781c39162 100644 --- a/Source/Celbridge/Resources/Strings/en-US/Resources.resw +++ b/Source/Celbridge/Resources/Strings/en-US/Resources.resw @@ -417,9 +417,24 @@ Workshop URL + + The web address of your Workshop server, where packages and pages are published. + Application Key + + The secret key issued by your Workshop. Keep it private; it authenticates your requests. + + + Author Name + + + Your name, recorded with the packages and pages you publish to the workshop. + + + Your name + Save @@ -1061,6 +1076,21 @@ Do you wish to continue? Unpublish package '{0}' from the workshop? The package and all its versions are removed permanently and cannot be recovered. + + Publish Page + + + Publish this folder as a page at '{0}'? It will be served publicly on the workshop. + + + Unpublish Page + + + Unpublish the page at '{0}' from the workshop? It will no longer be served. + + + Cannot Publish + Spreadsheet Editor diff --git a/Source/Core/Celbridge.Foundation/Credentials/ICredentialService.cs b/Source/Core/Celbridge.Foundation/Credentials/ICredentialService.cs index bb3bbbf3f..105e0b68e 100644 --- a/Source/Core/Celbridge.Foundation/Credentials/ICredentialService.cs +++ b/Source/Core/Celbridge.Foundation/Credentials/ICredentialService.cs @@ -1,11 +1,10 @@ namespace Celbridge.Credentials; /// -/// A Workshop server URL paired with the Application Key issued by that server. -/// The two values are stored and retrieved together because a key is only -/// meaningful against the server that issued it. +/// A Workshop server URL, the Application Key issued by that server, and the +/// Author name recorded as the publisher of packages and pages. /// -public record WorkshopConnection(string WorkshopUrl, string ApplicationKey); +public record WorkshopConnection(string WorkshopUrl, string ApplicationKey, string Author = ""); /// /// Summary of the stored Workshop connection, readable without decrypting it. diff --git a/Source/Core/Celbridge.Foundation/Packages/IPackageApiClient.cs b/Source/Core/Celbridge.Foundation/Packages/IPackageApiClient.cs index 8646ad352..b5ab0daf3 100644 --- a/Source/Core/Celbridge.Foundation/Packages/IPackageApiClient.cs +++ b/Source/Core/Celbridge.Foundation/Packages/IPackageApiClient.cs @@ -73,9 +73,11 @@ public interface IPackageApiClient /// /// Publishes a new package version from ZIP data, with an optional change - /// summary. The first publish of a new name registers the package implicitly. + /// summary and the publishing author. The first publish of a new name + /// registers the package implicitly. The author is recorded by the workshop + /// as the version's publisher. /// - Task> PublishVersionAsync(string packageName, byte[] zipData, string? summary = null); + Task> PublishVersionAsync(string packageName, byte[] zipData, string? summary = null, string? author = null); /// /// Downloads the ZIP data for a specific package version. diff --git a/Source/Core/Celbridge.Foundation/Packages/IPageApiClient.cs b/Source/Core/Celbridge.Foundation/Packages/IPageApiClient.cs new file mode 100644 index 000000000..74e5d93e9 --- /dev/null +++ b/Source/Core/Celbridge.Foundation/Packages/IPageApiClient.cs @@ -0,0 +1,40 @@ +namespace Celbridge.Packages; + +/// +/// A page published by the workshop. Path is the served path; Url is the public +/// address the rendered site is served at. +/// +public record RemotePage( + string Path, + string Url, + DateTime PublishedAt, + string PublishedBy, + string ContentHash); + +/// +/// Client for the workshop server's page REST API. Pages are publish-only: +/// there is no endpoint to download a published page back. +/// +public interface IPageApiClient +{ + /// + /// Publishes a page bundle as a new page and returns it. Path is the served + /// path declared in the bundle's manifest; author records the publisher. + /// + Task> PublishPageAsync(byte[] zipData, string path, string? author = null); + + /// + /// Lists the pages published to the workshop. + /// + Task>> ListPagesAsync(); + + /// + /// Gets a published page by its served path. + /// + Task> GetPageAsync(string path); + + /// + /// Unpublishes a page by its served path, removing its served content. + /// + Task UnpublishPageAsync(string path); +} diff --git a/Source/Core/Celbridge.Foundation/Packages/PackageInfo.cs b/Source/Core/Celbridge.Foundation/Packages/PackageInfo.cs index 2a7f98b81..687148392 100644 --- a/Source/Core/Celbridge.Foundation/Packages/PackageInfo.cs +++ b/Source/Core/Celbridge.Foundation/Packages/PackageInfo.cs @@ -35,12 +35,6 @@ public partial record PackageInfo /// public string Name { get; init; } = string.Empty; - /// - /// Optional author of the package (from the manifest's 'author' key). The - /// workshop server reads this when a version is published. - /// - public string Author { get; init; } = string.Empty; - /// /// Optional display name of the package (from the manifest's 'title' key). /// diff --git a/Source/Core/Celbridge.Foundation/Packages/PageConstants.cs b/Source/Core/Celbridge.Foundation/Packages/PageConstants.cs new file mode 100644 index 000000000..d257677ca --- /dev/null +++ b/Source/Core/Celbridge.Foundation/Packages/PageConstants.cs @@ -0,0 +1,19 @@ +namespace Celbridge.Packages; + +/// +/// Well-known names shared across the pages subsystem. Pages are decoupled from +/// packages: a page is a folder of static web content published to a public URL, +/// not a versioned artifact. +/// +public static class PageConstants +{ + /// + /// File name of the page manifest at the root of a page folder. + /// + public const string ManifestFileName = "pages.toml"; + + /// + /// Default publish-source folder for pages, relative to the project root. + /// + public const string DefaultPagesFolder = "pages"; +} diff --git a/Source/Core/Celbridge.Settings/Services/CredentialService.cs b/Source/Core/Celbridge.Settings/Services/CredentialService.cs index 751a57e04..bace75a3b 100644 --- a/Source/Core/Celbridge.Settings/Services/CredentialService.cs +++ b/Source/Core/Celbridge.Settings/Services/CredentialService.cs @@ -205,6 +205,14 @@ public async Task> GetWorkshopConnectionAsync() return Result.Fail(CorruptStoreMessage); } + // Connections saved before the Author field was added have no value + // for it; normalize the missing case to empty so callers gate on it + // uniformly rather than guarding against null. + if (connection.Author is null) + { + connection = connection with { Author = string.Empty }; + } + return connection; } finally diff --git a/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md b/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md index fb6c9adb4..c2da2df2a 100644 --- a/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md +++ b/Source/Core/Celbridge.Tools/Guides/Concepts/packages_overview.md @@ -13,14 +13,15 @@ Every package folder must contain a `package.toml` at its root with at minimum a ```toml [package] name = "my-widget" # identifier; matches the workshop's package name -author = "Acme" # read by the workshop when a version is published title = "My Widget" # display name [contributes] document_editors = ["my-editor.document.toml"] ``` -**Required:** `name`. **Optional:** `author`, `title`, `feature_flag`. The `[contributes]` section lists document editor manifests provided by the package. If your package contributes a document editor, also read `document_editor_contributions` for the manifest, handler, and read-only contract. +**Required:** `name`. **Optional:** `title`, `feature_flag`. The `[contributes]` section lists document editor manifests provided by the package. If your package contributes a document editor, also read `document_editor_contributions` for the manifest, handler, and read-only contract. + +The manifest carries no author: the publisher recorded on each version is the **Author** set once in Workshop settings (Settings page), not a per-package field. `package_publish` fails if no Author is configured. A package name is lowercase ASCII alphanumeric with single interior hyphens as the only separator, 1-64 characters. There is no version field in the manifest: version numbers are assigned by the workshop when a version is published. diff --git a/Source/Core/Celbridge.Tools/Guides/Concepts/pages_overview.md b/Source/Core/Celbridge.Tools/Guides/Concepts/pages_overview.md new file mode 100644 index 000000000..a13365594 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Guides/Concepts/pages_overview.md @@ -0,0 +1,37 @@ +# Pages + +A **page** is a folder of static web content (HTML, JavaScript, CSS, assets) published to the workshop and served at a public URL. Pages are a decoupled subsystem: they have no relationship to packages, and publishing or unpublishing a page never touches a package. + +## Manifest (`pages.toml`) + +A page folder must contain a `pages.toml` at its root naming the path the site is served at: + +```toml +[publish] +path = "my-site/home" +``` + +The path is multi-segment and becomes a subpath of the served URL. The page ZIP's root is the served site: everything in the folder is published verbatim except `pages.toml` itself, which the workshop reads to learn the path. + +## Publish-only by design + +There is **no pull or install of a page** — this is intentional, not a missing feature. A page is a deploy target: rendered static content served at a public URL, replaceable at any time. The `page` tools are publish, list, inspect, and unpublish only; the served site is the rendered output, not the original bundle, and the workshop keeps no recoverable copy of what you uploaded. + +The consequence to plan around: **a page published from a folder that is later lost cannot be retrieved.** If you need a versioned, content-addressed, recoverable, and pullable site, wrap the content in a **package** and publish that — the package is the versioned artifact, and the page is just the deployment of its content. Keeping the source folder under version control (or as a package) is the recommended safeguard. + +## Workflow + +| Tool | What it does | +|---|---| +| `page_list()` | List all pages published to the workshop | +| `page_info("my-site/home")` | Inspect one published page (served URL, publisher, content hash) | +| `page_publish("pages/site", confirmWithUser)` | Zip a folder and publish it as a page; the served path comes from `pages.toml` | +| `page_unpublish("my-site/home", confirmWithUser)` | Remove a page's served content | + +`page_publish` takes a folder resource key (or the `pages.toml` key), defaulting to `pages/` in the project root; the source can live under any readable root, including a `temp:` staging area. The served path always comes from the manifest, independent of the local folder name. + +The publisher recorded on a page is the **Author** set in Workshop settings (Settings page). `page_publish` fails if no Author is configured. + +## Confirmation prompts + +`page_publish` and `page_unpublish` are outward-facing and confirm by default. Both accept `confirmWithUser` (default `true`); pass `false` only when the user has explicitly asked for unattended operation. They are held to a more lenient bar than the package admin tools (`package_delete`, `package_unpublish`, which always prompt with no opt-out), because a page is re-publishable static content rather than irreversible version history. diff --git a/Source/Core/Celbridge.Tools/Guides/Namespaces/page.md b/Source/Core/Celbridge.Tools/Guides/Namespaces/page.md new file mode 100644 index 000000000..53d55b4ab --- /dev/null +++ b/Source/Core/Celbridge.Tools/Guides/Namespaces/page.md @@ -0,0 +1,18 @@ +# page + +The `page` namespace publishes, lists, inspects, and unpublishes static web pages on the connected workshop. A page is a folder of static content (HTML, JavaScript, CSS, assets) served at a public URL, with a `pages.toml` manifest at its root naming the served path. See `pages_overview` for the manifest and the publish-only model. + +## Must-knows + +- **Pages are publish-only.** There is no pull or install of a page, by design. The served site is the rendered output, not the original bundle, and the workshop keeps no recoverable copy. If you need a versioned, pullable site, wrap the content in a package and publish that — the package is the versioned artifact, the page is its deployment. +- **The served path comes from the manifest.** `page_publish` reads `[publish].path` from `pages.toml`; the local folder name does not matter and there is no separate path argument. +- **Pages are decoupled from packages.** Publishing or unpublishing a page never touches a package, and vice versa. +- **Publishing and unpublishing confirm by default.** `page_publish` and `page_unpublish` are outward-facing and prompt before acting; pass `confirmWithUser: false` only for unattended flows the user has consented to. See `silent_vs_interactive`. +- **`page_publish` requires a loaded project.** The source is a project folder, so a project must be open. + +## Tools + +- `page_list` — list the pages published to the workshop. +- `page_info` — inspect one published page (served URL, publisher, content hash). +- `page_publish` — zip a folder and publish it as a page; served path read from `pages.toml`. Interactive by default. +- `page_unpublish` — remove a page's served content. Interactive by default. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md index d3ef48858..e0818e844 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md @@ -22,6 +22,7 @@ When `true` (default), shows a confirmation dialog before uploading. Leave at th Before uploading, the tool verifies that: +- An **Author** is set in Workshop settings (it is recorded as the version's publisher). - `resource` resolves to a `package.toml` manifest (or a folder containing one). - The manifest is valid TOML with a `[package]` section whose `name` is a valid package name. - The `summary`, if given, is within the 512-character cap. @@ -57,4 +58,4 @@ The check needs the install record (`HISTORY.md`) to read which version the fold - Symlinks and other reparse points inside the package folder are skipped, not followed. - Publishing always creates a new version and never overwrites an earlier one. To remove a version, use `package_delete`; to remove a whole package, `package_unpublish`. -- The workshop reads the publisher from the manifest's `author` field, so set it before the first publish. +- The publisher recorded on the version is the **Author** set in Workshop settings (Settings page), not a manifest field. Publishing fails with a clear message (and an alert, when interactive) if no Author is configured. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/page_info.md b/Source/Core/Celbridge.Tools/Guides/Tools/page_info.md new file mode 100644 index 000000000..0d78f3a2c --- /dev/null +++ b/Source/Core/Celbridge.Tools/Guides/Tools/page_info.md @@ -0,0 +1,18 @@ +# page_info + +Inspects one published page by its served path. + +## Parameters + +### path + +The page's served path as it appears on the workshop (e.g. `my-site/home`). This is the `[publish].path` value from the page's `pages.toml`, not a local folder name. Multi-segment paths are written with `/` separators. + +## Returns + +A JSON object with `path`, `url` (the full public URL the page is served at), `publishedAt`, `publishedBy`, and `contentHash`. Fails with a clear message when no page is published at the given path. + +## Gotchas + +- The path is the served path from the manifest, not the local folder you published from. +- There is no way to download the page bundle back; this returns metadata only. See `pages_overview`. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/page_list.md b/Source/Core/Celbridge.Tools/Guides/Tools/page_list.md new file mode 100644 index 000000000..752485c79 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Guides/Tools/page_list.md @@ -0,0 +1,22 @@ +# page_list + +Lists every page published to the connected workshop. + +## Parameters + +None. + +## Returns + +A JSON array of page entries, each with: + +- `path` (string) — the served path declared in the page's `pages.toml` (e.g. `my-site/home`). +- `url` (string) — the full public URL the page is served at. +- `publishedAt` (string) — when the page was last published. +- `publishedBy` (string) — the publisher. +- `contentHash` (string) — fingerprint of the published bundle. + +## Gotchas + +- This lists pages, not packages. The two are separate subsystems; use `package_list` for packages. +- Pages cannot be downloaded back — there is no install. See `pages_overview` for the publish-only model. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/page_publish.md b/Source/Core/Celbridge.Tools/Guides/Tools/page_publish.md new file mode 100644 index 000000000..38b3489d9 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Guides/Tools/page_publish.md @@ -0,0 +1,43 @@ +# page_publish + +Zips a folder of static web content and publishes it to the connected workshop as a page. The served path is read from the folder's `pages.toml`, so the local folder name does not matter. The page is then served publicly at that path. + +By default a confirmation dialog is shown before publishing; pass `confirmWithUser: false` only when the user has explicitly asked for unattended operation. + +## Parameters + +### resource + +Resource key of the page folder, or of its `pages.toml` manifest. Defaults to `pages/` in the project root when omitted. The folder is what gets zipped and uploaded. Because this is a resource key, any readable root works — including assembling a page under `temp:` and publishing it from there. + +### confirmWithUser + +When `true` (default), shows a confirmation dialog naming the served path before uploading. Leave at the default unless the user has asked for an unattended run. + +## Validation + +Before uploading, the tool verifies that: + +- A project is loaded. +- An **Author** is set in Workshop settings (it is recorded as the page's publisher). +- `resource` resolves to a folder containing a `pages.toml` (or to that manifest). +- The manifest is valid TOML with a `[publish]` section whose `path` is a non-empty string. +- The folder contains at least one file. + +If any check fails, no upload is attempted. If a `page.toml` (singular) is present but no `pages.toml`, the error names the near-miss so you can rename it. + +## Returns + +A JSON object: + +- `path` (string) — the served path the workshop assigned (read back from the server, authoritative). +- `url` (string) — the full public URL the page is served at. +- `entries` (int) — number of files included in the uploaded zip. +- `size` (long) — uploaded zip size in bytes. + +## Gotchas + +- **The served site excludes `pages.toml`, but the bundle includes it.** The whole folder is zipped (the server reads the manifest from the bundle); everything except `pages.toml` is published verbatim. +- **A path overlap fails.** If a page is already published at the manifest's path, the workshop rejects the publish; unpublish the existing page first, or change `[publish].path`. +- **Pages are publish-only and not recoverable.** The workshop keeps no copy of the bundle you can pull back, so keep the source folder. For a versioned, pullable site, wrap the content in a package instead. See `pages_overview`. +- Symlinks and other reparse points inside the folder are skipped, not followed. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/page_unpublish.md b/Source/Core/Celbridge.Tools/Guides/Tools/page_unpublish.md new file mode 100644 index 000000000..12b711aa8 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Guides/Tools/page_unpublish.md @@ -0,0 +1,25 @@ +# page_unpublish + +Removes a page's served content from the workshop, identified by its served path. + +By default a confirmation dialog is shown; pass `confirmWithUser: false` only when the user has explicitly asked for unattended operation. This is held to a more lenient bar than the package admin tools (`package_delete`, `package_unpublish`, which always prompt) because a page is re-publishable static content, not irreversible version history. + +## Parameters + +### path + +The page's served path as it appears on the workshop (e.g. `my-site/home`) — the `[publish].path` from its `pages.toml`. Multi-segment paths use `/` separators. + +### confirmWithUser + +When `true` (default), shows a confirmation dialog naming the path before removing the page. Leave at the default unless the user has asked for an unattended run. + +## Returns + +A JSON object echoing `path` and `unpublished: true`. + +## Gotchas + +- **The workshop keeps no recoverable copy.** Re-publishing the page after unpublishing requires the original source folder. See `pages_overview`. +- Unpublishing a page does not touch any package; pages and packages are separate subsystems. +- Fails with a clear message when no page is published at the given path. diff --git a/Source/Core/Celbridge.Tools/Helpers/PublishAuthor.cs b/Source/Core/Celbridge.Tools/Helpers/PublishAuthor.cs new file mode 100644 index 000000000..5d90fedb9 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Helpers/PublishAuthor.cs @@ -0,0 +1,33 @@ +using Celbridge.Credentials; + +namespace Celbridge.Tools; + +/// +/// Resolves the publisher Author from the stored Workshop connection. Every +/// publish records who published it; until a user-account login exists the +/// Author is set once on the Settings page. Returns a clear, actionable failure +/// when no connection or no Author is configured, so the publish tools can both +/// surface it to the agent and alert the user. +/// +internal static class PublishAuthor +{ + public static async Task> ResolveAsync(ICredentialService credentialService) + { + var connectionResult = await credentialService.GetWorkshopConnectionAsync(); + if (connectionResult.IsFailure) + { + return Result.Fail( + "Cannot publish: no workshop connection is configured. Set it up on the Settings page, then try again.") + .WithErrors(connectionResult); + } + var connection = connectionResult.Value; + + if (string.IsNullOrWhiteSpace(connection.Author)) + { + return Result.Fail( + "Cannot publish: no Author is set. Add one on the Settings page, then try again."); + } + + return connection.Author.Trim(); + } +} diff --git a/Source/Core/Celbridge.Tools/Tools/AgentToolBase.cs b/Source/Core/Celbridge.Tools/Tools/AgentToolBase.cs index 6d000af50..8c8819c1e 100644 --- a/Source/Core/Celbridge.Tools/Tools/AgentToolBase.cs +++ b/Source/Core/Celbridge.Tools/Tools/AgentToolBase.cs @@ -71,6 +71,20 @@ protected async Task> ExecuteCommandAsync( protected static Result ParseJsonArgument(string json, string label) where T : class => JsonArgumentParser.Parse(json, label, JsonOptions); + /// + /// Shows a modal alert dialog so the human operating the app sees a message + /// that would otherwise only reach the agent through a tool error. Used when + /// a tool must communicate a setup problem the user has to fix. + /// + protected async Task ShowAlertAsync(string title, string message) + { + await ExecuteCommandAsync(command => + { + command.Title = title; + command.Message = message; + }); + } + /// /// Loads an embedded resource from the Celbridge.Tools assembly as a string. /// Returns a placeholder string when the resource is missing (build-time invariant). diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs index 07af4a149..38d1c8a4a 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs @@ -1,5 +1,6 @@ using System.IO.Compression; using System.Text.Json; +using Celbridge.Credentials; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; using Tomlyn; @@ -42,6 +43,15 @@ public async partial Task Publish(string resource, string summar return ToolResponse.Error("No project is loaded. Open a project before publishing a package."); } + // Every published version records its publisher, so a non-empty Author + // must be configured before any upload work begins. + var authorResult = await ResolvePublishAuthorAsync(confirmWithUser); + if (authorResult.IsFailure) + { + return ToolResponse.Error(authorResult); + } + var author = authorResult.Value; + var resourceService = workspaceWrapper.WorkspaceService.ResourceService; var resourceRegistry = resourceService.Registry; var fileSystem = GetRequiredService(); @@ -106,7 +116,7 @@ public async partial Task Publish(string resource, string summar var archive = buildResult.Value; var publishSummary = string.IsNullOrEmpty(summary) ? null : summary; - var publishResult = await packageApiClient.PublishVersionAsync(packageName, archive.ZipData, publishSummary); + var publishResult = await packageApiClient.PublishVersionAsync(packageName, archive.ZipData, publishSummary, author); if (publishResult.IsFailure) { return ToolResponse.Error(publishResult); diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs index 7d9aaddb3..3c9104b86 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs @@ -1,3 +1,4 @@ +using Celbridge.Credentials; using ModelContextProtocol.Server; namespace Celbridge.Tools; @@ -55,4 +56,26 @@ private async Task ConfirmActionAsync(string title, string message) var confirmResult = confirmResultWrapper.Value; return confirmResult.Confirmed; } + + // Resolves the publisher Author from Workshop settings, alerting the user + // (when interactive) if it is missing so the problem is visible and not just + // returned to the agent. + private async Task> ResolvePublishAuthorAsync(bool confirmWithUser) + { + var credentialService = GetRequiredService(); + var authorResult = await PublishAuthor.ResolveAsync(credentialService); + if (authorResult.IsFailure) + { + if (confirmWithUser) + { + var localizerService = GetRequiredService(); + var title = localizerService.GetString("Workshop_PublishBlocked_Title"); + await ShowAlertAsync(title, authorResult.FirstErrorMessage); + } + + return authorResult; + } + + return authorResult.Value; + } } diff --git a/Source/Core/Celbridge.Tools/Tools/Page/PageTools.Info.cs b/Source/Core/Celbridge.Tools/Tools/Page/PageTools.Info.cs new file mode 100644 index 000000000..647af94fc --- /dev/null +++ b/Source/Core/Celbridge.Tools/Tools/Page/PageTools.Info.cs @@ -0,0 +1,47 @@ +using System.Text.Json; +using ModelContextProtocol.Protocol; +using ModelContextProtocol.Server; + +namespace Celbridge.Tools; + +/// +/// Result returned by page_info: a published page's served URL, publisher, and content hash. +/// +public record class PageInfoResult( + string Path, + string Url, + DateTime PublishedAt, + string PublishedBy, + string ContentHash); + +public partial class PageTools +{ + /// Inspect a published workshop page by its served path: its URL, publisher, and content hash. + [McpServerTool(Name = "page_info", ReadOnly = true)] + [ToolAlias("page.info")] + [RelatedGuides("pages_overview")] + public async partial Task Info(string path) + { + if (string.IsNullOrWhiteSpace(path)) + { + return ToolResponse.Error("A page path is required, for example 'my-site/home'."); + } + + var pageApiClient = GetRequiredService(); + var pageResult = await pageApiClient.GetPageAsync(path.Trim()); + if (pageResult.IsFailure) + { + return ToolResponse.Error(pageResult); + } + var page = pageResult.Value; + + var result = new PageInfoResult( + page.Path, + page.Url, + page.PublishedAt, + page.PublishedBy, + page.ContentHash); + var json = JsonSerializer.Serialize(result, JsonOptions); + return ToolResponse.Success(json); + } +} diff --git a/Source/Core/Celbridge.Tools/Tools/Page/PageTools.List.cs b/Source/Core/Celbridge.Tools/Tools/Page/PageTools.List.cs new file mode 100644 index 000000000..8df759c43 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Tools/Page/PageTools.List.cs @@ -0,0 +1,48 @@ +using System.Text.Json; +using ModelContextProtocol.Protocol; +using ModelContextProtocol.Server; + +namespace Celbridge.Tools; + +/// +/// A page entry in the page_list result. +/// +public record class PageListEntry( + string Path, + string Url, + DateTime PublishedAt, + string PublishedBy, + string ContentHash); + +public partial class PageTools +{ + /// List all pages published to the connected workshop. + [McpServerTool(Name = "page_list", ReadOnly = true)] + [ToolAlias("page.list")] + [RelatedGuides("pages_overview")] + public async partial Task List() + { + var pageApiClient = GetRequiredService(); + var listResult = await pageApiClient.ListPagesAsync(); + + if (listResult.IsFailure) + { + return ToolResponse.Error(listResult); + } + + var pages = new List(); + foreach (var page in listResult.Value) + { + var entry = new PageListEntry( + page.Path, + page.Url, + page.PublishedAt, + page.PublishedBy, + page.ContentHash); + pages.Add(entry); + } + + var json = JsonSerializer.Serialize(pages, JsonOptions); + return ToolResponse.Success(json); + } +} diff --git a/Source/Core/Celbridge.Tools/Tools/Page/PageTools.Publish.cs b/Source/Core/Celbridge.Tools/Tools/Page/PageTools.Publish.cs new file mode 100644 index 000000000..66794fbdf --- /dev/null +++ b/Source/Core/Celbridge.Tools/Tools/Page/PageTools.Publish.cs @@ -0,0 +1,301 @@ +using System.IO.Compression; +using System.Text.Json; +using ModelContextProtocol.Protocol; +using ModelContextProtocol.Server; +using Tomlyn; +using Tomlyn.Model; +using MemoryStream = System.IO.MemoryStream; +using Path = System.IO.Path; + +namespace Celbridge.Tools; + +/// +/// Result returned by page_publish with the served path and URL the workshop +/// assigned, and the size of the uploaded bundle. +/// +public record class PagePublishResult(string Path, string Url, int Entries, long Size); + +public partial class PageTools +{ + /// Publish a folder of static web content to the workshop as a page (default pages/). + [McpServerTool(Name = "page_publish", Destructive = true)] + [ToolAlias("page.publish")] + [RelatedGuides("pages_overview", "resource_keys", "silent_vs_interactive")] + public async partial Task Publish(string resource = "", bool confirmWithUser = true) + { + var workspaceWrapper = GetRequiredService(); + if (!workspaceWrapper.IsWorkspacePageLoaded) + { + return ToolResponse.Error("No project is loaded. Open a project before publishing a page."); + } + + // Every published page records its publisher, so a non-empty Author must + // be configured before any upload work begins. + var authorResult = await ResolvePublishAuthorAsync(confirmWithUser); + if (authorResult.IsFailure) + { + return ToolResponse.Error(authorResult); + } + var author = authorResult.Value; + + ResourceKey resourceKey; + if (string.IsNullOrWhiteSpace(resource)) + { + resourceKey = new ResourceKey(PageConstants.DefaultPagesFolder); + } + else if (!ResourceKey.TryCreate(resource, out resourceKey)) + { + return ToolResponse.InvalidResourceKey(resource); + } + + var resourceService = workspaceWrapper.WorkspaceService.ResourceService; + var resourceRegistry = resourceService.Registry; + var fileSystem = GetRequiredService(); + + var resolveResult = resourceRegistry.ResolveResourcePath(resourceKey); + if (resolveResult.IsFailure) + { + return ToolResponse.Error(resolveResult.FirstErrorMessage); + } + var resolvedPath = resolveResult.Value; + + var locateResult = await LocatePageFolderAsync(fileSystem, resourceKey, resolvedPath); + if (locateResult.IsFailure) + { + return ToolResponse.Error(locateResult); + } + var pageSource = locateResult.Value; + + var pathResult = await ReadManifestPublishPathAsync(fileSystem, pageSource.ManifestPath); + if (pathResult.IsFailure) + { + return ToolResponse.Error(pathResult); + } + var publishPath = pathResult.Value; + + if (confirmWithUser) + { + var confirmed = await ConfirmPublishAsync(publishPath); + if (!confirmed) + { + return ToolResponse.Error("Publish cancelled by user."); + } + } + + var buildResult = await BuildPageArchiveAsync(fileSystem, pageSource.FolderPath); + if (buildResult.IsFailure) + { + return ToolResponse.Error(buildResult); + } + var archive = buildResult.Value; + + var pageApiClient = GetRequiredService(); + var publishResult = await pageApiClient.PublishPageAsync(archive.ZipData, publishPath, author); + if (publishResult.IsFailure) + { + return ToolResponse.Error(publishResult); + } + var page = publishResult.Value; + + // The server is authoritative for the served path, so report what it + // returned rather than the path read from the manifest. + var result = new PagePublishResult(page.Path, page.Url, archive.EntryCount, archive.ZipData.Length); + var json = JsonSerializer.Serialize(result, JsonOptions); + return ToolResponse.Success(json); + } + + private async Task ConfirmPublishAsync(string publishPath) + { + var localizerService = GetRequiredService(); + var title = localizerService.GetString("Page_PublishConfirm_Title"); + var message = localizerService.GetString("Page_PublishConfirm_Message", publishPath); + + return await ConfirmActionAsync(title, message); + } + + // The publish source is a folder containing a pages.toml. Accepts either the + // manifest's own resource key or the folder that holds it, so an agent can + // name whichever it has to hand (mirrors package_publish). + private static async Task> LocatePageFolderAsync( + ILocalFileSystem fileSystem, + ResourceKey resourceKey, + string resolvedPath) + { + var infoResult = await fileSystem.GetInfoAsync(resolvedPath); + if (infoResult.IsFailure + || infoResult.Value.Kind == StorageItemKind.NotFound) + { + return Result.Fail($"Resource not found: '{resourceKey}'."); + } + + string folderPath; + string manifestPath; + if (infoResult.Value.Kind == StorageItemKind.Folder) + { + folderPath = resolvedPath; + manifestPath = Path.Combine(resolvedPath, PageConstants.ManifestFileName); + } + else + { + var fileName = Path.GetFileName(resolvedPath); + if (!string.Equals(fileName, PageConstants.ManifestFileName, StringComparison.OrdinalIgnoreCase)) + { + return Result.Fail( + $"Expected the page's '{PageConstants.ManifestFileName}' manifest or its folder, " + + $"but '{resourceKey}' is a different file."); + } + manifestPath = resolvedPath; + folderPath = Path.GetDirectoryName(resolvedPath)!; + } + + var manifestInfoResult = await fileSystem.GetInfoAsync(manifestPath); + if (manifestInfoResult.IsFailure + || manifestInfoResult.Value.Kind != StorageItemKind.File) + { + // The plural 'pages.toml' is an easy thing to get wrong because every + // other manifest in the project is singular. If the singular spelling + // is present, point at it directly rather than reporting a bare miss. + var nearMissResult = await DetectManifestNearMissAsync(fileSystem, folderPath); + if (nearMissResult is not null) + { + return Result.Fail(nearMissResult); + } + + return Result.Fail( + $"Page manifest not found. Expected '{PageConstants.ManifestFileName}' in the page folder."); + } + + return new PageSource(folderPath, manifestPath); + } + + // The page manifest is 'pages.toml' (plural), unlike every other singular + // manifest in the project. Returns a targeted message when the singular + // 'page.toml' is present instead, so a near-miss is named rather than left + // as a silent "no manifest found". + private const string SingularManifestNearMiss = "page.toml"; + + private static async Task DetectManifestNearMissAsync(ILocalFileSystem fileSystem, string folderPath) + { + // Windows file lookup is case-insensitive, so a wrong-case name already + // resolves; the realistic near-miss is the singular spelling. + if (string.Equals(SingularManifestNearMiss, PageConstants.ManifestFileName, StringComparison.OrdinalIgnoreCase)) + { + return null; + } + + var nearMissPath = Path.Combine(folderPath, SingularManifestNearMiss); + var infoResult = await fileSystem.GetInfoAsync(nearMissPath); + if (infoResult.IsSuccess + && infoResult.Value.Kind == StorageItemKind.File) + { + return $"Found '{SingularManifestNearMiss}', but the page manifest must be named " + + $"'{PageConstants.ManifestFileName}' (plural). Rename it to '{PageConstants.ManifestFileName}'."; + } + + return null; + } + + private static async Task> ReadManifestPublishPathAsync(ILocalFileSystem fileSystem, string manifestPath) + { + var readResult = await fileSystem.ReadAllTextAsync(manifestPath); + if (readResult.IsFailure) + { + return Result.Fail($"Failed to read page manifest: {readResult.FirstErrorMessage}"); + } + var tomlContent = readResult.Value; + + TomlTable tomlTable; + try + { + tomlTable = Toml.ToModel(tomlContent); + } + catch (TomlException exception) + { + return Result.Fail($"Invalid TOML in page manifest: {exception.Message}"); + } + + if (!tomlTable.TryGetValue("publish", out var publishSection) + || publishSection is not TomlTable publishTable) + { + return Result.Fail($"Page manifest is missing the required [publish] section in '{PageConstants.ManifestFileName}'."); + } + + if (!publishTable.TryGetValue("path", out var pathValue) + || pathValue is not string pathString + || string.IsNullOrWhiteSpace(pathString)) + { + return Result.Fail($"Page manifest is missing a required 'path' field in the [publish] section."); + } + + return pathString.Trim(); + } + + // Zips the whole folder, including pages.toml. The current server reads the + // publish path from the manifest in the bundle, so it is still uploaded; the + // server serves everything else verbatim but does not serve pages.toml, so it + // is not exposed at the public URL. The path is now also sent as a separate + // form field (see PublishPageAsync), so once the server reads that field the + // manifest can be dropped from the upload entirely. + private static async Task> BuildPageArchiveAsync(ILocalFileSystem fileSystem, string folderPath) + { + int entryCount = 0; + byte[] zipData; + + try + { + using var memoryStream = new MemoryStream(); + using (var zipArchive = new ZipArchive(memoryStream, ZipArchiveMode.Create, leaveOpen: true)) + { + var enumerateResult = await fileSystem.EnumerateAsync(folderPath, "*", recursive: true); + if (enumerateResult.IsFailure) + { + return Result.Fail($"Failed to enumerate page files: {enumerateResult.FirstErrorMessage}"); + } + var fileEntries = enumerateResult.Value + .Where(entry => !entry.IsFolder) + .ToList(); + + foreach (var fileEntry in fileEntries) + { + // Skip symlinks and other reparse points rather than following them. + if (fileEntry.Attributes.HasFlag(FileSystemAttributes.ReparsePoint)) + { + continue; + } + + var filePath = fileEntry.FullPath; + var relativePath = Path.GetRelativePath(folderPath, filePath); + var entryName = relativePath.Replace('\\', '/'); + + var entry = zipArchive.CreateEntry(entryName, CompressionLevel.Optimal); + using var entryStream = entry.Open(); + var openResult = await fileSystem.OpenReadAsync(filePath); + if (openResult.IsFailure) + { + return Result.Fail($"Failed to open file for packaging '{filePath}': {openResult.FirstErrorMessage}"); + } + using var sourceStream = openResult.Value; + await sourceStream.CopyToAsync(entryStream); + entryCount++; + } + } + + zipData = memoryStream.ToArray(); + } + catch (System.IO.IOException exception) + { + return Result.Fail($"Failed to create page archive: {exception.Message}"); + } + + if (entryCount == 0) + { + return Result.Fail("The page folder contains no files to publish."); + } + + return new PageArchive(zipData, entryCount); + } + + private record class PageSource(string FolderPath, string ManifestPath); + + private record class PageArchive(byte[] ZipData, int EntryCount); +} diff --git a/Source/Core/Celbridge.Tools/Tools/Page/PageTools.Unpublish.cs b/Source/Core/Celbridge.Tools/Tools/Page/PageTools.Unpublish.cs new file mode 100644 index 000000000..ccf8f4af0 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Tools/Page/PageTools.Unpublish.cs @@ -0,0 +1,55 @@ +using System.Text.Json; +using ModelContextProtocol.Protocol; +using ModelContextProtocol.Server; + +namespace Celbridge.Tools; + +/// +/// Result returned by page_unpublish confirming the page's served content was removed. +/// +public record class PageUnpublishResult(string Path, bool Unpublished); + +public partial class PageTools +{ + /// Unpublish a page from the workshop, removing its served content. + [McpServerTool(Name = "page_unpublish", Destructive = true)] + [ToolAlias("page.unpublish")] + [RelatedGuides("pages_overview", "silent_vs_interactive")] + public async partial Task Unpublish(string path, bool confirmWithUser = true) + { + if (string.IsNullOrWhiteSpace(path)) + { + return ToolResponse.Error("A page path is required, for example 'my-site/home'."); + } + var pagePath = path.Trim(); + + if (confirmWithUser) + { + var confirmed = await ConfirmUnpublishAsync(pagePath); + if (!confirmed) + { + return ToolResponse.Error("Unpublish cancelled by user."); + } + } + + var pageApiClient = GetRequiredService(); + var unpublishResult = await pageApiClient.UnpublishPageAsync(pagePath); + if (unpublishResult.IsFailure) + { + return ToolResponse.Error(unpublishResult); + } + + var result = new PageUnpublishResult(pagePath, true); + var json = JsonSerializer.Serialize(result, JsonOptions); + return ToolResponse.Success(json); + } + + private async Task ConfirmUnpublishAsync(string path) + { + var localizerService = GetRequiredService(); + var title = localizerService.GetString("Page_UnpublishConfirm_Title"); + var message = localizerService.GetString("Page_UnpublishConfirm_Message", path); + + return await ConfirmActionAsync(title, message); + } +} diff --git a/Source/Core/Celbridge.Tools/Tools/Page/PageTools.cs b/Source/Core/Celbridge.Tools/Tools/Page/PageTools.cs new file mode 100644 index 000000000..ed1223e74 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Tools/Page/PageTools.cs @@ -0,0 +1,52 @@ +using Celbridge.Credentials; +using ModelContextProtocol.Server; + +namespace Celbridge.Tools; + +/// +/// MCP tools for workshop page operations: publishing, listing, inspecting, and unpublishing static pages. +/// +[McpServerToolType] +public partial class PageTools : AgentToolBase +{ + public PageTools(IApplicationServiceProvider services) : base(services) { } + + private async Task ConfirmActionAsync(string title, string message) + { + var confirmResultWrapper = await ExecuteCommandAsync(command => + { + command.Title = title; + command.Message = message; + }); + + if (confirmResultWrapper.IsFailure) + { + return false; + } + + var confirmResult = confirmResultWrapper.Value; + return confirmResult.Confirmed; + } + + // Resolves the publisher Author from Workshop settings, alerting the user + // (when interactive) if it is missing so the problem is visible and not just + // returned to the agent. + private async Task> ResolvePublishAuthorAsync(bool confirmWithUser) + { + var credentialService = GetRequiredService(); + var authorResult = await PublishAuthor.ResolveAsync(credentialService); + if (authorResult.IsFailure) + { + if (confirmWithUser) + { + var localizerService = GetRequiredService(); + var title = localizerService.GetString("Workshop_PublishBlocked_Title"); + await ShowAlertAsync(title, authorResult.FirstErrorMessage); + } + + return authorResult; + } + + return authorResult.Value; + } +} diff --git a/Source/Core/Celbridge.UserInterface/ViewModels/Pages/SettingsPageViewModel.cs b/Source/Core/Celbridge.UserInterface/ViewModels/Pages/SettingsPageViewModel.cs index 806ae75e6..d6ae09a8e 100644 --- a/Source/Core/Celbridge.UserInterface/ViewModels/Pages/SettingsPageViewModel.cs +++ b/Source/Core/Celbridge.UserInterface/ViewModels/Pages/SettingsPageViewModel.cs @@ -13,6 +13,9 @@ public partial class SettingsPageViewModel : ObservableObject [ObservableProperty] private string _workshopUrl = string.Empty; + [ObservableProperty] + private string _author = string.Empty; + [ObservableProperty] private string _applicationKey = string.Empty; @@ -93,6 +96,7 @@ public async Task InitializeAsync() { var connection = getResult.Value; WorkshopUrl = connection.WorkshopUrl; + Author = connection.Author; } else { @@ -145,7 +149,7 @@ private async Task SaveWorkshopConnectionAsync() applicationKey = storedConnection.ApplicationKey; } - var connection = new WorkshopConnection(workshopUrl, applicationKey); + var connection = new WorkshopConnection(workshopUrl, applicationKey, Author.Trim()); var setResult = await _credentialService.SetWorkshopConnectionAsync(connection); if (setResult.IsFailure) { @@ -191,6 +195,7 @@ private async Task ClearWorkshopConnectionAsync() _isConnectionStored = false; _isReplacingKey = false; WorkshopUrl = string.Empty; + Author = string.Empty; ApplicationKey = string.Empty; StoredKeyDisplay = string.Empty; diff --git a/Source/Core/Celbridge.UserInterface/Views/Pages/SettingsPage.xaml b/Source/Core/Celbridge.UserInterface/Views/Pages/SettingsPage.xaml index b02e2a5dd..d60996ca4 100644 --- a/Source/Core/Celbridge.UserInterface/Views/Pages/SettingsPage.xaml +++ b/Source/Core/Celbridge.UserInterface/Views/Pages/SettingsPage.xaml @@ -44,6 +44,7 @@ + @@ -56,7 +57,8 @@ + PlaceholderText="https://" + ToolTipService.ToolTip="{x:Bind WorkshopUrlTooltipString}"/> + Visibility="{x:Bind ViewModel.IsKeyEntryVisible, Mode=OneWay}" + ToolTipService.ToolTip="{x:Bind ApplicationKeyTooltipString}"/> - + + + + diff --git a/Source/Core/Celbridge.UserInterface/Views/Pages/SettingsPage.xaml.cs b/Source/Core/Celbridge.UserInterface/Views/Pages/SettingsPage.xaml.cs index 895ad3d4f..e901c7362 100644 --- a/Source/Core/Celbridge.UserInterface/Views/Pages/SettingsPage.xaml.cs +++ b/Source/Core/Celbridge.UserInterface/Views/Pages/SettingsPage.xaml.cs @@ -18,7 +18,12 @@ public sealed partial class SettingsPage : Page private string ApplicationThemeString => _stringLocalizer.GetString("SettingsPage_ApplicationTheme"); private string WorkshopSectionString => _stringLocalizer.GetString("SettingsPage_WorkshopSection"); private string WorkshopUrlString => _stringLocalizer.GetString("SettingsPage_WorkshopUrl"); + private string WorkshopUrlTooltipString => _stringLocalizer.GetString("SettingsPage_WorkshopUrlTooltip"); private string ApplicationKeyString => _stringLocalizer.GetString("SettingsPage_ApplicationKey"); + private string ApplicationKeyTooltipString => _stringLocalizer.GetString("SettingsPage_ApplicationKeyTooltip"); + private string AuthorString => _stringLocalizer.GetString("SettingsPage_Author"); + private string AuthorTooltipString => _stringLocalizer.GetString("SettingsPage_AuthorTooltip"); + private string AuthorPlaceholderString => _stringLocalizer.GetString("SettingsPage_AuthorPlaceholder"); private string SaveConnectionString => _stringLocalizer.GetString("SettingsPage_SaveConnection"); private string ClearConnectionString => _stringLocalizer.GetString("SettingsPage_ClearConnection"); private string ReplaceKeyString => _stringLocalizer.GetString("SettingsPage_ReplaceKey"); diff --git a/Source/Tests/Packages/ManifestTests.cs b/Source/Tests/Packages/ManifestTests.cs index 2ec640e09..6296c87ea 100644 --- a/Source/Tests/Packages/ManifestTests.cs +++ b/Source/Tests/Packages/ManifestTests.cs @@ -67,8 +67,11 @@ public void LoadPackage_ValidCustomDocument_ReturnsContribution() } [Test] - public void LoadPackage_NameAuthorAndTitle_PopulateInfo() + public void LoadPackage_NameAndTitle_PopulateInfo_StrayAuthorIgnored() { + // 'author' is no longer a manifest field (the publisher comes from + // Workshop settings), but a legacy manifest that still carries it must + // load fine with the key simply ignored. WritePackageToml(""" [package] name = "my-widget" @@ -82,12 +85,11 @@ public void LoadPackage_NameAuthorAndTitle_PopulateInfo() result.IsSuccess.Should().BeTrue(); result.Value.Info.Name.Should().Be("my-widget"); - result.Value.Info.Author.Should().Be("Acme"); result.Value.Info.Title.Should().Be("My Widget"); } [Test] - public void LoadPackage_AuthorAndTitleOmitted_DefaultToEmpty() + public void LoadPackage_TitleOmitted_DefaultsToEmpty() { WritePackageToml(""" [package] @@ -100,7 +102,6 @@ public void LoadPackage_AuthorAndTitleOmitted_DefaultToEmpty() result.IsSuccess.Should().BeTrue(); result.Value.Info.Name.Should().Be("my-widget"); - result.Value.Info.Author.Should().BeEmpty(); result.Value.Info.Title.Should().BeEmpty(); } diff --git a/Source/Tests/Packages/PackageApiClientTests.cs b/Source/Tests/Packages/PackageApiClientTests.cs index 3aea68a50..a09556bbe 100644 --- a/Source/Tests/Packages/PackageApiClientTests.cs +++ b/Source/Tests/Packages/PackageApiClientTests.cs @@ -135,7 +135,7 @@ public async Task PublishVersion_SendsMultipartAndParsesReceipt() """, HttpStatusCode.Created); var zipData = new byte[] { 0x50, 0x4B, 0x03, 0x04 }; - var result = await _client.PublishVersionAsync("my-widget", zipData, "Fixed the frobnicator"); + var result = await _client.PublishVersionAsync("my-widget", zipData, "Fixed the frobnicator", "alice"); result.IsSuccess.Should().BeTrue(); @@ -149,6 +149,8 @@ public async Task PublishVersion_SendsMultipartAndParsesReceipt() requestBody.Should().Contain("name=file; filename=my-widget.zip"); requestBody.Should().Contain("name=summary"); requestBody.Should().Contain("Fixed the frobnicator"); + requestBody.Should().Contain("name=author"); + requestBody.Should().Contain("alice"); var receipt = result.Value; receipt.PackageName.Should().Be("my-widget"); @@ -167,7 +169,9 @@ public async Task PublishVersion_WithoutSummary_OmitsSummaryPart() var result = await _client.PublishVersionAsync("my-widget", [0x50]); result.IsSuccess.Should().BeTrue(); - _messageHandler.RequestBodies.Single().Replace("\"", "").Should().NotContain("name=summary"); + var requestBody = _messageHandler.RequestBodies.Single().Replace("\"", ""); + requestBody.Should().NotContain("name=summary"); + requestBody.Should().NotContain("name=author"); } [Test] diff --git a/Source/Tests/Packages/PageApiClientTests.cs b/Source/Tests/Packages/PageApiClientTests.cs new file mode 100644 index 000000000..cc6cd64de --- /dev/null +++ b/Source/Tests/Packages/PageApiClientTests.cs @@ -0,0 +1,284 @@ +using System.Net; +using System.Text; +using Celbridge.Credentials; +using Celbridge.Packages; + +namespace Celbridge.Tests.Packages; + +[TestFixture] +public class PageApiClientTests +{ + private const string TestApplicationKey = "kpf_testkey_supersecret"; + + private StubMessageHandler _messageHandler = null!; + private ICredentialService _credentialService = null!; + private PageApiClient _client = null!; + + [SetUp] + public void Setup() + { + _messageHandler = new StubMessageHandler(); + _credentialService = Substitute.For(); + SetStoredConnection("https://workshop.example.com", TestApplicationKey); + _client = new PageApiClient(_credentialService, _messageHandler); + } + + [TearDown] + public void TearDown() + { + _client.Dispose(); + } + + [Test] + public async Task ListPages_SendsApiKeyHeaderAndParsesEntries() + { + _messageHandler.Responder = _ => JsonResponse(""" + [ + { + "path": "my-site/home", + "url": "https://workshop.example.com/pages/acme/my-site/home/", + "published_at": "2026-02-01T09:30:00Z", + "published_by": "alice", + "content_hash": "aaa111" + }, + { + "path": "marketing/launch", + "url": "https://workshop.example.com/pages/acme/marketing/launch/", + "published_at": "2026-03-05T11:15:00Z", + "published_by": "bob", + "content_hash": "bbb222" + } + ] + """); + + var result = await _client.ListPagesAsync(); + + result.IsSuccess.Should().BeTrue(); + + var request = _messageHandler.Requests.Single(); + request.Method.Should().Be(HttpMethod.Get); + request.RequestUri.Should().Be(new Uri("https://workshop.example.com/api/pages")); + request.Headers.Authorization.Should().NotBeNull(); + request.Headers.Authorization!.Scheme.Should().Be("Api-Key"); + request.Headers.Authorization.Parameter.Should().Be(TestApplicationKey); + + var pages = result.Value; + pages.Should().HaveCount(2); + pages[0].Path.Should().Be("my-site/home"); + pages[0].Url.Should().Be("https://workshop.example.com/pages/acme/my-site/home/"); + pages[0].PublishedBy.Should().Be("alice"); + pages[0].ContentHash.Should().Be("aaa111"); + pages[1].Path.Should().Be("marketing/launch"); + } + + [Test] + public async Task GetPage_MultiSegmentPath_BuildsEndpointAndParses() + { + _messageHandler.Responder = _ => JsonResponse(""" + { + "path": "my-site/home", + "url": "https://workshop.example.com/pages/acme/my-site/home/", + "published_at": "2026-02-01T09:30:00Z", + "published_by": "alice", + "content_hash": "aaa111" + } + """); + + var result = await _client.GetPageAsync("my-site/home"); + + result.IsSuccess.Should().BeTrue(); + + var request = _messageHandler.Requests.Single(); + request.RequestUri.Should().Be(new Uri("https://workshop.example.com/api/pages/my-site/home")); + + var page = result.Value; + page.Path.Should().Be("my-site/home"); + page.Url.Should().Be("https://workshop.example.com/pages/acme/my-site/home/"); + } + + [Test] + public async Task GetPage_RelativeUrl_ResolvedAgainstWorkshopBase() + { + _messageHandler.Responder = _ => JsonResponse(""" + { + "path": "my-site/home", + "url": "/pages/acme/my-site/home/", + "published_at": "2026-02-01T09:30:00Z", + "published_by": "alice", + "content_hash": "aaa111" + } + """); + + var result = await _client.GetPageAsync("my-site/home"); + + result.IsSuccess.Should().BeTrue(); + result.Value.Url.Should().Be("https://workshop.example.com/pages/acme/my-site/home/"); + } + + [Test] + public async Task GetPage_NotPublished_Fails() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.NotFound); + + var result = await _client.GetPageAsync("my-site/missing"); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("my-site/missing"); + result.MessageChain.Should().Contain("No page is published"); + } + + [Test] + public async Task PublishPage_SendsMultipartAndParsesPage() + { + _messageHandler.Responder = _ => JsonResponse(""" + { + "path": "my-site/home", + "url": "https://workshop.example.com/pages/acme/my-site/home/", + "published_at": "2026-04-01T08:00:00Z", + "published_by": "alice", + "content_hash": "ccc333" + } + """, HttpStatusCode.Created); + + var zipData = new byte[] { 0x50, 0x4B, 0x03, 0x04 }; + var result = await _client.PublishPageAsync(zipData, "my-site/home", "alice"); + + result.IsSuccess.Should().BeTrue(); + + var request = _messageHandler.Requests.Single(); + request.Method.Should().Be(HttpMethod.Post); + request.RequestUri.Should().Be(new Uri("https://workshop.example.com/api/pages")); + + var requestBody = _messageHandler.RequestBodies.Single().Replace("\"", ""); + requestBody.Should().Contain("name=file; filename=page.zip"); + requestBody.Should().Contain("name=path"); + requestBody.Should().Contain("my-site/home"); + requestBody.Should().Contain("name=author"); + requestBody.Should().Contain("alice"); + + var page = result.Value; + page.Path.Should().Be("my-site/home"); + page.Url.Should().Be("https://workshop.example.com/pages/acme/my-site/home/"); + page.ContentHash.Should().Be("ccc333"); + } + + [Test] + public async Task PublishPage_PathOverlap_Fails() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.Conflict); + + var result = await _client.PublishPageAsync([0x50], "my-site/home"); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("already published"); + } + + [Test] + public async Task PublishPage_BadBundle_SurfacesServerMessage() + { + _messageHandler.Responder = _ => new HttpResponseMessage((HttpStatusCode)422) + { + Content = new StringContent("pages.toml is missing [publish].path", Encoding.UTF8, "text/plain") + }; + + var result = await _client.PublishPageAsync([0x50], "my-site/home"); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("422"); + result.MessageChain.Should().Contain("pages.toml is missing"); + } + + [Test] + public async Task UnpublishPage_SendsDeleteToPathEndpoint() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.NoContent); + + var result = await _client.UnpublishPageAsync("my-site/home"); + + result.IsSuccess.Should().BeTrue(); + + var request = _messageHandler.Requests.Single(); + request.Method.Should().Be(HttpMethod.Delete); + request.RequestUri.Should().Be(new Uri("https://workshop.example.com/api/pages/my-site/home")); + } + + [Test] + public async Task UnpublishPage_NotPublished_Fails() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.NotFound); + + var result = await _client.UnpublishPageAsync("my-site/missing"); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("my-site/missing"); + result.MessageChain.Should().Contain("No page is published"); + } + + [Test] + public async Task Request_Unauthorized_FailsWithoutEchoingKey() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.Unauthorized); + + var result = await _client.ListPagesAsync(); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("Application Key"); + result.MessageChain.Should().NotContain(TestApplicationKey); + result.DiagnosticReport.Should().NotContain(TestApplicationKey); + } + + [Test] + public async Task HttpUrl_NonLoopbackHost_RejectedBeforeSending() + { + SetStoredConnection("http://workshop.example.com", TestApplicationKey); + + var result = await _client.ListPagesAsync(); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("HTTPS"); + _messageHandler.Requests.Should().BeEmpty(); + } + + private void SetStoredConnection(string workshopUrl, string applicationKey) + { + var connection = new WorkshopConnection(workshopUrl, applicationKey); + _credentialService.GetWorkshopConnectionAsync() + .Returns(Result.Ok(connection)); + } + + private static HttpResponseMessage JsonResponse(string json, HttpStatusCode statusCode = HttpStatusCode.OK) + { + return new HttpResponseMessage(statusCode) + { + Content = new StringContent(json, Encoding.UTF8, "application/json") + }; + } + + // Records every request (and its body, captured before the request is + // disposed) and serves the canned response from Responder. + private sealed class StubMessageHandler : HttpMessageHandler + { + public List Requests { get; } = new(); + public List RequestBodies { get; } = new(); + + public Func Responder { get; set; } = + _ => new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(string.Empty) + }; + + protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + Requests.Add(request); + + var body = string.Empty; + if (request.Content is not null) + { + body = await request.Content.ReadAsStringAsync(cancellationToken); + } + RequestBodies.Add(body); + + return Responder(request); + } + } +} diff --git a/Source/Tests/Settings/CredentialServiceTests.cs b/Source/Tests/Settings/CredentialServiceTests.cs index 540174b85..eeddfffa9 100644 --- a/Source/Tests/Settings/CredentialServiceTests.cs +++ b/Source/Tests/Settings/CredentialServiceTests.cs @@ -148,6 +148,20 @@ public async Task SetThenGet_RoundTripsConnection() storedConnection.Should().Be(connection); } + [Test] + public async Task SetThenGet_RoundTripsAuthor() + { + var connection = new WorkshopConnection(WorkshopUrl, ApplicationKey, "Ada Lovelace"); + + var setResult = await _credentialService.SetWorkshopConnectionAsync(connection); + setResult.IsSuccess.Should().BeTrue(); + + var getResult = await _credentialService.GetWorkshopConnectionAsync(); + getResult.IsSuccess.Should().BeTrue(); + + getResult.Value.Author.Should().Be("Ada Lovelace"); + } + [Test] public async Task Set_WritesNoPlaintextToDisk() { diff --git a/Source/Tests/UserInterface/SettingsPageViewModelTests.cs b/Source/Tests/UserInterface/SettingsPageViewModelTests.cs index 9e8956691..31f523822 100644 --- a/Source/Tests/UserInterface/SettingsPageViewModelTests.cs +++ b/Source/Tests/UserInterface/SettingsPageViewModelTests.cs @@ -104,6 +104,34 @@ public async Task Save_NewConnection_StoresAndShowsStoredKey() storedConnection.Should().Be(new WorkshopConnection(WorkshopUrl, ApplicationKey)); } + [Test] + public async Task Save_NewConnection_StoresAuthor() + { + await _viewModel.InitializeAsync(); + _viewModel.WorkshopUrl = WorkshopUrl; + _viewModel.ApplicationKey = ApplicationKey; + _viewModel.Author = "Ada Lovelace"; + + await _viewModel.SaveWorkshopConnectionCommand.ExecuteAsync(null); + + _viewModel.IsErrorVisible.Should().BeFalse(); + + var getResult = await _credentialService.GetWorkshopConnectionAsync(); + getResult.IsSuccess.Should().BeTrue(); + getResult.Value.Author.Should().Be("Ada Lovelace"); + } + + [Test] + public async Task Initialize_StoredConnection_PopulatesAuthor() + { + await _credentialService.SetWorkshopConnectionAsync( + new WorkshopConnection(WorkshopUrl, ApplicationKey, "Ada Lovelace")); + + await _viewModel.InitializeAsync(); + + _viewModel.Author.Should().Be("Ada Lovelace"); + } + [Test] public async Task Save_InvalidUrl_ShowsErrorAndStoresNothing() { diff --git a/Source/Workspace/Celbridge.Packages/ServiceConfiguration.cs b/Source/Workspace/Celbridge.Packages/ServiceConfiguration.cs index c6998a757..831613a4f 100644 --- a/Source/Workspace/Celbridge.Packages/ServiceConfiguration.cs +++ b/Source/Workspace/Celbridge.Packages/ServiceConfiguration.cs @@ -8,5 +8,6 @@ public static void ConfigureServices(IServiceCollection services) services.AddTransient(); services.AddTransient(); services.AddSingleton(); + services.AddSingleton(); } } diff --git a/Source/Workspace/Celbridge.Packages/Services/PackageApiClient.cs b/Source/Workspace/Celbridge.Packages/Services/PackageApiClient.cs index 9a2e5e49a..edb18e304 100644 --- a/Source/Workspace/Celbridge.Packages/Services/PackageApiClient.cs +++ b/Source/Workspace/Celbridge.Packages/Services/PackageApiClient.cs @@ -15,10 +15,7 @@ namespace Celbridge.Packages; /// public class PackageApiClient : IPackageApiClient, IDisposable { - private const string ApiKeyScheme = "Api-Key"; - - private readonly ICredentialService _credentialService; - private readonly HttpClient _httpClient; + private readonly WorkshopApiSender _sender; public PackageApiClient(ICredentialService credentialService) : this(credentialService, new HttpClientHandler()) @@ -31,13 +28,12 @@ public PackageApiClient(ICredentialService credentialService) /// public PackageApiClient(ICredentialService credentialService, HttpMessageHandler messageHandler) { - _credentialService = credentialService; - _httpClient = new HttpClient(messageHandler); + _sender = new WorkshopApiSender(credentialService, messageHandler); } public async Task>> ListPackagesAsync() { - var sendResult = await SendAsync(HttpMethod.Get, "api/packages/"); + var sendResult = await _sender.SendAsync(HttpMethod.Get, "api/packages/"); if (sendResult.IsFailure) { return Result.Fail("Failed to list workshop packages").WithErrors(sendResult); @@ -49,7 +45,7 @@ public async Task>> ListPackagesAsync return Result.Fail($"Failed to list workshop packages (HTTP {(int)response.StatusCode})"); } - var parseResult = await ParseJsonAsync>(response, "package list"); + var parseResult = await WorkshopApiSender.ParseJsonAsync>(response, "package list"); if (parseResult.IsFailure) { return Result.Fail(parseResult); @@ -67,7 +63,7 @@ public async Task>> ListPackagesAsync public async Task> GetPackageAsync(string packageName) { - var sendResult = await SendAsync(HttpMethod.Get, $"api/packages/{Uri.EscapeDataString(packageName)}/"); + var sendResult = await _sender.SendAsync(HttpMethod.Get, $"api/packages/{Uri.EscapeDataString(packageName)}/"); if (sendResult.IsFailure) { return Result.Fail($"Failed to get workshop package '{packageName}'").WithErrors(sendResult); @@ -84,7 +80,7 @@ public async Task> GetPackageAsync(string packageNa return Result.Fail($"Failed to get workshop package '{packageName}' (HTTP {(int)response.StatusCode})"); } - var parseResult = await ParseJsonAsync(response, "package metadata"); + var parseResult = await WorkshopApiSender.ParseJsonAsync(response, "package metadata"); if (parseResult.IsFailure) { return Result.Fail(parseResult); @@ -116,7 +112,7 @@ public async Task> GetPackageAsync(string packageNa aliases.AsReadOnly()); } - public async Task> PublishVersionAsync(string packageName, byte[] zipData, string? summary = null) + public async Task> PublishVersionAsync(string packageName, byte[] zipData, string? summary = null, string? author = null) { using var content = new MultipartFormDataContent(); @@ -129,7 +125,12 @@ public async Task> PublishVersionAsync(string packa content.Add(new StringContent(summary, Encoding.UTF8), "summary"); } - var sendResult = await SendAsync(HttpMethod.Post, $"api/packages/{Uri.EscapeDataString(packageName)}/versions/", content); + if (!string.IsNullOrEmpty(author)) + { + content.Add(new StringContent(author, Encoding.UTF8), "author"); + } + + var sendResult = await _sender.SendAsync(HttpMethod.Post, $"api/packages/{Uri.EscapeDataString(packageName)}/versions/", content); if (sendResult.IsFailure) { return Result.Fail($"Failed to publish package '{packageName}'").WithErrors(sendResult); @@ -144,7 +145,7 @@ public async Task> PublishVersionAsync(string packa return Result.Fail($"Failed to publish package '{packageName}' (HTTP {(int)response.StatusCode}): {errorBody}"); } - var parseResult = await ParseJsonAsync(response, "publish receipt"); + var parseResult = await WorkshopApiSender.ParseJsonAsync(response, "publish receipt"); if (parseResult.IsFailure) { return Result.Fail(parseResult); @@ -160,7 +161,7 @@ public async Task> PublishVersionAsync(string packa public async Task> DownloadVersionAsync(string packageName, int version) { - var sendResult = await SendAsync(HttpMethod.Get, $"api/packages/{Uri.EscapeDataString(packageName)}/versions/{version}/download/"); + var sendResult = await _sender.SendAsync(HttpMethod.Get, $"api/packages/{Uri.EscapeDataString(packageName)}/versions/{version}/download/"); if (sendResult.IsFailure) { return Result.Fail($"Failed to download version {version} of package '{packageName}'").WithErrors(sendResult); @@ -188,7 +189,7 @@ public async Task> DownloadVersionAsync(string packageName, int v public async Task> DownloadLatestAsync(string packageName) { - var sendResult = await SendAsync(HttpMethod.Get, $"api/packages/{Uri.EscapeDataString(packageName)}/latest/"); + var sendResult = await _sender.SendAsync(HttpMethod.Get, $"api/packages/{Uri.EscapeDataString(packageName)}/latest/"); if (sendResult.IsFailure) { return Result.Fail($"Failed to download the latest version of package '{packageName}'").WithErrors(sendResult); @@ -214,7 +215,7 @@ public async Task SetAliasAsync(string packageName, string alias, int ve var body = JsonSerializer.Serialize(new AliasVersionBodyDto(version)); using var content = new StringContent(body, Encoding.UTF8, "application/json"); - var sendResult = await SendAsync(HttpMethod.Put, $"api/packages/{Uri.EscapeDataString(packageName)}/aliases/{Uri.EscapeDataString(alias)}/", content); + var sendResult = await _sender.SendAsync(HttpMethod.Put, $"api/packages/{Uri.EscapeDataString(packageName)}/aliases/{Uri.EscapeDataString(alias)}/", content); if (sendResult.IsFailure) { return Result.Fail($"Failed to set alias '{alias}' on package '{packageName}'").WithErrors(sendResult); @@ -231,7 +232,7 @@ public async Task SetAliasAsync(string packageName, string alias, int ve public async Task RemoveAliasAsync(string packageName, string alias) { - var sendResult = await SendAsync(HttpMethod.Delete, $"api/packages/{Uri.EscapeDataString(packageName)}/aliases/{Uri.EscapeDataString(alias)}/"); + var sendResult = await _sender.SendAsync(HttpMethod.Delete, $"api/packages/{Uri.EscapeDataString(packageName)}/aliases/{Uri.EscapeDataString(alias)}/"); if (sendResult.IsFailure) { return Result.Fail($"Failed to remove alias '{alias}' from package '{packageName}'").WithErrors(sendResult); @@ -255,7 +256,7 @@ public async Task DeleteVersionAsync(string packageName, int version) { using var content = BuildDeleteReasonContent(); - var sendResult = await SendAsync(HttpMethod.Delete, $"api/packages/{Uri.EscapeDataString(packageName)}/versions/{version}/", content); + var sendResult = await _sender.SendAsync(HttpMethod.Delete, $"api/packages/{Uri.EscapeDataString(packageName)}/versions/{version}/", content); if (sendResult.IsFailure) { return Result.Fail($"Failed to delete version {version} of package '{packageName}'").WithErrors(sendResult); @@ -284,7 +285,7 @@ public async Task DeletePackageAsync(string packageName) { using var content = BuildDeleteReasonContent(); - var sendResult = await SendAsync(HttpMethod.Delete, $"api/packages/{Uri.EscapeDataString(packageName)}/", content); + var sendResult = await _sender.SendAsync(HttpMethod.Delete, $"api/packages/{Uri.EscapeDataString(packageName)}/", content); if (sendResult.IsFailure) { return Result.Fail($"Failed to delete package '{packageName}'").WithErrors(sendResult); @@ -314,7 +315,7 @@ private static StringContent BuildDeleteReasonContent() public async Task> GetVersionHistoryAsync(string packageName, int version) { - var sendResult = await SendAsync(HttpMethod.Get, $"api/packages/{Uri.EscapeDataString(packageName)}/versions/{version}/history/"); + var sendResult = await _sender.SendAsync(HttpMethod.Get, $"api/packages/{Uri.EscapeDataString(packageName)}/versions/{version}/history/"); if (sendResult.IsFailure) { return Result.Fail($"Failed to get the history of package '{packageName}'").WithErrors(sendResult); @@ -330,110 +331,6 @@ public async Task> GetVersionHistoryAsync(string packageName, int return history; } - // Builds and sends one authenticated request. Fails without sending when no - // connection is stored or the stored URL is unusable, and maps 401 to a - // single actionable message here so no call site can leak the key. - private async Task> SendAsync(HttpMethod method, string relativePath, HttpContent? content = null) - { - var connectionResult = await _credentialService.GetWorkshopConnectionAsync(); - if (connectionResult.IsFailure) - { - return Result.Fail("Failed to read the Workshop connection from the credential store") - .WithErrors(connectionResult); - } - var connection = connectionResult.Value; - - var baseUriResult = ValidateWorkshopUrl(connection.WorkshopUrl); - if (baseUriResult.IsFailure) - { - return Result.Fail(baseUriResult); - } - var baseUri = baseUriResult.Value; - - var request = new HttpRequestMessage(method, new Uri(baseUri, relativePath)); - request.Headers.Authorization = new AuthenticationHeaderValue(ApiKeyScheme, connection.ApplicationKey); - if (content is not null) - { - request.Content = content; - } - - try - { - var response = await _httpClient.SendAsync(request); - if (response.StatusCode == HttpStatusCode.Unauthorized) - { - response.Dispose(); - return Result.Fail( - "The workshop rejected the stored Application Key (HTTP 401). " + - "The key may be invalid, revoked, or no longer linked to the workshop. " + - "Update the Workshop connection on the Settings page."); - } - - return response; - } - catch (HttpRequestException exception) - { - return Result.Fail("A network error occurred while contacting the workshop") - .WithException(exception); - } - catch (TaskCanceledException exception) - { - return Result.Fail("The workshop request timed out") - .WithException(exception); - } - } - - // The Application Key is a bearer credential, so sending it over plain HTTP - // would fully compromise it to any network observer. Loopback hosts are - // exempt to support local development servers. - private static Result ValidateWorkshopUrl(string workshopUrl) - { - var urlText = workshopUrl.Trim(); - if (!urlText.EndsWith('/')) - { - // Ensure the trailing path segment is kept when combining with - // relative endpoint paths. - urlText += '/'; - } - - if (!Uri.TryCreate(urlText, UriKind.Absolute, out var uri)) - { - return Result.Fail("The stored Workshop URL is not a valid absolute URL. Update the Workshop connection on the Settings page."); - } - - if (uri.Scheme == Uri.UriSchemeHttps) - { - return uri; - } - - if (uri.Scheme == Uri.UriSchemeHttp && uri.IsLoopback) - { - return uri; - } - - return Result.Fail("The Workshop URL must use HTTPS. Plain HTTP would expose the Application Key to network observers and is only permitted for localhost development servers."); - } - - private static async Task> ParseJsonAsync(HttpResponseMessage response, string payloadDescription) where T : notnull - { - try - { - var json = await response.Content.ReadAsStringAsync(); - var parsed = JsonSerializer.Deserialize(json); - if (parsed is null) - { - return Result.Fail($"Failed to parse the workshop {payloadDescription} response"); - } - - return parsed; - } - catch (JsonException exception) - { - return Result.Fail($"Failed to parse the workshop {payloadDescription} response") - .WithException(exception); - } - } - private static RemotePackageSummary ToPackageSummary(PackageSummaryDto entry) { RemoteVersionSummary? latestVersion = null; @@ -498,6 +395,6 @@ private record DeleteReasonBodyDto( public void Dispose() { - _httpClient.Dispose(); + _sender.Dispose(); } } diff --git a/Source/Workspace/Celbridge.Packages/Services/PackageManifestLoader.cs b/Source/Workspace/Celbridge.Packages/Services/PackageManifestLoader.cs index e4b349ed1..b17ed8ab2 100644 --- a/Source/Workspace/Celbridge.Packages/Services/PackageManifestLoader.cs +++ b/Source/Workspace/Celbridge.Packages/Services/PackageManifestLoader.cs @@ -25,7 +25,6 @@ public static class PackageManifestLoader private const string IdKey = "id"; private const string NameKey = "name"; - private const string AuthorKey = "author"; private const string TitleKey = "title"; private const string FeatureFlagKey = "feature_flag"; private const string TypeKey = "type"; @@ -111,7 +110,6 @@ public static Result LoadPackage( return Result.Fail($"Package has invalid '{NameKey}' value '{packageName}': {packageTomlPath}. Package names must be lowercase ASCII letters and digits with single interior hyphens, at most {PackageConstants.MaxNameLength} characters."); } - var packageAuthor = GetString(packageTable, AuthorKey); var packageTitle = GetString(packageTable, TitleKey); var featureFlag = GetStringOrNull(packageTable, FeatureFlagKey); @@ -136,7 +134,6 @@ public static Result LoadPackage( var packageInfo = new PackageInfo { Name = packageName, - Author = packageAuthor, Title = packageTitle, FeatureFlag = featureFlag, PackageFolder = packageFolder, diff --git a/Source/Workspace/Celbridge.Packages/Services/PageApiClient.cs b/Source/Workspace/Celbridge.Packages/Services/PageApiClient.cs new file mode 100644 index 000000000..4364bd4cb --- /dev/null +++ b/Source/Workspace/Celbridge.Packages/Services/PageApiClient.cs @@ -0,0 +1,219 @@ +using System.Net; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json.Serialization; +using Celbridge.Credentials; + +namespace Celbridge.Packages; + +/// +/// IPageApiClient backed by WorkshopApiSender, which it shares with the package +/// client for the common connection plumbing. +/// +public class PageApiClient : IPageApiClient, IDisposable +{ + private readonly WorkshopApiSender _sender; + + public PageApiClient(ICredentialService credentialService) + : this(credentialService, new HttpClientHandler()) + { + } + + /// + /// Creates a client over an explicit message handler so tests can serve + /// canned responses without a network. + /// + public PageApiClient(ICredentialService credentialService, HttpMessageHandler messageHandler) + { + _sender = new WorkshopApiSender(credentialService, messageHandler); + } + + public async Task> PublishPageAsync(byte[] zipData, string path, string? author = null) + { + using var content = new MultipartFormDataContent(); + + var fileContent = new ByteArrayContent(zipData); + fileContent.Headers.ContentType = new MediaTypeHeaderValue("application/zip"); + content.Add(fileContent, "file", "page.zip"); + + // The publish path is also carried inside the bundle's manifest; sending + // it as a field too lets the server read it without parsing the manifest, + // so the manifest can later be dropped from the upload. + if (!string.IsNullOrEmpty(path)) + { + content.Add(new StringContent(path, Encoding.UTF8), "path"); + } + + if (!string.IsNullOrEmpty(author)) + { + content.Add(new StringContent(author, Encoding.UTF8), "author"); + } + + var sendResult = await _sender.SendAsync(HttpMethod.Post, "api/pages", content); + if (sendResult.IsFailure) + { + return Result.Fail("Failed to publish page").WithErrors(sendResult); + } + + using var response = sendResult.Value; + if (response.StatusCode == HttpStatusCode.Conflict) + { + return Result.Fail( + "A page is already published at this path. Unpublish the existing page before publishing over it, " + + "or change the [publish].path in pages.toml."); + } + + if (!response.IsSuccessStatusCode) + { + // The error body carries the server's validation message (e.g. a bad + // bundle); it never contains credential material. + var errorBody = await response.Content.ReadAsStringAsync(); + return Result.Fail($"Failed to publish page (HTTP {(int)response.StatusCode}): {errorBody}"); + } + + var parseResult = await WorkshopApiSender.ParseJsonAsync(response, "page publish"); + if (parseResult.IsFailure) + { + return Result.Fail(parseResult); + } + + var baseUri = await TryGetBaseUriAsync(); + return ToPage(parseResult.Value, baseUri); + } + + public async Task>> ListPagesAsync() + { + var sendResult = await _sender.SendAsync(HttpMethod.Get, "api/pages"); + if (sendResult.IsFailure) + { + return Result.Fail("Failed to list workshop pages").WithErrors(sendResult); + } + + using var response = sendResult.Value; + if (!response.IsSuccessStatusCode) + { + return Result.Fail($"Failed to list workshop pages (HTTP {(int)response.StatusCode})"); + } + + var parseResult = await WorkshopApiSender.ParseJsonAsync>(response, "page list"); + if (parseResult.IsFailure) + { + return Result.Fail(parseResult); + } + var entries = parseResult.Value; + + var baseUri = await TryGetBaseUriAsync(); + var pages = new List(entries.Count); + foreach (var entry in entries) + { + pages.Add(ToPage(entry, baseUri)); + } + + return pages.OkResult>(); + } + + public async Task> GetPageAsync(string path) + { + var sendResult = await _sender.SendAsync(HttpMethod.Get, $"api/pages/{EscapePagePath(path)}"); + if (sendResult.IsFailure) + { + return Result.Fail($"Failed to get workshop page '{path}'").WithErrors(sendResult); + } + + using var response = sendResult.Value; + if (response.StatusCode == HttpStatusCode.NotFound) + { + return Result.Fail($"No page is published at '{path}' on the workshop."); + } + + if (!response.IsSuccessStatusCode) + { + return Result.Fail($"Failed to get workshop page '{path}' (HTTP {(int)response.StatusCode})"); + } + + var parseResult = await WorkshopApiSender.ParseJsonAsync(response, "page detail"); + if (parseResult.IsFailure) + { + return Result.Fail(parseResult); + } + + var baseUri = await TryGetBaseUriAsync(); + return ToPage(parseResult.Value, baseUri); + } + + public async Task UnpublishPageAsync(string path) + { + var sendResult = await _sender.SendAsync(HttpMethod.Delete, $"api/pages/{EscapePagePath(path)}"); + if (sendResult.IsFailure) + { + return Result.Fail($"Failed to unpublish page '{path}'").WithErrors(sendResult); + } + + using var response = sendResult.Value; + if (response.StatusCode == HttpStatusCode.NotFound) + { + return Result.Fail($"No page is published at '{path}' on the workshop."); + } + + if (!response.IsSuccessStatusCode) + { + return Result.Fail($"Failed to unpublish page '{path}' (HTTP {(int)response.StatusCode})"); + } + + return Result.Ok(); + } + + // The publish path is multi-segment (e.g. "my-site/home"). The slashes are + // path separators that must survive into the URL, so each segment is escaped + // individually rather than escaping the whole string (which would encode the + // separators). Empty segments from a leading, trailing, or doubled slash are + // dropped so the endpoint path stays well formed. + private static string EscapePagePath(string path) + { + var segments = path.Split('/', StringSplitOptions.RemoveEmptyEntries); + var escaped = segments.Select(Uri.EscapeDataString); + return string.Join('/', escaped); + } + + // The configured workshop base, used to absolutize a relative page URL. + // Returns null when it cannot be read, in which case the server's URL passes + // through unchanged rather than failing the call. + private async Task TryGetBaseUriAsync() + { + var baseUriResult = await _sender.GetBaseUriAsync(); + return baseUriResult.IsSuccess ? baseUriResult.Value : null; + } + + // Resolves the server's URL against the workshop base, so a relative URL + // (e.g. "/pages/org/path/") becomes a full address. An already-absolute URL, + // and the no-base fallback, pass through unchanged. + private static RemotePage ToPage(PageDto dto, Uri? baseUri) + { + var url = dto.Url ?? string.Empty; + if (baseUri is not null + && !string.IsNullOrEmpty(url) + && Uri.TryCreate(baseUri, url, out var absoluteUri)) + { + url = absoluteUri.ToString(); + } + + return new RemotePage( + dto.Path ?? string.Empty, + url, + dto.PublishedAt, + dto.PublishedBy ?? string.Empty, + dto.ContentHash ?? string.Empty); + } + + private record PageDto( + [property: JsonPropertyName("path")] string? Path, + [property: JsonPropertyName("url")] string? Url, + [property: JsonPropertyName("published_at")] DateTime PublishedAt, + [property: JsonPropertyName("published_by")] string? PublishedBy, + [property: JsonPropertyName("content_hash")] string? ContentHash); + + public void Dispose() + { + _sender.Dispose(); + } +} diff --git a/Source/Workspace/Celbridge.Packages/Services/WorkshopApiSender.cs b/Source/Workspace/Celbridge.Packages/Services/WorkshopApiSender.cs new file mode 100644 index 000000000..24fc746de --- /dev/null +++ b/Source/Workspace/Celbridge.Packages/Services/WorkshopApiSender.cs @@ -0,0 +1,148 @@ +using System.Net; +using System.Net.Http.Headers; +using System.Text.Json; +using Celbridge.Credentials; + +namespace Celbridge.Packages; + +/// +/// Sends authenticated requests to the workshop server's REST API. Shared by the +/// package and page API clients so the connection and auth handling lives in one +/// place rather than being duplicated per client. +/// +internal sealed class WorkshopApiSender : IDisposable +{ + private const string ApiKeyScheme = "Api-Key"; + + private readonly ICredentialService _credentialService; + private readonly HttpClient _httpClient; + + public WorkshopApiSender(ICredentialService credentialService, HttpMessageHandler messageHandler) + { + _credentialService = credentialService; + _httpClient = new HttpClient(messageHandler); + } + + // Builds and sends one authenticated request. Fails without sending when no + // connection is stored or the stored URL is unusable, and maps 401 to a + // single actionable message here so no call site can leak the key. + public async Task> SendAsync(HttpMethod method, string relativePath, HttpContent? content = null) + { + var connectionResult = await _credentialService.GetWorkshopConnectionAsync(); + if (connectionResult.IsFailure) + { + return Result.Fail("Failed to read the Workshop connection from the credential store") + .WithErrors(connectionResult); + } + var connection = connectionResult.Value; + + var baseUriResult = ValidateWorkshopUrl(connection.WorkshopUrl); + if (baseUriResult.IsFailure) + { + return Result.Fail(baseUriResult); + } + var baseUri = baseUriResult.Value; + + var request = new HttpRequestMessage(method, new Uri(baseUri, relativePath)); + request.Headers.Authorization = new AuthenticationHeaderValue(ApiKeyScheme, connection.ApplicationKey); + if (content is not null) + { + request.Content = content; + } + + try + { + var response = await _httpClient.SendAsync(request); + if (response.StatusCode == HttpStatusCode.Unauthorized) + { + response.Dispose(); + return Result.Fail( + "The workshop rejected the stored Application Key (HTTP 401). " + + "The key may be invalid, revoked, or no longer linked to the workshop. " + + "Update the Workshop connection on the Settings page."); + } + + return response; + } + catch (HttpRequestException exception) + { + return Result.Fail("A network error occurred while contacting the workshop") + .WithException(exception); + } + catch (TaskCanceledException exception) + { + return Result.Fail("The workshop request timed out") + .WithException(exception); + } + } + + // The validated base URI of the configured workshop, so callers can resolve a + // server-returned relative URL (e.g. a page's served path) to an absolute one. + public async Task> GetBaseUriAsync() + { + var connectionResult = await _credentialService.GetWorkshopConnectionAsync(); + if (connectionResult.IsFailure) + { + return Result.Fail("Failed to read the Workshop connection from the credential store") + .WithErrors(connectionResult); + } + + return ValidateWorkshopUrl(connectionResult.Value.WorkshopUrl); + } + + // The Application Key is a bearer credential, so sending it over plain HTTP + // would fully compromise it to any network observer. Loopback hosts are + // exempt to support local development servers. + private static Result ValidateWorkshopUrl(string workshopUrl) + { + var urlText = workshopUrl.Trim(); + if (!urlText.EndsWith('/')) + { + // Ensure the trailing path segment is kept when combining with + // relative endpoint paths. + urlText += '/'; + } + + if (!Uri.TryCreate(urlText, UriKind.Absolute, out var uri)) + { + return Result.Fail("The stored Workshop URL is not a valid absolute URL. Update the Workshop connection on the Settings page."); + } + + if (uri.Scheme == Uri.UriSchemeHttps) + { + return uri; + } + + if (uri.Scheme == Uri.UriSchemeHttp && uri.IsLoopback) + { + return uri; + } + + return Result.Fail("The Workshop URL must use HTTPS. Plain HTTP would expose the Application Key to network observers and is only permitted for localhost development servers."); + } + + public static async Task> ParseJsonAsync(HttpResponseMessage response, string payloadDescription) where T : notnull + { + try + { + var json = await response.Content.ReadAsStringAsync(); + var parsed = JsonSerializer.Deserialize(json); + if (parsed is null) + { + return Result.Fail($"Failed to parse the workshop {payloadDescription} response"); + } + + return parsed; + } + catch (JsonException exception) + { + return Result.Fail($"Failed to parse the workshop {payloadDescription} response") + .WithException(exception); + } + } + + public void Dispose() + { + _httpClient.Dispose(); + } +} From b486d7355a47a4bc7a34a31c87f28b6d8b5c66c3 Mon Sep 17 00:00:00 2001 From: Chris Gregan Date: Mon, 15 Jun 2026 13:38:25 +0100 Subject: [PATCH 07/21] Add scheduled automated dialog answers Introduce a test automation surface that can schedule automated answers for modal dialogs. Adds DialogKinds and DialogAnswerMessage types and a ScheduleAnswer method to IDialogService. Implements DialogAnswerScheduler and wires it into DialogService; confirmation and input-text dialogs now listen for scheduled answers and self-close with the provided payload. Adds a debug-only MCP tool (app_answer_dialog) gated by the new feature flag "answer-dialog", documentation for the tool, and troubleshooting notes. Includes unit tests for the scheduler behavior and end-to-end Python integration tests and ensures schedules are cleared on workspace unload. --- .../Dialog/DialogMessages.cs | 11 + .../Dialog/IDialogService.cs | 18 ++ .../Settings/FeatureFlagConstants.cs | 7 + .../Celbridge.Tools/Guides/Namespaces/app.md | 3 +- .../Guides/Tools/app_answer_dialog.md | 68 ++++ .../troubleshoot_feature_flag.md | 1 + .../Tools/App/AppTools.AnswerDialog.cs | 35 +++ .../Helpers/DialogAnswerScheduler.cs | 106 +++++++ .../Services/Dialogs/DialogService.cs | 23 +- .../Views/Dialogs/ConfirmationDialog.xaml.cs | 30 +- .../Views/Dialogs/InputTextDialog.xaml.cs | 36 ++- .../UserInterface/DialogServiceAnswerTests.cs | 295 ++++++++++++++++++ .../Python/celbridge-0.1.0-py3-none-any.whl | Bin 49404 -> 50841 bytes .../celbridge/integration_tests/conftest.py | 19 ++ .../integration_tests/test_answer_dialog.py | 73 +++++ 15 files changed, 716 insertions(+), 9 deletions(-) create mode 100644 Source/Core/Celbridge.Foundation/Dialog/DialogMessages.cs create mode 100644 Source/Core/Celbridge.Tools/Guides/Tools/app_answer_dialog.md create mode 100644 Source/Core/Celbridge.Tools/Tools/App/AppTools.AnswerDialog.cs create mode 100644 Source/Core/Celbridge.UserInterface/Helpers/DialogAnswerScheduler.cs create mode 100644 Source/Tests/UserInterface/DialogServiceAnswerTests.cs create mode 100644 Source/Workspace/Celbridge.Python/packages/celbridge/src/celbridge/integration_tests/test_answer_dialog.py diff --git a/Source/Core/Celbridge.Foundation/Dialog/DialogMessages.cs b/Source/Core/Celbridge.Foundation/Dialog/DialogMessages.cs new file mode 100644 index 000000000..cf5e57321 --- /dev/null +++ b/Source/Core/Celbridge.Foundation/Dialog/DialogMessages.cs @@ -0,0 +1,11 @@ +namespace Celbridge.Dialog; + +/// +/// Broadcast by IDialogService to answer the currently-displayed modal dialog +/// on behalf of an automated test. The payload is interpreted by whichever +/// dialog is listening: an empty payload answers a confirmation dialog +/// affirmatively; a non-empty string is the text for an input-text dialog. +/// A dialog that receives a payload incompatible with its own contract logs +/// a warning and continues to block on the user. +/// +public record DialogAnswerMessage(string Payload); diff --git a/Source/Core/Celbridge.Foundation/Dialog/IDialogService.cs b/Source/Core/Celbridge.Foundation/Dialog/IDialogService.cs index 44b03e181..c7d1e4feb 100644 --- a/Source/Core/Celbridge.Foundation/Dialog/IDialogService.cs +++ b/Source/Core/Celbridge.Foundation/Dialog/IDialogService.cs @@ -3,6 +3,16 @@ namespace Celbridge.Dialog; +/// +/// Identifies the dialog kinds that support automated answers through +/// IDialogService.ScheduleAnswer. +/// +public enum DialogKind +{ + Confirmation, + InputText, +} + /// /// Manages the display of modal dialogs to the user. /// @@ -56,5 +66,13 @@ public interface IDialogService /// Returns the selected index and checkbox state, or fails if the user cancels. /// Task> ShowChoiceDialogAsync(string titleText, string messageText, IReadOnlyList options, int defaultIndex = 0, ChoiceDialogCheckbox? checkbox = null, string? primaryButtonText = null, string? secondaryButtonText = null); + + /// + /// Schedule an automated answer for the next modal dialog of the named + /// kind. The delay timer begins when that dialog is displayed; if a dialog + /// of a different kind appears first, the schedule stays pending. A + /// subsequent call overwrites the schedule. + /// + void ScheduleAnswer(DialogKind dialogKind, string payload = "", int delayMs = 250); } diff --git a/Source/Core/Celbridge.Foundation/Settings/FeatureFlagConstants.cs b/Source/Core/Celbridge.Foundation/Settings/FeatureFlagConstants.cs index 91245a923..efb815be8 100644 --- a/Source/Core/Celbridge.Foundation/Settings/FeatureFlagConstants.cs +++ b/Source/Core/Celbridge.Foundation/Settings/FeatureFlagConstants.cs @@ -29,4 +29,11 @@ public static class FeatureFlagConstants /// webview_* namespace. /// public const string WebViewDevToolsEval = "webview-dev-tools-eval"; + + /// + /// Enables the app_answer_dialog MCP tool that lets a script answer a + /// modal dialog without a human present. The tool only ships in debug + /// builds, so the flag has no effect in release even when set. + /// + public const string AnswerDialog = "answer-dialog"; } diff --git a/Source/Core/Celbridge.Tools/Guides/Namespaces/app.md b/Source/Core/Celbridge.Tools/Guides/Namespaces/app.md index d569fdab4..4e71229b7 100644 --- a/Source/Core/Celbridge.Tools/Guides/Namespaces/app.md +++ b/Source/Core/Celbridge.Tools/Guides/Namespaces/app.md @@ -11,7 +11,8 @@ The `app` namespace covers application-level concerns that are not tied to a spe ## Tools -- `app_get_state` — workspace state snapshot (app version, project load, feature flags, focused panel, layout). +- `app_get_state` — workspace state snapshot (app version, project load, feature flags, focused panel, layout, registered UI automations). - `app_log`, `app_log_warning`, `app_log_error` — write a message to the console panel at the named severity. - `app_refresh_files` — rescan the project's content folder for external changes. - `app_show_alert` — show a modal alert dialog and wait for the user to dismiss it. +- `app_answer_dialog` *(debug-only; gated by `answer-dialog`)* — schedules an automated answer for the next modal dialog, so a script can drive a flow that would otherwise block on user interaction. Used by integration tests for the always-prompt admin tools (`package_delete`, `package_unpublish`) and the dialog-driven `explorer_rename`. Structurally absent from release builds. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/app_answer_dialog.md b/Source/Core/Celbridge.Tools/Guides/Tools/app_answer_dialog.md new file mode 100644 index 000000000..38725f76b --- /dev/null +++ b/Source/Core/Celbridge.Tools/Guides/Tools/app_answer_dialog.md @@ -0,0 +1,68 @@ +# app_answer_dialog + +Schedules an automated answer for the next modal dialog of the named kind, so a script can drive a flow that would otherwise block on user interaction. When a dialog of that kind is displayed, the timer begins; after the delay the answer is broadcast and the dialog closes itself with an affirmative response (OK, Confirm, Create, Delete, Rename — whichever the dialog's affirmative action is). + +The dialog actually displays briefly before auto-closing. This is by design: an integration test exercises the real end-to-end UI flow, screenshots are useful, and the audit trail matches what a real user would have done. + +**Debug-only.** The tool is wrapped in `#if DEBUG`, so it does not exist in release builds — `tools/list` does not advertise it and `app_call` returns "denied". Inside debug builds it is also gated by the `answer-dialog` user-level feature flag. + +## When to call it + +Right before triggering the call that opens the modal dialog. Order matters: + +1. Call `app_answer_dialog(dialogKind, payload?, delayMs?)` to schedule the answer. +2. Call the tool that triggers the dialog (e.g. `package_unpublish`, `explorer_rename`). + +The delay timer starts when the matching dialog is displayed, not when this call returns — so it's fine for agent timing to vary between the schedule and the dialog appearing. + +## Parameters + +- `dialogKind` (required string) — identifies which dialog kind the answer is for. The schedule only fires when a dialog of this kind appears; if a different dialog appears first, the schedule stays pending and the unexpected dialog blocks on the user. Valid values: + - `"Confirmation"` — yes/no prompts (e.g. `package_delete`). + - `"InputText"` — single-string text-entry prompts (e.g. `explorer_rename`). +- `payload` (optional string, default `""`) — the content the dialog should receive: + - **Confirmation dialogs**: payload is ignored; the dialog OKs unconditionally. + - **Input-text dialogs**: payload is the text to enter. Empty payload enters an empty string. +- `delayMs` (optional int, default `250`) — milliseconds to wait *after the dialog is displayed* before broadcasting the answer. Tune up for dialogs with slow initialization; tune down for tight test loops. + +Only one schedule is held at a time. A subsequent call overwrites; the schedule is cleared on workspace teardown. + +## Returns + +`"ok"` on success. Errors when the `answer-dialog` feature flag is off. + +The schedule itself is fire-and-forget: the tool returns immediately after recording the schedule. If no dialog of the scheduled kind ever appears, no broadcast happens. If a dialog of a *different* kind appears first, a warning is logged and the schedule stays pending — the unexpected dialog blocks on the user, which is the right outcome since it wasn't expected by the script. + +## Examples + +### Python (`cel.app.answer_dialog`) + +```python +# Confirm the next package_unpublish prompt. +cel.app.answer_dialog("Confirmation") +package.unpublish("test-integration-pkg") + +# Provide rename text for the next explorer_rename. +cel.app.answer_dialog("InputText", "Renamed.txt") +cel.explorer.rename("/Folder/Old.txt") + +# Give a slow-loading dialog more headroom. +cel.app.answer_dialog("Confirmation", delayMs=500) +package.delete("heavy-package") +``` + +### JavaScript + +```javascript +await app.answerDialog("Confirmation"); // confirm +await app.answerDialog("InputText", "Renamed.txt"); // rename +await app.answerDialog("Confirmation", "", 500); // longer delay +``` + +## Gotchas + +- **Single schedule, single use.** A second call overwrites. The schedule is consumed by the first dialog of the matching kind. +- **Wrong-kind dialogs do not consume the schedule.** If the test is expected to walk through several dialogs and you only want to auto-answer one of them, schedule for that specific kind — interleaved dialogs of other kinds will not eat the schedule. The unexpected dialog still blocks on the user, so a script-induced unexpected dialog will hang the test (with a clear warning in the log). +- **No "decline" mechanism.** The tool only schedules an *affirmative* answer. Testing a "user cancels" path is not what this tool is for — exercise the underlying command directly or test the cancelled-outcome branch without going through the dialog. +- **Workspace teardown clears the schedule.** Re-schedule after each workspace load if your fixture runs across workspaces. +- **Delay is observable.** Every automated test pays the `delayMs` cost. Default 250ms is comfortable for normal dialogs; for tight loops with simple confirmations you can lower it. diff --git a/Source/Core/Celbridge.Tools/Guides/Troubleshooters/troubleshoot_feature_flag.md b/Source/Core/Celbridge.Tools/Guides/Troubleshooters/troubleshoot_feature_flag.md index 3a464c6c4..af29c4336 100644 --- a/Source/Core/Celbridge.Tools/Guides/Troubleshooters/troubleshoot_feature_flag.md +++ b/Source/Core/Celbridge.Tools/Guides/Troubleshooters/troubleshoot_feature_flag.md @@ -14,3 +14,4 @@ To find which flags are currently on, call `app_get_state` and read the `feature - **`webview-dev-tools-eval`** is a separate, narrower flag that gates only `webview_eval` because arbitrary JavaScript evaluation is the riskiest webview surface. - **`mcp-tools`** gates the broker itself; if it is off, you would not see this error from a tool call (the MCP server would not be running). - **`console-panel`** gates the console UI feature; tools may reference it for layout reporting. +- **`answer-dialog`** gates the `app_answer_dialog` MCP tool, which lets a script answer a modal dialog without a human present. The tool itself only ships in debug builds, so setting the flag in a release build has no effect — `app_answer_dialog` does not appear in `tools/list` regardless. To enable in a debug build, set `answer-dialog = true` in the user-level `.celbridge`. diff --git a/Source/Core/Celbridge.Tools/Tools/App/AppTools.AnswerDialog.cs b/Source/Core/Celbridge.Tools/Tools/App/AppTools.AnswerDialog.cs new file mode 100644 index 000000000..81ab44848 --- /dev/null +++ b/Source/Core/Celbridge.Tools/Tools/App/AppTools.AnswerDialog.cs @@ -0,0 +1,35 @@ +#if DEBUG +using Celbridge.Dialog; +using Celbridge.Settings; +using ModelContextProtocol.Protocol; +using ModelContextProtocol.Server; + +namespace Celbridge.Tools; + +public partial class AppTools +{ + /// Schedule an automated answer for the next modal dialog (debug-only test automation). + [McpServerTool(Name = "app_answer_dialog", ReadOnly = false, Idempotent = false)] + [ToolAlias("app.answer_dialog")] + [RelatedGuides] + public partial CallToolResult AnswerDialog(string dialogKind, string payload = "", int delayMs = 250) + { + var featureFlags = GetRequiredService(); + if (!featureFlags.IsEnabled(FeatureFlagConstants.AnswerDialog)) + { + return ToolResponse.FeatureFlagDisabled(FeatureFlagConstants.AnswerDialog); + } + + if (!Enum.TryParse(dialogKind, ignoreCase: false, out var kind)) + { + var validNames = string.Join(", ", Enum.GetNames()); + return ToolResponse.Error($"Invalid dialogKind '{dialogKind}'. Valid values: {validNames}."); + } + + var dialogService = GetRequiredService(); + dialogService.ScheduleAnswer(kind, payload, delayMs); + + return ToolResponse.Success("ok"); + } +} +#endif diff --git a/Source/Core/Celbridge.UserInterface/Helpers/DialogAnswerScheduler.cs b/Source/Core/Celbridge.UserInterface/Helpers/DialogAnswerScheduler.cs new file mode 100644 index 000000000..0e6a4af7a --- /dev/null +++ b/Source/Core/Celbridge.UserInterface/Helpers/DialogAnswerScheduler.cs @@ -0,0 +1,106 @@ +using Celbridge.Dialog; +using Celbridge.Logging; + +namespace Celbridge.UserInterface.Helpers; + +/// +/// Holds the pending automated-answer slot used by IDialogService.ScheduleAnswer. +/// When a dialog of the scheduled kind is displayed the slot is consumed and a +/// DialogAnswerMessage is broadcast after the requested delay; subscribed +/// dialogs receive the message and self-close with the affirmative response. +/// +internal sealed class DialogAnswerScheduler +{ + private readonly ILogger _logger; + private readonly IMessengerService _messengerService; + private readonly object _lock = new(); + private bool _set; + private DialogKind _dialogKind; + private string _payload = string.Empty; + private int _delayMs; + + public DialogAnswerScheduler( + ILogger logger, + IMessengerService messengerService) + { + _logger = logger; + _messengerService = messengerService; + } + + public void Schedule(DialogKind dialogKind, string payload, int delayMs) + { + lock (_lock) + { + _set = true; + _dialogKind = dialogKind; + _payload = payload; + _delayMs = delayMs; + } + } + + public void Clear() + { + lock (_lock) + { + _set = false; + _dialogKind = default; + _payload = string.Empty; + _delayMs = 0; + } + } + + /// + /// Notify the scheduler that a dialog of the given kind is about to display. + /// A matching pending schedule is consumed and the answer broadcast after + /// the delay; a non-matching schedule stays pending and the dialog shows + /// normally. + /// + public void OnDialogShown(DialogKind dialogKind) + { + string payload; + int delayMs; + lock (_lock) + { + if (!_set) + { + return; + } + + if (_dialogKind != dialogKind) + { + _logger.LogWarning( + $"Dialog '{dialogKind}' is being shown but a scheduled answer is pending for '{_dialogKind}'. Leaving the schedule in place."); + return; + } + + payload = _payload; + delayMs = _delayMs; + _set = false; + _dialogKind = default; + _payload = string.Empty; + _delayMs = 0; + } + + _ = DelayThenBroadcastAsync(payload, delayMs); + } + + private async Task DelayThenBroadcastAsync(string payload, int delayMs) + { + // The await captures the calling SynchronizationContext (the UI thread + // when OnDialogShown is invoked from a Show* method), so the broadcast + // and any Hide() calls in dialog message handlers resume on the UI + // thread without explicit marshalling. + try + { + if (delayMs > 0) + { + await Task.Delay(delayMs); + } + _messengerService.Send(new DialogAnswerMessage(payload)); + } + catch (Exception ex) + { + _logger.LogError(ex, "Scheduled dialog answer broadcast failed."); + } + } +} diff --git a/Source/Core/Celbridge.UserInterface/Services/Dialogs/DialogService.cs b/Source/Core/Celbridge.UserInterface/Services/Dialogs/DialogService.cs index 7f8e10f24..02a4e6707 100644 --- a/Source/Core/Celbridge.UserInterface/Services/Dialogs/DialogService.cs +++ b/Source/Core/Celbridge.UserInterface/Services/Dialogs/DialogService.cs @@ -1,4 +1,5 @@ using Celbridge.Dialog; +using Celbridge.Logging; using Celbridge.Projects; using Celbridge.Validators; using Celbridge.Workspace; @@ -9,17 +10,25 @@ public class DialogService : IDialogService { private readonly IDialogFactory _dialogFactory; private readonly IWorkspaceWrapper _workspaceWrapper; + private readonly IMessengerService _messengerService; + private readonly DialogAnswerScheduler _answerScheduler; private readonly object _tokenLock = new(); private IProgressDialog? _progressDialog; private bool _suppressProgressDialog; private List _progressDialogTokens = []; public DialogService( + ILogger logger, IDialogFactory dialogFactory, - IWorkspaceWrapper workspaceWrapper) + IWorkspaceWrapper workspaceWrapper, + IMessengerService messengerService) { _dialogFactory = dialogFactory; _workspaceWrapper = workspaceWrapper; + _messengerService = messengerService; + _answerScheduler = new DialogAnswerScheduler(logger, messengerService); + + _messengerService.Register(this, OnWorkspaceUnloaded); } public async Task ShowAlertDialogAsync(string titleText, string messageText) @@ -35,6 +44,7 @@ await ShowDialogAsync(async () => public async Task> ShowConfirmationDialogAsync(string titleText, string messageText, string? primaryButtonText = null, string? secondaryButtonText = null) { var dialog = _dialogFactory.CreateConfirmationDialog(titleText, messageText, primaryButtonText, secondaryButtonText); + _answerScheduler.OnDialogShown(DialogKind.Confirmation); var showResult = await ShowDialogAsync(dialog.ShowDialogAsync); return Result.Ok(showResult); } @@ -127,6 +137,7 @@ public async Task> ShowNewProjectDialogAsync() public async Task> ShowInputTextDialogAsync(string titleText, string messageText, string defaultText, Range selectionRange, IValidator validator, string? submitButtonKey = null) { var dialog = _dialogFactory.CreateInputTextDialog(titleText, messageText, defaultText, selectionRange, validator, submitButtonKey); + _answerScheduler.OnDialogShown(DialogKind.InputText); return await ShowDialogAsync(dialog.ShowDialogAsync); } @@ -152,4 +163,14 @@ public async Task> ShowChoiceDialogAsync(string title var dialog = _dialogFactory.CreateChoiceDialog(titleText, messageText, options, defaultIndex, checkbox, primaryButtonText, secondaryButtonText); return await ShowDialogAsync(dialog.ShowDialogAsync); } + + public void ScheduleAnswer(DialogKind dialogKind, string payload = "", int delayMs = 250) + { + _answerScheduler.Schedule(dialogKind, payload, delayMs); + } + + private void OnWorkspaceUnloaded(object recipient, WorkspaceUnloadedMessage message) + { + _answerScheduler.Clear(); + } } diff --git a/Source/Core/Celbridge.UserInterface/Views/Dialogs/ConfirmationDialog.xaml.cs b/Source/Core/Celbridge.UserInterface/Views/Dialogs/ConfirmationDialog.xaml.cs index 715a33c88..a567b9252 100644 --- a/Source/Core/Celbridge.UserInterface/Views/Dialogs/ConfirmationDialog.xaml.cs +++ b/Source/Core/Celbridge.UserInterface/Views/Dialogs/ConfirmationDialog.xaml.cs @@ -1,9 +1,14 @@ using Celbridge.Dialog; +using Celbridge.Logging; namespace Celbridge.UserInterface.Views; public sealed partial class ConfirmationDialog : ContentDialog, IConfirmationDialog { + private readonly ILogger _logger; + private readonly IMessengerService _messengerService; + private bool _autoAnswered; + public ConfirmationDialogViewModel ViewModel { get; } public string TitleText @@ -24,6 +29,8 @@ public ConfirmationDialog() XamlRoot = userInterfaceService.XamlRoot as XamlRoot; ViewModel = ServiceLocator.AcquireService(); + _logger = ServiceLocator.AcquireService>(); + _messengerService = ServiceLocator.AcquireService(); this.InitializeComponent(); @@ -38,7 +45,26 @@ public ConfirmationDialog() public async Task ShowDialogAsync() { - var result = await ShowAsync(); - return result == ContentDialogResult.Primary; + _messengerService.Register(this, OnDialogAnswer); + try + { + var result = await ShowAsync(); + if (_autoAnswered) + { + return true; + } + return result == ContentDialogResult.Primary; + } + finally + { + _messengerService.UnregisterAll(this); + } + } + + private void OnDialogAnswer(object recipient, DialogAnswerMessage message) + { + _autoAnswered = true; + _logger.LogInformation("Confirmation dialog answered automatically."); + Hide(); } } diff --git a/Source/Core/Celbridge.UserInterface/Views/Dialogs/InputTextDialog.xaml.cs b/Source/Core/Celbridge.UserInterface/Views/Dialogs/InputTextDialog.xaml.cs index fb304eadd..82d26bfd7 100644 --- a/Source/Core/Celbridge.UserInterface/Views/Dialogs/InputTextDialog.xaml.cs +++ b/Source/Core/Celbridge.UserInterface/Views/Dialogs/InputTextDialog.xaml.cs @@ -1,12 +1,16 @@ using Celbridge.Dialog; +using Celbridge.Logging; using Windows.System; namespace Celbridge.UserInterface.Views; public sealed partial class InputTextDialog : ContentDialog, IInputTextDialog { + private readonly ILogger _logger; + private readonly IMessengerService _messengerService; private readonly IStringLocalizer _stringLocalizer; private string _submitButtonKey = "DialogButton_Ok"; + private bool _autoAnswered; public InputTextDialogViewModel ViewModel { get; } @@ -39,6 +43,8 @@ public string SubmitButtonKey public InputTextDialog() { _stringLocalizer = ServiceLocator.AcquireService(); + _logger = ServiceLocator.AcquireService>(); + _messengerService = ServiceLocator.AcquireService(); var userInterfaceService = ServiceLocator.AcquireService(); XamlRoot = userInterfaceService.XamlRoot as XamlRoot; @@ -69,13 +75,25 @@ private void InputTextBox_KeyDown(object sender, KeyRoutedEventArgs e) public async Task> ShowDialogAsync() { - var contentDialogResult = await ShowAsync(); - if (contentDialogResult == ContentDialogResult.Primary || _pressedEnter) + _messengerService.Register(this, OnDialogAnswer); + try { - return Result.Ok(ViewModel.InputText); + var contentDialogResult = await ShowAsync(); + if (_autoAnswered) + { + return Result.Ok(ViewModel.InputText); + } + if (contentDialogResult == ContentDialogResult.Primary || _pressedEnter) + { + return Result.Ok(ViewModel.InputText); + } + + return Result.Fail("Failed to input text"); + } + finally + { + _messengerService.UnregisterAll(this); } - - return Result.Fail("Failed to input text"); } public void SetDefaultText(string defaultText, Range selectionRange) @@ -88,4 +106,12 @@ public void SetDefaultText(string defaultText, Range selectionRange) var (offset, length) = selectionRange.GetOffsetAndLength(defaultText.Length); InputTextBox.Select(offset, length); } + + private void OnDialogAnswer(object recipient, DialogAnswerMessage message) + { + _autoAnswered = true; + ViewModel.InputText = message.Payload; + _logger.LogInformation($"Input-text dialog answered automatically with '{message.Payload}'."); + Hide(); + } } diff --git a/Source/Tests/UserInterface/DialogServiceAnswerTests.cs b/Source/Tests/UserInterface/DialogServiceAnswerTests.cs new file mode 100644 index 000000000..8e9d06d52 --- /dev/null +++ b/Source/Tests/UserInterface/DialogServiceAnswerTests.cs @@ -0,0 +1,295 @@ +using Celbridge.Dialog; +using Celbridge.Messaging; +using Celbridge.UserInterface.Services.Dialogs; +using Celbridge.Validators; +using Celbridge.Workspace; + +namespace Celbridge.Tests.UserInterface; + +[TestFixture] +public class DialogServiceAnswerTests +{ + private CapturingMessengerService _messengerService = null!; + private IDialogFactory _dialogFactory = null!; + private IWorkspaceWrapper _workspaceWrapper = null!; + private ILogger _logger = null!; + private DialogService _dialogService = null!; + + [SetUp] + public void SetUp() + { + _messengerService = new CapturingMessengerService(); + _dialogFactory = Substitute.For(); + _workspaceWrapper = Substitute.For(); + _logger = Substitute.For>(); + + _dialogService = new DialogService(_logger, _dialogFactory, _workspaceWrapper, _messengerService); + + // The DialogService awaits dialog.ShowDialogAsync; stub something sensible. + var fakeConfirm = Substitute.For(); + fakeConfirm.ShowDialogAsync().Returns(Task.FromResult(false)); + _dialogFactory.CreateConfirmationDialog( + Arg.Any(), Arg.Any(), Arg.Any(), Arg.Any()) + .Returns(fakeConfirm); + + var fakeInputText = Substitute.For(); + fakeInputText.ShowDialogAsync().Returns(Task.FromResult(Result.Fail("cancelled"))); + _dialogFactory.CreateInputTextDialog( + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any(), + Arg.Any()).Returns(fakeInputText); + } + + [Test] + public void ScheduleAnswer_AloneDoesNotBroadcast() + { + _dialogService.ScheduleAnswer(DialogKind.Confirmation, payload: "", delayMs: 0); + + // No dialog has been shown; the broadcast must not have fired. + _messengerService.Sent().Should().BeEmpty(); + } + + [Test] + public async Task ShowingConfirmationDialog_FiresScheduledAnswer() + { + _dialogService.ScheduleAnswer(DialogKind.Confirmation, payload: "", delayMs: 0); + + await _dialogService.ShowConfirmationDialogAsync("Title", "Message"); + await WaitForBroadcast(); + + var sent = _messengerService.Sent(); + sent.Should().HaveCount(1); + sent[0].Payload.Should().BeEmpty(); + } + + [Test] + public async Task ShowingInputTextDialog_FiresScheduledAnswerWithPayload() + { + _dialogService.ScheduleAnswer(DialogKind.InputText, payload: "Renamed.txt", delayMs: 0); + + await _dialogService.ShowInputTextDialogAsync( + "Title", "Message", "default", 0..0, Substitute.For()); + await WaitForBroadcast(); + + var sent = _messengerService.Sent(); + sent.Should().HaveCount(1); + sent[0].Payload.Should().Be("Renamed.txt"); + } + + [Test] + public async Task ShowingConfirmationDialog_WithoutSchedule_DoesNotBroadcast() + { + await _dialogService.ShowConfirmationDialogAsync("Title", "Message"); + await Task.Delay(50); + + _messengerService.Sent().Should().BeEmpty(); + } + + [Test] + public async Task ReSchedule_OverwritesPendingAnswer() + { + _dialogService.ScheduleAnswer(DialogKind.InputText, payload: "first", delayMs: 0); + _dialogService.ScheduleAnswer(DialogKind.InputText, payload: "second", delayMs: 0); + + await _dialogService.ShowInputTextDialogAsync( + "Title", "Message", "default", 0..0, Substitute.For()); + await WaitForBroadcast(); + + var sent = _messengerService.Sent(); + sent.Should().HaveCount(1); + sent[0].Payload.Should().Be("second"); + } + + [Test] + public async Task WorkspaceUnloaded_ClearsSchedule() + { + _dialogService.ScheduleAnswer(DialogKind.Confirmation, payload: "", delayMs: 0); + _messengerService.Send(new WorkspaceUnloadedMessage()); + + await _dialogService.ShowConfirmationDialogAsync("Title", "Message"); + await Task.Delay(50); + + _messengerService.Sent().Should().BeEmpty(); + } + + [Test] + public async Task DelayedSchedule_BroadcastsAfterAtLeastDelay() + { + const int delayMs = 80; + _dialogService.ScheduleAnswer(DialogKind.Confirmation, payload: "", delayMs: delayMs); + + var stopwatch = System.Diagnostics.Stopwatch.StartNew(); + await _dialogService.ShowConfirmationDialogAsync("Title", "Message"); + await WaitForBroadcast(); + stopwatch.Stop(); + + stopwatch.ElapsedMilliseconds.Should().BeGreaterThanOrEqualTo(delayMs - 20); + _messengerService.Sent().Should().HaveCount(1); + } + + [Test] + public async Task ScheduleConsumedByFirstShowOnly() + { + _dialogService.ScheduleAnswer(DialogKind.Confirmation, payload: "", delayMs: 0); + + await _dialogService.ShowConfirmationDialogAsync("Title", "Message"); + await WaitForBroadcast(); + await _dialogService.ShowConfirmationDialogAsync("Title", "Message"); + await Task.Delay(50); + + _messengerService.Sent().Should().HaveCount(1); + } + + [Test] + public async Task KindMismatch_LeavesScheduleInPlace_AndIntendedDialogStillFires() + { + // Schedule for an input-text dialog, then show a confirmation dialog + // first: the confirmation should not consume the schedule. + _dialogService.ScheduleAnswer(DialogKind.InputText, payload: "Renamed.txt", delayMs: 0); + + await _dialogService.ShowConfirmationDialogAsync("Title", "Message"); + await Task.Delay(50); + + _messengerService.Sent().Should().BeEmpty(); + + // The schedule is still pending; the input-text dialog now consumes it. + await _dialogService.ShowInputTextDialogAsync( + "Title", "Message", "default", 0..0, Substitute.For()); + await WaitForBroadcast(); + + var sent = _messengerService.Sent(); + sent.Should().HaveCount(1); + sent[0].Payload.Should().Be("Renamed.txt"); + } + + // Spin-wait until the messenger captures at least one message of the + // requested type or a short timeout elapses, so tests don't race the + // background broadcast task spawned by OnDialogShown. + private async Task WaitForBroadcast(int timeoutMs = 500) where TMessage : class + { + var deadline = Environment.TickCount + timeoutMs; + while (Environment.TickCount < deadline) + { + if (_messengerService.Sent().Count > 0) + { + return; + } + await Task.Delay(10); + } + } + + // Captures Send calls so tests can assert on broadcast payloads + // without sharing the static WeakReferenceMessenger.Default state with + // other fixtures. + private sealed class CapturingMessengerService : IMessengerService + { + private readonly Dictionary>> _handlers = new(); + private readonly Dictionary> _recipients = new(); + private readonly Dictionary> _sent = new(); + private readonly object _gate = new(); + + public IReadOnlyList Sent() where TMessage : class + { + lock (_gate) + { + if (_sent.TryGetValue(typeof(TMessage), out var list)) + { + return list.Cast().ToList(); + } + return Array.Empty(); + } + } + + public void Register(object recipient, MessageHandler handler) + where TMessage : class + { + lock (_gate) + { + var type = typeof(TMessage); + if (!_handlers.TryGetValue(type, out var list)) + { + list = new List>(); + _handlers[type] = list; + _recipients[type] = new List(); + } + list.Add(msg => handler(recipient, (TMessage)msg)); + _recipients[type].Add(recipient); + } + } + + public void Unregister(object recipient) where TMessage : class + { + lock (_gate) + { + var type = typeof(TMessage); + if (_recipients.TryGetValue(type, out var recipients)) + { + var index = recipients.IndexOf(recipient); + if (index >= 0) + { + recipients.RemoveAt(index); + _handlers[type].RemoveAt(index); + } + } + } + } + + public void UnregisterAll(object recipient) + { + lock (_gate) + { + foreach (var type in _recipients.Keys.ToList()) + { + var recipients = _recipients[type]; + for (int i = recipients.Count - 1; i >= 0; i--) + { + if (recipients[i] == recipient) + { + recipients.RemoveAt(i); + _handlers[type].RemoveAt(i); + } + } + } + } + } + + public TMessage Send() where TMessage : class, new() + => Send(new TMessage()); + + public TMessage Send(TMessage message) where TMessage : class + { + List>? snapshot; + lock (_gate) + { + if (!_sent.TryGetValue(typeof(TMessage), out var sentList)) + { + sentList = new List(); + _sent[typeof(TMessage)] = sentList; + } + sentList.Add(message); + + if (_handlers.TryGetValue(typeof(TMessage), out var list)) + { + snapshot = list.ToList(); + } + else + { + snapshot = null; + } + } + + if (snapshot is not null) + { + foreach (var handler in snapshot) + { + handler(message); + } + } + + return message; + } + } +} diff --git a/Source/Workspace/Celbridge.Python/Assets/Python/celbridge-0.1.0-py3-none-any.whl b/Source/Workspace/Celbridge.Python/Assets/Python/celbridge-0.1.0-py3-none-any.whl index 39955b343842ee90b5ab6a9f162edd63f0017f8f..ab24faaacf093c2a96a07d7467ca393d6560b563 100644 GIT binary patch delta 3456 zcmZXWXEYq#_QuC3VVLM*^fpAaXo=1nK@dIqh~A=iA~A>%T}*VDLA*vVL>G)vhv5)p}48rH25ttTt|!^1z%YBy}X#JFC2mS-45=_9+eiF<2^gkhCfvIDNgy+ zQW4XN4sEie19x%9yaNp-B^pcyg!R8`ljQf;q#q(ls|8lxKxov}`xzED1P zzw4UbDBQc?>R=ljm~ElE=05GFRE^K`P_?*C5d?P;)tpm6rM)GIfU-FnMz{sN?<*)N zsnQo4JOKp75}0je8=$m9y$fzkW7_zfN3Hp(Fuhul_@vMjznqPXDga=}c6T|^2;t{u zh8=yz$%Jup=_r&-KW%omR{d^@#yG=KA;x9-fpu>mPeh%LmD+*F4Nf0+2yDPe+%hHa z!kxSJo-gy5bug@C$w9J|U0dZxN(K${^cuAyOtAjElBf3Q2UGJ8oDH*C23UPJS>BbD zbUQDNQWd@Lz(1wApY@ktHeO`;wT_{~sFEKyVrwy6sLjE1zlMnC#MObsO}cF4Mo)3B z3dH~0L*i`BRb7>$7b_@4+$mID#_^Yu23#)xiQHbaIK6de zxY~(s#+Jiaz5u$#NSQFu@Lijz@E|aTUN(qLh7GDe*XSJQ@qAdShoX9v1s~lovOq8( z;o0OWv-zdpgZg73y-Z^k8u*PL@>GpCSlthPw+?%MY&Uo+h|#9xzUj76CLN3w z3RD+3#c&p&KA;NMd*hb;{J6PE?35>hSBAERdgRp%zR6=5aQD&qzx0^LnA(m2B_2Xc z4c+>#h6FF$BA?G588ef-b4l_O2-#hY0`Ss?1pcDVTMAP@C)KlXaK!7s_}XyIvHh8*62yCyYaeDp(%9l=yQh`uGvDlIV6ga zA!r5ydQqT;R8%-W>29=`y{Ju!p)TKWNW3OU#;1H8rmcEy6*yP`VPAa0E|gXh;O*<{WxLjbT!WFiseO+wG^Ek+h{#yxIlD#4%OS|224 zo={aM<2flX=T(6_Z}A``Y6kxjx1atqeWQUO)7&_1*y?M6 zyN?=uQxvdAWr`mX ztyrcpAv=ryu3oz#PRmk$A>s3}DPr(-;Wnj9?B|&BzTyl62i`>If|@wlp>yK@{?RGp z_ps`G)g4#jzwL*r(=gG*e*}V~GW{ynR|a0i+f};PFFA|`3MKfE0sv+pL_*a~_@|^& zGbXi_V-cCu7RdKje>_=28ws;sxr?Z`PLPg$tF`r0sAHVINmo3DT}`@7sGlzAXQ75U z(FF;h)`et_xS~@ZFV%e^{rDxj$7_M#Ye!~;gP|zh8JZkbUd7ZBsz{j zpit{daSGn6OL5XaFbqsEGDd1^^BT|litmf|R1*hFAI6k@uQ+F(AmQ~ri_U6inQO5m zfRk7;rY2py`fA z7a2>7>yAx*fSrKYOnl0*Li9Q}45hbcraNl0C_6SCNg0xAbJMqPox8jZ&`seLT>NCW zI=}`D9XDZA7EW~P`7#1>o8icj&2qFZ{}n>FOWNkUekY9s&4rzizLbafN-3f2jv zq^(b>N3y83JNiDSPj)E=2}e3Z6#yKA(*`a4it)XZvJQ)IJ9;UBR9r?Y=@+XDM}zU| zRJ2gniTDj9Xyun-#=Wrqrs%KMmN691X8(?p6xjFBdQf=u!hESXnl(ZcFJ83E41D(~ z0a}Da`GNC%f?dG<3ySt4cB932EWuItN52n|0GwRpOV=J{$Q@>HKl`EXMO{11T?T6g zY4R$uo?5WOpN~pd+T_X|5BUw=0u-eB?*?6O*|zmb|^_WBrW z*Q%IMogd|G89sx(_R$sCYl0t<|KW_k(N3e%M8`0Kk>rLCtZ(u`V&d}5)lb^$`)`ME z%Xq>0de__a2=RkfKIpAdiDR*O^G1h>-t3TDacd-+EwMp_iY}g=(V~9&-4`cZ$`Vou z@?*=>sJi8?aV+j4XZrZ~h#5vAm0ja+D8@K9{FuL{0~S?(kEEaj3GQ8=c$KG#JRxIe z$#(iZkZ2pF|CeEazy7SR4`U*&VKEyl62quhm>X zKwRsGkWM*&fz$C4i)qzwvM7-p{`lj$%nW8|*i<3O!F02tj*mhb*ZCilMFeC3R3Fzz z1OKn*Pi%Aqvfe}Nm~bHInwYr$pX0i;X8&1r5r_9L3wgo+%YY#uO(MYj2Y)UQZcVp= z`C3=9wMiU!$NWk%HA@2*Eia`gBDDDy5Nm%$xv(UN!DbO4i~B$69iV3DKavy?j+F(5 z!>`gAtR(PT#FYd;2O(5iZUMDkUZu$`lEA%W1W~*=;^2~k@(|oO*{cC$g<}P#S0PfW z*buE4as(1bb%imPn6vH*FXL#h(8(og_&1VlW#jrYwM1fH#ZX-~eQ7uW0{?0LGS)?i s*I&JjZ-rbH{&&0q09^m0uqGn%PazMJzBVb@AI^Tc4Jj`DEA~(JFPk!OeEPD3<@a!!j_iJX-w$1FJ%VV>krb4oc)6mu?CIebS{q)3X)Hq4>S zFo#4Ak33G(#6(G+$sDpC?Rh=_{C@ZAb-zCE>$*SJ{qI$97xFU^QhgmM(S5PbD@Wz& zn4ka%^b`Wn4E6z=G%;LmmP1h6kzJ`=@T5<_mPfmgCM@fx2>Bx7yy=MHV6c4qxbp*^370(_hVn;Ho z#p5&cF`jXi7L-43x+`BdBJ=$2d73=x)>FB6)l*6eK9iG%bo46-_QI%C>pUFVi}rq< z{B`rg>yF#mmZ$AD`!t;WqDb23p)pe%`-3V&npDPVe)>xA*Vrp1AwT@<4;B%mOss@XQZ}$ zFSFk8juSgTphXc7NEINFm2fWBmX0n~cXLl=R@CoJ<8GntY72Y@@F{BhErdmOs43v- z32ie+1iOed)AI|m&zh+VAqZlTEsyC)+o(RxUW9U7qe`;+KwA0<%4}g`Ub*~;hvPA}lED7aJFRJy6 zGv+8ysPTEIo*NgTpATjW9+0u=t)3ou2$!BZ9GKhbz*Q9~!{J@NtyzltZ+_KW7AvzX zDB4WH9z2sb(g3}Jru(RsdoNctdMwH^P_L+0zo7g^e1qZXwSv{lwWfwGA8yJqm0>3E z@M+WC6Hv{irvg-QMJMFSuV9T$G`C-`Z#B%1c`mCgsY(TKNt0ow{jnCq600>1+Z@|T z=z=VxdBhrA{G{8@Di@EYNi3^?8~8pnZZT9P*>38)ZT=ZEns7_?Z3}Z1l>KEobq(w) z81-eIFfgaPJMWk&`{y%pXEW`|T$xrqCSk+iV$#sj$Yr-z-n*?SdfY?25fODVQ)2aU z@65DS6=(WrMeJMsN$}{D_ch(5<#aWT7*<6ozMpqt zMs8#N2gfe7+3(%l22)AvaZl?kFDu<);C0s^%_jgL@fr!jk5h_|9QGKjepvoVvf&qk zA+&$%#qqCIVVx}B@YVSv^rib04<%$cTLl-k^bxglQPBc(qH&9UwvcU#C6x6&0B6I`eWn|2u5vC z#3je>1Ob{xf+pjOM{eiK{`C>NvN$uZI=k_&$%Amb{Kz1sjyOw)$R%4Tym%InzFug> zt9WrOSvAOIV&;o@^P3E-!mfHjBj!Q&2=4PQgjxsF5a40Z z=F=;vRQI0H24u;hxZ!gWX`dH{_rv`X$dspdfG0BRoDSOzIu~}RfAGDiH2e~QNNETR z;Z`M~1hHq|8Ie(TxMLujSSUR;!{&hNLw_^f2*+gW2~DP4;Pna zk}i%!cF&|Iw$TNI(-Vy-CUS#i-ya+;_CMo`b6Y93bRLrvjtfL^zhTNm0?~WYdM+SI zMe>5XMxT}2h300A22UH>j{bEY)<*jK>g!7#ETkdJH^BmzECF?(@Ypt*?Z!fpGT(-|hiUd5iW83> zDa9o+d)8Ph>Z62k6c-N1*;@DZAIhq!-?NeFqs)`?*O#r=h74ceO~4O3Z`I#XBKk3r zAoty*rFjW68>O6_>GQ||nxG}>V(Nu*&!HHMq)hfKIV&O`JC}In|!H|BnnJ!%VP vGCvzg6-PLs1i&y5Y Date: Mon, 15 Jun 2026 14:06:30 +0100 Subject: [PATCH 08/21] Support auto-answer for dialogs via messenger Add support for automatic dialog answers through a messenger. DialogKind now includes Alert and ResourcePicker, and DialogService notifies the answer scheduler when those dialogs are shown. AlertDialog and ResourcePickerDialog acquire ILogger and IMessengerService, register for DialogAnswerMessage, and unregister in a finally block. AlertDialog hides itself when an auto-answer arrives; ResourcePickerDialog validates the payload as a ResourceKey, selects the matching item, marks confirmation, logs outcomes, and returns appropriate Result values on success or cancellation. --- .../Dialog/IDialogService.cs | 2 + .../Guides/Tools/app_answer_dialog.md | 14 +++++ .../Services/Dialogs/DialogService.cs | 2 + .../Views/Dialogs/AlertDialog.xaml.cs | 27 ++++++++-- .../Dialogs/ResourcePickerDialog.xaml.cs | 50 +++++++++++++++--- .../UserInterface/DialogServiceAnswerTests.cs | 39 ++++++++++++++ .../Python/celbridge-0.1.0-py3-none-any.whl | Bin 50841 -> 51079 bytes .../integration_tests/test_answer_dialog.py | 8 +++ 8 files changed, 132 insertions(+), 10 deletions(-) diff --git a/Source/Core/Celbridge.Foundation/Dialog/IDialogService.cs b/Source/Core/Celbridge.Foundation/Dialog/IDialogService.cs index c7d1e4feb..900b8b86f 100644 --- a/Source/Core/Celbridge.Foundation/Dialog/IDialogService.cs +++ b/Source/Core/Celbridge.Foundation/Dialog/IDialogService.cs @@ -9,8 +9,10 @@ namespace Celbridge.Dialog; /// public enum DialogKind { + Alert, Confirmation, InputText, + ResourcePicker, } /// diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/app_answer_dialog.md b/Source/Core/Celbridge.Tools/Guides/Tools/app_answer_dialog.md index 38725f76b..e98acb8b1 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/app_answer_dialog.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/app_answer_dialog.md @@ -18,11 +18,15 @@ The delay timer starts when the matching dialog is displayed, not when this call ## Parameters - `dialogKind` (required string) — identifies which dialog kind the answer is for. The schedule only fires when a dialog of this kind appears; if a different dialog appears first, the schedule stays pending and the unexpected dialog blocks on the user. Valid values: + - `"Alert"` — info-only OK prompts (e.g. an error notice surfaced after a failed operation). - `"Confirmation"` — yes/no prompts (e.g. `package_delete`). - `"InputText"` — single-string text-entry prompts (e.g. `explorer_rename`). + - `"ResourcePicker"` — file-resource pickers (e.g. JS contribution `PickFile` / `PickImage`). - `payload` (optional string, default `""`) — the content the dialog should receive: + - **Alert dialogs**: payload is ignored; the dialog closes unconditionally. - **Confirmation dialogs**: payload is ignored; the dialog OKs unconditionally. - **Input-text dialogs**: payload is the text to enter. Empty payload enters an empty string. + - **Resource-picker dialogs**: payload is the resource key to select (e.g. `"Folder/picked.txt"` or `"root:path/inside.bin"`). The key must match an item currently visible in the picker (i.e. matching the picker's extension filter); a non-matching or malformed key logs a warning and closes the dialog without confirming, so the picker call returns `Result.Fail` and the test fails loudly. - `delayMs` (optional int, default `250`) — milliseconds to wait *after the dialog is displayed* before broadcasting the answer. Tune up for dialogs with slow initialization; tune down for tight test loops. Only one schedule is held at a time. A subsequent call overwrites; the schedule is cleared on workspace teardown. @@ -46,6 +50,14 @@ package.unpublish("test-integration-pkg") cel.app.answer_dialog("InputText", "Renamed.txt") cel.explorer.rename("/Folder/Old.txt") +# Close the next alert (e.g. one raised by a failed operation). +cel.app.answer_dialog("Alert") +do_something_that_triggers_alert() + +# Pick a specific resource in the next resource-picker dialog. +cel.app.answer_dialog("ResourcePicker", "Folder/picked.txt") +trigger_resource_pick() + # Give a slow-loading dialog more headroom. cel.app.answer_dialog("Confirmation", delayMs=500) package.delete("heavy-package") @@ -54,8 +66,10 @@ package.delete("heavy-package") ### JavaScript ```javascript +await app.answerDialog("Alert"); // close info alert await app.answerDialog("Confirmation"); // confirm await app.answerDialog("InputText", "Renamed.txt"); // rename +await app.answerDialog("ResourcePicker", "docs/photo.png"); // pick a file await app.answerDialog("Confirmation", "", 500); // longer delay ``` diff --git a/Source/Core/Celbridge.UserInterface/Services/Dialogs/DialogService.cs b/Source/Core/Celbridge.UserInterface/Services/Dialogs/DialogService.cs index 02a4e6707..8b92535b1 100644 --- a/Source/Core/Celbridge.UserInterface/Services/Dialogs/DialogService.cs +++ b/Source/Core/Celbridge.UserInterface/Services/Dialogs/DialogService.cs @@ -34,6 +34,7 @@ public DialogService( public async Task ShowAlertDialogAsync(string titleText, string messageText) { var dialog = _dialogFactory.CreateAlertDialog(titleText, messageText); + _answerScheduler.OnDialogShown(DialogKind.Alert); await ShowDialogAsync(async () => { await dialog.ShowDialogAsync(); @@ -155,6 +156,7 @@ public async Task> ShowResourcePickerDialogAsync(IReadOnlyLi } var dialog = _dialogFactory.CreateResourcePickerDialog(extensions, title, showPreview); + _answerScheduler.OnDialogShown(DialogKind.ResourcePicker); return await ShowDialogAsync(dialog.ShowDialogAsync); } diff --git a/Source/Core/Celbridge.UserInterface/Views/Dialogs/AlertDialog.xaml.cs b/Source/Core/Celbridge.UserInterface/Views/Dialogs/AlertDialog.xaml.cs index db6356f76..49eecd7fc 100644 --- a/Source/Core/Celbridge.UserInterface/Views/Dialogs/AlertDialog.xaml.cs +++ b/Source/Core/Celbridge.UserInterface/Views/Dialogs/AlertDialog.xaml.cs @@ -1,10 +1,13 @@ using Celbridge.Dialog; +using Celbridge.Logging; namespace Celbridge.UserInterface.Views; public sealed partial class AlertDialog : ContentDialog, IAlertDialog { private readonly IStringLocalizer _stringLocalizer; + private readonly ILogger _logger; + private readonly IMessengerService _messengerService; public AlertDialogViewModel ViewModel { get; } @@ -14,10 +17,10 @@ public string TitleText set => ViewModel.TitleText = value; } - public string MessageText - { + public string MessageText + { get => ViewModel.MessageText; - set => ViewModel.MessageText = value; + set => ViewModel.MessageText = value; } public string OkString => _stringLocalizer.GetString("DialogButton_Ok"); @@ -25,6 +28,8 @@ public string MessageText public AlertDialog() { _stringLocalizer = ServiceLocator.AcquireService(); + _logger = ServiceLocator.AcquireService>(); + _messengerService = ServiceLocator.AcquireService(); var userInterfaceService = ServiceLocator.AcquireService(); XamlRoot = userInterfaceService.XamlRoot as XamlRoot; @@ -38,6 +43,20 @@ public AlertDialog() public async Task ShowDialogAsync() { - await ShowAsync(); + _messengerService.Register(this, OnDialogAnswer); + try + { + await ShowAsync(); + } + finally + { + _messengerService.UnregisterAll(this); + } + } + + private void OnDialogAnswer(object recipient, DialogAnswerMessage message) + { + _logger.LogInformation("Alert dialog answered automatically."); + Hide(); } } diff --git a/Source/Core/Celbridge.UserInterface/Views/Dialogs/ResourcePickerDialog.xaml.cs b/Source/Core/Celbridge.UserInterface/Views/Dialogs/ResourcePickerDialog.xaml.cs index 1583ee07e..87dcf7717 100644 --- a/Source/Core/Celbridge.UserInterface/Views/Dialogs/ResourcePickerDialog.xaml.cs +++ b/Source/Core/Celbridge.UserInterface/Views/Dialogs/ResourcePickerDialog.xaml.cs @@ -1,4 +1,5 @@ using Celbridge.Dialog; +using Celbridge.Logging; using Windows.System; namespace Celbridge.UserInterface.Views; @@ -6,6 +7,8 @@ namespace Celbridge.UserInterface.Views; public sealed partial class ResourcePickerDialog : ContentDialog, IResourcePickerDialog { private readonly IStringLocalizer _stringLocalizer; + private readonly ILogger _logger; + private readonly IMessengerService _messengerService; private bool _confirmed; private string? _customTitle; @@ -19,6 +22,8 @@ public sealed partial class ResourcePickerDialog : ContentDialog, IResourcePicke public ResourcePickerDialog() { _stringLocalizer = ServiceLocator.AcquireService(); + _logger = ServiceLocator.AcquireService>(); + _messengerService = ServiceLocator.AcquireService(); var userInterfaceService = ServiceLocator.AcquireService(); XamlRoot = userInterfaceService.XamlRoot as XamlRoot; @@ -86,16 +91,49 @@ private void ResourceListView_KeyDown(object sender, KeyRoutedEventArgs e) public async Task> ShowDialogAsync() { _confirmed = false; - var contentDialogResult = await ShowAsync(); - - if (contentDialogResult == ContentDialogResult.Primary || _confirmed) + _messengerService.Register(this, OnDialogAnswer); + try { - if (ViewModel.SelectedItem is { } selected) + var contentDialogResult = await ShowAsync(); + + if (contentDialogResult == ContentDialogResult.Primary || _confirmed) { - return Result.Ok(selected.ResourceKey); + if (ViewModel.SelectedItem is { } selected) + { + return Result.Ok(selected.ResourceKey); + } } + + return Result.Fail("Resource picker was cancelled"); + } + finally + { + _messengerService.UnregisterAll(this); + } + } + + private void OnDialogAnswer(object recipient, DialogAnswerMessage message) + { + if (!ResourceKey.TryCreate(message.Payload, out var targetKey)) + { + _logger.LogWarning( + $"Resource picker auto-answer failed: payload '{message.Payload}' is not a valid resource key."); + Hide(); + return; + } + + var match = ViewModel.FilteredItems.FirstOrDefault(item => item.ResourceKey.Equals(targetKey)); + if (match is null) + { + _logger.LogWarning( + $"Resource picker auto-answer failed: no filtered item matches resource key '{targetKey}'."); + Hide(); + return; } - return Result.Fail("Resource picker was cancelled"); + ViewModel.SelectedItem = match; + _confirmed = true; + _logger.LogInformation($"Resource picker answered automatically with '{targetKey}'."); + Hide(); } } diff --git a/Source/Tests/UserInterface/DialogServiceAnswerTests.cs b/Source/Tests/UserInterface/DialogServiceAnswerTests.cs index 8e9d06d52..9a968fee0 100644 --- a/Source/Tests/UserInterface/DialogServiceAnswerTests.cs +++ b/Source/Tests/UserInterface/DialogServiceAnswerTests.cs @@ -41,6 +41,19 @@ public void SetUp() Arg.Any(), Arg.Any(), Arg.Any()).Returns(fakeInputText); + + var fakeAlert = Substitute.For(); + fakeAlert.ShowDialogAsync().Returns(Task.CompletedTask); + _dialogFactory.CreateAlertDialog(Arg.Any(), Arg.Any()).Returns(fakeAlert); + + // The ResourcePicker happy path requires a loaded workspace; the + // dialog itself is a substitute that completes immediately so the + // schedule-fires-broadcast contract is what we test here. + _workspaceWrapper.IsWorkspacePageLoaded.Returns(true); + var fakeResourcePicker = Substitute.For(); + fakeResourcePicker.ShowDialogAsync().Returns(Task.FromResult(Result.Fail("cancelled"))); + _dialogFactory.CreateResourcePickerDialog( + Arg.Any>(), Arg.Any(), Arg.Any()).Returns(fakeResourcePicker); } [Test] @@ -143,6 +156,32 @@ public async Task ScheduleConsumedByFirstShowOnly() _messengerService.Sent().Should().HaveCount(1); } + [Test] + public async Task ShowingAlertDialog_FiresScheduledAnswer() + { + _dialogService.ScheduleAnswer(DialogKind.Alert, payload: "", delayMs: 0); + + await _dialogService.ShowAlertDialogAsync("Title", "Message"); + await WaitForBroadcast(); + + var sent = _messengerService.Sent(); + sent.Should().HaveCount(1); + sent[0].Payload.Should().BeEmpty(); + } + + [Test] + public async Task ShowingResourcePickerDialog_FiresScheduledAnswerWithPayload() + { + _dialogService.ScheduleAnswer(DialogKind.ResourcePicker, payload: "Folder/picked.txt", delayMs: 0); + + await _dialogService.ShowResourcePickerDialogAsync(new List { "txt" }); + await WaitForBroadcast(); + + var sent = _messengerService.Sent(); + sent.Should().HaveCount(1); + sent[0].Payload.Should().Be("Folder/picked.txt"); + } + [Test] public async Task KindMismatch_LeavesScheduleInPlace_AndIntendedDialogStillFires() { diff --git a/Source/Workspace/Celbridge.Python/Assets/Python/celbridge-0.1.0-py3-none-any.whl b/Source/Workspace/Celbridge.Python/Assets/Python/celbridge-0.1.0-py3-none-any.whl index ab24faaacf093c2a96a07d7467ca393d6560b563..0ac583b72536bd27cd8be37587006732323e5469 100644 GIT binary patch delta 2898 zcmZWrXEYmZ8;;l-JGDnute{G5v6>=kwr1^3shVw&RwRnrBh=niv+7Ok5}Vd;i_#Z0 zUaK}$n-AZ4|9szb&V8p$4G5~-RgrnC`Ko3Om z%P|n;ek*~MSvl9HkICR~KHt)9KdV|zXvJ5LKCJmzB_?Cyem=Rw5ZDgWPWZvIA44Fl zF#)%^y3LEqHj5!955OO?(kWH7T-A=b@fQ5jIqJ`>i?Y@dMd7_iGP<`S3u~1X~pGf5a0m{ zYqpZ&1=UH#rS8UB>|q0I+u^Rt{HYY;6j_MXeN_Q)w7|Z+b&*r*ZOvkIZGBWSV)PHy zSHD8(#Yb+Ij0wQ3+Aqpp{v?1KRt_V*A_L$ZE|k!3#Shzcn; ztV9#Ya;#O5A6n0whE-9!XV?+v$sn3e|q<@q- zqUGB~cIej2ONQZ3CJNRQ7PxMkTR$+G#Xu0CenKX*^mxJ@H41BVUie&X%h>^QEp%D- zor(#!gTZ-lq|)%7y_ z3lJihtNslw_uV1+$ev18E)J<7kZjd{Q=4&uNEjLq>jBzAeYNMJuuK%M1Zkt{2OZifD`e5D~~q9WxW^)v)0 zzg>85t~7YOA*9_aY2B8goqW? z(_WCD^=&GfH&)Cr|HSSv(#GRulu>Nw44*=@W`gylIf5k!UR%@M{belvv#>)=avYl; zPO$7qUjGe+vhOOS;kcc7^&p#UF2dVx66+SE(vtVLdea-9LAsq2sxr@#ED4>TlqTfl%+JOXH1AI07FdmH_K_`1fW)I&{Ih!=4-O%)iB*-m6s-l#V7q<)mhM^Py zm96`~HSYPH*mmfRe-R8Eds-VopYezN+cu{|;fEquD;6Se*yvPhPPsE9Y+nm+Kg#G% ztfI3SUl`~O`W{>r!}+&3tx(haL~=J@dBfy`=V;x&s=}IZGB}|vRHLpVbtB54@@AGDjl-4z2<7M7pwx-d!#-Kou}CK zfez8Ei#Z1c8>4Do!n|&1tI6Qos1xly?=_$Yj9@zOEZdMZ*m%#<`#O1#rrJb%0 zs77Z%Yv&@7aHl4H5)^3cd!PLE!V${=I%%OLU=D;WYEnw+wdin%eJ3Xmbal;v=aIGk zh)J7)Q4zbZV!C>lC0w~@w@oSeSd8u<%TD2W=KTof1Y;`Mj&QB@F+_k;eM&BJfK)!s zx&h{0oSa-yF5bh8h>&XjL%qOI0rB)>NB^h=c0}2?%uY!cDpufAGrWF&9qTR&8`EX- zqg9(Pe^!A*&07a(;XhGhuGgmLxr79=(w2C(0*>>~ZL`-lr;G;7`?O+x%sxpk$gF22 zGnEXhthF1YiYCvs^LRu<$l}1nh4cHX-~SFDY7*+e>y5CEs?#%nU$mVKT)G1%L#sJO zfenk&)^w)Ljj>E@iZmko_ zT^fRQZoh?`w!W&yM1 zs|l}WlpoYF|0TBERl{1J5)^nUHTThLwXY`Fzqr?x;QbON@PrX8bbEvzlC!#5N_N*N|Kes26in*O`$lDEpVry=Ijq z8nx2{V~;dVRJT=Uy{TC38g=B`&$-k9-7n8g7aE|_PdT<J86Y0V+sMhFg0TP-#U^y>C! z^Z$|&*Ra~$AY`8SPq0^?R&wx!>0*#M?*})7DdRA5zNSDK{cX0^HE7>_MY+aVqhDf$ zpR)RP@&SrGE=j7lZer5p9m)*DnHZt4$nm+YUNskC@q!K$jf~ptN5>zHaXND_#r$3< zJ5klL%y&XH*m-}1lJ66-Ts@7swvdy%ZwIiLf0((~SsX<=t!HJ-k`l}l51R{mxMRK& zL%c0R1_KY$?E$?>5pBxutli95fLb}&=}YNDzD$?r$Tzcx)Gu#Z<~(&(u!wJEb=+z71MQWmlhx1?gCI%Q*72HKCG z?PfC0_&QI@K3_}Z5!2*)7W>N_#rw;T_xYz&9_s7~@V(S>4;vVM52!0)KZ>_!KVx(P z&rNXBCfgjrAar~FJ#pU{mU0n>j`^FVbQ3|`b4fF@TAQ}m{H{DKb49KHpbGGj-ME;O zXQOh!LRs3n(d0xfZDwcm_CIObAjc1-%-Ex+)0NP_EAKL(PbdkJ%`&9_`x*3a^#aCg z6aWe=C99rjMQPh*faOkC)U{m_NacA&o7-i9 zaxbnZTZanpqKGd;ThP?FR~=AbFZN1uVrg(w7g9d_N{V&f0nR2|P)p!J-jy@k$#U5l zhn-x&R|K4WHy3UYPlZ$J0$sY=U0fuzIJ<6cTxb^~?`5YX0*$DDG=6X1#Dj7 z#%CY~YI;)zcnBc@Qsw@*j@0fJt)CprnGJ$;b|_q;_|0rbdl{Hfdj(&OhmE(v;V0CQ z&=7WGOw+k4Y#PRmuE`H#wvyhf)f~CCJ52O%YjMzRiHPIm2%F*sJucKkYik|vcQrXo zpVX!%Kq^-5BIu>)#C6X(8DFDc0`?d33eI~AYUF*()~CipH2tg*)l6bC+eO`_2omi~ zL9LE!*7PDTz^Mw2pklf((hc8~=Hz4G+!DFREcG+m=&jubB!c(8UImUjS|?FDLgUHtBD=DK2^nfX%1=Dv)-2`9(K$Dub* zF0bF40LfKGz}C-s1VYuEJp!60uG~LlzdGrPcSFX9w`OtuWc6MUmAz&1n}KY}BtC

eG(mtR~H?mEMGn-<` zy(Xsptdu!ct<*o}m)-r`nMU0qa9N_o!4v3#g-eahPnY;X)k@#Cn!I&0591ti%Ph!t z60RUfH2clR%KQPu@8HxPN)w`0>&18g-l{`-m~UH#rdU~{jg7^uXCEm1lIyOf4p-Sp zDF0e{%ri>uT*1&!Hk`V$2#@a1gCL)&^SSET?k*L-BiJ@+2@jT4 zGtOg$ajd>w;IwgE)p}?kR?Dvm5}+1o1P$s+dA^+dN?aa^uny(5?19=DjxQ?0L!H57 zPU0P6^AC@Ul}Q-;wf2$EBYtuHMpi!HzxX{(VaUiW=Y9HjvJ5etb=mo$ae6{&B|Zd( zguq>wmjZrA*Lv+WuizrA-pkI196h&TD;d=J@#21b5#pF$&Fy0xzu}ra0Bu=6*IYPw9+GKG~)T)a-jp&ztnq&r!=th2x&e zYC=$&ORiNXXQYn%CJFnGnZ5s^+Z39a_#scs(9IlfM##*hbQ;q_N6Rt z85j`=*A}ju?N}Mg@2z~KwbEeWkU5OTklb2W8&<9CaHYl z(e7jWg_3yw7&)>+@g@)O)%z4^F&YyD&JPUt1ozEpyIq70m8d#`cDWHX2GnQ=$BfjZk3P(6FxaJnrjULO? z+h&(4p-0xq>I<6>&zoAf%3=Ig+IV~22kiYL7d@Cq(5`yV4)C}v^WW}{_GK+om<@E3 zw=|Pz_%?sutW?~do<;H4K+jynJd89$7dt1lu>%soT08kxH+q?zR9usnSWusJcuxvf z6Et*qlv0oo<~`F7-&xeivgTq<`L$4bsB2*RDmb#yH5^|8-D!;mO6r^EK@YDN5o6Q8 zT2ZfX1xb;dm+HBnA??!l@UV96lc6OD3CO|Ydr=gh!ui5j9) zT>Vv=LLwU6vpo7V-w?e|C&-uMar6OkC(isY%aCC6=?8(F2xyBstrD$P2y%ePE74L` zl*%W4bwAY`^rgy$YSmoxoY1`Du~@fMXSFR~$>U#ASh?->V)QU8w}9b*>m8nw^XmHS zvq>|m25T;{RmA*wc{~55ydXeQ``d_4jbO=xPo)l%*Cy047jq?&_hzy(d7x2WifFbc zTeM9BwK9Z{cOlh6tt!<2J&s@me_+84)D;IYRKFn|>X0A}EHFBy;wXwS9m=vp8mMZ2 zO65@Hr*Pi!%*f)*grXx#1MzNW)S9RO(l{8!_Octni2J&QyMrC zb4CfB%0QzhXONGi1l&qHgTm?OP|+l5ASnNgQb=6Fe?r5#KWog6$1GJri?dDj)}d<5Xt={8^90 zyw6fNW$LFT{jaHpvnf<7S>R8GO^^ks6i@|ZL6mI+SmwW5{*`S1x1}Z~_H4$di;GSH H|3~;A!vgud diff --git a/Source/Workspace/Celbridge.Python/packages/celbridge/src/celbridge/integration_tests/test_answer_dialog.py b/Source/Workspace/Celbridge.Python/packages/celbridge/src/celbridge/integration_tests/test_answer_dialog.py index d8552bff9..03f56aad3 100644 --- a/Source/Workspace/Celbridge.Python/packages/celbridge/src/celbridge/integration_tests/test_answer_dialog.py +++ b/Source/Workspace/Celbridge.Python/packages/celbridge/src/celbridge/integration_tests/test_answer_dialog.py @@ -2,6 +2,14 @@ Skipped (whole class) when the build is release or the user has not set `answer-dialog = true` in their .celbridge. + +Coverage boundary: Confirmation and InputText are exercised here because +`explorer.delete(showDialog=True)` and `explorer.rename` reliably surface +those dialogs through MCP. Alert and ResourcePicker have no clean MCP +trigger (Alert is reached only via error paths like rename-on-readonly; +ResourcePicker fires only from JS contribution `PickFile`/`PickImage` +calls), so their schedule-to-broadcast contract is covered by C# unit +tests in DialogServiceAnswerTests rather than here. """ import pytest From d00a9a4466c0a0f146d4a81f0016c995238a55a4 Mon Sep 17 00:00:00 2001 From: Chris Gregan Date: Mon, 15 Jun 2026 14:52:15 +0100 Subject: [PATCH 09/21] Add integration publish/install/unpublish tests Refactor and expand integration tests for package publishing. Introduces constants and a temporary manifest workaround for server validation, plus helper functions (_build_integration_package, _drop_integration_package_if_published) and tighter workspace cleanup. Replaces skipped uninstall tests with a full publish -> install -> delete flow (confirm handling updated via confirmWithUser and app.answer_dialog), strengthens list() assertions, and adds a test for publishing an invalid package name. Also updates the built wheel asset (rebuilt celbridge-0.1.0 wheel). --- .../Python/celbridge-0.1.0-py3-none-any.whl | Bin 51079 -> 51970 bytes .../integration_tests/test_package.py | 121 +++++++++++++----- 2 files changed, 89 insertions(+), 32 deletions(-) diff --git a/Source/Workspace/Celbridge.Python/Assets/Python/celbridge-0.1.0-py3-none-any.whl b/Source/Workspace/Celbridge.Python/Assets/Python/celbridge-0.1.0-py3-none-any.whl index 0ac583b72536bd27cd8be37587006732323e5469..eb7aa8e1589e241d113e6633880cf6b63fc9c01a 100644 GIT binary patch delta 3501 zcmZ8kcRbYpA3y7moXs7H&eLq0~y8KE;yl&pwI=&YQKJ1M2? zb!3F>C`zSYeSd%a#_RETJ)W=ee7^pCKOgT~nM{d!O!Xs)5WnC-Y)@kNr7|!GWGqbN zE|CU8J-vfG{e6+4_)SOC%xMj7ipUN?y2SNWkW6GetHyx6+8&mQCYoo35HCcQyjytb zl@GVOvnzoBn&@Shbpct_Qz0acDP9Px!}#gFxjF0%wxbQ=?v6bQ z`}{-aIR$s-7L0j+jhzpip9+JW1Zqh1D z88qdmBP&_loch8a3^3*OXjSr^CT2z_$_F#)dOxY`RtCQhm3^7&##ZYs@FFY!ettIs zn@FDX8N4CkJSR-x!%9?ITy3`dR=bwrX(NJzbml%9Zu2g*K4r;p zS=j%yT;JY&yw^szsXwBdCqxA)P>t$MU;I8i4+8D)R-9k9ReYumaDR{W<|houV#ngM zs$@Lni{gAHFW|dLVM)h65e9zYH}R%Jf#q4yu7??F!JRaa96{_8 zu%CW~Vzj60-N#hjhXm?K1L{@SZ<;dP zryuV_Q`i^DqA2Nu7}v<#fnC8PV>P~9{SC4_(HU~(>7lJBSn{oV%_`o~RA?Kr_wH~g zg^oC+)Ev)LuJI?(YGLo=yizpZ3ky$v8PQej)=Ku<$W+_KaXH8EuN8@q?1b}9Xq(V; z0bXtlW$JQ^zMWhcC>6sRwZoyaRWY<7k%yq$+roarmPZi#8^Yt!VT3fn5vtkwY=Pne`#b z_S$)-Q=4oGZ=3LX%4c?ewUhkxMd0ug0t(*sIh@^qDv2n=P%|*bb&LA0{jilEqGf5L zeR=(ckLFF0ek&eNJ(>q=pELi%f=PSH%g57ZKy{elVDphkN92}CM6fvgVldq<>}{)% zn`v6b?uZ#KMg*jGS>c>iok?nfWF(eeJ_C8RtSA>;^MPlf`p})^U0$PKO37)}G@RKX zNJImj-Y@7AkkJ+C@U7XH(_pE`5iU=cIN3!{uXwtyrGchD@t<@oeGdg|Xyi2BOQNyeHS|1^l+mO6ov>Wx-yEPbBiY z^=0So+a(cL{m(>jROIQV=~!-)*E7&g?ap?qxj62LO2*T)>ktD2TY0etgqfPwY`#6Uqa=sD`OL9r19n$E zhRE&Go#-O1y{}b8TZKON8mBjFJ1Zi>wd+LC!KBcKaUv`(J-)Tul#o$=$H1sB5+7C7 zYW&~0?z9<=<)!*^2wAs010Az@HudBY;Nz5*L__oa4sV!@E7HO5BA`(Y({Z(?j;~4N z_RcG1`=ei{(}WxbU*t>Fe_U-J7O0bc=yuZ*n)dcXsoR29Pzp$icV>1tZeVc^3M;RF z;V8!pCujLbTA|J!71(UP?-(KEYRT?)Ov(wz$$lYJT=uXHQ41u18ep z9Jsl(|5{uCE+)RQ5TexA-edgbhp1e9n-;Z%Wfrv>1FtSY5jklQoAQ9G_{}&pXqyBb&$?p65(W$&n*kd+bzFX0wqho{CoC9C? zBri_R6vuLu;gabnKF5T*E91epybbs8a)TVcRx(L0$WCB9≥2Iak(f)#X zDk}BG!NAE#0q6(KYuYE});GK^H6bNmNku8yoRW>zEjUkg_U`CSTP1u%r7>yr1tiZ_ z4n?sW%P|**^+`ZndaoDN<+^c=GP%~bSjz$`mHa}FYZR%f#(gRs5cYYGCh?63*fg1O zajeU;ul4!uSBYmXO=7Hx3CZc9+3h#zJc)$Zq+h9ept zH+nRGc3MC(k1BkcK9Jw zNHs*HJ2LlBgqrLdD>Le%2hNXLeNcuS@J1 zTyp!4waCZ^5b|xX#y!_!ypGWANx<$Iwv#G@<&N`M+eeq5T%Kx;T$)fN7L2-xrw9tg zzClxOSPn*n=*amDJ&`*@o~|ayc+(3xu_SA0x6?^IOVQX zV+!zVg0-S_30sIWMvG3P0_wl(YF+TVp$^-nOmkk$mPd%}-vM$XdZ0pn_3uZuI?M3v z{E(?cR&Sy8pSmy|BiC(qd;#|l8@*Pep%oVx<|&|#y)UXgpc|4IWi^k4UM(hq^lFw6vyZHig+hm+ zf>&#MjqmclJeMX2KnK~7+Jq*}9gFt)iZ{he5k#pg%d_Pp-2UsU9!Jg! zjPe_=VqbKoDE*X2_u%x*$Bu$!XRUOj`+$ks{ER1E=k-p7*-%II88tpV#HuRbcV6OO zOn*U3r{_UXy#9nM?-J@{me0+wC9{3hw7PlsC(oY9lP6t0z}rXt{_N&@N-?Ci3$jhU z7_aUq+llmF;5I?^#U1s!MSumC;CN`ri09t&THscST7wCoj@VhR^>H%2mvDK~Q`bwP zS00>8e7dQ};_tZmDZQ3~P`0X29^4?%5l(!wPZisUxgh=d(sSsg<(`S4HD(D)VRMZ! zS>AiX{WloXC{A$$|NoaHy*H4NOd#qxOAv*rs*LVA|CHj{5Mn%4jxn`?A1a8Pkk&?g z(0BsnV2nnaVN4kq`9b`D_H3ZPs;uIE66#NP=1JtRt?fSd<7ijcpivR!s delta 2553 zcmY+GXE+-Q7sn%LNv%litwF6)6<2MwSE)U!wrJIE%~ph>sFlR1SukJ^PJ!R>p3ShnKUwCIyy}2^WW|?!$VUnQ1kwTk!kE!0 zK8$=XC34R6p~s9kU9iTYqJIDglK<5~_73%nXC*Z`3dTW#M$7z672>{*HkRw<+32Y# z6<~PDwq1bor?p06OXJz_vrO%9*c`9R_R(pxY9Bl*MDZ4*826iy$YON5s9nJde5csZ z(4?lF-XElG6~-*h{aS?5u0wKjw&L5Fp*jV94YFMqrGFRuE@C&c-*$x9m5?A%Y${U# zKCaNJH+~-hvty5d6^usTw)LDKg6~^`*@K8QlJm){^WS0n8`l_OcT%ka_<2l5(en3q z2FHwuuPK)(K=wzK)zrM?P##@ayz+@P%SsuUel(9{oLKawSJ-@lKR+}oUx`kd>2NO@ zNfLjF;C<+(m$#g~k;x6wb;-{-3y%atC~l}FYTeN`i)jFX^EC^*Xfe49TItSl(i3UY zf;KCc$!(BD;7|BY{zTgeP6@%@R|j35MLX@W~Vs;_Y zp6DuUSC4J8lxg`fG7Ob#;-vaooxPE=h%@0vhd$L#JahN_S>@MsoStt+N8TG&OuQ(o z#Hhuv9UOlf;$iXmrlcKWM#lBwl44t3``Ahl(?S&1SCSFIs_#VxQ!uThu4Fy4_9MNjiGu-vA61^QwXq-26`;8ownK*Ar#a8iUp~~z z(h(eQ2u7RTprDFf`itD+#Yc#XBaKEnVSjyZZx0|hkX;W3H7Gz@(x-`ipVP~ieZ4On z_Fic_V{HERn@=zC(Qz&n&PgyCj^w;uzDbWGI~h#pb_f5mNJEI>#4sNOi;Ozr4t$-Z zH?l4e>2A0_OTgTQT-dScj<$Aa$DgYDO)Q3Hh*t5 zACzl@YvB1oftg_;g_B8BD!0086#lO@SVgofc6CP|U=Vfu75-30h)O9jp2sgVLmZq`YJf-!s4}En9D)Oo$$W-v3A{bXHSdKfLSo+=vfKbH^L(wcAadTLR;JRoEFv(#!cGwjS5)2W!@=wS*ghP z?6=Ee&qQdBGi>kqrfWw+5{xONIwCX(lbZgrbxGNNL!{EF*7b6pI4riTRI~@8iHwwJ zKBQcsD}%ZDu*}o|JEHAc7v?1MWXdX2(mcM6O?H=rPwLY9P%AB!z9_>4Em`|(RQ6J$ z7;4_VcMb_)qAqf41Dxet+GK7M=ZuET2Q*^6%z7nP;Dih;ebI=*_9y)mVeH~3PS+S1 zSsb)wu2Wte=vli>%iZiP!N z1!bwMFg;jLd!3-UdmZ0zK>im{?=7#c6PPS8b5lpPSz!&Nba8C#nyMbu{iw6fRHSB` zGAh3>n-ZWq>T9}EFC^*Cy8H6kbwApPxjVNwnm}HnEl;;hv*gAmkX}^~t5EulY}lTS zUN|d-Ehd-$+b`jxVofYdysPKhw*9-u0xHndEu2w+NB48)=c8$H^;^yL0_KU!!5#w| z*x+f?)j)Huau@wM<8X4GrU3YZUFNoJp@F5cQng_UB5;xuS+uj$=^0t!JYU`B6(K zdrw2Q4eb1W{!l1t8NxwebP(*cUVxjS6U-A&nsa+N-j20|cv^)F2OPh%1N5UK+vT4! zbwfe{N~KW8(PBfMbZ1|`r)J~Majt`cQ7wHbkY^qdxYZWVNIY& zsm*71 zda>gmmZ_?Ake=dNyF3%h1<#ZzVJQW3LE~DKuQ!yWA$@K#^I|c^44g?vJgrfu^s|Hv zmRDIa+Uv+CM>pq_40R6pcwT9Q4~={P)E2Rv#@n&{0y{z%r`f2nk5A=bGzZUn;{K*v z%hoh-$RQTfOb2o-qQ6UN5N$$py55IE%4(huD*!EQr$ijx8sz;~N>T|%v(wqs>76mV z%c)va4&}v=&{OyMk3wHR-ls#JUpLSp0YU*#005uYZhDR8-!Bm6dhZjUbOffo=O!kp z_bzZD;ZK=`rNNx_3IUJb|7o@R7`cD1a_b`OjMmlbL`?wz@B;pc8q{k%CZtdN4MW_6WZ|1f|W6m>0Tr0@5|z+CpR0}E?0N{u2It$wOMHqodzkyKYW=qd&NW#g*5 l0@*Q%{VadLKlg+j05HEIx&IHWLV5lVT 0 - - inst_result = package.install("test-integration-pkg") - assert inst_result["packageName"] == "test-integration-pkg" - - uninst_result = package.uninstall("test-integration-pkg") - assert uninst_result["packageName"] == "test-integration-pkg" + # Each entry follows the v8 package summary shape. Tolerant of an + # empty workshop, but every present entry must carry the full shape. + for entry in result: + assert "packageName" in entry + assert "latestVersion" in entry + assert "publishedAt" in entry + assert "versionsCount" in entry + assert isinstance(entry["packageName"], str) + assert isinstance(entry["versionsCount"], int) + + def test_publish_install_unpublish(self, answer_dialog_available, app, explorer, file, package): + _build_integration_package(explorer, file) + _drop_integration_package_if_published(app, package) + + try: + pub_result = package.publish( + INTEGRATION_PACKAGE_FOLDER, + summary="integration round-trip publish", + confirmWithUser=False, + ) + assert pub_result["packageName"] == INTEGRATION_PACKAGE_NAME + assert pub_result["version"] >= 1 + assert pub_result["entries"] > 0 + published_version = pub_result["version"] + + inst_result = package.install( + INTEGRATION_PACKAGE_NAME, + confirmWithUser=False, + ) + assert inst_result["packageName"] == INTEGRATION_PACKAGE_NAME + assert inst_result["version"] == published_version + + app.answer_dialog("Confirmation") + delete_result = package.delete(INTEGRATION_PACKAGE_NAME, str(published_version)) + assert delete_result["packageName"] == INTEGRATION_PACKAGE_NAME + assert delete_result["version"] == published_version + assert delete_result["deleted"] is True + finally: + # Whether the body succeeded, raised, or the delete failed, we + # must leave the workshop clean so the next run can publish again. + _drop_integration_package_if_published(app, package) def test_archive_invalid_source(self, package): with pytest.raises(CelError): @@ -78,26 +136,25 @@ def test_unarchive_invalid_archive(self, package): def test_install_nonexistent_package(self, package): with pytest.raises(CelError): - package.install("nonexistent-package-xyz-999") + package.install("nonexistent-package-xyz-999", confirmWithUser=False) def test_install_invalid_package_name(self, package): with pytest.raises(CelError): - package.install("INVALID PACKAGE NAME!") - - @pytest.mark.skip(reason="package.uninstall tool not yet implemented") - def test_uninstall_not_installed(self, package): - with pytest.raises(CelError): - package.uninstall("not-installed-package-xyz") - - @pytest.mark.skip(reason="package.uninstall tool not yet implemented") - def test_uninstall_invalid_package_name(self, package): - with pytest.raises(CelError): - package.uninstall("INVALID!") - - def test_publish_invalid_package_name(self, package): - with pytest.raises(CelError): - package.publish("TestPackage", "INVALID NAME!") + package.install("INVALID PACKAGE NAME!", confirmWithUser=False) + + def test_publish_invalid_package_name(self, explorer, file, package): + # A manifest with an invalid name is rejected before any upload. + explorer.create_folder("packages/invalid-name-source") + file.write( + "packages/invalid-name-source/package.toml", + "[package]\nname = \"INVALID NAME!\"\n", + ) + try: + with pytest.raises(CelError): + package.publish("packages/invalid-name-source", confirmWithUser=False) + finally: + delete_if_exists(explorer, "packages/invalid-name-source") def test_publish_nonexistent_source(self, package): with pytest.raises(CelError): - package.publish("NonExistentFolder", "test-pkg") + package.publish("NonExistentFolder", confirmWithUser=False) From 2fd8742900032e1f561ea648fded3d782d3abef3 Mon Sep 17 00:00:00 2001 From: Chris Gregan Date: Mon, 15 Jun 2026 20:34:38 +0100 Subject: [PATCH 10/21] Add rescan, hash option, and alias handling Introduce session-mid package rescans and optional file hashing, improve alias parsing, and tidy related semantics and docs. - Add IPackageService.RescanProjectPackagesAsync and implement it to re-run discovery and refresh the load report without firing PackagesInitializedMessage (enables package_status refresh without full reload). - Add computeHash:boolean to file_get_info and return uppercase-hex SHA-256 when requested; implement ComputeFileHashAsync and surface hash in the FileInfoResult. - Make package_publish warning a non-null string (empty when none) and adjust BuildPublishWarning accordingly. - In PackageTools.Status add a refresh parameter to opt-in to rescan behavior. - Update PackageApiClient to accept alias name under either "name" or "alias" and return a clearer error when setting an alias to a nonexistent version (maps 404 to a helpful message). Add tests for alias parsing and SetAlias error handling. - Fix CopyPathMenuOption to resolve resource paths via ResolveResourcePath and bail on failure. - Update numerous guides to document computeHash, package list/info semantics, and the prompting/irreversibility wording. Includes small XML/doc tweaks and unit tests to cover the new behaviors. --- .../Packages/IPackageService.cs | 9 ++++ .../Celbridge.Tools/Guides/Namespaces/file.md | 2 +- .../Guides/Namespaces/package.md | 2 +- .../Guides/Tools/file_get_info.md | 17 +++++++- .../Guides/Tools/package_info.md | 9 ++-- .../Guides/Tools/package_list.md | 15 +++++-- .../Guides/Tools/package_publish.md | 2 +- .../Guides/Tools/package_status.md | 8 +++- .../Guides/Tools/package_unpublish.md | 4 +- .../Tools/File/FileTools.GetInfo.cs | 38 +++++++++++++++-- .../Tools/Package/PackageTools.Publish.cs | 17 +++++--- .../Tools/Package/PackageTools.Status.cs | 17 +++++++- .../Tools/Package/PackageTools.Unpublish.cs | 2 +- .../Tests/Packages/PackageApiClientTests.cs | 41 +++++++++++++++++++ .../Menu/Options/CopyPathMenuOption.cs | 11 ++--- .../Services/PackageApiClient.cs | 17 +++++++- .../Services/PackageService.cs | 20 +++++++++ 17 files changed, 199 insertions(+), 32 deletions(-) diff --git a/Source/Core/Celbridge.Foundation/Packages/IPackageService.cs b/Source/Core/Celbridge.Foundation/Packages/IPackageService.cs index e9e06e80d..eba638220 100644 --- a/Source/Core/Celbridge.Foundation/Packages/IPackageService.cs +++ b/Source/Core/Celbridge.Foundation/Packages/IPackageService.cs @@ -20,6 +20,15 @@ public interface IPackageService ///

Task RegisterPackagesAsync(string projectFolderPath); + /// + /// Re-runs project-package discovery against the on-disk state and refreshes + /// the load report, without firing PackagesInitializedMessage. Lets a + /// session-mid caller (such as package_status) see packages added or + /// removed after the workspace loaded. Editor-contribution registration is + /// workspace-load-scoped and is not refreshed by this call. + /// + Task RescanProjectPackagesAsync(string projectFolderPath); + /// /// Gets document type entries from discovered packages that declare templates. /// Packages with a disabled feature flag are excluded from the results. diff --git a/Source/Core/Celbridge.Tools/Guides/Namespaces/file.md b/Source/Core/Celbridge.Tools/Guides/Namespaces/file.md index 2a6cd2fe1..a52238f5d 100644 --- a/Source/Core/Celbridge.Tools/Guides/Namespaces/file.md +++ b/Source/Core/Celbridge.Tools/Guides/Namespaces/file.md @@ -26,7 +26,7 @@ The `file` namespace operates on the contents of files in the project tree: read - `file_read_many` — read many files in one call (cheaper than N calls when scanning). - `file_read_binary` — read raw bytes as base64. - `file_read_image` — read an image inline as a vision content block. -- `file_get_info` — file metadata (size, mtime, hash) without reading content. +- `file_get_info` — file metadata (size, mtime; optional SHA-256 content hash via `computeHash: true`) without otherwise reading the file. - `file_get_tree` — directory tree under a resource. - `file_list_contents` — direct children of a folder. diff --git a/Source/Core/Celbridge.Tools/Guides/Namespaces/package.md b/Source/Core/Celbridge.Tools/Guides/Namespaces/package.md index 4c2714921..e44315e91 100644 --- a/Source/Core/Celbridge.Tools/Guides/Namespaces/package.md +++ b/Source/Core/Celbridge.Tools/Guides/Namespaces/package.md @@ -10,7 +10,7 @@ The `package` namespace installs, inspects, publishes, archives, and curates Cel - **`package_status` is the local installed-package map.** It reports each project package's name, version, and folder, plus any load failures (such as a duplicate-name fault). Use it to decide install locations and to diagnose why a package is not loading; it reads no workshop. - **The package name comes from the manifest.** `package_publish` reads the name from `[package].name`; there is no folder-name rule and no separate name argument. - **Aliases are non-destructive.** `package_set_alias` and `package_remove_alias` only repoint or detach a label; they never touch version content. -- **Delete and unpublish are destructive and always confirm.** `package_delete` (one version) and `package_unpublish` (the whole package) remove content permanently and prompt every time — there is no `confirmWithUser` opt-out. Celbridge does not model the server's hidden tombstone state; deleted bytes are gone, though the version's history entry and content hash are kept. Durability rests on consumers vendoring, not on the workshop promising eternal availability. +- **The irreversible workshop-admin tools always prompt.** `package_delete` (one version) and `package_unpublish` (every version of a package) remove content irreversibly and prompt every time — there is no `confirmWithUser` opt-out, unlike `package_install` and `package_publish`, which are also destructive but opt-outable for agent workflows. Celbridge does not model the server's hidden tombstone state; deleted bytes are gone, though the version's history entry and content hash are kept. Durability rests on consumers vendoring, not on the workshop promising eternal availability. - **Packages are not Python packages.** Despite some tooling overlap, this namespace is for Celbridge's own package format. To see the project's Python dependencies, read the `.celbridge` project file (`[project].dependencies`). - **There is no create tool.** A package is a folder with a `package.toml` manifest; scaffold one by writing the manifest with the file tools. See `packages_overview` for the manifest schema. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/file_get_info.md b/Source/Core/Celbridge.Tools/Guides/Tools/file_get_info.md index 51f890eba..b3ba9ec26 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/file_get_info.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/file_get_info.md @@ -2,6 +2,20 @@ Returns metadata for a file or folder without reading its content. Useful before reading large files (check `size` and `lineCount` to decide whether to page via `file_read`'s `offset` and `limit`) or to confirm a resource exists at the expected key. +## Parameters + +### resource + +The resource key of the file or folder. + +### computeHash (optional, default `false`) + +When `true` and the resource is a file, reads the bytes once and returns an uppercase-hex SHA-256 in the result's `hash` field. When `false` (or for folders), `hash` is `null` and no bytes are read. The default is `false` because hashing a large file is expensive — opt in only when you actually need to compare content (e.g. walking two trees during a three-way merge and identifying which files differ without reading each one). + +The hash format matches the rest of the codebase (`RemotePackageVersion.contentHash`, `HISTORY.md`'s short fingerprint): compare two `hash` strings with ordinary equality. + +The hash is captured after the rest of the metadata snapshot. In the microsecond gap between the two reads a file could in principle change so that `size` and `hash` disagree; for session-mid agent usage this is acceptable. + ## Returns The shape depends on whether the resource is a file or a folder. Both shapes carry a `type` discriminator (`"file"` or `"folder"`) and a `modified` timestamp in ISO 8601 (round-trip) format. @@ -16,7 +30,8 @@ For files: - `lineCount` — number of lines for text files, otherwise null. - `isReadOnly` — true when the file carries the filesystem read-only attribute. Write operations (`file_write`, `file_edit`, etc.) will fail until the attribute is cleared via `file_set_writeable`. Common on files imported from archives, source-control checkouts, or network shares. - `sidecar`, `sidecarStatus` — the paired `.cel` sidecar's resource key and parse state (`"healthy"`, `"broken"`, or `"none"` when absent). +- `hash` — uppercase-hex SHA-256 of the file's bytes when `computeHash: true` was passed; otherwise `null`. Stable across reads of the same content; flipping a single byte changes the whole hex string. Compare two `hash` values with string equality to decide whether two files are byte-identical without reading both. -For folders the result is `type`, `modified`, and `isReadOnly`. +For folders the result is `type`, `modified`, and `isReadOnly`. The `computeHash` parameter has no effect on folders. The call fails with a "Resource not found" error if the resource does not exist. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_info.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_info.md index 8102c666d..4f1925470 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/package_info.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_info.md @@ -18,12 +18,13 @@ A JSON object: - `version` (int) — the version number. - `author` (string) — who published it. - `date` (datetime) — when it was published. - - `tombstoned` (bool) — true if the version's content has been removed; it cannot be installed. - - `contentHash` (string) — the uploaded content's hash. - - `summary` (string) — the publisher's change summary. + - `deleted` (bool) — true if the version's content has been removed; it cannot be installed. (The server's wire field is still `tombstoned`; the client maps it to `deleted` because Celbridge does not model a dead-but-retained state.) + - `contentHash` (string) — the uploaded content's hash; retained even when `deleted` is true so vendored copies stay verifiable. + - `summary` (string) — the publisher's change summary; emptied on delete so the renderer shows the `[package_deleted]` sentinel. - `aliases` (array) — one object per alias, each with `alias` (string) and `version` (int). `latest` is managed by the workshop; others such as `stable` are publisher-defined. ## Gotchas - A `404` from the workshop (no such package) surfaces as an error; check the name with `package_list`. -- Tombstoned versions still appear in the list with `tombstoned: true` so history reads correctly; filter them out when choosing what to install. +- Deleted versions still appear in the list with `deleted: true` so the history numbering stays intact and `HISTORY.md` can render the gap. Filter on `!deleted` when choosing what to install. +- The version flag is `deleted` (not `tombstoned`) on the client side — an agent that filters on `tombstoned` will silently include deleted versions as live. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_list.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_list.md index 3fc451c87..ebbc05f21 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/package_list.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_list.md @@ -7,8 +7,17 @@ Returns the packages currently published to the connected workshop. Use this to A JSON array of objects, one per package: - `packageName` (string) — the package's unique name on the workshop. -- `latestVersion` (int or null) — the number of the latest live version, or null when the package has no live versions. -- `publishedAt` (datetime or null) — UTC timestamp of when the latest live version was published. -- `versionsCount` (int) — total number of versions the package has. +- `latestVersion` (int or null) — the highest version number the server reports for the package. **See caveat below**: this may name a deleted version when no live versions remain, pending a server-side fix. Treat a non-null value as advisory — confirm with `package_info` before trusting it as installable. +- `publishedAt` (datetime or null) — UTC timestamp of when the latest version was published. +- `versionsCount` (int) — total number of versions the package has (deleted versions included). The array is in the order returned by the workshop; it is not sorted alphabetically. + +## Caveat: `latestVersion` after delete or unpublish + +Until the server delete-contract alignment lands (tracked in the migration follow-ups), `latestVersion` is **not** filtered to live versions: + +- After `package_delete` removes the highest version, the server may still report it under `latestVersion` until the next publish. +- After `package_unpublish` removes every version, every entry's `latestVersion` is non-null but installing that version returns "has been deleted and cannot be installed." + +When you need certainty, call `package_info(packageName)` and select the highest `version` whose `deleted` is false. The version resolver inside `package_install` already does this for `latest`, so resolving `latest` continues to work correctly — only consumers reading `package_list` directly need the caveat. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md index e0818e844..d746daf07 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_publish.md @@ -37,7 +37,7 @@ A JSON object: - `version` (int) — the version number the workshop assigned to this publish. - `entries` (int) — number of files included in the uploaded zip. - `size` (long) — uploaded zip size in bytes. -- `warning` (string or null) — an advisory note, or `null`. Currently set when this folder was published from a stale base (see Concurrent publishing). +- `warning` (string) — an advisory note, or the empty string when there is none. Currently populated when this folder was published from a stale base (see Concurrent publishing); branch on a non-empty value rather than on key presence. ## HISTORY.md diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_status.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_status.md index 71a2c0e6a..ae2196da8 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/package_status.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_status.md @@ -6,7 +6,11 @@ Only project packages are reported. Bundled packages that ship inside the applic ## Parameters -None. +### refresh (optional, default `false`) + +Re-run package discovery against the on-disk state before returning. Set this to `true` when an agent installed or removed a package in the current session and wants to see it reflected without a full project reload. A refresh re-reads every project `package.toml` and updates the load-failure list (so a freshly-introduced duplicate-name fault becomes visible); editor-contribution registration is workspace-load-scoped and is **not** refreshed — packages contributing new editors still need a reload before those editors become available. + +The default is `false` to keep the typical read cheap: a refresh walks the project's visible resource set. ## Returns @@ -19,4 +23,4 @@ A JSON object with two arrays: - **A `DuplicateName` failure means two manifests claim the same name, and all of them are skipped** — none loads until the conflict is resolved. Move, rename, or remove one of the colliding folders, then reload the project. - **`version` reflects the installed/published version recorded in `HISTORY.md`**, not a manifest field — the manifest carries no authoritative version under the workshop model. A `null` version is normal for a package authored in place. -- The reported state is from the last project load; install or publish actions taken in this session are reflected after the project re-scans. +- Without `refresh: true`, the reported state is from the last project load. Pass `refresh: true` after a session-mid install / uninstall / manifest edit to see it. A full project reload is still required for packages contributing new document editors to become usable. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_unpublish.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_unpublish.md index b36e9a171..0f471c530 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/package_unpublish.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_unpublish.md @@ -1,8 +1,8 @@ # package_unpublish -Removes a whole package and all its versions from the workshop, permanently. This is the package counterpart of `page_unpublish`: where `package_delete` removes one version, `package_unpublish` removes the package itself and every version it holds. +Removes a whole package and all its versions from the workshop. This is the package counterpart of `page_unpublish`: where `package_delete` removes one version, `package_unpublish` removes the package itself and every version it holds. -This is destructive administration and **always prompts for confirmation**: there is no `confirmWithUser` opt-out. The content bytes are not recoverable through the workshop afterward. +This is destructive administration and **always prompts for confirmation**: there is no `confirmWithUser` opt-out. Version content is not recoverable through the workshop afterward. ## Parameters diff --git a/Source/Core/Celbridge.Tools/Tools/File/FileTools.GetInfo.cs b/Source/Core/Celbridge.Tools/Tools/File/FileTools.GetInfo.cs index dde883a01..4292e9f76 100644 --- a/Source/Core/Celbridge.Tools/Tools/File/FileTools.GetInfo.cs +++ b/Source/Core/Celbridge.Tools/Tools/File/FileTools.GetInfo.cs @@ -10,7 +10,8 @@ namespace Celbridge.Tools; /// Absence is signalled by sidecar_status = "none" with sidecar = null. /// IsReadOnly reflects the filesystem read-only attribute — a true value /// means write operations will fail until the attribute is cleared with -/// file_set_writeable. +/// file_set_writeable. Hash is the uppercase-hex SHA-256 of the file's +/// bytes when computeHash was set; null otherwise. /// public record class FileInfoResult( string Type, @@ -21,7 +22,8 @@ public record class FileInfoResult( int? LineCount, bool IsReadOnly, string? Sidecar, - string SidecarStatus); + string SidecarStatus, + string? Hash = null); /// /// Result returned by file_get_info for folder resources. @@ -34,7 +36,7 @@ public partial class FileTools [McpServerTool(Name = "file_get_info", ReadOnly = true)] [ToolAlias("file.get_info")] [RelatedGuides("resource_keys")] - public async partial Task GetInfo(string resource) + public async partial Task GetInfo(string resource, bool computeHash = false) { if (!ResourceKey.TryCreate(resource, out var resourceKey)) { @@ -66,6 +68,17 @@ public async partial Task GetInfo(string resource) _ => "none", }; + string? hash = null; + if (computeHash) + { + var hashResult = await ComputeFileHashAsync(resourceKey); + if (hashResult.IsFailure) + { + return ToolResponse.Error(hashResult); + } + hash = hashResult.Value; + } + var fileResult = new FileInfoResult( "file", snapshot.Size, @@ -75,7 +88,8 @@ public async partial Task GetInfo(string resource) snapshot.LineCount, snapshot.IsReadOnly, snapshot.SidecarKey?.ToString(), - sidecarStatusText); + sidecarStatusText, + hash); return ToolResponse.Success(SerializeJson(fileResult)); } @@ -85,4 +99,20 @@ public async partial Task GetInfo(string resource) snapshot.IsReadOnly); return ToolResponse.Success(SerializeJson(folderResult)); } + + // The hash is read after the snapshot rather than inside the command, so a + // file changing in the microsecond gap could in principle disagree with the + // reported Size. For session-mid agent usage that is acceptable; callers + // needing strict consistency would have to add a hash to the snapshot itself. + private async Task> ComputeFileHashAsync(ResourceKey resourceKey) + { + var workspaceWrapper = GetRequiredService(); + if (!workspaceWrapper.IsWorkspacePageLoaded) + { + return Result.Fail("No project is loaded."); + } + + var resourceFileSystem = workspaceWrapper.WorkspaceService.ResourceService.FileSystem; + return await resourceFileSystem.ComputeHashAsync(resourceKey); + } } diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs index 38d1c8a4a..43e2cc36e 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs @@ -13,9 +13,16 @@ namespace Celbridge.Tools; /// /// Result returned by package_publish with the published package details, /// including the version number assigned by the workshop. Warning carries an -/// advisory note (e.g. a stale-base concurrent-publish warning) or is null. +/// advisory note (e.g. a stale-base concurrent-publish warning) or is the +/// empty string when there is no advisory; callers branch on the value, not +/// on whether the key is present. /// -public record class PackagePublishResult(string PackageName, int Version, int Entries, long Size, string? Warning = null); +public record class PackagePublishResult( + string PackageName, + int Version, + int Entries, + long Size, + string Warning = ""); public partial class PackageTools { @@ -103,7 +110,7 @@ public async partial Task Publish(string resource, string summar } var publishWarning = BuildPublishWarning(packageName, baseCheck); - if (publishWarning is not null) + if (publishWarning.Length > 0) { Logger.LogWarning(publishWarning); } @@ -155,7 +162,7 @@ private async Task ConfirmPublishAsync(string packageName, PublishBaseChec return await ConfirmActionAsync(title, message); } - private static string? BuildPublishWarning(string packageName, PublishBaseCheck baseCheck) + private static string BuildPublishWarning(string packageName, PublishBaseCheck baseCheck) { switch (baseCheck.Concern) { @@ -171,7 +178,7 @@ private async Task ConfirmPublishAsync(string packageName, PublishBaseChec "is not based on a superseded version before relying on this publish."; default: - return null; + return string.Empty; } } diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Status.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Status.cs index cf91712a9..31ab5b126 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Status.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Status.cs @@ -1,5 +1,6 @@ using System.Text.Json; using Celbridge.Packages; +using Celbridge.Projects; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; @@ -32,7 +33,7 @@ public partial class PackageTools [McpServerTool(Name = "package_status", ReadOnly = true)] [ToolAlias("package.status")] [RelatedGuides("packages_overview")] - public async partial Task Status() + public async partial Task Status(bool refresh = false) { var workspaceWrapper = GetRequiredService(); if (!workspaceWrapper.IsWorkspacePageLoaded) @@ -43,6 +44,20 @@ public async partial Task Status() var workspaceService = workspaceWrapper.WorkspaceService; var packageService = workspaceService.PackageService; var resourceService = workspaceService.ResourceService; + + // The registry only re-scans on workspace load by default, so a tool + // call that installed or removed a manifest in this session would + // otherwise read stale state. The opt-in refresh re-runs discovery + // without firing PackagesInitializedMessage. + if (refresh) + { + var projectService = GetRequiredService(); + var currentProject = projectService.CurrentProject; + if (currentProject is not null) + { + await packageService.RescanProjectPackagesAsync(currentProject.ProjectFolderPath); + } + } var resourceRegistry = resourceService.Registry; var resourceFileSystem = resourceService.FileSystem; diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Unpublish.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Unpublish.cs index 2ca92c32d..426f2dcdf 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Unpublish.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Unpublish.cs @@ -11,7 +11,7 @@ public record class PackageUnpublishResult(string PackageName, bool Unpublished) public partial class PackageTools { - /// Unpublish a whole package and all its versions from the workshop, permanently. + /// Unpublish a whole package and all its versions from the workshop. [McpServerTool(Name = "package_unpublish", Destructive = true)] [ToolAlias("package.unpublish")] [RelatedGuides("packages_overview")] diff --git a/Source/Tests/Packages/PackageApiClientTests.cs b/Source/Tests/Packages/PackageApiClientTests.cs index a09556bbe..bb1add681 100644 --- a/Source/Tests/Packages/PackageApiClientTests.cs +++ b/Source/Tests/Packages/PackageApiClientTests.cs @@ -109,6 +109,34 @@ public async Task GetPackage_ParsesVersionsAndAliases() details.Aliases[0].Version.Should().Be(2); } + [Test] + public async Task GetPackage_AliasesKeyedAsName_ArePopulated() + { + // The live workshop returns the alias label under the "name" key, not + // "alias". The DTO accepts both so install-by-alias works either way. + _messageHandler.Responder = _ => JsonResponse(""" + { + "name": "my-widget", + "created_at": "2026-01-10T12:00:00Z", + "versions": [ + { "version": 1, "author": "alice", "date": "2026-01-10T12:00:00Z", "tombstoned": false, "content_hash": "aaa111", "summary": "" } + ], + "aliases": [ + { "name": "latest", "version": 1 }, + { "name": "stable", "version": 1 } + ] + } + """); + + var result = await _client.GetPackageAsync("my-widget"); + + result.IsSuccess.Should().BeTrue(); + var details = result.Value; + details.Aliases.Should().HaveCount(2); + details.Aliases[0].Alias.Should().Be("latest"); + details.Aliases[1].Alias.Should().Be("stable"); + } + [Test] public async Task GetPackage_UnknownPackage_Fails() { @@ -232,6 +260,19 @@ public async Task SetAlias_SendsPutWithVersionBody() _messageHandler.RequestBodies.Single().Should().Be("""{"version":2}"""); } + [Test] + public async Task SetAlias_NonexistentVersion_NamesTheVersion() + { + _messageHandler.Responder = _ => new HttpResponseMessage(HttpStatusCode.NotFound); + + var result = await _client.SetAliasAsync("my-widget", "stable", 9999); + + result.IsFailure.Should().BeTrue(); + result.MessageChain.Should().Contain("9999"); + result.MessageChain.Should().Contain("my-widget"); + result.MessageChain.Should().NotContain("HTTP 404"); + } + [Test] public async Task RemoveAlias_SendsDelete() { diff --git a/Source/Workspace/Celbridge.Explorer/Menu/Options/CopyPathMenuOption.cs b/Source/Workspace/Celbridge.Explorer/Menu/Options/CopyPathMenuOption.cs index e6c3c8d92..31535fd8e 100644 --- a/Source/Workspace/Celbridge.Explorer/Menu/Options/CopyPathMenuOption.cs +++ b/Source/Workspace/Celbridge.Explorer/Menu/Options/CopyPathMenuOption.cs @@ -45,11 +45,12 @@ public void Execute(ExplorerMenuContext context) var target = context.ClickedResource ?? context.ProjectFolder; var resourceRegistry = _workspaceWrapper.WorkspaceService.ResourceService.Registry; - var resourceKey = resourceRegistry.GetResourceKey(target); - // Use .Path (the path portion only) for filesystem-path construction; - // ToString() now emits the canonical "project:" prefix that does not - // belong in a filesystem path. - var filePath = Path.Combine(resourceRegistry.ProjectFolderPath, resourceKey.Path); + var resolveResult = resourceRegistry.ResolveResourcePath(target); + if (resolveResult.IsFailure) + { + return; + } + var filePath = resolveResult.Value; _commandService.Execute(command => { diff --git a/Source/Workspace/Celbridge.Packages/Services/PackageApiClient.cs b/Source/Workspace/Celbridge.Packages/Services/PackageApiClient.cs index edb18e304..204beb07c 100644 --- a/Source/Workspace/Celbridge.Packages/Services/PackageApiClient.cs +++ b/Source/Workspace/Celbridge.Packages/Services/PackageApiClient.cs @@ -102,7 +102,13 @@ public async Task> GetPackageAsync(string packageNa var aliases = new List(); foreach (var alias in details.Aliases ?? []) { - aliases.Add(new RemotePackageAlias(alias.Alias ?? string.Empty, alias.Version)); + // The live workshop returns the alias name under "name", while + // earlier drafts of the wire contract (and our canned test + // payloads) used "alias". The DTO carries both, and we pick + // whichever the server populated so install-by-alias works + // either way until the wire contract is settled. + var aliasName = alias.Name ?? alias.Alias ?? string.Empty; + aliases.Add(new RemotePackageAlias(aliasName, alias.Version)); } return new RemotePackageDetails( @@ -222,6 +228,14 @@ public async Task SetAliasAsync(string packageName, string alias, int ve } using var response = sendResult.Value; + if (response.StatusCode == HttpStatusCode.NotFound) + { + // The server returns 404 for both "package not found" and "version + // does not exist on this package"; the more common scripting + // mistake is the version target, so name it first. + return Result.Fail($"Cannot point alias '{alias}' at version {version}: version {version} does not exist on package '{packageName}' (or the package itself was not found)."); + } + if (!response.IsSuccessStatusCode) { return Result.Fail($"Failed to set alias '{alias}' to version {version} of package '{packageName}' (HTTP {(int)response.StatusCode})"); @@ -372,6 +386,7 @@ private record VersionDetailDto( private record AliasDto( [property: JsonPropertyName("alias")] string? Alias, + [property: JsonPropertyName("name")] string? Name, [property: JsonPropertyName("version")] int Version); private record PackageDetailsDto( diff --git a/Source/Workspace/Celbridge.Packages/Services/PackageService.cs b/Source/Workspace/Celbridge.Packages/Services/PackageService.cs index fdebec93e..71177db29 100644 --- a/Source/Workspace/Celbridge.Packages/Services/PackageService.cs +++ b/Source/Workspace/Celbridge.Packages/Services/PackageService.cs @@ -45,6 +45,26 @@ public async Task RegisterPackagesAsync(string projectFolderPath) _messengerService.Send(new PackagesInitializedMessage()); } + public async Task RescanProjectPackagesAsync(string projectFolderPath) + { + // A rescan re-runs discovery and refreshes the load report but skips + // PackagesInitializedMessage. Editor contributions are registered + // once at workspace load and re-firing the message would replay every + // factory registration, which is not idempotent and would surprise + // unrelated subscribers expecting one-shot initialization. + var report = await _registry.DiscoverPackagesAsync(projectFolderPath); + + _loadReporter.RecordPackageReport(report); + await _loadReporter.FlushAsync(); + + if (report.Failures.Count > 0) + { + var projectName = Path.GetFileName(projectFolderPath) ?? string.Empty; + var message = new ConsoleErrorMessage(ConsoleErrorType.PackageLoadError, projectName); + _messengerService.Send(message); + } + } + public IReadOnlyList GetAllPackages() { return _registry.GetAllPackages(); From 1351c33415ffc935bba75abbf6a651103d619633 Mon Sep 17 00:00:00 2001 From: Chris Gregan Date: Mon, 15 Jun 2026 21:05:24 +0100 Subject: [PATCH 11/21] Update package_info.md --- Source/Core/Celbridge.Tools/Guides/Tools/package_info.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_info.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_info.md index 4f1925470..6168f6cae 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/package_info.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_info.md @@ -20,11 +20,11 @@ A JSON object: - `date` (datetime) — when it was published. - `deleted` (bool) — true if the version's content has been removed; it cannot be installed. (The server's wire field is still `tombstoned`; the client maps it to `deleted` because Celbridge does not model a dead-but-retained state.) - `contentHash` (string) — the uploaded content's hash; retained even when `deleted` is true so vendored copies stay verifiable. - - `summary` (string) — the publisher's change summary; emptied on delete so the renderer shows the `[package_deleted]` sentinel. + - `summary` (string) — the publisher's change summary, as written at publish time. Retained when `deleted` is true (the workshop does not erase it on delete). - `aliases` (array) — one object per alias, each with `alias` (string) and `version` (int). `latest` is managed by the workshop; others such as `stable` are publisher-defined. ## Gotchas -- A `404` from the workshop (no such package) surfaces as an error; check the name with `package_list`. -- Deleted versions still appear in the list with `deleted: true` so the history numbering stays intact and `HISTORY.md` can render the gap. Filter on `!deleted` when choosing what to install. +- A `404` from the workshop (no such package) surfaces as an error; check the name with `package_list`. After a `package_unpublish`, the package is **not** 404 — it stays listed with every version flagged `deleted: true` and an empty `aliases[]`, pending the server-side full-removal endpoint tracked in the migration follow-up. +- Deleted versions still appear in the list with `deleted: true` so the history numbering stays intact and `HISTORY.md` can render the gap. Filter on `!deleted` when choosing what to install. The `[package_deleted]` text the `HISTORY.md` renderer substitutes for a deleted version's body is keyed off the `deleted` flag, not off an emptied `summary`. - The version flag is `deleted` (not `tombstoned`) on the client side — an agent that filters on `tombstoned` will silently include deleted versions as live. From ac2d119fa0ddcd5f67db71ab828343d0ff731296 Mon Sep 17 00:00:00 2001 From: Chris Gregan Date: Tue, 16 Jun 2026 10:39:17 +0100 Subject: [PATCH 12/21] Exclude app_answer_dialog guide from Release builds Remove the Guides/Tools/app_answer_dialog.md embedded resource for non-Debug configurations in the Celbridge.Tools project. This matches the tool's debug-only (#if DEBUG) gating and keeps the loader's tool-coverage validator happy; the change adds a conditional EmbeddedResource Remove entry and an explanatory comment in the .csproj. --- Source/Core/Celbridge.Tools/Celbridge.Tools.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/Core/Celbridge.Tools/Celbridge.Tools.csproj b/Source/Core/Celbridge.Tools/Celbridge.Tools.csproj index 9073c4487..6ae33ae1f 100644 --- a/Source/Core/Celbridge.Tools/Celbridge.Tools.csproj +++ b/Source/Core/Celbridge.Tools/Celbridge.Tools.csproj @@ -29,6 +29,8 @@ + + From 3d7da88850f0f00a1891cebd9e26992d4511bcfc Mon Sep 17 00:00:00 2001 From: Chris Gregan Date: Tue, 16 Jun 2026 10:47:18 +0100 Subject: [PATCH 13/21] Bump esbuild and update tiptap bundle Upgrade esbuild devDependency from ^0.25.0 to ^0.28.1 in lib/build/package.json and update the generated tiptap.js bundle accordingly (rebuilt/minified output changed). Also add package-lock.json. --- .../Editors/Notes/lib/build/package.json | 2 +- .../Editors/Notes/lib/tiptap.js | 205 +++++++++--------- package-lock.json | 6 + 3 files changed, 115 insertions(+), 98 deletions(-) create mode 100644 package-lock.json diff --git a/Source/Modules/Celbridge.DocumentEditors/Editors/Notes/lib/build/package.json b/Source/Modules/Celbridge.DocumentEditors/Editors/Notes/lib/build/package.json index 1bad7e528..1650caa36 100644 --- a/Source/Modules/Celbridge.DocumentEditors/Editors/Notes/lib/build/package.json +++ b/Source/Modules/Celbridge.DocumentEditors/Editors/Notes/lib/build/package.json @@ -23,6 +23,6 @@ "@tiptap/markdown": "^3.0.0" }, "devDependencies": { - "esbuild": "^0.25.0" + "esbuild": "^0.28.1" } } diff --git a/Source/Modules/Celbridge.DocumentEditors/Editors/Notes/lib/tiptap.js b/Source/Modules/Celbridge.DocumentEditors/Editors/Notes/lib/tiptap.js index 5d5cea040..ea17bcadf 100644 --- a/Source/Modules/Celbridge.DocumentEditors/Editors/Notes/lib/tiptap.js +++ b/Source/Modules/Celbridge.DocumentEditors/Editors/Notes/lib/tiptap.js @@ -1,16 +1,18 @@ -var Yh=Object.defineProperty;var Zh=(t,e,n)=>e in t?Yh(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var j=(t,e,n)=>Zh(t,typeof e!="symbol"?e+"":e,n);function se(t){this.content=t}se.prototype={constructor:se,find:function(t){for(var e=0;e>1}};se.from=function(t){if(t instanceof se)return t;var e=[];if(t)for(var n in t)e.push(n,t[n]);return new se(e)};var si=se;function ta(t,e,n){for(let r=0;;r++){if(r==t.childCount||r==e.childCount)return t.childCount==e.childCount?null:n;let s=t.child(r),i=e.child(r);if(s==i){n+=s.nodeSize;continue}if(!s.sameMarkup(i))return n;if(s.isText&&s.text!=i.text){for(let o=0;s.text[o]==i.text[o];o++)n++;return n}if(s.content.size||i.content.size){let o=ta(s.content,i.content,n+1);if(o!=null)return o}n+=s.nodeSize}}function na(t,e,n,r){for(let s=t.childCount,i=e.childCount;;){if(s==0||i==0)return s==i?null:{a:n,b:r};let o=t.child(--s),l=e.child(--i),a=o.nodeSize;if(o==l){n-=a,r-=a;continue}if(!o.sameMarkup(l))return{a:n,b:r};if(o.isText&&o.text!=l.text){let c=0,u=Math.min(o.text.length,l.text.length);for(;ce&&r(a,s+l,i||null,o)!==!1&&a.content.size){let u=l+1;a.nodesBetween(Math.max(0,e-u),Math.min(a.content.size,n-u),r,s+u)}l=c}}descendants(e){this.nodesBetween(0,this.size,e)}textBetween(e,n,r,s){let i="",o=!0;return this.nodesBetween(e,n,(l,a)=>{let c=l.isText?l.text.slice(Math.max(e,a)-a,n-a):l.isLeaf?s?typeof s=="function"?s(l):s:l.type.spec.leafText?l.type.spec.leafText(l):"":"";l.isBlock&&(l.isLeaf&&c||l.isTextblock)&&r&&(o?o=!1:i+=r),i+=c},0),i}append(e){if(!e.size)return this;if(!this.size)return e;let n=this.lastChild,r=e.firstChild,s=this.content.slice(),i=0;for(n.isText&&n.sameMarkup(r)&&(s[s.length-1]=n.withText(n.text+r.text),i=1);ie)for(let i=0,o=0;oe&&((on)&&(l.isText?l=l.cut(Math.max(0,e-o),Math.min(l.text.length,n-o)):l=l.cut(Math.max(0,e-o-1),Math.min(l.content.size,n-o-1))),r.push(l),s+=l.nodeSize),o=a}return new t(r,s)}cutByIndex(e,n){return e==n?t.empty:e==0&&n==this.content.length?this:new t(this.content.slice(e,n))}replaceChild(e,n){let r=this.content[e];if(r==n)return this;let s=this.content.slice(),i=this.size+n.nodeSize-r.nodeSize;return s[e]=n,new t(s,i)}addToStart(e){return new t([e].concat(this.content),this.size+e.nodeSize)}addToEnd(e){return new t(this.content.concat(e),this.size+e.nodeSize)}eq(e){if(this.content.length!=e.content.length)return!1;for(let n=0;nthis.size||e<0)throw new RangeError(`Position ${e} outside of fragment (${this})`);for(let n=0,r=0;;n++){let s=this.child(n),i=r+s.nodeSize;if(i>=e)return i==e?ar(n+1,i):ar(n,r);r=i}}toString(){return"<"+this.toStringInner()+">"}toStringInner(){return this.content.join(", ")}toJSON(){return this.content.length?this.content.map(e=>e.toJSON()):null}static fromJSON(e,n){if(!n)return t.empty;if(!Array.isArray(n))throw new RangeError("Invalid input for Fragment.fromJSON");return new t(n.map(e.nodeFromJSON))}static fromArray(e){if(!e.length)return t.empty;let n,r=0;for(let s=0;sthis.type.rank&&(n||(n=e.slice(0,s)),n.push(this),r=!0),n&&n.push(i)}}return n||(n=e.slice()),r||n.push(this),n}removeFromSet(e){for(let n=0;nr.type.rank-s.type.rank),n}};F.none=[];var St=class extends Error{},C=class t{constructor(e,n,r){this.content=e,this.openStart=n,this.openEnd=r}get size(){return this.content.size-this.openStart-this.openEnd}insertAt(e,n){let r=sa(this.content,e+this.openStart,n);return r&&new t(r,this.openStart,this.openEnd)}removeBetween(e,n){return new t(ra(this.content,e+this.openStart,n+this.openStart),this.openStart,this.openEnd)}eq(e){return this.content.eq(e.content)&&this.openStart==e.openStart&&this.openEnd==e.openEnd}toString(){return this.content+"("+this.openStart+","+this.openEnd+")"}toJSON(){if(!this.content.size)return null;let e={content:this.content.toJSON()};return this.openStart>0&&(e.openStart=this.openStart),this.openEnd>0&&(e.openEnd=this.openEnd),e}static fromJSON(e,n){if(!n)return t.empty;let r=n.openStart||0,s=n.openEnd||0;if(typeof r!="number"||typeof s!="number")throw new RangeError("Invalid input for Slice.fromJSON");return new t(b.fromJSON(e,n.content),r,s)}static maxOpen(e,n=!0){let r=0,s=0;for(let i=e.firstChild;i&&!i.isLeaf&&(n||!i.type.spec.isolating);i=i.firstChild)r++;for(let i=e.lastChild;i&&!i.isLeaf&&(n||!i.type.spec.isolating);i=i.lastChild)s++;return new t(e,r,s)}};C.empty=new C(b.empty,0,0);function ra(t,e,n){let{index:r,offset:s}=t.findIndex(e),i=t.maybeChild(r),{index:o,offset:l}=t.findIndex(n);if(s==e||i.isText){if(l!=n&&!t.child(o).isText)throw new RangeError("Removing non-flat range");return t.cut(0,e).append(t.cut(n))}if(r!=o)throw new RangeError("Removing non-flat range");return t.replaceChild(r,i.copy(ra(i.content,e-s-1,n-s-1)))}function sa(t,e,n,r){let{index:s,offset:i}=t.findIndex(e),o=t.maybeChild(s);if(i==e||o.isText)return r&&!r.canReplace(s,s,n)?null:t.cut(0,e).append(n).append(t.cut(e));let l=sa(o.content,e-i-1,n,o);return l&&t.replaceChild(s,o.copy(l))}function ef(t,e,n){if(n.openStart>t.depth)throw new St("Inserted content deeper than insertion position");if(t.depth-n.openStart!=e.depth-n.openEnd)throw new St("Inconsistent open depths");return ia(t,e,n,0)}function ia(t,e,n,r){let s=t.index(r),i=t.node(r);if(s==e.index(r)&&r=0&&t.isText&&t.sameMarkup(e[n])?e[n]=t.withText(e[n].text+t.text):e.push(t)}function gn(t,e,n,r){let s=(e||t).node(n),i=0,o=e?e.index(n):s.childCount;t&&(i=t.index(n),t.depth>n?i++:t.textOffset&&(xt(t.nodeAfter,r),i++));for(let l=i;ls&&li(t,e,s+1),o=r.depth>s&&li(n,r,s+1),l=[];return gn(null,t,s,l),i&&o&&e.index(s)==n.index(s)?(oa(i,o),xt(wt(i,la(t,e,n,r,s+1)),l)):(i&&xt(wt(i,dr(t,e,s+1)),l),gn(e,n,s,l),o&&xt(wt(o,dr(n,r,s+1)),l)),gn(r,null,s,l),new b(l)}function dr(t,e,n){let r=[];if(gn(null,t,n,r),t.depth>n){let s=li(t,e,n+1);xt(wt(s,dr(t,e,n+1)),r)}return gn(e,null,n,r),new b(r)}function tf(t,e){let n=e.depth-t.openStart,s=e.node(n).copy(t.content);for(let i=n-1;i>=0;i--)s=e.node(i).copy(b.from(s));return{start:s.resolveNoCache(t.openStart+n),end:s.resolveNoCache(s.content.size-t.openEnd-n)}}var hr=class t{constructor(e,n,r){this.pos=e,this.path=n,this.parentOffset=r,this.depth=n.length/3-1}resolveDepth(e){return e==null?this.depth:e<0?this.depth+e:e}get parent(){return this.node(this.depth)}get doc(){return this.node(0)}node(e){return this.path[this.resolveDepth(e)*3]}index(e){return this.path[this.resolveDepth(e)*3+1]}indexAfter(e){return e=this.resolveDepth(e),this.index(e)+(e==this.depth&&!this.textOffset?0:1)}start(e){return e=this.resolveDepth(e),e==0?0:this.path[e*3-1]+1}end(e){return e=this.resolveDepth(e),this.start(e)+this.node(e).content.size}before(e){if(e=this.resolveDepth(e),!e)throw new RangeError("There is no position before the top-level node");return e==this.depth+1?this.pos:this.path[e*3-1]}after(e){if(e=this.resolveDepth(e),!e)throw new RangeError("There is no position after the top-level node");return e==this.depth+1?this.pos:this.path[e*3-1]+this.path[e*3].nodeSize}get textOffset(){return this.pos-this.path[this.path.length-1]}get nodeAfter(){let e=this.parent,n=this.index(this.depth);if(n==e.childCount)return null;let r=this.pos-this.path[this.path.length-1],s=e.child(n);return r?e.child(n).cut(r):s}get nodeBefore(){let e=this.index(this.depth),n=this.pos-this.path[this.path.length-1];return n?this.parent.child(e).cut(0,n):e==0?null:this.parent.child(e-1)}posAtIndex(e,n){n=this.resolveDepth(n);let r=this.path[n*3],s=n==0?0:this.path[n*3-1]+1;for(let i=0;i0;n--)if(this.start(n)<=e&&this.end(n)>=e)return n;return 0}blockRange(e=this,n){if(e.pos=0;r--)if(e.pos<=this.end(r)&&(!n||n(this.node(r))))return new Ct(this,e,r);return null}sameParent(e){return this.pos-this.parentOffset==e.pos-e.parentOffset}max(e){return e.pos>this.pos?e:this}min(e){return e.pos=0&&n<=e.content.size))throw new RangeError("Position "+n+" out of range");let r=[],s=0,i=n;for(let o=e;;){let{index:l,offset:a}=o.content.findIndex(i),c=i-a;if(r.push(o,l,s+a),!c||(o=o.child(l),o.isText))break;i=c-1,s+=a+1}return new t(n,r,i)}static resolveCached(e,n){let r=Ul.get(e);if(r)for(let i=0;ie&&this.nodesBetween(e,n,i=>(r.isInSet(i.marks)&&(s=!0),!s)),s}get isBlock(){return this.type.isBlock}get isTextblock(){return this.type.isTextblock}get inlineContent(){return this.type.inlineContent}get isInline(){return this.type.isInline}get isText(){return this.type.isText}get isLeaf(){return this.type.isLeaf}get isAtom(){return this.type.isAtom}toString(){if(this.type.spec.toDebugString)return this.type.spec.toDebugString(this);let e=this.type.name;return this.content.size&&(e+="("+this.content.toStringInner()+")"),aa(this.marks,e)}contentMatchAt(e){let n=this.type.contentMatch.matchFragment(this.content,0,e);if(!n)throw new Error("Called contentMatchAt on a node with invalid content");return n}canReplace(e,n,r=b.empty,s=0,i=r.childCount){let o=this.contentMatchAt(e).matchFragment(r,s,i),l=o&&o.matchFragment(this.content,n);if(!l||!l.validEnd)return!1;for(let a=s;an.type.name)}`);this.content.forEach(n=>n.check())}toJSON(){let e={type:this.type.name};for(let n in this.attrs){e.attrs=this.attrs;break}return this.content.size&&(e.content=this.content.toJSON()),this.marks.length&&(e.marks=this.marks.map(n=>n.toJSON())),e}static fromJSON(e,n){if(!n)throw new RangeError("Invalid input for Node.fromJSON");let r;if(n.marks){if(!Array.isArray(n.marks))throw new RangeError("Invalid mark data for Node.fromJSON");r=n.marks.map(e.markFromJSON)}if(n.type=="text"){if(typeof n.text!="string")throw new RangeError("Invalid text node in JSON");return e.text(n.text,r)}let s=b.fromJSON(e,n.content),i=e.nodeType(n.type).create(n.attrs,s,r);return i.type.checkAttrs(i.attrs),i}};me.prototype.text=void 0;var ci=class t extends me{constructor(e,n,r,s){if(super(e,n,null,s),!r)throw new RangeError("Empty text nodes are not allowed");this.text=r}toString(){return this.type.spec.toDebugString?this.type.spec.toDebugString(this):aa(this.marks,JSON.stringify(this.text))}get textContent(){return this.text}textBetween(e,n){return this.text.slice(e,n)}get nodeSize(){return this.text.length}mark(e){return e==this.marks?this:new t(this.type,this.attrs,this.text,e)}withText(e){return e==this.text?this:new t(this.type,this.attrs,e,this.marks)}cut(e=0,n=this.text.length){return e==0&&n==this.text.length?this:this.withText(this.text.slice(e,n))}eq(e){return this.sameMarkup(e)&&this.text==e.text}toJSON(){let e=super.toJSON();return e.text=this.text,e}};function aa(t,e){for(let n=t.length-1;n>=0;n--)e=t[n].type.name+"("+e+")";return e}var Mt=class t{constructor(e){this.validEnd=e,this.next=[],this.wrapCache=[]}static parse(e,n){let r=new ui(e,n);if(r.next==null)return t.empty;let s=ca(r);r.next&&r.err("Unexpected trailing text");let i=df(uf(s));return hf(i,r),i}matchType(e){for(let n=0;nc.createAndFill()));for(let c=0;c=this.next.length)throw new RangeError(`There's no ${e}th edge in this content match`);return this.next[e]}toString(){let e=[];function n(r){e.push(r);for(let s=0;s{let i=s+(r.validEnd?"*":" ")+" ";for(let o=0;o"+e.indexOf(r.next[o].next);return i}).join(` -`)}};Mt.empty=new Mt(!0);var ui=class{constructor(e,n){this.string=e,this.nodeTypes=n,this.inline=null,this.pos=0,this.tokens=e.split(/\s*(?=\b|\W|$)/),this.tokens[this.tokens.length-1]==""&&this.tokens.pop(),this.tokens[0]==""&&this.tokens.shift()}get next(){return this.tokens[this.pos]}eat(e){return this.next==e&&(this.pos++||!0)}err(e){throw new SyntaxError(e+" (in content expression '"+this.string+"')")}};function ca(t){let e=[];do e.push(sf(t));while(t.eat("|"));return e.length==1?e[0]:{type:"choice",exprs:e}}function sf(t){let e=[];do e.push(of(t));while(t.next&&t.next!=")"&&t.next!="|");return e.length==1?e[0]:{type:"seq",exprs:e}}function of(t){let e=cf(t);for(;;)if(t.eat("+"))e={type:"plus",expr:e};else if(t.eat("*"))e={type:"star",expr:e};else if(t.eat("?"))e={type:"opt",expr:e};else if(t.eat("{"))e=lf(t,e);else break;return e}function ql(t){/\D/.test(t.next)&&t.err("Expected number, got '"+t.next+"'");let e=Number(t.next);return t.pos++,e}function lf(t,e){let n=ql(t),r=n;return t.eat(",")&&(t.next!="}"?r=ql(t):r=-1),t.eat("}")||t.err("Unclosed braced range"),{type:"range",min:n,max:r,expr:e}}function af(t,e){let n=t.nodeTypes,r=n[e];if(r)return[r];let s=[];for(let i in n){let o=n[i];o.isInGroup(e)&&s.push(o)}return s.length==0&&t.err("No node type or group '"+e+"' found"),s}function cf(t){if(t.eat("(")){let e=ca(t);return t.eat(")")||t.err("Missing closing paren"),e}else if(/\W/.test(t.next))t.err("Unexpected token '"+t.next+"'");else{let e=af(t,t.next).map(n=>(t.inline==null?t.inline=n.isInline:t.inline!=n.isInline&&t.err("Mixing inline and block content"),{type:"name",value:n}));return t.pos++,e.length==1?e[0]:{type:"choice",exprs:e}}}function uf(t){let e=[[]];return s(i(t,0),n()),e;function n(){return e.push([])-1}function r(o,l,a){let c={term:a,to:l};return e[o].push(c),c}function s(o,l){o.forEach(a=>a.to=l)}function i(o,l){if(o.type=="choice")return o.exprs.reduce((a,c)=>a.concat(i(c,l)),[]);if(o.type=="seq")for(let a=0;;a++){let c=i(o.exprs[a],l);if(a==o.exprs.length-1)return c;s(c,l=n())}else if(o.type=="star"){let a=n();return r(l,a),s(i(o.expr,a),a),[r(a)]}else if(o.type=="plus"){let a=n();return s(i(o.expr,l),a),s(i(o.expr,a),a),[r(a)]}else{if(o.type=="opt")return[r(l)].concat(i(o.expr,l));if(o.type=="range"){let a=l;for(let c=0;c{t[o].forEach(({term:l,to:a})=>{if(!l)return;let c;for(let u=0;u{c||s.push([l,c=[]]),c.indexOf(u)==-1&&c.push(u)})})});let i=e[r.join(",")]=new Mt(r.indexOf(t.length-1)>-1);for(let o=0;o-1}get whitespace(){return this.spec.whitespace||(this.spec.code?"pre":"normal")}hasRequiredAttrs(){for(let e in this.attrs)if(this.attrs[e].isRequired)return!0;return!1}compatibleContent(e){return this==e||this.contentMatch.compatible(e.contentMatch)}computeAttrs(e){return!e&&this.defaultAttrs?this.defaultAttrs:ha(this.attrs,e)}create(e=null,n,r){if(this.isText)throw new Error("NodeType.create can't construct text nodes");return new me(this,this.computeAttrs(e),b.from(n),F.setFrom(r))}createChecked(e=null,n,r){return n=b.from(n),this.checkContent(n),new me(this,this.computeAttrs(e),n,F.setFrom(r))}createAndFill(e=null,n,r){if(e=this.computeAttrs(e),n=b.from(n),n.size){let o=this.contentMatch.fillBefore(n);if(!o)return null;n=o.append(n)}let s=this.contentMatch.matchFragment(n),i=s&&s.fillBefore(b.empty,!0);return i?new me(this,e,n.append(i),F.setFrom(r)):null}validContent(e){let n=this.contentMatch.matchFragment(e);if(!n||!n.validEnd)return!1;for(let r=0;r-1}allowsMarks(e){if(this.markSet==null)return!0;for(let n=0;nr[i]=new t(i,n,o));let s=n.spec.topNode||"doc";if(!r[s])throw new RangeError("Schema is missing its top node type ('"+s+"')");if(!r.text)throw new RangeError("Every schema needs a 'text' type");for(let i in r.text.attrs)throw new RangeError("The text node type should not have attributes");return r}};function ff(t,e,n){let r=n.split("|");return s=>{let i=s===null?"null":typeof s;if(r.indexOf(i)<0)throw new RangeError(`Expected value of type ${r} for attribute ${e} on type ${t}, got ${i}`)}}var di=class{constructor(e,n,r){this.hasDefault=Object.prototype.hasOwnProperty.call(r,"default"),this.default=r.default,this.validate=typeof r.validate=="string"?ff(e,n,r.validate):r.validate}get isRequired(){return!this.hasDefault}},kn=class t{constructor(e,n,r,s){this.name=e,this.rank=n,this.schema=r,this.spec=s,this.attrs=pa(e,s.attrs),this.excluded=null;let i=da(this.attrs);this.instance=i?new F(this,i):null}create(e=null){return!e&&this.instance?this.instance:new F(this,ha(this.attrs,e))}static compile(e,n){let r=Object.create(null),s=0;return e.forEach((i,o)=>r[i]=new t(i,s++,n,o)),r}removeFromSet(e){for(var n=0;n-1}},Gt=class{constructor(e){this.linebreakReplacement=null,this.cached=Object.create(null);let n=this.spec={};for(let s in e)n[s]=e[s];n.nodes=si.from(e.nodes),n.marks=si.from(e.marks||{}),this.nodes=fr.compile(this.spec.nodes,this),this.marks=kn.compile(this.spec.marks,this);let r=Object.create(null);for(let s in this.nodes){if(s in this.marks)throw new RangeError(s+" can not be both a node and a mark");let i=this.nodes[s],o=i.spec.content||"",l=i.spec.marks;if(i.contentMatch=r[o]||(r[o]=Mt.parse(o,this.nodes)),i.inlineContent=i.contentMatch.inlineContent,i.spec.linebreakReplacement){if(this.linebreakReplacement)throw new RangeError("Multiple linebreak nodes defined");if(!i.isInline||!i.isLeaf)throw new RangeError("Linebreak replacement nodes must be inline leaf nodes");this.linebreakReplacement=i}i.markSet=l=="_"?null:l?Gl(this,l.split(" ")):l==""||!i.inlineContent?[]:null}for(let s in this.marks){let i=this.marks[s],o=i.spec.excludes;i.excluded=o==null?[i]:o==""?[]:Gl(this,o.split(" "))}this.nodeFromJSON=s=>me.fromJSON(this,s),this.markFromJSON=s=>F.fromJSON(this,s),this.topNodeType=this.nodes[this.spec.topNode||"doc"],this.cached.wrappings=Object.create(null)}node(e,n=null,r,s){if(typeof e=="string")e=this.nodeType(e);else if(e instanceof fr){if(e.schema!=this)throw new RangeError("Node type from different schema used ("+e.name+")")}else throw new RangeError("Invalid node type: "+e);return e.createChecked(n,r,s)}text(e,n){let r=this.nodes.text;return new ci(r,r.defaultAttrs,e,F.setFrom(n))}mark(e,n){return typeof e=="string"&&(e=this.marks[e]),e.create(n)}nodeType(e){let n=this.nodes[e];if(!n)throw new RangeError("Unknown node type: "+e);return n}};function Gl(t,e){let n=[];for(let r=0;r-1)&&n.push(o=a)}if(!o)throw new SyntaxError("Unknown mark type: '"+e[r]+"'")}return n}function pf(t){return t.tag!=null}function mf(t){return t.style!=null}var Ie=class t{constructor(e,n){this.schema=e,this.rules=n,this.tags=[],this.styles=[];let r=this.matchedStyles=[];n.forEach(s=>{if(pf(s))this.tags.push(s);else if(mf(s)){let i=/[^=]*/.exec(s.style)[0];r.indexOf(i)<0&&r.push(i),this.styles.push(s)}}),this.normalizeLists=!this.tags.some(s=>{if(!/^(ul|ol)\b/.test(s.tag)||!s.node)return!1;let i=e.nodes[s.node];return i.contentMatch.matchType(i)})}parse(e,n={}){let r=new pr(this,n,!1);return r.addAll(e,F.none,n.from,n.to),r.finish()}parseSlice(e,n={}){let r=new pr(this,n,!0);return r.addAll(e,F.none,n.from,n.to),C.maxOpen(r.finish())}matchTag(e,n,r){for(let s=r?this.tags.indexOf(r)+1:0;se.length&&(l.charCodeAt(e.length)!=61||l.slice(e.length+1)!=n))){if(o.getAttrs){let a=o.getAttrs(n);if(a===!1)continue;o.attrs=a||void 0}return o}}}static schemaRules(e){let n=[];function r(s){let i=s.priority==null?50:s.priority,o=0;for(;o{r(o=Ql(o)),o.mark||o.ignore||o.clearMark||(o.mark=s)})}for(let s in e.nodes){let i=e.nodes[s].spec.parseDOM;i&&i.forEach(o=>{r(o=Ql(o)),o.node||o.ignore||o.mark||(o.node=s)})}return n}static fromSchema(e){return e.cached.domParser||(e.cached.domParser=new t(e,t.schemaRules(e)))}},ma={address:!0,article:!0,aside:!0,blockquote:!0,canvas:!0,dd:!0,div:!0,dl:!0,fieldset:!0,figcaption:!0,figure:!0,footer:!0,form:!0,h1:!0,h2:!0,h3:!0,h4:!0,h5:!0,h6:!0,header:!0,hgroup:!0,hr:!0,li:!0,noscript:!0,ol:!0,output:!0,p:!0,pre:!0,section:!0,table:!0,tfoot:!0,ul:!0},gf={head:!0,noscript:!0,object:!0,script:!0,style:!0,title:!0},ga={ol:!0,ul:!0},bn=1,hi=2,yn=4;function Xl(t,e,n){return e!=null?(e?bn:0)|(e==="full"?hi:0):t&&t.whitespace=="pre"?bn|hi:n&~yn}var Jt=class{constructor(e,n,r,s,i,o){this.type=e,this.attrs=n,this.marks=r,this.solid=s,this.options=o,this.content=[],this.activeMarks=F.none,this.match=i||(o&yn?null:e.contentMatch)}findWrapping(e){if(!this.match){if(!this.type)return[];let n=this.type.contentMatch.fillBefore(b.from(e));if(n)this.match=this.type.contentMatch.matchFragment(n);else{let r=this.type.contentMatch,s;return(s=r.findWrapping(e.type))?(this.match=r,s):null}}return this.match.findWrapping(e.type)}finish(e){if(!(this.options&bn)){let r=this.content[this.content.length-1],s;if(r&&r.isText&&(s=/[ \t\r\n\u000c]+$/.exec(r.text))){let i=r;r.text.length==s[0].length?this.content.pop():this.content[this.content.length-1]=i.withText(i.text.slice(0,i.text.length-s[0].length))}}let n=b.from(this.content);return!e&&this.match&&(n=n.append(this.match.fillBefore(b.empty,!0))),this.type?this.type.create(this.attrs,n,this.marks):n}inlineContext(e){return this.type?this.type.inlineContent:this.content.length?this.content[0].isInline:e.parentNode&&!ma.hasOwnProperty(e.parentNode.nodeName.toLowerCase())}},pr=class{constructor(e,n,r){this.parser=e,this.options=n,this.isOpen=r,this.open=0,this.localPreserveWS=!1;let s=n.topNode,i,o=Xl(null,n.preserveWhitespace,0)|(r?yn:0);s?i=new Jt(s.type,s.attrs,F.none,!0,n.topMatch||s.type.contentMatch,o):r?i=new Jt(null,null,F.none,!0,null,o):i=new Jt(e.schema.topNodeType,null,F.none,!0,null,o),this.nodes=[i],this.find=n.findPositions,this.needsBlock=!1}get top(){return this.nodes[this.open]}addDOM(e,n){e.nodeType==3?this.addTextNode(e,n):e.nodeType==1&&this.addElement(e,n)}addTextNode(e,n){let r=e.nodeValue,s=this.top,i=s.options&hi?"full":this.localPreserveWS||(s.options&bn)>0,{schema:o}=this.parser;if(i==="full"||s.inlineContext(e)||/[^ \t\r\n\u000c]/.test(r)){if(i)if(i==="full")r=r.replace(/\r\n?/g,` -`);else if(o.linebreakReplacement&&/[\r\n]/.test(r)&&this.top.findWrapping(o.linebreakReplacement.create())){let l=r.split(/\r?\n|\r/);for(let a=0;a!a.clearMark(c)):n=n.concat(this.parser.schema.marks[a.mark].create(a.attrs)),a.consuming===!1)l=a;else break}}return n}addElementByRule(e,n,r,s){let i,o;if(n.node)if(o=this.parser.schema.nodes[n.node],o.isLeaf)this.insertNode(o.create(n.attrs),r,e.nodeName=="BR")||this.leafFallback(e,r);else{let a=this.enter(o,n.attrs||null,r,n.preserveWhitespace);a&&(i=!0,r=a)}else{let a=this.parser.schema.marks[n.mark];r=r.concat(a.create(n.attrs))}let l=this.top;if(o&&o.isLeaf)this.findInside(e);else if(s)this.addElement(e,r,s);else if(n.getContent)this.findInside(e),n.getContent(e,this.parser.schema).forEach(a=>this.insertNode(a,r,!1));else{let a=e;typeof n.contentElement=="string"?a=e.querySelector(n.contentElement):typeof n.contentElement=="function"?a=n.contentElement(e):n.contentElement&&(a=n.contentElement),this.findAround(e,a,!0),this.addAll(a,r),this.findAround(e,a,!1)}i&&this.sync(l)&&this.open--}addAll(e,n,r,s){let i=r||0;for(let o=r?e.childNodes[r]:e.firstChild,l=s==null?null:e.childNodes[s];o!=l;o=o.nextSibling,++i)this.findAtPoint(e,i),this.addDOM(o,n);this.findAtPoint(e,i)}findPlace(e,n,r){let s,i;for(let o=this.open,l=0;o>=0;o--){let a=this.nodes[o],c=a.findWrapping(e);if(c&&(!s||s.length>c.length+l)&&(s=c,i=a,!c.length))break;if(a.solid){if(r)break;l+=2}}if(!s)return null;this.sync(i);for(let o=0;o(o.type?o.type.allowsMarkType(c.type):Yl(c.type,e))?(a=c.addToSet(a),!1):!0),this.nodes.push(new Jt(e,n,a,s,null,l)),this.open++,r}closeExtra(e=!1){let n=this.nodes.length-1;if(n>this.open){for(;n>this.open;n--)this.nodes[n-1].content.push(this.nodes[n].finish(e));this.nodes.length=this.open+1}}finish(){return this.open=0,this.closeExtra(this.isOpen),this.nodes[0].finish(!!(this.isOpen||this.options.topOpen))}sync(e){for(let n=this.open;n>=0;n--){if(this.nodes[n]==e)return this.open=n,!0;this.localPreserveWS&&(this.nodes[n].options|=bn)}return!1}get currentPos(){this.closeExtra();let e=0;for(let n=this.open;n>=0;n--){let r=this.nodes[n].content;for(let s=r.length-1;s>=0;s--)e+=r[s].nodeSize;n&&e++}return e}findAtPoint(e,n){if(this.find)for(let r=0;r-1)return e.split(/\s*\|\s*/).some(this.matchesContext,this);let n=e.split("/"),r=this.options.context,s=!this.isOpen&&(!r||r.parent.type==this.nodes[0].type),i=-(r?r.depth+1:0)+(s?0:1),o=(l,a)=>{for(;l>=0;l--){let c=n[l];if(c==""){if(l==n.length-1||l==0)continue;for(;a>=i;a--)if(o(l-1,a))return!0;return!1}else{let u=a>0||a==0&&s?this.nodes[a].type:r&&a>=i?r.node(a-i).type:null;if(!u||u.name!=c&&!u.isInGroup(c))return!1;a--}}return!0};return o(n.length-1,this.open)}textblockFromContext(){let e=this.options.context;if(e)for(let n=e.depth;n>=0;n--){let r=e.node(n).contentMatchAt(e.indexAfter(n)).defaultType;if(r&&r.isTextblock&&r.defaultAttrs)return r}for(let n in this.parser.schema.nodes){let r=this.parser.schema.nodes[n];if(r.isTextblock&&r.defaultAttrs)return r}}};function yf(t){for(let e=t.firstChild,n=null;e;e=e.nextSibling){let r=e.nodeType==1?e.nodeName.toLowerCase():null;r&&ga.hasOwnProperty(r)&&n?(n.appendChild(e),e=n):r=="li"?n=e:r&&(n=null)}}function kf(t,e){return(t.matches||t.msMatchesSelector||t.webkitMatchesSelector||t.mozMatchesSelector).call(t,e)}function Ql(t){let e={};for(let n in t)e[n]=t[n];return e}function Yl(t,e){let n=e.schema.nodes;for(let r in n){let s=n[r];if(!s.allowsMarkType(t))continue;let i=[],o=l=>{i.push(l);for(let a=0;a{if(i.length||o.marks.length){let l=0,a=0;for(;l=0;s--){let i=this.serializeMark(e.marks[s],e.isInline,n);i&&((i.contentDOM||i.dom).appendChild(r),r=i.dom)}return r}serializeMark(e,n,r={}){let s=this.marks[e.type.name];return s&&cr(oi(r),s(e,n),null,e.attrs)}static renderSpec(e,n,r=null,s){return cr(e,n,r,s)}static fromSchema(e){return e.cached.domSerializer||(e.cached.domSerializer=new t(this.nodesFromSchema(e),this.marksFromSchema(e)))}static nodesFromSchema(e){let n=Zl(e.nodes);return n.text||(n.text=r=>r.text),n}static marksFromSchema(e){return Zl(e.marks)}};function Zl(t){let e={};for(let n in t){let r=t[n].spec.toDOM;r&&(e[n]=r)}return e}function oi(t){return t.document||window.document}var ea=new WeakMap;function bf(t){let e=ea.get(t);return e===void 0&&ea.set(t,e=xf(t)),e}function xf(t){let e=null;function n(r){if(r&&typeof r=="object")if(Array.isArray(r))if(typeof r[0]=="string")e||(e=[]),e.push(r);else for(let s=0;s-1)throw new RangeError("Using an array from an attribute object as a DOM spec. This may be an attempted cross site scripting attack.");let o=s.indexOf(" ");o>0&&(n=s.slice(0,o),s=s.slice(o+1));let l,a=n?t.createElementNS(n,s):t.createElement(s),c=e[1],u=1;if(c&&typeof c=="object"&&c.nodeType==null&&!Array.isArray(c)){u=2;for(let d in c)if(c[d]!=null){let h=d.indexOf(" ");h>0?a.setAttributeNS(d.slice(0,h),d.slice(h+1),c[d]):d=="style"&&a.style?a.style.cssText=c[d]:a.setAttribute(d,c[d])}}for(let d=u;du)throw new RangeError("Content hole must be the only child of its parent node");return{dom:a,contentDOM:a}}else{let{dom:f,contentDOM:p}=cr(t,h,n,r);if(a.appendChild(f),p){if(l)throw new RangeError("Multiple content holes");l=p}}}return{dom:a,contentDOM:l}}var ba=65535,xa=Math.pow(2,16);function wf(t,e){return t+e*xa}function ya(t){return t&ba}function Sf(t){return(t-(t&ba))/xa}var wa=1,Sa=2,mr=4,Ca=8,Sn=class{constructor(e,n,r){this.pos=e,this.delInfo=n,this.recover=r}get deleted(){return(this.delInfo&Ca)>0}get deletedBefore(){return(this.delInfo&(wa|mr))>0}get deletedAfter(){return(this.delInfo&(Sa|mr))>0}get deletedAcross(){return(this.delInfo&mr)>0}},Je=class t{constructor(e,n=!1){if(this.ranges=e,this.inverted=n,!e.length&&t.empty)return t.empty}recover(e){let n=0,r=ya(e);if(!this.inverted)for(let s=0;se)break;let c=this.ranges[l+i],u=this.ranges[l+o],d=a+c;if(e<=d){let h=c?e==a?-1:e==d?1:n:n,f=a+s+(h<0?0:u);if(r)return f;let p=e==(n<0?a:d)?null:wf(l/3,e-a),m=e==a?Sa:e==d?wa:mr;return(n<0?e!=a:e!=d)&&(m|=Ca),new Sn(f,m,p)}s+=u-c}return r?e+s:new Sn(e+s,0,null)}touches(e,n){let r=0,s=ya(n),i=this.inverted?2:1,o=this.inverted?1:2;for(let l=0;le)break;let c=this.ranges[l+i],u=a+c;if(e<=u&&l==s*3)return!0;r+=this.ranges[l+o]-c}return!1}forEach(e){let n=this.inverted?2:1,r=this.inverted?1:2;for(let s=0,i=0;s=0;n--){let s=e.getMirror(n);this.appendMap(e._maps[n].invert(),s!=null&&s>n?r-s-1:void 0)}}invert(){let e=new t;return e.appendMappingInverted(this),e}map(e,n=1){if(this.mirror)return this._map(e,n,!0);for(let r=this.from;ri&&a!o.isAtom||!l.type.allowsMarkType(this.mark.type)?o:o.mark(this.mark.addToSet(o.marks)),s),n.openStart,n.openEnd);return ee.fromReplace(e,this.from,this.to,i)}invert(){return new Ge(this.from,this.to,this.mark)}map(e){let n=e.mapResult(this.from,1),r=e.mapResult(this.to,-1);return n.deleted&&r.deleted||n.pos>=r.pos?null:new t(n.pos,r.pos,this.mark)}merge(e){return e instanceof t&&e.mark.eq(this.mark)&&this.from<=e.to&&this.to>=e.from?new t(Math.min(this.from,e.from),Math.max(this.to,e.to),this.mark):null}toJSON(){return{stepType:"addMark",mark:this.mark.toJSON(),from:this.from,to:this.to}}static fromJSON(e,n){if(typeof n.from!="number"||typeof n.to!="number")throw new RangeError("Invalid input for AddMarkStep.fromJSON");return new t(n.from,n.to,e.markFromJSON(n.mark))}};Z.jsonID("addMark",Mn);var Ge=class t extends Z{constructor(e,n,r){super(),this.from=e,this.to=n,this.mark=r}apply(e){let n=e.slice(this.from,this.to),r=new C(ki(n.content,s=>s.mark(this.mark.removeFromSet(s.marks)),e),n.openStart,n.openEnd);return ee.fromReplace(e,this.from,this.to,r)}invert(){return new Mn(this.from,this.to,this.mark)}map(e){let n=e.mapResult(this.from,1),r=e.mapResult(this.to,-1);return n.deleted&&r.deleted||n.pos>=r.pos?null:new t(n.pos,r.pos,this.mark)}merge(e){return e instanceof t&&e.mark.eq(this.mark)&&this.from<=e.to&&this.to>=e.from?new t(Math.min(this.from,e.from),Math.max(this.to,e.to),this.mark):null}toJSON(){return{stepType:"removeMark",mark:this.mark.toJSON(),from:this.from,to:this.to}}static fromJSON(e,n){if(typeof n.from!="number"||typeof n.to!="number")throw new RangeError("Invalid input for RemoveMarkStep.fromJSON");return new t(n.from,n.to,e.markFromJSON(n.mark))}};Z.jsonID("removeMark",Ge);var Tn=class t extends Z{constructor(e,n){super(),this.pos=e,this.mark=n}apply(e){let n=e.nodeAt(this.pos);if(!n)return ee.fail("No node at mark step's position");let r=n.type.create(n.attrs,null,this.mark.addToSet(n.marks));return ee.fromReplace(e,this.pos,this.pos+1,new C(b.from(r),0,n.isLeaf?0:1))}invert(e){let n=e.nodeAt(this.pos);if(n){let r=this.mark.addToSet(n.marks);if(r.length==n.marks.length){for(let s=0;sr.pos?null:new t(n.pos,r.pos,s,i,this.slice,this.insert,this.structure)}toJSON(){let e={stepType:"replaceAround",from:this.from,to:this.to,gapFrom:this.gapFrom,gapTo:this.gapTo,insert:this.insert};return this.slice.size&&(e.slice=this.slice.toJSON()),this.structure&&(e.structure=!0),e}static fromJSON(e,n){if(typeof n.from!="number"||typeof n.to!="number"||typeof n.gapFrom!="number"||typeof n.gapTo!="number"||typeof n.insert!="number")throw new RangeError("Invalid input for ReplaceAroundStep.fromJSON");return new t(n.from,n.to,n.gapFrom,n.gapTo,C.fromJSON(e,n.slice),n.insert,!!n.structure)}};Z.jsonID("replaceAround",X);function gi(t,e,n){let r=t.resolve(e),s=n-e,i=r.depth;for(;s>0&&i>0&&r.indexAfter(i)==r.node(i).childCount;)i--,s--;if(s>0){let o=r.node(i).maybeChild(r.indexAfter(i));for(;s>0;){if(!o||o.isLeaf)return!0;o=o.firstChild,s--}}return!1}function Cf(t,e,n,r){let s=[],i=[],o,l;t.doc.nodesBetween(e,n,(a,c,u)=>{if(!a.isInline)return;let d=a.marks;if(!r.isInSet(d)&&u.type.allowsMarkType(r.type)){let h=Math.max(c,e),f=Math.min(c+a.nodeSize,n),p=r.addToSet(d);for(let m=0;mt.step(a)),i.forEach(a=>t.step(a))}function Mf(t,e,n,r){let s=[],i=0;t.doc.nodesBetween(e,n,(o,l)=>{if(!o.isInline)return;i++;let a=null;if(r instanceof kn){let c=o.marks,u;for(;u=r.isInSet(c);)(a||(a=[])).push(u),c=u.removeFromSet(c)}else r?r.isInSet(o.marks)&&(a=[r]):a=o.marks;if(a&&a.length){let c=Math.min(l+o.nodeSize,n);for(let u=0;ut.step(new Ge(o.from,o.to,o.style)))}function bi(t,e,n,r=n.contentMatch,s=!0){let i=t.doc.nodeAt(e),o=[],l=e+1;for(let a=0;a=0;a--)t.step(o[a])}function Tf(t,e,n){return(e==0||t.canReplace(e,t.childCount))&&(n==t.childCount||t.canReplace(0,n))}function Xe(t){let n=t.parent.content.cutByIndex(t.startIndex,t.endIndex);for(let r=t.depth,s=0,i=0;;--r){let o=t.$from.node(r),l=t.$from.index(r)+s,a=t.$to.indexAfter(r)-i;if(rn;p--)m||r.index(p)>0?(m=!0,u=b.from(r.node(p).copy(u)),d++):a--;let h=b.empty,f=0;for(let p=i,m=!1;p>n;p--)m||s.after(p+1)=0;o--){if(r.size){let l=n[o].type.contentMatch.matchFragment(r);if(!l||!l.validEnd)throw new RangeError("Wrapper type given to Transform.wrap does not form valid content of its parent wrapper")}r=b.from(n[o].type.create(n[o].attrs,r))}let s=e.start,i=e.end;t.step(new X(s,i,s,i,new C(r,0,0),n.length,!0))}function Rf(t,e,n,r,s){if(!r.isTextblock)throw new RangeError("Type given to setBlockType should be a textblock");let i=t.steps.length;t.doc.nodesBetween(e,n,(o,l)=>{let a=typeof s=="function"?s(o):s;if(o.isTextblock&&!o.hasMarkup(r,a)&&Of(t.doc,t.mapping.slice(i).map(l),r)){let c=null;if(r.schema.linebreakReplacement){let f=r.whitespace=="pre",p=!!r.contentMatch.matchType(r.schema.linebreakReplacement);f&&!p?c=!1:!f&&p&&(c=!0)}c===!1&&Ta(t,o,l,i),bi(t,t.mapping.slice(i).map(l,1),r,void 0,c===null);let u=t.mapping.slice(i),d=u.map(l,1),h=u.map(l+o.nodeSize,1);return t.step(new X(d,h,d+1,h-1,new C(b.from(r.create(a,null,o.marks)),0,0),1,!0)),c===!0&&Ma(t,o,l,i),!1}})}function Ma(t,e,n,r){e.forEach((s,i)=>{if(s.isText){let o,l=/\r?\n|\r/g;for(;o=l.exec(s.text);){let a=t.mapping.slice(r).map(n+1+i+o.index);t.replaceWith(a,a+1,e.type.schema.linebreakReplacement.create())}}})}function Ta(t,e,n,r){e.forEach((s,i)=>{if(s.type==s.type.schema.linebreakReplacement){let o=t.mapping.slice(r).map(n+1+i);t.replaceWith(o,o+1,e.type.schema.text(` -`))}})}function Of(t,e,n){let r=t.resolve(e),s=r.index();return r.parent.canReplaceWith(s,s+1,n)}function If(t,e,n,r,s){let i=t.doc.nodeAt(e);if(!i)throw new RangeError("No node at given position");n||(n=i.type);let o=n.create(r,null,s||i.marks);if(i.isLeaf)return t.replaceWith(e,e+i.nodeSize,o);if(!n.validContent(i.content))throw new RangeError("Invalid content for node type "+n.name);t.step(new X(e,e+i.nodeSize,e+1,e+i.nodeSize-1,new C(b.from(o),0,0),1,!0))}function be(t,e,n=1,r){let s=t.resolve(e),i=s.depth-n,o=r&&r[r.length-1]||s.parent;if(i<0||s.parent.type.spec.isolating||!s.parent.canReplace(s.index(),s.parent.childCount)||!o.type.validContent(s.parent.content.cutByIndex(s.index(),s.parent.childCount)))return!1;for(let c=s.depth-1,u=n-2;c>i;c--,u--){let d=s.node(c),h=s.index(c);if(d.type.spec.isolating)return!1;let f=d.content.cutByIndex(h,d.childCount),p=r&&r[u+1];p&&(f=f.replaceChild(0,p.type.create(p.attrs)));let m=r&&r[u]||d;if(!d.canReplace(h+1,d.childCount)||!m.type.validContent(f))return!1}let l=s.indexAfter(i),a=r&&r[0];return s.node(i).canReplaceWith(l,l,a?a.type:s.node(i+1).type)}function Df(t,e,n=1,r){let s=t.doc.resolve(e),i=b.empty,o=b.empty;for(let l=s.depth,a=s.depth-n,c=n-1;l>a;l--,c--){i=b.from(s.node(l).copy(i));let u=r&&r[c];o=b.from(u?u.type.create(u.attrs,o):s.node(l).copy(o))}t.step(new ie(e,e,new C(i.append(o),n,n),!0))}function Ce(t,e){let n=t.resolve(e),r=n.index();return va(n.nodeBefore,n.nodeAfter)&&n.parent.canReplace(r,r+1)}function Pf(t,e){e.content.size||t.type.compatibleContent(e.type);let n=t.contentMatchAt(t.childCount),{linebreakReplacement:r}=t.type.schema;for(let s=0;s0?(i=r.node(s+1),l++,o=r.node(s).maybeChild(l)):(i=r.node(s).maybeChild(l-1),o=r.node(s+1)),i&&!i.isTextblock&&va(i,o)&&r.node(s).canReplace(l,l+1))return e;if(s==0)break;e=n<0?r.before(s):r.after(s)}}function Lf(t,e,n){let r=null,{linebreakReplacement:s}=t.doc.type.schema,i=t.doc.resolve(e-n),o=i.node().type;if(s&&o.inlineContent){let u=o.whitespace=="pre",d=!!o.contentMatch.matchType(s);u&&!d?r=!1:!u&&d&&(r=!0)}let l=t.steps.length;if(r===!1){let u=t.doc.resolve(e+n);Ta(t,u.node(),u.before(),l)}o.inlineContent&&bi(t,e+n-1,o,i.node().contentMatchAt(i.index()),r==null);let a=t.mapping.slice(l),c=a.map(e-n);if(t.step(new ie(c,a.map(e+n,-1),C.empty,!0)),r===!0){let u=t.doc.resolve(c);Ma(t,u.node(),u.before(),t.steps.length)}return t}function zf(t,e,n){let r=t.resolve(e);if(r.parent.canReplaceWith(r.index(),r.index(),n))return e;if(r.parentOffset==0)for(let s=r.depth-1;s>=0;s--){let i=r.index(s);if(r.node(s).canReplaceWith(i,i,n))return r.before(s+1);if(i>0)return null}if(r.parentOffset==r.parent.content.size)for(let s=r.depth-1;s>=0;s--){let i=r.indexAfter(s);if(r.node(s).canReplaceWith(i,i,n))return r.after(s+1);if(i=0;o--){let l=o==r.depth?0:r.pos<=(r.start(o+1)+r.end(o+1))/2?-1:1,a=r.index(o)+(l>0?1:0),c=r.node(o),u=!1;if(i==1)u=c.canReplace(a,a,s);else{let d=c.contentMatchAt(a).findWrapping(s.firstChild.type);u=d&&c.canReplaceWith(a,a,d[0])}if(u)return l==0?r.pos:l<0?r.before(o+1):r.after(o+1)}return null}function vn(t,e,n=e,r=C.empty){if(e==n&&!r.size)return null;let s=t.resolve(e),i=t.resolve(n);return Aa(s,i,r)?new ie(e,n,r):new yi(s,i,r).fit()}function Aa(t,e,n){return!n.openStart&&!n.openEnd&&t.start()==e.start()&&t.parent.canReplace(t.index(),e.index(),n.content)}var yi=class{constructor(e,n,r){this.$from=e,this.$to=n,this.unplaced=r,this.frontier=[],this.placed=b.empty;for(let s=0;s<=e.depth;s++){let i=e.node(s);this.frontier.push({type:i.type,match:i.contentMatchAt(e.indexAfter(s))})}for(let s=e.depth;s>0;s--)this.placed=b.from(e.node(s).copy(this.placed))}get depth(){return this.frontier.length-1}fit(){for(;this.unplaced.size;){let c=this.findFittable();c?this.placeNodes(c):this.openMore()||this.dropNode()}let e=this.mustMoveInline(),n=this.placed.size-this.depth-this.$from.depth,r=this.$from,s=this.close(e<0?this.$to:r.doc.resolve(e));if(!s)return null;let i=this.placed,o=r.depth,l=s.depth;for(;o&&l&&i.childCount==1;)i=i.firstChild.content,o--,l--;let a=new C(i,o,l);return e>-1?new X(r.pos,e,this.$to.pos,this.$to.end(),a,n):a.size||r.pos!=this.$to.pos?new ie(r.pos,s.pos,a):null}findFittable(){let e=this.unplaced.openStart;for(let n=this.unplaced.content,r=0,s=this.unplaced.openEnd;r1&&(s=0),i.type.spec.isolating&&s<=r){e=r;break}n=i.content}for(let n=1;n<=2;n++)for(let r=n==1?e:this.unplaced.openStart;r>=0;r--){let s,i=null;r?(i=pi(this.unplaced.content,r-1).firstChild,s=i.content):s=this.unplaced.content;let o=s.firstChild;for(let l=this.depth;l>=0;l--){let{type:a,match:c}=this.frontier[l],u,d=null;if(n==1&&(o?c.matchType(o.type)||(d=c.fillBefore(b.from(o),!1)):i&&a.compatibleContent(i.type)))return{sliceDepth:r,frontierDepth:l,parent:i,inject:d};if(n==2&&o&&(u=c.findWrapping(o.type)))return{sliceDepth:r,frontierDepth:l,parent:i,wrap:u};if(i&&c.matchType(i.type))break}}}openMore(){let{content:e,openStart:n,openEnd:r}=this.unplaced,s=pi(e,n);return!s.childCount||s.firstChild.isLeaf?!1:(this.unplaced=new C(e,n+1,Math.max(r,s.size+n>=e.size-r?n+1:0)),!0)}dropNode(){let{content:e,openStart:n,openEnd:r}=this.unplaced,s=pi(e,n);if(s.childCount<=1&&n>0){let i=e.size-n<=n+s.size;this.unplaced=new C(xn(e,n-1,1),n-1,i?n-1:r)}else this.unplaced=new C(xn(e,n,1),n,r)}placeNodes({sliceDepth:e,frontierDepth:n,parent:r,inject:s,wrap:i}){for(;this.depth>n;)this.closeFrontierNode();if(i)for(let m=0;m1||a==0||m.content.size)&&(d=g,u.push(Ea(m.mark(h.allowedMarks(m.marks)),c==1?a:0,c==l.childCount?f:-1)))}let p=c==l.childCount;p||(f=-1),this.placed=wn(this.placed,n,b.from(u)),this.frontier[n].match=d,p&&f<0&&r&&r.type==this.frontier[this.depth].type&&this.frontier.length>1&&this.closeFrontierNode();for(let m=0,g=l;m1&&s==this.$to.end(--r);)++s;return s}findCloseLevel(e){e:for(let n=Math.min(this.depth,e.depth);n>=0;n--){let{match:r,type:s}=this.frontier[n],i=n=0;l--){let{match:a,type:c}=this.frontier[l],u=mi(e,l,c,a,!0);if(!u||u.childCount)continue e}return{depth:n,fit:o,move:i?e.doc.resolve(e.after(n+1)):e}}}}close(e){let n=this.findCloseLevel(e);if(!n)return null;for(;this.depth>n.depth;)this.closeFrontierNode();n.fit.childCount&&(this.placed=wn(this.placed,n.depth,n.fit)),e=n.move;for(let r=n.depth+1;r<=e.depth;r++){let s=e.node(r),i=s.type.contentMatch.fillBefore(s.content,!0,e.index(r));this.openFrontierNode(s.type,s.attrs,i)}return e}openFrontierNode(e,n=null,r){let s=this.frontier[this.depth];s.match=s.match.matchType(e),this.placed=wn(this.placed,this.depth,b.from(e.create(n,r))),this.frontier.push({type:e,match:e.contentMatch})}closeFrontierNode(){let n=this.frontier.pop().match.fillBefore(b.empty,!0);n.childCount&&(this.placed=wn(this.placed,this.frontier.length,n))}};function xn(t,e,n){return e==0?t.cutByIndex(n,t.childCount):t.replaceChild(0,t.firstChild.copy(xn(t.firstChild.content,e-1,n)))}function wn(t,e,n){return e==0?t.append(n):t.replaceChild(t.childCount-1,t.lastChild.copy(wn(t.lastChild.content,e-1,n)))}function pi(t,e){for(let n=0;n1&&(r=r.replaceChild(0,Ea(r.firstChild,e-1,r.childCount==1?n-1:0))),e>0&&(r=t.type.contentMatch.fillBefore(r).append(r),n<=0&&(r=r.append(t.type.contentMatch.matchFragment(r).fillBefore(b.empty,!0)))),t.copy(r)}function mi(t,e,n,r,s){let i=t.node(e),o=s?t.indexAfter(e):t.index(e);if(o==i.childCount&&!n.compatibleContent(i.type))return null;let l=r.fillBefore(i.content,!0,o);return l&&!Bf(n,i.content,o)?l:null}function Bf(t,e,n){for(let r=n;r0;h--,f--){let p=s.node(h).type.spec;if(p.defining||p.definingAsContext||p.isolating)break;o.indexOf(h)>-1?l=h:s.before(h)==f&&o.splice(1,0,-h)}let a=o.indexOf(l),c=[],u=r.openStart;for(let h=r.content,f=0;;f++){let p=h.firstChild;if(c.push(p),f==r.openStart)break;h=p.content}for(let h=u-1;h>=0;h--){let f=c[h],p=$f(f.type);if(p&&!f.sameMarkup(s.node(Math.abs(l)-1)))u=h;else if(p||!f.type.isTextblock)break}for(let h=r.openStart;h>=0;h--){let f=(h+u+1)%(r.openStart+1),p=c[f];if(p)for(let m=0;m=0&&(t.replace(e,n,r),!(t.steps.length>d));h--){let f=o[h];f<0||(e=s.before(f),n=i.after(f))}}function Na(t,e,n,r,s){if(er){let i=s.contentMatchAt(0),o=i.fillBefore(t).append(t);t=o.append(i.matchFragment(o).fillBefore(b.empty,!0))}return t}function Ff(t,e,n,r){if(!r.isInline&&e==n&&t.doc.resolve(e).parent.content.size){let s=zf(t.doc,e,r.type);s!=null&&(e=n=s)}t.replaceRange(e,n,new C(b.from(r),0,0))}function _f(t,e,n){let r=t.doc.resolve(e),s=t.doc.resolve(n),i=Ra(r,s);for(let o=0;o0&&(a||r.node(l-1).canReplace(r.index(l-1),s.indexAfter(l-1))))return t.delete(r.before(l),s.after(l))}for(let o=1;o<=r.depth&&o<=s.depth;o++)if(e-r.start(o)==r.depth-o&&n>r.end(o)&&s.end(o)-n!=s.depth-o&&r.start(o-1)==s.start(o-1)&&r.node(o-1).canReplace(r.index(o-1),s.index(o-1)))return t.delete(r.before(o),n);t.delete(e,n)}function Ra(t,e){let n=[],r=Math.min(t.depth,e.depth);for(let s=r;s>=0;s--){let i=t.start(s);if(ie.pos+(e.depth-s)||t.node(s).type.spec.isolating||e.node(s).type.spec.isolating)break;(i==e.start(s)||s==t.depth&&s==e.depth&&t.parent.inlineContent&&e.parent.inlineContent&&s&&e.start(s-1)==i-1)&&n.push(s)}return n}var gr=class t extends Z{constructor(e,n,r){super(),this.pos=e,this.attr=n,this.value=r}apply(e){let n=e.nodeAt(this.pos);if(!n)return ee.fail("No node at attribute step's position");let r=Object.create(null);for(let i in n.attrs)r[i]=n.attrs[i];r[this.attr]=this.value;let s=n.type.create(r,null,n.marks);return ee.fromReplace(e,this.pos,this.pos+1,new C(b.from(s),0,n.isLeaf?0:1))}getMap(){return Je.empty}invert(e){return new t(this.pos,this.attr,e.nodeAt(this.pos).attrs[this.attr])}map(e){let n=e.mapResult(this.pos,1);return n.deletedAfter?null:new t(n.pos,this.attr,this.value)}toJSON(){return{stepType:"attr",pos:this.pos,attr:this.attr,value:this.value}}static fromJSON(e,n){if(typeof n.pos!="number"||typeof n.attr!="string")throw new RangeError("Invalid input for AttrStep.fromJSON");return new t(n.pos,n.attr,n.value)}};Z.jsonID("attr",gr);var yr=class t extends Z{constructor(e,n){super(),this.attr=e,this.value=n}apply(e){let n=Object.create(null);for(let s in e.attrs)n[s]=e.attrs[s];n[this.attr]=this.value;let r=e.type.create(n,e.content,e.marks);return ee.ok(r)}getMap(){return Je.empty}invert(e){return new t(this.attr,e.attrs[this.attr])}map(e){return this}toJSON(){return{stepType:"docAttr",attr:this.attr,value:this.value}}static fromJSON(e,n){if(typeof n.attr!="string")throw new RangeError("Invalid input for DocAttrStep.fromJSON");return new t(n.attr,n.value)}};Z.jsonID("docAttr",yr);var Qt=class extends Error{};Qt=function t(e){let n=Error.call(this,e);return n.__proto__=t.prototype,n};Qt.prototype=Object.create(Error.prototype);Qt.prototype.constructor=Qt;Qt.prototype.name="TransformError";var at=class{constructor(e){this.doc=e,this.steps=[],this.docs=[],this.mapping=new Cn}get before(){return this.docs.length?this.docs[0]:this.doc}step(e){let n=this.maybeStep(e);if(n.failed)throw new Qt(n.failed);return this}maybeStep(e){let n=e.apply(this.doc);return n.failed||this.addStep(e,n.doc),n}get docChanged(){return this.steps.length>0}changedRange(){let e=1e9,n=-1e9;for(let r=0;r{e=Math.min(e,l),n=Math.max(n,a)})}return e==1e9?null:{from:e,to:n}}addStep(e,n){this.docs.push(this.doc),this.steps.push(e),this.mapping.appendMap(e.getMap()),this.doc=n}replace(e,n=e,r=C.empty){let s=vn(this.doc,e,n,r);return s&&this.step(s),this}replaceWith(e,n,r){return this.replace(e,n,new C(b.from(r),0,0))}delete(e,n){return this.replace(e,n,C.empty)}insert(e,n){return this.replaceWith(e,e,n)}replaceRange(e,n,r){return Hf(this,e,n,r),this}replaceRangeWith(e,n,r){return Ff(this,e,n,r),this}deleteRange(e,n){return _f(this,e,n),this}lift(e,n){return vf(this,e,n),this}join(e,n=1){return Lf(this,e,n),this}wrap(e,n){return Nf(this,e,n),this}setBlockType(e,n=e,r,s=null){return Rf(this,e,n,r,s),this}setNodeMarkup(e,n,r=null,s){return If(this,e,n,r,s),this}setNodeAttribute(e,n,r){return this.step(new gr(e,n,r)),this}setDocAttribute(e,n){return this.step(new yr(e,n)),this}addNodeMark(e,n){return this.step(new Tn(e,n)),this}removeNodeMark(e,n){let r=this.doc.nodeAt(e);if(!r)throw new RangeError("No node at position "+e);if(n instanceof F)n.isInSet(r.marks)&&this.step(new Xt(e,n));else{let s=r.marks,i,o=[];for(;i=n.isInSet(s);)o.push(new Xt(e,i)),s=i.removeFromSet(s);for(let l=o.length-1;l>=0;l--)this.step(o[l])}return this}split(e,n=1,r){return Df(this,e,n,r),this}addMark(e,n,r){return Cf(this,e,n,r),this}removeMark(e,n,r){return Mf(this,e,n,r),this}clearIncompatible(e,n,r){return bi(this,e,n,r),this}};var xi=Object.create(null),R=class{constructor(e,n,r){this.$anchor=e,this.$head=n,this.ranges=r||[new en(e.min(n),e.max(n))]}get anchor(){return this.$anchor.pos}get head(){return this.$head.pos}get from(){return this.$from.pos}get to(){return this.$to.pos}get $from(){return this.ranges[0].$from}get $to(){return this.ranges[0].$to}get empty(){let e=this.ranges;for(let n=0;n=0;i--){let o=n<0?Zt(e.node(0),e.node(i),e.before(i+1),e.index(i),n,r):Zt(e.node(0),e.node(i),e.after(i+1),e.index(i)+1,n,r);if(o)return o}return null}static near(e,n=1){return this.findFrom(e,n)||this.findFrom(e,-n)||new ue(e.node(0))}static atStart(e){return Zt(e,e,0,0,1)||new ue(e)}static atEnd(e){return Zt(e,e,e.content.size,e.childCount,-1)||new ue(e)}static fromJSON(e,n){if(!n||!n.type)throw new RangeError("Invalid input for Selection.fromJSON");let r=xi[n.type];if(!r)throw new RangeError(`No selection type ${n.type} defined`);return r.fromJSON(e,n)}static jsonID(e,n){if(e in xi)throw new RangeError("Duplicate use of selection JSON ID "+e);return xi[e]=n,n.prototype.jsonID=e,n}getBookmark(){return A.between(this.$anchor,this.$head).getBookmark()}};R.prototype.visible=!0;var en=class{constructor(e,n){this.$from=e,this.$to=n}},Oa=!1;function Ia(t){!Oa&&!t.parent.inlineContent&&(Oa=!0,console.warn("TextSelection endpoint not pointing into a node with inline content ("+t.parent.type.name+")"))}var A=class t extends R{constructor(e,n=e){Ia(e),Ia(n),super(e,n)}get $cursor(){return this.$anchor.pos==this.$head.pos?this.$head:null}map(e,n){let r=e.resolve(n.map(this.head));if(!r.parent.inlineContent)return R.near(r);let s=e.resolve(n.map(this.anchor));return new t(s.parent.inlineContent?s:r,r)}replace(e,n=C.empty){if(super.replace(e,n),n==C.empty){let r=this.$from.marksAcross(this.$to);r&&e.ensureMarks(r)}}eq(e){return e instanceof t&&e.anchor==this.anchor&&e.head==this.head}getBookmark(){return new xr(this.anchor,this.head)}toJSON(){return{type:"text",anchor:this.anchor,head:this.head}}static fromJSON(e,n){if(typeof n.anchor!="number"||typeof n.head!="number")throw new RangeError("Invalid input for TextSelection.fromJSON");return new t(e.resolve(n.anchor),e.resolve(n.head))}static create(e,n,r=n){let s=e.resolve(n);return new this(s,r==n?s:e.resolve(r))}static between(e,n,r){let s=e.pos-n.pos;if((!r||s)&&(r=s>=0?1:-1),!n.parent.inlineContent){let i=R.findFrom(n,r,!0)||R.findFrom(n,-r,!0);if(i)n=i.$head;else return R.near(n,r)}return e.parent.inlineContent||(s==0?e=n:(e=(R.findFrom(e,-r,!0)||R.findFrom(e,r,!0)).$anchor,e.pos0?0:1);s>0?o=0;o+=s){let l=e.child(o);if(l.isAtom){if(!i&&E.isSelectable(l))return E.create(t,n-(s<0?l.nodeSize:0))}else{let a=Zt(t,l,n+s,s<0?l.childCount:0,s,i);if(a)return a}n+=l.nodeSize*s}return null}function Da(t,e,n){let r=t.steps.length-1;if(r{o==null&&(o=u)}),t.setSelection(R.near(t.doc.resolve(o),n))}var Pa=1,br=2,La=4,Ci=class extends at{constructor(e){super(e.doc),this.curSelectionFor=0,this.updated=0,this.meta=Object.create(null),this.time=Date.now(),this.curSelection=e.selection,this.storedMarks=e.storedMarks}get selection(){return this.curSelectionFor0}setStoredMarks(e){return this.storedMarks=e,this.updated|=br,this}ensureMarks(e){return F.sameSet(this.storedMarks||this.selection.$from.marks(),e)||this.setStoredMarks(e),this}addStoredMark(e){return this.ensureMarks(e.addToSet(this.storedMarks||this.selection.$head.marks()))}removeStoredMark(e){return this.ensureMarks(e.removeFromSet(this.storedMarks||this.selection.$head.marks()))}get storedMarksSet(){return(this.updated&br)>0}addStep(e,n){super.addStep(e,n),this.updated=this.updated&~br,this.storedMarks=null}setTime(e){return this.time=e,this}replaceSelection(e){return this.selection.replace(this,e),this}replaceSelectionWith(e,n=!0){let r=this.selection;return n&&(e=e.mark(this.storedMarks||(r.empty?r.$from.marks():r.$from.marksAcross(r.$to)||F.none))),r.replaceWith(this,e),this}deleteSelection(){return this.selection.replace(this),this}insertText(e,n,r){let s=this.doc.type.schema;if(n==null)return e?this.replaceSelectionWith(s.text(e),!0):this.deleteSelection();{if(r==null&&(r=n),!e)return this.deleteRange(n,r);let i=this.storedMarks;if(!i){let o=this.doc.resolve(n);i=r==n?o.marks():o.marksAcross(this.doc.resolve(r))}return this.replaceRangeWith(n,r,s.text(e,i)),!this.selection.empty&&this.selection.to==n+e.length&&this.setSelection(R.near(this.selection.$to)),this}}setMeta(e,n){return this.meta[typeof e=="string"?e:e.key]=n,this}getMeta(e){return this.meta[typeof e=="string"?e:e.key]}get isGeneric(){for(let e in this.meta)return!1;return!0}scrollIntoView(){return this.updated|=La,this}get scrolledIntoView(){return(this.updated&La)>0}};function za(t,e){return!e||!t?t:t.bind(e)}var vt=class{constructor(e,n,r){this.name=e,this.init=za(n.init,r),this.apply=za(n.apply,r)}},Wf=[new vt("doc",{init(t){return t.doc||t.schema.topNodeType.createAndFill()},apply(t){return t.doc}}),new vt("selection",{init(t,e){return t.selection||R.atStart(e.doc)},apply(t){return t.selection}}),new vt("storedMarks",{init(t){return t.storedMarks||null},apply(t,e,n,r){return r.selection.$cursor?t.storedMarks:null}}),new vt("scrollToSelection",{init(){return 0},apply(t,e){return t.scrolledIntoView?e+1:e}})],An=class{constructor(e,n){this.schema=e,this.plugins=[],this.pluginsByKey=Object.create(null),this.fields=Wf.slice(),n&&n.forEach(r=>{if(this.pluginsByKey[r.key])throw new RangeError("Adding different instances of a keyed plugin ("+r.key+")");this.plugins.push(r),this.pluginsByKey[r.key]=r,r.spec.state&&this.fields.push(new vt(r.key,r.spec.state,r))})}},wr=class t{constructor(e){this.config=e}get schema(){return this.config.schema}get plugins(){return this.config.plugins}apply(e){return this.applyTransaction(e).state}filterTransaction(e,n=-1){for(let r=0;rr.toJSON())),e&&typeof e=="object")for(let r in e){if(r=="doc"||r=="selection")throw new RangeError("The JSON fields `doc` and `selection` are reserved");let s=e[r],i=s.spec.state;i&&i.toJSON&&(n[r]=i.toJSON.call(s,this[s.key]))}return n}static fromJSON(e,n,r){if(!n)throw new RangeError("Invalid input for EditorState.fromJSON");if(!e.schema)throw new RangeError("Required config field 'schema' missing");let s=new An(e.schema,e.plugins),i=new t(s);return s.fields.forEach(o=>{if(o.name=="doc")i.doc=me.fromJSON(e.schema,n.doc);else if(o.name=="selection")i.selection=R.fromJSON(i.doc,n.selection);else if(o.name=="storedMarks")n.storedMarks&&(i.storedMarks=n.storedMarks.map(e.schema.markFromJSON));else{if(r)for(let l in r){let a=r[l],c=a.spec.state;if(a.key==o.name&&c&&c.fromJSON&&Object.prototype.hasOwnProperty.call(n,l)){i[o.name]=c.fromJSON.call(a,e,n[l],i);return}}i[o.name]=o.init(e,i)}}),i}};function Ba(t,e,n){for(let r in t){let s=t[r];s instanceof Function?s=s.bind(e):r=="handleDOMEvents"&&(s=Ba(s,e,{})),n[r]=s}return n}var O=class{constructor(e){this.spec=e,this.props={},e.props&&Ba(e.props,this,this.props),this.key=e.key?e.key.key:$a("plugin")}getState(e){return e[this.key]}},wi=Object.create(null);function $a(t){return t in wi?t+"$"+ ++wi[t]:(wi[t]=0,t+"$")}var P=class{constructor(e="key"){this.key=$a(e)}get(e){return e.config.pluginsByKey[this.key]}getState(e){return e[this.key]}};var Sr=(t,e)=>t.selection.empty?!1:(e&&e(t.tr.deleteSelection().scrollIntoView()),!0);function Fa(t,e){let{$cursor:n}=t.selection;return!n||(e?!e.endOfTextblock("backward",t):n.parentOffset>0)?null:n}var Ti=(t,e,n)=>{let r=Fa(t,n);if(!r)return!1;let s=Ai(r);if(!s){let o=r.blockRange(),l=o&&Xe(o);return l==null?!1:(e&&e(t.tr.lift(o,l).scrollIntoView()),!0)}let i=s.nodeBefore;if(Ga(t,s,e,-1))return!0;if(r.parent.content.size==0&&(tn(i,"end")||E.isSelectable(i)))for(let o=r.depth;;o--){let l=vn(t.doc,r.before(o),r.after(o),C.empty);if(l&&l.slice.size1)break}return i.isAtom&&s.depth==r.depth-1?(e&&e(t.tr.delete(s.pos-i.nodeSize,s.pos).scrollIntoView()),!0):!1},_a=(t,e,n)=>{let r=Fa(t,n);if(!r)return!1;let s=Ai(r);return s?Wa(t,s,e):!1},Va=(t,e,n)=>{let r=ja(t,n);if(!r)return!1;let s=Ri(r);return s?Wa(t,s,e):!1};function Wa(t,e,n){let r=e.nodeBefore,s=r,i=e.pos-1;for(;!s.isTextblock;i--){if(s.type.spec.isolating)return!1;let u=s.lastChild;if(!u)return!1;s=u}let o=e.nodeAfter,l=o,a=e.pos+1;for(;!l.isTextblock;a++){if(l.type.spec.isolating)return!1;let u=l.firstChild;if(!u)return!1;l=u}let c=vn(t.doc,i,a,C.empty);if(!c||c.from!=i||c instanceof ie&&c.slice.size>=a-i)return!1;if(n){let u=t.tr.step(c);u.setSelection(A.create(u.doc,i)),n(u.scrollIntoView())}return!0}function tn(t,e,n=!1){for(let r=t;r;r=e=="start"?r.firstChild:r.lastChild){if(r.isTextblock)return!0;if(n&&r.childCount!=1)return!1}return!1}var vi=(t,e,n)=>{let{$head:r,empty:s}=t.selection,i=r;if(!s)return!1;if(r.parent.isTextblock){if(n?!n.endOfTextblock("backward",t):r.parentOffset>0)return!1;i=Ai(r)}let o=i&&i.nodeBefore;return!o||!E.isSelectable(o)?!1:(e&&e(t.tr.setSelection(E.create(t.doc,i.pos-o.nodeSize)).scrollIntoView()),!0)};function Ai(t){if(!t.parent.type.spec.isolating)for(let e=t.depth-1;e>=0;e--){if(t.index(e)>0)return t.doc.resolve(t.before(e+1));if(t.node(e).type.spec.isolating)break}return null}function ja(t,e){let{$cursor:n}=t.selection;return!n||(e?!e.endOfTextblock("forward",t):n.parentOffset{let r=ja(t,n);if(!r)return!1;let s=Ri(r);if(!s)return!1;let i=s.nodeAfter;if(Ga(t,s,e,1))return!0;if(r.parent.content.size==0&&(tn(i,"start")||E.isSelectable(i))){let o=vn(t.doc,r.before(),r.after(),C.empty);if(o&&o.slice.size{let{$head:r,empty:s}=t.selection,i=r;if(!s)return!1;if(r.parent.isTextblock){if(n?!n.endOfTextblock("forward",t):r.parentOffset=0;e--){let n=t.node(e);if(t.index(e)+1{let n=t.selection,r=n instanceof E,s;if(r){if(n.node.isTextblock||!Ce(t.doc,n.from))return!1;s=n.from}else if(s=Tt(t.doc,n.from,-1),s==null)return!1;if(e){let i=t.tr.join(s);r&&i.setSelection(E.create(i.doc,s-t.doc.resolve(s).nodeBefore.nodeSize)),e(i.scrollIntoView())}return!0},Ua=(t,e)=>{let n=t.selection,r;if(n instanceof E){if(n.node.isTextblock||!Ce(t.doc,n.to))return!1;r=n.to}else if(r=Tt(t.doc,n.to,1),r==null)return!1;return e&&e(t.tr.join(r).scrollIntoView()),!0},qa=(t,e)=>{let{$from:n,$to:r}=t.selection,s=n.blockRange(r),i=s&&Xe(s);return i==null?!1:(e&&e(t.tr.lift(s,i).scrollIntoView()),!0)},Oi=(t,e)=>{let{$head:n,$anchor:r}=t.selection;return!n.parent.type.spec.code||!n.sameParent(r)?!1:(e&&e(t.tr.insertText(` -`).scrollIntoView()),!0)};function Ii(t){for(let e=0;e{let{$head:n,$anchor:r}=t.selection;if(!n.parent.type.spec.code||!n.sameParent(r))return!1;let s=n.node(-1),i=n.indexAfter(-1),o=Ii(s.contentMatchAt(i));if(!o||!s.canReplaceWith(i,i,o))return!1;if(e){let l=n.after(),a=t.tr.replaceWith(l,l,o.createAndFill());a.setSelection(R.near(a.doc.resolve(l),1)),e(a.scrollIntoView())}return!0},Pi=(t,e)=>{let n=t.selection,{$from:r,$to:s}=n;if(n instanceof ue||r.parent.inlineContent||s.parent.inlineContent)return!1;let i=Ii(s.parent.contentMatchAt(s.indexAfter()));if(!i||!i.isTextblock)return!1;if(e){let o=(!r.parentOffset&&s.index(){let{$cursor:n}=t.selection;if(!n||n.parent.content.size)return!1;if(n.depth>1&&n.after()!=n.end(-1)){let i=n.before();if(be(t.doc,i))return e&&e(t.tr.split(i).scrollIntoView()),!0}let r=n.blockRange(),s=r&&Xe(r);return s==null?!1:(e&&e(t.tr.lift(r,s).scrollIntoView()),!0)};function jf(t){return(e,n)=>{let{$from:r,$to:s}=e.selection;if(e.selection instanceof E&&e.selection.node.isBlock)return!r.parentOffset||!be(e.doc,r.pos)?!1:(n&&n(e.tr.split(r.pos).scrollIntoView()),!0);if(!r.depth)return!1;let i=[],o,l,a=!1,c=!1;for(let f=r.depth;;f--)if(r.node(f).isBlock){a=r.end(f)==r.pos+(r.depth-f),c=r.start(f)==r.pos-(r.depth-f),l=Ii(r.node(f-1).contentMatchAt(r.indexAfter(f-1)));let m=t&&t(s.parent,a,r);i.unshift(m||(a&&l?{type:l}:null)),o=f;break}else{if(f==1)return!1;i.unshift(null)}let u=e.tr;(e.selection instanceof A||e.selection instanceof ue)&&u.deleteSelection();let d=u.mapping.map(r.pos),h=be(u.doc,d,i.length,i);if(h||(i[0]=l?{type:l}:null,h=be(u.doc,d,i.length,i)),!h)return!1;if(u.split(d,i.length,i),!a&&c&&r.node(o).type!=l){let f=u.mapping.map(r.before(o)),p=u.doc.resolve(f);l&&r.node(o-1).canReplaceWith(p.index(),p.index()+1,l)&&u.setNodeMarkup(u.mapping.map(r.before(o)),l)}return n&&n(u.scrollIntoView()),!0}}var Kf=jf();var Ja=(t,e)=>{let{$from:n,to:r}=t.selection,s,i=n.sharedDepth(r);return i==0?!1:(s=n.before(i),e&&e(t.tr.setSelection(E.create(t.doc,s))),!0)},Uf=(t,e)=>(e&&e(t.tr.setSelection(new ue(t.doc))),!0);function qf(t,e,n){let r=e.nodeBefore,s=e.nodeAfter,i=e.index();return!r||!s||!r.type.compatibleContent(s.type)?!1:!r.content.size&&e.parent.canReplace(i-1,i)?(n&&n(t.tr.delete(e.pos-r.nodeSize,e.pos).scrollIntoView()),!0):!e.parent.canReplace(i,i+1)||!(s.isTextblock||Ce(t.doc,e.pos))?!1:(n&&n(t.tr.join(e.pos).scrollIntoView()),!0)}function Ga(t,e,n,r){let s=e.nodeBefore,i=e.nodeAfter,o,l,a=s.type.spec.isolating||i.type.spec.isolating;if(!a&&qf(t,e,n))return!0;let c=!a&&e.parent.canReplace(e.index(),e.index()+1);if(c&&(o=(l=s.contentMatchAt(s.childCount)).findWrapping(i.type))&&l.matchType(o[0]||i.type).validEnd){if(n){let f=e.pos+i.nodeSize,p=b.empty;for(let y=o.length-1;y>=0;y--)p=b.from(o[y].create(null,p));p=b.from(s.copy(p));let m=t.tr.step(new X(e.pos-1,f,e.pos,f,new C(p,1,0),o.length,!0)),g=m.doc.resolve(f+2*o.length);g.nodeAfter&&g.nodeAfter.type==s.type&&Ce(m.doc,g.pos)&&m.join(g.pos),n(m.scrollIntoView())}return!0}let u=i.type.spec.isolating||r>0&&a?null:R.findFrom(e,1),d=u&&u.$from.blockRange(u.$to),h=d&&Xe(d);if(h!=null&&h>=e.depth)return n&&n(t.tr.lift(d,h).scrollIntoView()),!0;if(c&&tn(i,"start",!0)&&tn(s,"end")){let f=s,p=[];for(;p.push(f),!f.isTextblock;)f=f.lastChild;let m=i,g=1;for(;!m.isTextblock;m=m.firstChild)g++;if(f.canReplace(f.childCount,f.childCount,m.content)){if(n){let y=b.empty;for(let w=p.length-1;w>=0;w--)y=b.from(p[w].copy(y));let k=t.tr.step(new X(e.pos-p.length,e.pos+i.nodeSize,e.pos+g,e.pos+i.nodeSize-g,new C(y,p.length,0),0,!0));n(k.scrollIntoView())}return!0}}return!1}function Xa(t){return function(e,n){let r=e.selection,s=t<0?r.$from:r.$to,i=s.depth;for(;s.node(i).isInline;){if(!i)return!1;i--}return s.node(i).isTextblock?(n&&n(e.tr.setSelection(A.create(e.doc,t<0?s.start(i):s.end(i)))),!0):!1}}var zi=Xa(-1),Bi=Xa(1);function Qa(t,e=null){return function(n,r){let{$from:s,$to:i}=n.selection,o=s.blockRange(i),l=o&&Yt(o,t,e);return l?(r&&r(n.tr.wrap(o,l).scrollIntoView()),!0):!1}}function $i(t,e=null){return function(n,r){let s=!1;for(let i=0;i{if(s)return!1;if(!(!a.isTextblock||a.hasMarkup(t,e)))if(a.type==t)s=!0;else{let u=n.doc.resolve(c),d=u.index();s=u.parent.canReplaceWith(d,d+1,t)}})}if(!s)return!1;if(r){let i=n.tr;for(let o=0;o=2&&e.$from.node(e.depth-1).type.compatibleContent(n)&&e.startIndex==0){if(e.$from.index(e.depth-1)==0)return!1;let a=o.resolve(e.start-2);i=new Ct(a,a,e.depth),e.endIndex=0;u--)i=b.from(n[u].type.create(n[u].attrs,i));t.step(new X(e.start-(r?2:0),e.end,e.start,e.end,new C(i,0,0),n.length,!0));let o=0;for(let u=0;uo.childCount>0&&o.firstChild.type==t);return i?n?r.node(i.depth-1).type==t?Qf(e,n,t,i):Yf(e,n,i):!0:!1}}function Qf(t,e,n,r){let s=t.tr,i=r.end,o=r.$to.end(r.depth);im;p--)f-=s.child(p).nodeSize,r.delete(f-1,f+1);let i=r.doc.resolve(n.start),o=i.nodeAfter;if(r.mapping.map(n.end)!=n.start+i.nodeAfter.nodeSize)return!1;let l=n.startIndex==0,a=n.endIndex==s.childCount,c=i.node(-1),u=i.index(-1);if(!c.canReplace(u+(l?0:1),u+1,o.content.append(a?b.empty:b.from(s))))return!1;let d=i.pos,h=d+o.nodeSize;return r.step(new X(d-(l?1:0),h+(a?1:0),d+1,h-1,new C((l?b.empty:b.from(s.copy(b.empty))).append(a?b.empty:b.from(s.copy(b.empty))),l?0:1,a?0:1),l?0:1)),e(r.scrollIntoView()),!0}function ec(t){return function(e,n){let{$from:r,$to:s}=e.selection,i=r.blockRange(s,c=>c.childCount>0&&c.firstChild.type==t);if(!i)return!1;let o=i.startIndex;if(o==0)return!1;let l=i.parent,a=l.child(o-1);if(a.type!=t)return!1;if(n){let c=a.lastChild&&a.lastChild.type==l.type,u=b.from(c?t.create():null),d=new C(b.from(t.create(null,b.from(l.type.create(null,u)))),c?3:1,0),h=i.start,f=i.end;n(e.tr.step(new X(h-(c?3:1),f,h,f,d,1,!0)).scrollIntoView())}return!0}}var te=function(t){for(var e=0;;e++)if(t=t.previousSibling,!t)return e},ln=function(t){let e=t.assignedSlot||t.parentNode;return e&&e.nodeType==11?e.host:e},Ki=null,Ye=function(t,e,n){let r=Ki||(Ki=document.createRange());return r.setEnd(t,n??t.nodeValue.length),r.setStart(t,e||0),r},Zf=function(){Ki=null},Dt=function(t,e,n,r){return n&&(tc(t,e,n,r,-1)||tc(t,e,n,r,1))},ep=/^(img|br|input|textarea|hr)$/i;function tc(t,e,n,r,s){for(var i;;){if(t==n&&e==r)return!0;if(e==(s<0?0:Te(t))){let o=t.parentNode;if(!o||o.nodeType!=1||Ln(t)||ep.test(t.nodeName)||t.contentEditable=="false")return!1;e=te(t)+(s<0?0:1),t=o}else if(t.nodeType==1){let o=t.childNodes[e+(s<0?-1:0)];if(o.nodeType==1&&o.contentEditable=="false")if(!((i=o.pmViewDesc)===null||i===void 0)&&i.ignoreForSelection)e+=s;else return!1;else t=o,e=s<0?Te(t):0}else return!1}}function Te(t){return t.nodeType==3?t.nodeValue.length:t.childNodes.length}function tp(t,e){for(;;){if(t.nodeType==3&&e)return t;if(t.nodeType==1&&e>0){if(t.contentEditable=="false")return null;t=t.childNodes[e-1],e=Te(t)}else if(t.parentNode&&!Ln(t))e=te(t),t=t.parentNode;else return null}}function np(t,e){for(;;){if(t.nodeType==3&&e2),Me=an||(ze?/Mac/.test(ze.platform):!1),zc=ze?/Win/.test(ze.platform):!1,Ze=/Android \d/.test(mt),zn=!!nc&&"webkitFontSmoothing"in nc.documentElement.style,op=zn?+(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent)||[0,0])[1]:0;function lp(t){let e=t.defaultView&&t.defaultView.visualViewport;return e?{left:0,right:e.width,top:0,bottom:e.height}:{left:0,right:t.documentElement.clientWidth,top:0,bottom:t.documentElement.clientHeight}}function Qe(t,e){return typeof t=="number"?t:t[e]}function ap(t){let e=t.getBoundingClientRect(),n=e.width/t.offsetWidth||1,r=e.height/t.offsetHeight||1;return{left:e.left,right:e.left+t.clientWidth*n,top:e.top,bottom:e.top+t.clientHeight*r}}function rc(t,e,n){let r=t.someProp("scrollThreshold")||0,s=t.someProp("scrollMargin")||5,i=t.dom.ownerDocument;for(let o=n||t.dom;o;){if(o.nodeType!=1){o=ln(o);continue}let l=o,a=l==i.body,c=a?lp(i):ap(l),u=0,d=0;if(e.topc.bottom-Qe(r,"bottom")&&(d=e.bottom-e.top>c.bottom-c.top?e.top+Qe(s,"top")-c.top:e.bottom-c.bottom+Qe(s,"bottom")),e.leftc.right-Qe(r,"right")&&(u=e.right-c.right+Qe(s,"right")),u||d)if(a)i.defaultView.scrollBy(u,d);else{let f=l.scrollLeft,p=l.scrollTop;d&&(l.scrollTop+=d),u&&(l.scrollLeft+=u);let m=l.scrollLeft-f,g=l.scrollTop-p;e={left:e.left-m,top:e.top-g,right:e.right-m,bottom:e.bottom-g}}let h=a?"fixed":getComputedStyle(o).position;if(/^(fixed|sticky)$/.test(h))break;o=h=="absolute"?o.offsetParent:ln(o)}}function cp(t){let e=t.dom.getBoundingClientRect(),n=Math.max(0,e.top),r,s;for(let i=(e.left+e.right)/2,o=n+1;o=n-20){r=l,s=a.top;break}}return{refDOM:r,refTop:s,stack:Bc(t.dom)}}function Bc(t){let e=[],n=t.ownerDocument;for(let r=t;r&&(e.push({dom:r,top:r.scrollTop,left:r.scrollLeft}),t!=n);r=ln(r));return e}function up({refDOM:t,refTop:e,stack:n}){let r=t?t.getBoundingClientRect().top:0;$c(n,r==0?0:r-e)}function $c(t,e){for(let n=0;n=l){o=Math.max(p.bottom,o),l=Math.min(p.top,l);let m=p.left>e.left?p.left-e.left:p.right=(p.left+p.right)/2?1:0));continue}}else p.top>e.top&&!a&&p.left<=e.left&&p.right>=e.left&&(a=u,c={left:Math.max(p.left,Math.min(p.right,e.left)),top:p.top});!n&&(e.left>=p.right&&e.top>=p.top||e.left>=p.left&&e.top>=p.bottom)&&(i=d+1)}}return!n&&a&&(n=a,s=c,r=0),n&&n.nodeType==3?hp(n,s):!n||r&&n.nodeType==1?{node:t,offset:i}:Hc(n,s)}function hp(t,e){let n=t.nodeValue.length,r=document.createRange(),s;for(let i=0;i=(o.left+o.right)/2?1:0)};break}}return r.detach(),s||{node:t,offset:0}}function co(t,e){return t.left>=e.left-1&&t.left<=e.right+1&&t.top>=e.top-1&&t.top<=e.bottom+1}function fp(t,e){let n=t.parentNode;return n&&/^li$/i.test(n.nodeName)&&e.left(o.left+o.right)/2?1:-1}return t.docView.posFromDOM(r,s,i)}function mp(t,e,n,r){let s=-1;for(let i=e,o=!1;i!=t.dom;){let l=t.docView.nearestDesc(i,!0),a;if(!l)return null;if(l.dom.nodeType==1&&(l.node.isBlock&&l.parent||!l.contentDOM)&&((a=l.dom.getBoundingClientRect()).width||a.height)&&(l.node.isBlock&&l.parent&&!/^T(R|BODY|HEAD|FOOT)$/.test(l.dom.nodeName)&&(!o&&a.left>r.left||a.top>r.top?s=l.posBefore:(!o&&a.right-1?s:t.docView.posFromDOM(e,n,-1)}function Fc(t,e,n){let r=t.childNodes.length;if(r&&n.tope.top&&s++}let c;zn&&s&&r.nodeType==1&&(c=r.childNodes[s-1]).nodeType==1&&c.contentEditable=="false"&&c.getBoundingClientRect().top>=e.top&&s--,r==t.dom&&s==r.childNodes.length-1&&r.lastChild.nodeType==1&&e.top>r.lastChild.getBoundingClientRect().bottom?l=t.state.doc.content.size:(s==0||r.nodeType!=1||r.childNodes[s-1].nodeName!="BR")&&(l=mp(t,r,s,e))}l==null&&(l=pp(t,o,e));let a=t.docView.nearestDesc(o,!0);return{pos:l,inside:a?a.posAtStart-a.border:-1}}function sc(t){return t.top=0&&s==r.nodeValue.length?(a--,u=1):n<0?a--:c++,En(ut(Ye(r,a,c),u),u<0)}if(!t.state.doc.resolve(e-(i||0)).parent.inlineContent){if(i==null&&s&&(n<0||s==Te(r))){let a=r.childNodes[s-1];if(a.nodeType==1)return Fi(a.getBoundingClientRect(),!1)}if(i==null&&s=0)}if(i==null&&s&&(n<0||s==Te(r))){let a=r.childNodes[s-1],c=a.nodeType==3?Ye(a,Te(a)-(o?0:1)):a.nodeType==1&&(a.nodeName!="BR"||!a.nextSibling)?a:null;if(c)return En(ut(c,1),!1)}if(i==null&&s=0)}function En(t,e){if(t.width==0)return t;let n=e?t.left:t.right;return{top:t.top,bottom:t.bottom,left:n,right:n}}function Fi(t,e){if(t.height==0)return t;let n=e?t.top:t.bottom;return{top:n,bottom:n,left:t.left,right:t.right}}function Vc(t,e,n){let r=t.state,s=t.root.activeElement;r!=e&&t.updateState(e),s!=t.dom&&t.focus();try{return n()}finally{r!=e&&t.updateState(r),s!=t.dom&&s&&s.focus()}}function kp(t,e,n){let r=e.selection,s=n=="up"?r.$from:r.$to;return Vc(t,e,()=>{let{node:i}=t.docView.domFromPos(s.pos,n=="up"?-1:1);for(;;){let l=t.docView.nearestDesc(i,!0);if(!l)break;if(l.node.isBlock){i=l.contentDOM||l.dom;break}i=l.dom.parentNode}let o=_c(t,s.pos,1);for(let l=i.firstChild;l;l=l.nextSibling){let a;if(l.nodeType==1)a=l.getClientRects();else if(l.nodeType==3)a=Ye(l,0,l.nodeValue.length).getClientRects();else continue;for(let c=0;cu.top+1&&(n=="up"?o.top-u.top>(u.bottom-o.top)*2:u.bottom-o.bottom>(o.bottom-u.top)*2))return!1}}return!0})}var bp=/[\u0590-\u08ac]/;function xp(t,e,n){let{$head:r}=e.selection;if(!r.parent.isTextblock)return!1;let s=r.parentOffset,i=!s,o=s==r.parent.content.size,l=t.domSelection();return l?!bp.test(r.parent.textContent)||!l.modify?n=="left"||n=="backward"?i:o:Vc(t,e,()=>{let{focusNode:a,focusOffset:c,anchorNode:u,anchorOffset:d}=t.domSelectionRange(),h=l.caretBidiLevel;l.modify("move",n,"character");let f=r.depth?t.docView.domAfterPos(r.before()):t.dom,{focusNode:p,focusOffset:m}=t.domSelectionRange(),g=p&&!f.contains(p.nodeType==1?p:p.parentNode)||a==p&&c==m;try{l.collapse(u,d),a&&(a!=u||c!=d)&&l.extend&&l.extend(a,c)}catch{}return h!=null&&(l.caretBidiLevel=h),g}):r.pos==r.start()||r.pos==r.end()}var ic=null,oc=null,lc=!1;function wp(t,e,n){return ic==e&&oc==n?lc:(ic=e,oc=n,lc=n=="up"||n=="down"?kp(t,e,n):xp(t,e,n))}var Ae=0,ac=1,Et=2,Be=3,Pt=class{constructor(e,n,r,s){this.parent=e,this.children=n,this.dom=r,this.contentDOM=s,this.dirty=Ae,r.pmViewDesc=this}matchesWidget(e){return!1}matchesMark(e){return!1}matchesNode(e,n,r){return!1}matchesHack(e){return!1}parseRule(){return null}stopEvent(e){return!1}get size(){let e=0;for(let n=0;nte(this.contentDOM);else if(this.contentDOM&&this.contentDOM!=this.dom&&this.dom.contains(this.contentDOM))s=e.compareDocumentPosition(this.contentDOM)&2;else if(this.dom.firstChild){if(n==0)for(let i=e;;i=i.parentNode){if(i==this.dom){s=!1;break}if(i.previousSibling)break}if(s==null&&n==e.childNodes.length)for(let i=e;;i=i.parentNode){if(i==this.dom){s=!0;break}if(i.nextSibling)break}}return s??r>0?this.posAtEnd:this.posAtStart}nearestDesc(e,n=!1){for(let r=!0,s=e;s;s=s.parentNode){let i=this.getDesc(s),o;if(i&&(!n||i.node))if(r&&(o=i.nodeDOM)&&!(o.nodeType==1?o.contains(e.nodeType==1?e:e.parentNode):o==e))r=!1;else return i}}getDesc(e){let n=e.pmViewDesc;for(let r=n;r;r=r.parent)if(r==this)return n}posFromDOM(e,n,r){for(let s=e;s;s=s.parentNode){let i=this.getDesc(s);if(i)return i.localPosFromDOM(e,n,r)}return-1}descAt(e){for(let n=0,r=0;ne||o instanceof Tr){s=e-i;break}i=l}if(s)return this.children[r].domFromPos(s-this.children[r].border,n);for(let i;r&&!(i=this.children[r-1]).size&&i instanceof Cr&&i.side>=0;r--);if(n<=0){let i,o=!0;for(;i=r?this.children[r-1]:null,!(!i||i.dom.parentNode==this.contentDOM);r--,o=!1);return i&&n&&o&&!i.border&&!i.domAtom?i.domFromPos(i.size,n):{node:this.contentDOM,offset:i?te(i.dom)+1:0}}else{let i,o=!0;for(;i=r=u&&n<=c-a.border&&a.node&&a.contentDOM&&this.contentDOM.contains(a.contentDOM))return a.parseRange(e,n,u);e=o;for(let d=l;d>0;d--){let h=this.children[d-1];if(h.size&&h.dom.parentNode==this.contentDOM&&!h.emptyChildAt(1)){s=te(h.dom)+1;break}e-=h.size}s==-1&&(s=0)}if(s>-1&&(c>n||l==this.children.length-1)){n=c;for(let u=l+1;up&&on){let p=l;l=a,a=p}let f=document.createRange();f.setEnd(a.node,a.offset),f.setStart(l.node,l.offset),c.removeAllRanges(),c.addRange(f)}}ignoreMutation(e){return!this.contentDOM&&e.type!="selection"}get contentLost(){return this.contentDOM&&this.contentDOM!=this.dom&&!this.dom.contains(this.contentDOM)}markDirty(e,n){for(let r=0,s=0;s=r:er){let l=r+i.border,a=o-i.border;if(e>=l&&n<=a){this.dirty=e==r||n==o?Et:ac,e==l&&n==a&&(i.contentLost||i.dom.parentNode!=this.contentDOM)?i.dirty=Be:i.markDirty(e-l,n-l);return}else i.dirty=i.dom==i.contentDOM&&i.dom.parentNode==this.contentDOM&&!i.children.length?Et:Be}r=o}this.dirty=Et}markParentsDirty(){let e=1;for(let n=this.parent;n;n=n.parent,e++){let r=e==1?Et:ac;n.dirty{if(!i)return s;if(i.parent)return i.parent.posBeforeChild(i)})),!n.type.spec.raw){if(o.nodeType!=1){let l=document.createElement("span");l.appendChild(o),o=l}o.contentEditable="false",o.classList.add("ProseMirror-widget")}super(e,[],o,null),this.widget=n,this.widget=n,i=this}matchesWidget(e){return this.dirty==Ae&&e.type.eq(this.widget.type)}parseRule(){return{ignore:!0}}stopEvent(e){let n=this.widget.spec.stopEvent;return n?n(e):!1}ignoreMutation(e){return e.type!="selection"||this.widget.spec.ignoreSelection}destroy(){this.widget.type.destroy(this.dom),super.destroy()}get domAtom(){return!0}get ignoreForSelection(){return!!this.widget.type.spec.relaxedSide}get side(){return this.widget.type.side}},Gi=class extends Pt{constructor(e,n,r,s){super(e,[],n,null),this.textDOM=r,this.text=s}get size(){return this.text.length}localPosFromDOM(e,n){return e!=this.textDOM?this.posAtStart+(n?this.size:0):this.posAtStart+n}domFromPos(e){return{node:this.textDOM,offset:e}}ignoreMutation(e){return e.type==="characterData"&&e.target.nodeValue==e.oldValue}},cn=class t extends Pt{constructor(e,n,r,s,i){super(e,[],r,s),this.mark=n,this.spec=i}static create(e,n,r,s){let i=s.nodeViews[n.type.name],o=i&&i(n,s,r);return(!o||!o.dom)&&(o=qe.renderSpec(document,n.type.spec.toDOM(n,r),null,n.attrs)),new t(e,n,o.dom,o.contentDOM||o.dom,o)}parseRule(){return this.dirty&Be||this.mark.type.spec.reparseInView?null:{mark:this.mark.type.name,attrs:this.mark.attrs,contentElement:this.contentDOM}}matchesMark(e){return this.dirty!=Be&&this.mark.eq(e)}markDirty(e,n){if(super.markDirty(e,n),this.dirty!=Ae){let r=this.parent;for(;!r.node;)r=r.parent;r.dirty0&&(i=Zi(i,0,e,r));for(let l=0;l{if(!a)return o;if(a.parent)return a.parent.posBeforeChild(a)},r,s),u=c&&c.dom,d=c&&c.contentDOM;if(n.isText){if(!u)u=document.createTextNode(n.text);else if(u.nodeType!=3)throw new RangeError("Text must be rendered as a DOM text node")}else u||({dom:u,contentDOM:d}=qe.renderSpec(document,n.type.spec.toDOM(n),null,n.attrs));!d&&!n.isText&&u.nodeName!="BR"&&(u.hasAttribute("contenteditable")||(u.contentEditable="false"),n.type.spec.draggable&&(u.draggable=!0));let h=u;return u=Kc(u,r,n),c?a=new Xi(e,n,r,s,u,d||null,h,c,i,o+1):n.isText?new Mr(e,n,r,s,u,h,i):new t(e,n,r,s,u,d||null,h,i,o+1)}parseRule(){if(this.node.type.spec.reparseInView)return null;let e={node:this.node.type.name,attrs:this.node.attrs};if(this.node.type.whitespace=="pre"&&(e.preserveWhitespace="full"),!this.contentDOM)e.getContent=()=>this.node.content;else if(!this.contentLost)e.contentElement=this.contentDOM;else{for(let n=this.children.length-1;n>=0;n--){let r=this.children[n];if(this.dom.contains(r.dom.parentNode)){e.contentElement=r.dom.parentNode;break}}e.contentElement||(e.getContent=()=>b.empty)}return e}matchesNode(e,n,r){return this.dirty==Ae&&e.eq(this.node)&&vr(n,this.outerDeco)&&r.eq(this.innerDeco)}get size(){return this.node.nodeSize}get border(){return this.node.isLeaf?0:1}updateChildren(e,n){let r=this.node.inlineContent,s=n,i=e.composing?this.localCompositionInfo(e,n):null,o=i&&i.pos>-1?i:null,l=i&&i.pos<0,a=new Yi(this,o&&o.node,e);Tp(this.node,this.innerDeco,(c,u,d)=>{c.spec.marks?a.syncToMarks(c.spec.marks,r,e,u):c.type.side>=0&&!d&&a.syncToMarks(u==this.node.childCount?F.none:this.node.child(u).marks,r,e,u),a.placeWidget(c,e,s)},(c,u,d,h)=>{a.syncToMarks(c.marks,r,e,h);let f;a.findNodeMatch(c,u,d,h)||l&&e.state.selection.from>s&&e.state.selection.to-1&&a.updateNodeAt(c,u,d,f,e)||a.updateNextNode(c,u,d,e,h,s)||a.addNode(c,u,d,e,s),s+=c.nodeSize}),a.syncToMarks([],r,e,0),this.node.isTextblock&&a.addTextblockHacks(),a.destroyRest(),(a.changed||this.dirty==Et)&&(o&&this.protectLocalComposition(e,o),Wc(this.contentDOM,this.children,e),an&&vp(this.dom))}localCompositionInfo(e,n){let{from:r,to:s}=e.state.selection;if(!(e.state.selection instanceof A)||rn+this.node.content.size)return null;let i=e.input.compositionNode;if(!i||!this.dom.contains(i.parentNode))return null;if(this.node.inlineContent){let o=i.nodeValue,l=Ap(this.node.content,o,r-n,s-n);return l<0?null:{node:i,pos:l,text:o}}else return{node:i,pos:-1,text:""}}protectLocalComposition(e,{node:n,pos:r,text:s}){if(this.getDesc(n))return;let i=n;for(;i.parentNode!=this.contentDOM;i=i.parentNode){for(;i.previousSibling;)i.parentNode.removeChild(i.previousSibling);for(;i.nextSibling;)i.parentNode.removeChild(i.nextSibling);i.pmViewDesc&&(i.pmViewDesc=void 0)}let o=new Gi(this,i,n,s);e.input.compositionNodes.push(o),this.children=Zi(this.children,r,r+s.length,e,o)}update(e,n,r,s){return this.dirty==Be||!e.sameMarkup(this.node)?!1:(this.updateInner(e,n,r,s),!0)}updateInner(e,n,r,s){this.updateOuterDeco(n),this.node=e,this.innerDeco=r,this.contentDOM&&this.updateChildren(s,this.posAtStart),this.dirty=Ae}updateOuterDeco(e){if(vr(e,this.outerDeco))return;let n=this.nodeDOM.nodeType!=1,r=this.dom;this.dom=jc(this.dom,this.nodeDOM,Qi(this.outerDeco,this.node,n),Qi(e,this.node,n)),this.dom!=r&&(r.pmViewDesc=void 0,this.dom.pmViewDesc=this),this.outerDeco=e}selectNode(){this.nodeDOM.nodeType==1&&(this.nodeDOM.classList.add("ProseMirror-selectednode"),(this.contentDOM||!this.node.type.spec.draggable)&&(this.nodeDOM.draggable=!0))}deselectNode(){this.nodeDOM.nodeType==1&&(this.nodeDOM.classList.remove("ProseMirror-selectednode"),(this.contentDOM||!this.node.type.spec.draggable)&&this.nodeDOM.removeAttribute("draggable"))}get domAtom(){return this.node.isAtom}};function cc(t,e,n,r,s){Kc(r,e,t);let i=new pt(void 0,t,e,n,r,r,r,s,0);return i.contentDOM&&i.updateChildren(s,0),i}var Mr=class t extends pt{constructor(e,n,r,s,i,o,l){super(e,n,r,s,i,null,o,l,0)}parseRule(){let e=this.nodeDOM.parentNode;for(;e&&e!=this.dom&&!e.pmIsDeco;)e=e.parentNode;return{skip:e||!0}}update(e,n,r,s){return this.dirty==Be||this.dirty!=Ae&&!this.inParent()||!e.sameMarkup(this.node)?!1:(this.updateOuterDeco(n),(this.dirty!=Ae||e.text!=this.node.text)&&e.text!=this.nodeDOM.nodeValue&&(this.nodeDOM.nodeValue=e.text,s.trackWrites==this.nodeDOM&&(s.trackWrites=null)),this.node=e,this.dirty=Ae,!0)}inParent(){let e=this.parent.contentDOM;for(let n=this.nodeDOM;n;n=n.parentNode)if(n==e)return!0;return!1}domFromPos(e){return{node:this.nodeDOM,offset:e}}localPosFromDOM(e,n,r){return e==this.nodeDOM?this.posAtStart+Math.min(n,this.node.text.length):super.localPosFromDOM(e,n,r)}ignoreMutation(e){return e.type!="characterData"&&e.type!="selection"}slice(e,n,r){let s=this.node.cut(e,n),i=document.createTextNode(s.text);return new t(this.parent,s,this.outerDeco,this.innerDeco,i,i,r)}markDirty(e,n){super.markDirty(e,n),this.dom!=this.nodeDOM&&(e==0||n==this.nodeDOM.nodeValue.length)&&(this.dirty=Be)}get domAtom(){return!1}isText(e){return this.node.text==e}},Tr=class extends Pt{parseRule(){return{ignore:!0}}matchesHack(e){return this.dirty==Ae&&this.dom.nodeName==e}get domAtom(){return!0}get ignoreForCoords(){return this.dom.nodeName=="IMG"}},Xi=class extends pt{constructor(e,n,r,s,i,o,l,a,c,u){super(e,n,r,s,i,o,l,c,u),this.spec=a}update(e,n,r,s){if(this.dirty==Be)return!1;if(this.spec.update&&(this.node.type==e.type||this.spec.multiType)){let i=this.spec.update(e,n,r);return i&&this.updateInner(e,n,r,s),i}else return!this.contentDOM&&!e.isLeaf?!1:super.update(e,n,r,s)}selectNode(){this.spec.selectNode?this.spec.selectNode():super.selectNode()}deselectNode(){this.spec.deselectNode?this.spec.deselectNode():super.deselectNode()}setSelection(e,n,r,s){this.spec.setSelection?this.spec.setSelection(e,n,r.root):super.setSelection(e,n,r,s)}destroy(){this.spec.destroy&&this.spec.destroy(),super.destroy()}stopEvent(e){return this.spec.stopEvent?this.spec.stopEvent(e):!1}ignoreMutation(e){return this.spec.ignoreMutation?this.spec.ignoreMutation(e):super.ignoreMutation(e)}};function Wc(t,e,n){let r=t.firstChild,s=!1;for(let i=0;i>1,l=Math.min(o,e.length);for(;i-1)a>this.index&&(this.changed=!0,this.destroyBetween(this.index,a)),this.top=this.top.children[this.index];else{let u=cn.create(this.top,e[o],n,r);this.top.children.splice(this.index,0,u),this.top=u,this.changed=!0}this.index=0,o++}}findNodeMatch(e,n,r,s){let i=-1,o;if(s>=this.preMatch.index&&(o=this.preMatch.matches[s-this.preMatch.index]).parent==this.top&&o.matchesNode(e,n,r))i=this.top.children.indexOf(o,this.index);else for(let l=this.index,a=Math.min(this.top.children.length,l+5);l0;){let l;for(;;)if(r){let c=n.children[r-1];if(c instanceof cn)n=c,r=c.children.length;else{l=c,r--;break}}else{if(n==e)break e;r=n.parent.children.indexOf(n),n=n.parent}let a=l.node;if(a){if(a!=t.child(s-1))break;--s,i.set(l,s),o.push(l)}}return{index:s,matched:i,matches:o.reverse()}}function Mp(t,e){return t.type.side-e.type.side}function Tp(t,e,n,r){let s=e.locals(t),i=0;if(s.length==0){for(let c=0;ci;)l.push(s[o++]);let p=i+h.nodeSize;if(h.isText){let g=p;o!g.inline):l.slice();r(h,m,e.forChild(i,h),f),i=p}}function vp(t){if(t.nodeName=="UL"||t.nodeName=="OL"){let e=t.style.cssText;t.style.cssText=e+"; list-style: square !important",window.getComputedStyle(t).listStyle,t.style.cssText=e}}function Ap(t,e,n,r){for(let s=0,i=0;s=n){if(i>=r&&a.slice(r-e.length-l,r-l)==e)return r-e.length;let c=l=0&&c+e.length+l>=n)return l+c;if(n==r&&a.length>=r+e.length-l&&a.slice(r-l,r-l+e.length)==e)return r}}return-1}function Zi(t,e,n,r,s){let i=[];for(let o=0,l=0;o=n||u<=e?i.push(a):(cn&&i.push(a.slice(n-c,a.size,r)))}return i}function uo(t,e=null){let n=t.domSelectionRange(),r=t.state.doc;if(!n.focusNode)return null;let s=t.docView.nearestDesc(n.focusNode),i=s&&s.size==0,o=t.docView.posFromDOM(n.focusNode,n.focusOffset,1);if(o<0)return null;let l=r.resolve(o),a,c;if(Dr(n)){for(a=o;s&&!s.node;)s=s.parent;let d=s.node;if(s&&d.isAtom&&E.isSelectable(d)&&s.parent&&!(d.isInline&&rp(n.focusNode,n.focusOffset,s.dom))){let h=s.posBefore;c=new E(o==h?l:r.resolve(h))}}else{if(n instanceof t.dom.ownerDocument.defaultView.Selection&&n.rangeCount>1){let d=o,h=o;for(let f=0;f{(n.anchorNode!=r||n.anchorOffset!=s)&&(e.removeEventListener("selectionchange",t.input.hideSelectionGuard),setTimeout(()=>{(!Uc(t)||t.state.selection.visible)&&t.dom.classList.remove("ProseMirror-hideselection")},20))})}function Np(t){let e=t.domSelection();if(!e)return;let n=t.cursorWrapper.dom,r=n.nodeName=="IMG";r?e.collapse(n.parentNode,te(n)+1):e.collapse(n,0),!r&&!t.state.selection.visible&&ge&&ft<=11&&(n.disabled=!0,n.disabled=!1)}function qc(t,e){if(e instanceof E){let n=t.docView.descAt(e.from);n!=t.lastSelectedViewDesc&&(pc(t),n&&n.selectNode(),t.lastSelectedViewDesc=n)}else pc(t)}function pc(t){t.lastSelectedViewDesc&&(t.lastSelectedViewDesc.parent&&t.lastSelectedViewDesc.deselectNode(),t.lastSelectedViewDesc=void 0)}function ho(t,e,n,r){return t.someProp("createSelectionBetween",s=>s(t,e,n))||A.between(e,n,r)}function mc(t){return t.editable&&!t.hasFocus()?!1:Jc(t)}function Jc(t){let e=t.domSelectionRange();if(!e.anchorNode)return!1;try{return t.dom.contains(e.anchorNode.nodeType==3?e.anchorNode.parentNode:e.anchorNode)&&(t.editable||t.dom.contains(e.focusNode.nodeType==3?e.focusNode.parentNode:e.focusNode))}catch{return!1}}function Rp(t){let e=t.docView.domFromPos(t.state.selection.anchor,0),n=t.domSelectionRange();return Dt(e.node,e.offset,n.anchorNode,n.anchorOffset)}function eo(t,e){let{$anchor:n,$head:r}=t.selection,s=e>0?n.max(r):n.min(r),i=s.parent.inlineContent?s.depth?t.doc.resolve(e>0?s.after():s.before()):null:s;return i&&R.findFrom(i,e)}function dt(t,e){return t.dispatch(t.state.tr.setSelection(e).scrollIntoView()),!0}function gc(t,e,n){let r=t.state.selection;if(r instanceof A)if(n.indexOf("s")>-1){let{$head:s}=r,i=s.textOffset?null:e<0?s.nodeBefore:s.nodeAfter;if(!i||i.isText||!i.isLeaf)return!1;let o=t.state.doc.resolve(s.pos+i.nodeSize*(e<0?-1:1));return dt(t,new A(r.$anchor,o))}else if(r.empty){if(t.endOfTextblock(e>0?"forward":"backward")){let s=eo(t.state,e);return s&&s instanceof E?dt(t,s):!1}else if(!(Me&&n.indexOf("m")>-1)){let s=r.$head,i=s.textOffset?null:e<0?s.nodeBefore:s.nodeAfter,o;if(!i||i.isText)return!1;let l=e<0?s.pos-i.nodeSize:s.pos;return i.isAtom||(o=t.docView.descAt(l))&&!o.contentDOM?E.isSelectable(i)?dt(t,new E(e<0?t.state.doc.resolve(s.pos-i.nodeSize):s)):zn?dt(t,new A(t.state.doc.resolve(e<0?l:l+i.nodeSize))):!1:!1}}else return!1;else{if(r instanceof E&&r.node.isInline)return dt(t,new A(e>0?r.$to:r.$from));{let s=eo(t.state,e);return s?dt(t,s):!1}}}function Ar(t){return t.nodeType==3?t.nodeValue.length:t.childNodes.length}function Rn(t,e){let n=t.pmViewDesc;return n&&n.size==0&&(e<0||t.nextSibling||t.nodeName!="BR")}function rn(t,e){return e<0?Op(t):Ip(t)}function Op(t){let e=t.domSelectionRange(),n=e.focusNode,r=e.focusOffset;if(!n)return;let s,i,o=!1;for(ve&&n.nodeType==1&&r0){if(n.nodeType!=1)break;{let l=n.childNodes[r-1];if(Rn(l,-1))s=n,i=--r;else if(l.nodeType==3)n=l,r=n.nodeValue.length;else break}}else{if(Gc(n))break;{let l=n.previousSibling;for(;l&&Rn(l,-1);)s=n.parentNode,i=te(l),l=l.previousSibling;if(l)n=l,r=Ar(n);else{if(n=n.parentNode,n==t.dom)break;r=0}}}o?to(t,n,r):s&&to(t,s,i)}function Ip(t){let e=t.domSelectionRange(),n=e.focusNode,r=e.focusOffset;if(!n)return;let s=Ar(n),i,o;for(;;)if(r{t.state==s&&et(t)},50)}function yc(t,e){let n=t.state.doc.resolve(e);if(!(ne||zc)&&n.parent.inlineContent){let s=t.coordsAtPos(e);if(e>n.start()){let i=t.coordsAtPos(e-1),o=(i.top+i.bottom)/2;if(o>s.top&&o1)return i.lefts.top&&o1)return i.left>s.left?"ltr":"rtl"}}return getComputedStyle(t.dom).direction=="rtl"?"rtl":"ltr"}function kc(t,e,n){let r=t.state.selection;if(r instanceof A&&!r.empty||n.indexOf("s")>-1||Me&&n.indexOf("m")>-1)return!1;let{$from:s,$to:i}=r;if(!s.parent.inlineContent||t.endOfTextblock(e<0?"up":"down")){let o=eo(t.state,e);if(o&&o instanceof E)return dt(t,o)}if(!s.parent.inlineContent){let o=e<0?s:i,l=r instanceof ue?R.near(o,e):R.findFrom(o,e);return l?dt(t,l):!1}return!1}function bc(t,e){if(!(t.state.selection instanceof A))return!0;let{$head:n,$anchor:r,empty:s}=t.state.selection;if(!n.sameParent(r))return!0;if(!s)return!1;if(t.endOfTextblock(e>0?"forward":"backward"))return!0;let i=!n.textOffset&&(e<0?n.nodeBefore:n.nodeAfter);if(i&&!i.isText){let o=t.state.tr;return e<0?o.delete(n.pos-i.nodeSize,n.pos):o.delete(n.pos,n.pos+i.nodeSize),t.dispatch(o),!0}return!1}function xc(t,e,n){t.domObserver.stop(),e.contentEditable=n,t.domObserver.start()}function Lp(t){if(!le||t.state.selection.$head.parentOffset>0)return!1;let{focusNode:e,focusOffset:n}=t.domSelectionRange();if(e&&e.nodeType==1&&n==0&&e.firstChild&&e.firstChild.contentEditable=="false"){let r=e.firstChild;xc(t,r,"true"),setTimeout(()=>xc(t,r,"false"),20)}return!1}function zp(t){let e="";return t.ctrlKey&&(e+="c"),t.metaKey&&(e+="m"),t.altKey&&(e+="a"),t.shiftKey&&(e+="s"),e}function Bp(t,e){let n=e.keyCode,r=zp(e);if(n==8||Me&&n==72&&r=="c")return bc(t,-1)||rn(t,-1);if(n==46&&!e.shiftKey||Me&&n==68&&r=="c")return bc(t,1)||rn(t,1);if(n==13||n==27)return!0;if(n==37||Me&&n==66&&r=="c"){let s=n==37?yc(t,t.state.selection.from)=="ltr"?-1:1:-1;return gc(t,s,r)||rn(t,s)}else if(n==39||Me&&n==70&&r=="c"){let s=n==39?yc(t,t.state.selection.from)=="ltr"?1:-1:1;return gc(t,s,r)||rn(t,s)}else{if(n==38||Me&&n==80&&r=="c")return kc(t,-1,r)||rn(t,-1);if(n==40||Me&&n==78&&r=="c")return Lp(t)||kc(t,1,r)||rn(t,1);if(r==(Me?"m":"c")&&(n==66||n==73||n==89||n==90))return!0}return!1}function fo(t,e){t.someProp("transformCopied",f=>{e=f(e,t)});let n=[],{content:r,openStart:s,openEnd:i}=e;for(;s>1&&i>1&&r.childCount==1&&r.firstChild.childCount==1;){s--,i--;let f=r.firstChild;n.push(f.type.name,f.attrs!=f.type.defaultAttrs?f.attrs:null),r=f.content}let o=t.someProp("clipboardSerializer")||qe.fromSchema(t.state.schema),l=tu(),a=l.createElement("div");a.appendChild(o.serializeFragment(r,{document:l}));let c=a.firstChild,u,d=0;for(;c&&c.nodeType==1&&(u=eu[c.nodeName.toLowerCase()]);){for(let f=u.length-1;f>=0;f--){let p=l.createElement(u[f]);for(;a.firstChild;)p.appendChild(a.firstChild);a.appendChild(p),d++}c=a.firstChild}c&&c.nodeType==1&&c.setAttribute("data-pm-slice",`${s} ${i}${d?` -${d}`:""} ${JSON.stringify(n)}`);let h=t.someProp("clipboardTextSerializer",f=>f(e,t))||e.content.textBetween(0,e.content.size,` - -`);return{dom:a,text:h,slice:e}}function Xc(t,e,n,r,s){let i=s.parent.type.spec.code,o,l;if(!n&&!e)return null;let a=!!e&&(r||i||!n);if(a){if(t.someProp("transformPastedText",h=>{e=h(e,i||r,t)}),i)return l=new C(b.from(t.state.schema.text(e.replace(/\r\n?/g,` -`))),0,0),t.someProp("transformPasted",h=>{l=h(l,t,!0)}),l;let d=t.someProp("clipboardTextParser",h=>h(e,s,r,t));if(d)l=d;else{let h=s.marks(),{schema:f}=t.state,p=qe.fromSchema(f);o=document.createElement("div"),e.split(/(?:\r\n?|\n)+/).forEach(m=>{let g=o.appendChild(document.createElement("p"));m&&g.appendChild(p.serializeNode(f.text(m,h)))})}}else t.someProp("transformPastedHTML",d=>{n=d(n,t)}),o=_p(n),zn&&Vp(o);let c=o&&o.querySelector("[data-pm-slice]"),u=c&&/^(\d+) (\d+)(?: -(\d+))? (.*)/.exec(c.getAttribute("data-pm-slice")||"");if(u&&u[3])for(let d=+u[3];d>0;d--){let h=o.firstChild;for(;h&&h.nodeType!=1;)h=h.nextSibling;if(!h)break;o=h}if(l||(l=(t.someProp("clipboardParser")||t.someProp("domParser")||Ie.fromSchema(t.state.schema)).parseSlice(o,{preserveWhitespace:!!(a||u),context:s,ruleFromNode(h){return h.nodeName=="BR"&&!h.nextSibling&&h.parentNode&&!$p.test(h.parentNode.nodeName)?{ignore:!0}:null}})),u)l=Wp(wc(l,+u[1],+u[2]),u[4]);else if(l=C.maxOpen(Hp(l.content,s),!0),l.openStart||l.openEnd){let d=0,h=0;for(let f=l.content.firstChild;d{l=d(l,t,a)}),l}var $p=/^(a|abbr|acronym|b|cite|code|del|em|i|ins|kbd|label|output|q|ruby|s|samp|span|strong|sub|sup|time|u|tt|var)$/i;function Hp(t,e){if(t.childCount<2)return t;for(let n=e.depth;n>=0;n--){let s=e.node(n).contentMatchAt(e.index(n)),i,o=[];if(t.forEach(l=>{if(!o)return;let a=s.findWrapping(l.type),c;if(!a)return o=null;if(c=o.length&&i.length&&Yc(a,i,l,o[o.length-1],0))o[o.length-1]=c;else{o.length&&(o[o.length-1]=Zc(o[o.length-1],i.length));let u=Qc(l,a);o.push(u),s=s.matchType(u.type),i=a}}),o)return b.from(o)}return t}function Qc(t,e,n=0){for(let r=e.length-1;r>=n;r--)t=e[r].create(null,b.from(t));return t}function Yc(t,e,n,r,s){if(s1&&(i=0),s=n&&(l=e<0?o.contentMatchAt(0).fillBefore(l,i<=s).append(l):l.append(o.contentMatchAt(o.childCount).fillBefore(b.empty,!0))),t.replaceChild(e<0?0:t.childCount-1,o.copy(l))}function wc(t,e,n){return en})),Vi.createHTML(t)):t}function _p(t){let e=/^(\s*]*>)*/.exec(t);e&&(t=t.slice(e[0].length));let n=tu().createElement("div"),r=/<([a-z][^>\s]+)/i.exec(t),s;if((s=r&&eu[r[1].toLowerCase()])&&(t=s.map(i=>"<"+i+">").join("")+t+s.map(i=>"").reverse().join("")),n.innerHTML=Fp(t),s)for(let i=0;i=0;l-=2){let a=n.nodes[r[l]];if(!a||a.hasRequiredAttrs())break;s=b.from(a.create(r[l+1],s)),i++,o++}return new C(s,i,o)}var de={},he={},jp={touchstart:!0,touchmove:!0},ro=class{constructor(){this.shiftKey=!1,this.mouseDown=null,this.lastKeyCode=null,this.lastKeyCodeTime=0,this.lastClick={time:0,x:0,y:0,type:"",button:0},this.lastSelectionOrigin=null,this.lastSelectionTime=0,this.lastIOSEnter=0,this.lastIOSEnterFallbackTimeout=-1,this.lastFocus=0,this.lastTouch=0,this.lastChromeDelete=0,this.composing=!1,this.compositionNode=null,this.composingTimeout=-1,this.compositionNodes=[],this.compositionEndedAt=-2e8,this.compositionID=1,this.badSafariComposition=!1,this.compositionPendingChanges=0,this.domChangeCount=0,this.eventHandlers=Object.create(null),this.hideSelectionGuard=null}};function Kp(t){for(let e in de){let n=de[e];t.dom.addEventListener(e,t.input.eventHandlers[e]=r=>{qp(t,r)&&!po(t,r)&&(t.editable||!(r.type in he))&&n(t,r)},jp[e]?{passive:!0}:void 0)}le&&t.dom.addEventListener("input",()=>null),so(t)}function ht(t,e){t.input.lastSelectionOrigin=e,t.input.lastSelectionTime=Date.now()}function Up(t){t.domObserver.stop();for(let e in t.input.eventHandlers)t.dom.removeEventListener(e,t.input.eventHandlers[e]);clearTimeout(t.input.composingTimeout),clearTimeout(t.input.lastIOSEnterFallbackTimeout)}function so(t){t.someProp("handleDOMEvents",e=>{for(let n in e)t.input.eventHandlers[n]||t.dom.addEventListener(n,t.input.eventHandlers[n]=r=>po(t,r))})}function po(t,e){return t.someProp("handleDOMEvents",n=>{let r=n[e.type];return r?r(t,e)||e.defaultPrevented:!1})}function qp(t,e){if(!e.bubbles)return!0;if(e.defaultPrevented)return!1;for(let n=e.target;n!=t.dom;n=n.parentNode)if(!n||n.nodeType==11||n.pmViewDesc&&n.pmViewDesc.stopEvent(e))return!1;return!0}function Jp(t,e){!po(t,e)&&de[e.type]&&(t.editable||!(e.type in he))&&de[e.type](t,e)}he.keydown=(t,e)=>{let n=e;if(t.input.shiftKey=n.keyCode==16||n.shiftKey,!ru(t,n)&&(t.input.lastKeyCode=n.keyCode,t.input.lastKeyCodeTime=Date.now(),!(Ze&&ne&&n.keyCode==13)))if(n.keyCode!=229&&t.domObserver.forceFlush(),an&&n.keyCode==13&&!n.ctrlKey&&!n.altKey&&!n.metaKey){let r=Date.now();t.input.lastIOSEnter=r,t.input.lastIOSEnterFallbackTimeout=setTimeout(()=>{t.input.lastIOSEnter==r&&(t.someProp("handleKeyDown",s=>s(t,At(13,"Enter"))),t.input.lastIOSEnter=0)},200)}else t.someProp("handleKeyDown",r=>r(t,n))||Bp(t,n)?n.preventDefault():ht(t,"key")};he.keyup=(t,e)=>{e.keyCode==16&&(t.input.shiftKey=!1)};he.keypress=(t,e)=>{let n=e;if(ru(t,n)||!n.charCode||n.ctrlKey&&!n.altKey||Me&&n.metaKey)return;if(t.someProp("handleKeyPress",s=>s(t,n))){n.preventDefault();return}let r=t.state.selection;if(!(r instanceof A)||!r.$from.sameParent(r.$to)){let s=String.fromCharCode(n.charCode),i=()=>t.state.tr.insertText(s).scrollIntoView();!/[\r\n]/.test(s)&&!t.someProp("handleTextInput",o=>o(t,r.$from.pos,r.$to.pos,s,i))&&t.dispatch(i()),n.preventDefault()}};function Pr(t){return{left:t.clientX,top:t.clientY}}function Gp(t,e){let n=e.x-t.clientX,r=e.y-t.clientY;return n*n+r*r<100}function mo(t,e,n,r,s){if(r==-1)return!1;let i=t.state.doc.resolve(r);for(let o=i.depth+1;o>0;o--)if(t.someProp(e,l=>o>i.depth?l(t,n,i.nodeAfter,i.before(o),s,!0):l(t,n,i.node(o),i.before(o),s,!1)))return!0;return!1}function on(t,e,n){if(t.focused||t.focus(),t.state.selection.eq(e))return;let r=t.state.tr.setSelection(e);n=="pointer"&&r.setMeta("pointer",!0),t.dispatch(r)}function Xp(t,e){if(e==-1)return!1;let n=t.state.doc.resolve(e),r=n.nodeAfter;return r&&r.isAtom&&E.isSelectable(r)?(on(t,new E(n),"pointer"),!0):!1}function Qp(t,e){if(e==-1)return!1;let n=t.state.selection,r,s;n instanceof E&&(r=n.node);let i=t.state.doc.resolve(e);for(let o=i.depth+1;o>0;o--){let l=o>i.depth?i.nodeAfter:i.node(o);if(E.isSelectable(l)){r&&n.$from.depth>0&&o>=n.$from.depth&&i.before(n.$from.depth+1)==n.$from.pos?s=i.before(n.$from.depth):s=i.before(o);break}}return s!=null?(on(t,E.create(t.state.doc,s),"pointer"),!0):!1}function Yp(t,e,n,r,s){return mo(t,"handleClickOn",e,n,r)||t.someProp("handleClick",i=>i(t,e,r))||(s?Qp(t,n):Xp(t,n))}function Zp(t,e,n,r){return mo(t,"handleDoubleClickOn",e,n,r)||t.someProp("handleDoubleClick",s=>s(t,e,r))}function em(t,e,n,r){return mo(t,"handleTripleClickOn",e,n,r)||t.someProp("handleTripleClick",s=>s(t,e,r))||tm(t,n,r)}function tm(t,e,n){if(n.button!=0)return!1;let r=t.state.doc;if(e==-1)return r.inlineContent?(on(t,A.create(r,0,r.content.size),"pointer"),!0):!1;let s=r.resolve(e);for(let i=s.depth+1;i>0;i--){let o=i>s.depth?s.nodeAfter:s.node(i),l=s.before(i);if(o.inlineContent)on(t,A.create(r,l+1,l+1+o.content.size),"pointer");else if(E.isSelectable(o))on(t,E.create(r,l),"pointer");else continue;return!0}}function go(t){return Er(t)}var nu=Me?"metaKey":"ctrlKey";de.mousedown=(t,e)=>{let n=e;t.input.shiftKey=n.shiftKey;let r=go(t),s=Date.now(),i="singleClick";s-t.input.lastClick.time<500&&Gp(n,t.input.lastClick)&&!n[nu]&&t.input.lastClick.button==n.button&&(t.input.lastClick.type=="singleClick"?i="doubleClick":t.input.lastClick.type=="doubleClick"&&(i="tripleClick")),t.input.lastClick={time:s,x:n.clientX,y:n.clientY,type:i,button:n.button};let o=t.posAtCoords(Pr(n));o&&(i=="singleClick"?(t.input.mouseDown&&t.input.mouseDown.done(),t.input.mouseDown=new io(t,o,n,!!r)):(i=="doubleClick"?Zp:em)(t,o.pos,o.inside,n)?n.preventDefault():ht(t,"pointer"))};var io=class{constructor(e,n,r,s){this.view=e,this.pos=n,this.event=r,this.flushed=s,this.delayedSelectionSync=!1,this.mightDrag=null,this.startDoc=e.state.doc,this.selectNode=!!r[nu],this.allowDefault=r.shiftKey;let i,o;if(n.inside>-1)i=e.state.doc.nodeAt(n.inside),o=n.inside;else{let u=e.state.doc.resolve(n.pos);i=u.parent,o=u.depth?u.before():0}let l=s?null:r.target,a=l?e.docView.nearestDesc(l,!0):null;this.target=a&&a.nodeDOM.nodeType==1?a.nodeDOM:null;let{selection:c}=e.state;(r.button==0&&i.type.spec.draggable&&i.type.spec.selectable!==!1||c instanceof E&&c.from<=o&&c.to>o)&&(this.mightDrag={node:i,pos:o,addAttr:!!(this.target&&!this.target.draggable),setUneditable:!!(this.target&&ve&&!this.target.hasAttribute("contentEditable"))}),this.target&&this.mightDrag&&(this.mightDrag.addAttr||this.mightDrag.setUneditable)&&(this.view.domObserver.stop(),this.mightDrag.addAttr&&(this.target.draggable=!0),this.mightDrag.setUneditable&&setTimeout(()=>{this.view.input.mouseDown==this&&this.target.setAttribute("contentEditable","false")},20),this.view.domObserver.start()),e.root.addEventListener("mouseup",this.up=this.up.bind(this)),e.root.addEventListener("mousemove",this.move=this.move.bind(this)),ht(e,"pointer")}done(){this.view.root.removeEventListener("mouseup",this.up),this.view.root.removeEventListener("mousemove",this.move),this.mightDrag&&this.target&&(this.view.domObserver.stop(),this.mightDrag.addAttr&&this.target.removeAttribute("draggable"),this.mightDrag.setUneditable&&this.target.removeAttribute("contentEditable"),this.view.domObserver.start()),this.delayedSelectionSync&&setTimeout(()=>et(this.view)),this.view.input.mouseDown=null}up(e){if(this.done(),!this.view.dom.contains(e.target))return;let n=this.pos;this.view.state.doc!=this.startDoc&&(n=this.view.posAtCoords(Pr(e))),this.updateAllowDefault(e),this.allowDefault||!n?ht(this.view,"pointer"):Yp(this.view,n.pos,n.inside,e,this.selectNode)?e.preventDefault():e.button==0&&(this.flushed||le&&this.mightDrag&&!this.mightDrag.node.isAtom||ne&&!this.view.state.selection.visible&&Math.min(Math.abs(n.pos-this.view.state.selection.from),Math.abs(n.pos-this.view.state.selection.to))<=2)?(on(this.view,R.near(this.view.state.doc.resolve(n.pos)),"pointer"),e.preventDefault()):ht(this.view,"pointer")}move(e){this.updateAllowDefault(e),ht(this.view,"pointer"),e.buttons==0&&this.done()}updateAllowDefault(e){!this.allowDefault&&(Math.abs(this.event.x-e.clientX)>4||Math.abs(this.event.y-e.clientY)>4)&&(this.allowDefault=!0)}};de.touchstart=t=>{t.input.lastTouch=Date.now(),go(t),ht(t,"pointer")};de.touchmove=t=>{t.input.lastTouch=Date.now(),ht(t,"pointer")};de.contextmenu=t=>go(t);function ru(t,e){return t.composing?!0:le&&Math.abs(e.timeStamp-t.input.compositionEndedAt)<500?(t.input.compositionEndedAt=-2e8,!0):!1}var nm=Ze?5e3:-1;he.compositionstart=he.compositionupdate=t=>{if(!t.composing){t.domObserver.flush();let{state:e}=t,n=e.selection.$to;if(e.selection instanceof A&&(e.storedMarks||!n.textOffset&&n.parentOffset&&n.nodeBefore.marks.some(r=>r.type.spec.inclusive===!1)||ne&&zc&&rm(t)))t.markCursor=t.state.storedMarks||n.marks(),Er(t,!0),t.markCursor=null;else if(Er(t,!e.selection.empty),ve&&e.selection.empty&&n.parentOffset&&!n.textOffset&&n.nodeBefore.marks.length){let r=t.domSelectionRange();for(let s=r.focusNode,i=r.focusOffset;s&&s.nodeType==1&&i!=0;){let o=i<0?s.lastChild:s.childNodes[i-1];if(!o)break;if(o.nodeType==3){let l=t.domSelection();l&&l.collapse(o,o.nodeValue.length);break}else s=o,i=-1}}t.input.composing=!0}su(t,nm)};function rm(t){let{focusNode:e,focusOffset:n}=t.domSelectionRange();if(!e||e.nodeType!=1||n>=e.childNodes.length)return!1;let r=e.childNodes[n];return r.nodeType==1&&r.contentEditable=="false"}he.compositionend=(t,e)=>{t.composing&&(t.input.composing=!1,t.input.compositionEndedAt=e.timeStamp,t.input.compositionPendingChanges=t.domObserver.pendingRecords().length?t.input.compositionID:0,t.input.compositionNode=null,t.input.badSafariComposition?t.domObserver.forceFlush():t.input.compositionPendingChanges&&Promise.resolve().then(()=>t.domObserver.flush()),t.input.compositionID++,su(t,20))};function su(t,e){clearTimeout(t.input.composingTimeout),e>-1&&(t.input.composingTimeout=setTimeout(()=>Er(t),e))}function iu(t){for(t.composing&&(t.input.composing=!1,t.input.compositionEndedAt=im());t.input.compositionNodes.length>0;)t.input.compositionNodes.pop().markParentsDirty()}function sm(t){let e=t.domSelectionRange();if(!e.focusNode)return null;let n=tp(e.focusNode,e.focusOffset),r=np(e.focusNode,e.focusOffset);if(n&&r&&n!=r){let s=r.pmViewDesc,i=t.domObserver.lastChangedTextNode;if(n==i||r==i)return i;if(!s||!s.isText(r.nodeValue))return r;if(t.input.compositionNode==r){let o=n.pmViewDesc;if(!(!o||!o.isText(n.nodeValue)))return r}}return n||r}function im(){let t=document.createEvent("Event");return t.initEvent("event",!0,!0),t.timeStamp}function Er(t,e=!1){if(!(Ze&&t.domObserver.flushingSoon>=0)){if(t.domObserver.forceFlush(),iu(t),e||t.docView&&t.docView.dirty){let n=uo(t),r=t.state.selection;return n&&!n.eq(r)?t.dispatch(t.state.tr.setSelection(n)):(t.markCursor||e)&&!r.$from.node(r.$from.sharedDepth(r.to)).inlineContent?t.dispatch(t.state.tr.deleteSelection()):t.updateState(t.state),!0}return!1}}function om(t,e){if(!t.dom.parentNode)return;let n=t.dom.parentNode.appendChild(document.createElement("div"));n.appendChild(e),n.style.cssText="position: fixed; left: -10000px; top: 10px";let r=getSelection(),s=document.createRange();s.selectNodeContents(e),t.dom.blur(),r.removeAllRanges(),r.addRange(s),setTimeout(()=>{n.parentNode&&n.parentNode.removeChild(n),t.focus()},50)}var On=ge&&ft<15||an&&op<604;de.copy=he.cut=(t,e)=>{let n=e,r=t.state.selection,s=n.type=="cut";if(r.empty)return;let i=On?null:n.clipboardData,o=r.content(),{dom:l,text:a}=fo(t,o);i?(n.preventDefault(),i.clearData(),i.setData("text/html",l.innerHTML),i.setData("text/plain",a)):om(t,l),s&&t.dispatch(t.state.tr.deleteSelection().scrollIntoView().setMeta("uiEvent","cut"))};function lm(t){return t.openStart==0&&t.openEnd==0&&t.content.childCount==1?t.content.firstChild:null}function am(t,e){if(!t.dom.parentNode)return;let n=t.input.shiftKey||t.state.selection.$from.parent.type.spec.code,r=t.dom.parentNode.appendChild(document.createElement(n?"textarea":"div"));n||(r.contentEditable="true"),r.style.cssText="position: fixed; left: -10000px; top: 10px",r.focus();let s=t.input.shiftKey&&t.input.lastKeyCode!=45;setTimeout(()=>{t.focus(),r.parentNode&&r.parentNode.removeChild(r),n?In(t,r.value,null,s,e):In(t,r.textContent,r.innerHTML,s,e)},50)}function In(t,e,n,r,s){let i=Xc(t,e,n,r,t.state.selection.$from);if(t.someProp("handlePaste",a=>a(t,s,i||C.empty)))return!0;if(!i)return!1;let o=lm(i),l=o?t.state.tr.replaceSelectionWith(o,r):t.state.tr.replaceSelection(i);return t.dispatch(l.scrollIntoView().setMeta("paste",!0).setMeta("uiEvent","paste")),!0}function ou(t){let e=t.getData("text/plain")||t.getData("Text");if(e)return e;let n=t.getData("text/uri-list");return n?n.replace(/\r?\n/g," "):""}he.paste=(t,e)=>{let n=e;if(t.composing&&!Ze)return;let r=On?null:n.clipboardData,s=t.input.shiftKey&&t.input.lastKeyCode!=45;r&&In(t,ou(r),r.getData("text/html"),s,n)?n.preventDefault():am(t,n)};var Nr=class{constructor(e,n,r){this.slice=e,this.move=n,this.node=r}},cm=Me?"altKey":"ctrlKey";function lu(t,e){let n=t.someProp("dragCopies",r=>!r(e));return n??!e[cm]}de.dragstart=(t,e)=>{let n=e,r=t.input.mouseDown;if(r&&r.done(),!n.dataTransfer)return;let s=t.state.selection,i=s.empty?null:t.posAtCoords(Pr(n)),o;if(!(i&&i.pos>=s.from&&i.pos<=(s instanceof E?s.to-1:s.to))){if(r&&r.mightDrag)o=E.create(t.state.doc,r.mightDrag.pos);else if(n.target&&n.target.nodeType==1){let d=t.docView.nearestDesc(n.target,!0);d&&d.node.type.spec.draggable&&d!=t.docView&&(o=E.create(t.state.doc,d.posBefore))}}let l=(o||t.state.selection).content(),{dom:a,text:c,slice:u}=fo(t,l);(!n.dataTransfer.files.length||!ne||Lc>120)&&n.dataTransfer.clearData(),n.dataTransfer.setData(On?"Text":"text/html",a.innerHTML),n.dataTransfer.effectAllowed="copyMove",On||n.dataTransfer.setData("text/plain",c),t.dragging=new Nr(u,lu(t,n),o)};de.dragend=t=>{let e=t.dragging;window.setTimeout(()=>{t.dragging==e&&(t.dragging=null)},50)};he.dragover=he.dragenter=(t,e)=>e.preventDefault();he.drop=(t,e)=>{try{um(t,e,t.dragging)}finally{t.dragging=null}};function um(t,e,n){if(!e.dataTransfer)return;let r=t.posAtCoords(Pr(e));if(!r)return;let s=t.state.doc.resolve(r.pos),i=n&&n.slice;i?t.someProp("transformPasted",f=>{i=f(i,t,!1)}):i=Xc(t,ou(e.dataTransfer),On?null:e.dataTransfer.getData("text/html"),!1,s);let o=!!(n&&lu(t,e));if(t.someProp("handleDrop",f=>f(t,e,i||C.empty,o))){e.preventDefault();return}if(!i)return;e.preventDefault();let l=i?kr(t.state.doc,s.pos,i):s.pos;l==null&&(l=s.pos);let a=t.state.tr;if(o){let{node:f}=n;f?f.replace(a):a.deleteSelection()}let c=a.mapping.map(l),u=i.openStart==0&&i.openEnd==0&&i.content.childCount==1,d=a.doc;if(u?a.replaceRangeWith(c,c,i.content.firstChild):a.replaceRange(c,c,i),a.doc.eq(d))return;let h=a.doc.resolve(c);if(u&&E.isSelectable(i.content.firstChild)&&h.nodeAfter&&h.nodeAfter.sameMarkup(i.content.firstChild))a.setSelection(new E(h));else{let f=a.mapping.map(l);a.mapping.maps[a.mapping.maps.length-1].forEach((p,m,g,y)=>f=y),a.setSelection(ho(t,h,a.doc.resolve(f)))}t.focus(),t.dispatch(a.setMeta("uiEvent","drop"))}de.focus=t=>{t.input.lastFocus=Date.now(),t.focused||(t.domObserver.stop(),t.dom.classList.add("ProseMirror-focused"),t.domObserver.start(),t.focused=!0,setTimeout(()=>{t.docView&&t.hasFocus()&&!t.domObserver.currentSelection.eq(t.domSelectionRange())&&et(t)},20))};de.blur=(t,e)=>{let n=e;t.focused&&(t.domObserver.stop(),t.dom.classList.remove("ProseMirror-focused"),t.domObserver.start(),n.relatedTarget&&t.dom.contains(n.relatedTarget)&&t.domObserver.currentSelection.clear(),t.focused=!1)};de.beforeinput=(t,e)=>{if(ne&&Ze&&e.inputType=="deleteContentBackward"){t.domObserver.flushSoon();let{domChangeCount:r}=t.input;setTimeout(()=>{if(t.input.domChangeCount!=r||(t.dom.blur(),t.focus(),t.someProp("handleKeyDown",i=>i(t,At(8,"Backspace")))))return;let{$cursor:s}=t.state.selection;s&&s.pos>0&&t.dispatch(t.state.tr.delete(s.pos-1,s.pos).scrollIntoView())},50)}};for(let t in he)de[t]=he[t];function Dn(t,e){if(t==e)return!0;for(let n in t)if(t[n]!==e[n])return!1;for(let n in e)if(!(n in t))return!1;return!0}var Rr=class t{constructor(e,n){this.toDOM=e,this.spec=n||Ot,this.side=this.spec.side||0}map(e,n,r,s){let{pos:i,deleted:o}=e.mapResult(n.from+s,this.side<0?-1:1);return o?null:new Q(i-r,i-r,this)}valid(){return!0}eq(e){return this==e||e instanceof t&&(this.spec.key&&this.spec.key==e.spec.key||this.toDOM==e.toDOM&&Dn(this.spec,e.spec))}destroy(e){this.spec.destroy&&this.spec.destroy(e)}},Rt=class t{constructor(e,n){this.attrs=e,this.spec=n||Ot}map(e,n,r,s){let i=e.map(n.from+s,this.spec.inclusiveStart?-1:1)-r,o=e.map(n.to+s,this.spec.inclusiveEnd?1:-1)-r;return i>=o?null:new Q(i,o,this)}valid(e,n){return n.from=e&&(!i||i(l.spec))&&r.push(l.copy(l.from+s,l.to+s))}for(let o=0;oe){let l=this.children[o]+1;this.children[o+2].findInner(e-l,n-l,r,s+l,i)}}map(e,n,r){return this==oe||e.maps.length==0?this:this.mapInner(e,n,0,0,r||Ot)}mapInner(e,n,r,s,i){let o;for(let l=0;l{let c=a+r,u;if(u=cu(n,l,c)){for(s||(s=this.children.slice());il&&d.to=e){this.children[l]==e&&(r=this.children[l+2]);break}let i=e+1,o=i+n.content.size;for(let l=0;li&&a.type instanceof Rt){let c=Math.max(i,a.from)-i,u=Math.min(o,a.to)-i;cs.map(e,n,Ot));return t.from(r)}forChild(e,n){if(n.isLeaf)return K.empty;let r=[];for(let s=0;sn instanceof K)?e:e.reduce((n,r)=>n.concat(r instanceof K?r:r.members),[]))}}forEachSet(e){for(let n=0;n{let g=m-p-(f-h);for(let y=0;yk+u-d)continue;let w=l[y]+u-d;f>=w?l[y+1]=h<=w?-2:-1:h>=u&&g&&(l[y]+=g,l[y+1]+=g)}d+=g}),u=n.maps[c].map(u,-1)}let a=!1;for(let c=0;c=r.content.size){a=!0;continue}let h=n.map(t[c+1]+i,-1),f=h-s,{index:p,offset:m}=r.content.findIndex(d),g=r.maybeChild(p);if(g&&m==d&&m+g.nodeSize==f){let y=l[c+2].mapInner(n,g,u+1,t[c]+i+1,o);y!=oe?(l[c]=d,l[c+1]=f,l[c+2]=y):(l[c+1]=-2,a=!0)}else a=!0}if(a){let c=hm(l,t,e,n,s,i,o),u=Ir(c,r,0,o);e=u.local;for(let d=0;dn&&o.to{let c=cu(t,l,a+n);if(c){i=!0;let u=Ir(c,l,n+a+1,r);u!=oe&&s.push(a,a+l.nodeSize,u)}});let o=au(i?uu(t):t,-n).sort(It);for(let l=0;l0;)e++;t.splice(e,0,n)}function Wi(t){let e=[];return t.someProp("decorations",n=>{let r=n(t.state);r&&r!=oe&&e.push(r)}),t.cursorWrapper&&e.push(K.create(t.state.doc,[t.cursorWrapper.deco])),Or.from(e)}var fm={childList:!0,characterData:!0,characterDataOldValue:!0,attributes:!0,attributeOldValue:!0,subtree:!0},pm=ge&&ft<=11,lo=class{constructor(){this.anchorNode=null,this.anchorOffset=0,this.focusNode=null,this.focusOffset=0}set(e){this.anchorNode=e.anchorNode,this.anchorOffset=e.anchorOffset,this.focusNode=e.focusNode,this.focusOffset=e.focusOffset}clear(){this.anchorNode=this.focusNode=null}eq(e){return e.anchorNode==this.anchorNode&&e.anchorOffset==this.anchorOffset&&e.focusNode==this.focusNode&&e.focusOffset==this.focusOffset}},ao=class{constructor(e,n){this.view=e,this.handleDOMChange=n,this.queue=[],this.flushingSoon=-1,this.observer=null,this.currentSelection=new lo,this.onCharData=null,this.suppressingSelectionUpdates=!1,this.lastChangedTextNode=null,this.observer=window.MutationObserver&&new window.MutationObserver(r=>{for(let s=0;ss.type=="childList"&&s.removedNodes.length||s.type=="characterData"&&s.oldValue.length>s.target.nodeValue.length)?this.flushSoon():le&&e.composing&&r.some(s=>s.type=="childList"&&s.target.nodeName=="TR")?(e.input.badSafariComposition=!0,this.flushSoon()):this.flush()}),pm&&(this.onCharData=r=>{this.queue.push({target:r.target,type:"characterData",oldValue:r.prevValue}),this.flushSoon()}),this.onSelectionChange=this.onSelectionChange.bind(this)}flushSoon(){this.flushingSoon<0&&(this.flushingSoon=window.setTimeout(()=>{this.flushingSoon=-1,this.flush()},20))}forceFlush(){this.flushingSoon>-1&&(window.clearTimeout(this.flushingSoon),this.flushingSoon=-1,this.flush())}start(){this.observer&&(this.observer.takeRecords(),this.observer.observe(this.view.dom,fm)),this.onCharData&&this.view.dom.addEventListener("DOMCharacterDataModified",this.onCharData),this.connectSelection()}stop(){if(this.observer){let e=this.observer.takeRecords();if(e.length){for(let n=0;nthis.flush(),20)}this.observer.disconnect()}this.onCharData&&this.view.dom.removeEventListener("DOMCharacterDataModified",this.onCharData),this.disconnectSelection()}connectSelection(){this.view.dom.ownerDocument.addEventListener("selectionchange",this.onSelectionChange)}disconnectSelection(){this.view.dom.ownerDocument.removeEventListener("selectionchange",this.onSelectionChange)}suppressSelectionUpdates(){this.suppressingSelectionUpdates=!0,setTimeout(()=>this.suppressingSelectionUpdates=!1,50)}onSelectionChange(){if(mc(this.view)){if(this.suppressingSelectionUpdates)return et(this.view);if(ge&&ft<=11&&!this.view.state.selection.empty){let e=this.view.domSelectionRange();if(e.focusNode&&Dt(e.focusNode,e.focusOffset,e.anchorNode,e.anchorOffset))return this.flushSoon()}this.flush()}}setCurSelection(){this.currentSelection.set(this.view.domSelectionRange())}ignoreSelectionChange(e){if(!e.focusNode)return!0;let n=new Set,r;for(let i=e.focusNode;i;i=ln(i))n.add(i);for(let i=e.anchorNode;i;i=ln(i))if(n.has(i)){r=i;break}let s=r&&this.view.docView.nearestDesc(r);if(s&&s.ignoreMutation({type:"selection",target:r.nodeType==3?r.parentNode:r}))return this.setCurSelection(),!0}pendingRecords(){if(this.observer)for(let e of this.observer.takeRecords())this.queue.push(e);return this.queue}flush(){let{view:e}=this;if(!e.docView||this.flushingSoon>-1)return;let n=this.pendingRecords();n.length&&(this.queue=[]);let r=e.domSelectionRange(),s=!this.suppressingSelectionUpdates&&!this.currentSelection.eq(r)&&mc(e)&&!this.ignoreSelectionChange(r),i=-1,o=-1,l=!1,a=[];if(e.editable)for(let u=0;uu.nodeName=="BR")&&(e.input.lastKeyCode==8||e.input.lastKeyCode==46)){for(let u of a)if(u.nodeName=="BR"&&u.parentNode){let d=u.nextSibling;d&&d.nodeType==1&&d.contentEditable=="false"&&u.parentNode.removeChild(u)}}else if(ve&&a.length){let u=a.filter(d=>d.nodeName=="BR");if(u.length==2){let[d,h]=u;d.parentNode&&d.parentNode.parentNode==h.parentNode?h.remove():d.remove()}else{let{focusNode:d}=this.currentSelection;for(let h of u){let f=h.parentNode;f&&f.nodeName=="LI"&&(!d||ym(e,d)!=f)&&h.remove()}}}let c=null;i<0&&s&&e.input.lastFocus>Date.now()-200&&Math.max(e.input.lastTouch,e.input.lastClick.time)-1||s)&&(i>-1&&(e.docView.markDirty(i,o),mm(e)),e.input.badSafariComposition&&(e.input.badSafariComposition=!1,km(e,a)),this.handleDOMChange(i,o,l,a),e.docView&&e.docView.dirty?e.updateState(e.state):this.currentSelection.eq(r)||et(e),this.currentSelection.set(r))}registerMutation(e,n){if(n.indexOf(e.target)>-1)return null;let r=this.view.docView.nearestDesc(e.target);if(e.type=="attributes"&&(r==this.view.docView||e.attributeName=="contenteditable"||e.attributeName=="style"&&!e.oldValue&&!e.target.getAttribute("style"))||!r||r.ignoreMutation(e))return null;if(e.type=="childList"){for(let u=0;us;g--){let y=r.childNodes[g-1],k=y.pmViewDesc;if(y.nodeName=="BR"&&!k){i=g;break}if(!k||k.size)break}let d=t.state.doc,h=t.someProp("domParser")||Ie.fromSchema(t.state.schema),f=d.resolve(o),p=null,m=h.parse(r,{topNode:f.parent,topMatch:f.parent.contentMatchAt(f.index()),topOpen:!0,from:s,to:i,preserveWhitespace:f.parent.type.whitespace=="pre"?"full":!0,findPositions:c,ruleFromNode:xm,context:f});if(c&&c[0].pos!=null){let g=c[0].pos,y=c[1]&&c[1].pos;y==null&&(y=g),p={anchor:g+o,head:y+o}}return{doc:m,sel:p,from:o,to:l}}function xm(t){let e=t.pmViewDesc;if(e)return e.parseRule();if(t.nodeName=="BR"&&t.parentNode){if(le&&/^(ul|ol)$/i.test(t.parentNode.nodeName)){let n=document.createElement("div");return n.appendChild(document.createElement("li")),{skip:n}}else if(t.parentNode.lastChild==t||le&&/^(tr|table)$/i.test(t.parentNode.nodeName))return{ignore:!0}}else if(t.nodeName=="IMG"&&t.getAttribute("mark-placeholder"))return{ignore:!0};return null}var wm=/^(a|abbr|acronym|b|bd[io]|big|br|button|cite|code|data(list)?|del|dfn|em|i|img|ins|kbd|label|map|mark|meter|output|q|ruby|s|samp|small|span|strong|su[bp]|time|u|tt|var)$/i;function Sm(t,e,n,r,s){let i=t.input.compositionPendingChanges||(t.composing?t.input.compositionID:0);if(t.input.compositionPendingChanges=0,e<0){let v=t.input.lastSelectionTime>Date.now()-50?t.input.lastSelectionOrigin:null,D=uo(t,v);if(D&&!t.state.selection.eq(D)){if(ne&&Ze&&t.input.lastKeyCode===13&&Date.now()-100ke(t,At(13,"Enter"))))return;let z=t.state.tr.setSelection(D);v=="pointer"?z.setMeta("pointer",!0):v=="key"&&z.scrollIntoView(),i&&z.setMeta("composition",i),t.dispatch(z)}return}let o=t.state.doc.resolve(e),l=o.sharedDepth(n);e=o.before(l+1),n=t.state.doc.resolve(n).after(l+1);let a=t.state.selection,c=bm(t,e,n),u=t.state.doc,d=u.slice(c.from,c.to),h,f;t.input.lastKeyCode===8&&Date.now()-100Date.now()-225||Ze)&&s.some(v=>v.nodeType==1&&!wm.test(v.nodeName))&&(!p||p.endA>=p.endB)&&t.someProp("handleKeyDown",v=>v(t,At(13,"Enter")))){t.input.lastIOSEnter=0;return}if(!p)if(r&&a instanceof A&&!a.empty&&a.$head.sameParent(a.$anchor)&&!t.composing&&!(c.sel&&c.sel.anchor!=c.sel.head))p={start:a.from,endA:a.to,endB:a.to};else{if(c.sel){let v=Ac(t,t.state.doc,c.sel);if(v&&!v.eq(t.state.selection)){let D=t.state.tr.setSelection(v);i&&D.setMeta("composition",i),t.dispatch(D)}}return}t.state.selection.fromt.state.selection.from&&p.start<=t.state.selection.from+2&&t.state.selection.from>=c.from?p.start=t.state.selection.from:p.endA=t.state.selection.to-2&&t.state.selection.to<=c.to&&(p.endB+=t.state.selection.to-p.endA,p.endA=t.state.selection.to)),ge&&ft<=11&&p.endB==p.start+1&&p.endA==p.start&&p.start>c.from&&c.doc.textBetween(p.start-c.from-1,p.start-c.from+1)==" \xA0"&&(p.start--,p.endA--,p.endB--);let m=c.doc.resolveNoCache(p.start-c.from),g=c.doc.resolveNoCache(p.endB-c.from),y=u.resolve(p.start),k=m.sameParent(g)&&m.parent.inlineContent&&y.end()>=p.endA;if((an&&t.input.lastIOSEnter>Date.now()-225&&(!k||s.some(v=>v.nodeName=="DIV"||v.nodeName=="P"))||!k&&m.posv(t,At(13,"Enter")))){t.input.lastIOSEnter=0;return}if(t.state.selection.anchor>p.start&&Mm(u,p.start,p.endA,m,g)&&t.someProp("handleKeyDown",v=>v(t,At(8,"Backspace")))){Ze&&ne&&t.domObserver.suppressSelectionUpdates();return}ne&&p.endB==p.start&&(t.input.lastChromeDelete=Date.now()),Ze&&!k&&m.start()!=g.start()&&g.parentOffset==0&&m.depth==g.depth&&c.sel&&c.sel.anchor==c.sel.head&&c.sel.head==p.endA&&(p.endB-=2,g=c.doc.resolveNoCache(p.endB-c.from),setTimeout(()=>{t.someProp("handleKeyDown",function(v){return v(t,At(13,"Enter"))})},20));let w=p.start,M=p.endA,S=v=>{let D=v||t.state.tr.replace(w,M,c.doc.slice(p.start-c.from,p.endB-c.from));if(c.sel){let z=Ac(t,D.doc,c.sel);z&&!(ne&&t.composing&&z.empty&&(p.start!=p.endB||t.input.lastChromeDeleteet(t),20));let v=S(t.state.tr.delete(w,M)),D=u.resolve(p.start).marksAcross(u.resolve(p.endA));D&&v.ensureMarks(D),t.dispatch(v)}else if(p.endA==p.endB&&(N=Cm(m.parent.content.cut(m.parentOffset,g.parentOffset),y.parent.content.cut(y.parentOffset,p.endA-y.start())))){let v=S(t.state.tr);N.type=="add"?v.addMark(w,M,N.mark):v.removeMark(w,M,N.mark),t.dispatch(v)}else if(m.parent.child(m.index()).isText&&m.index()==g.index()-(g.textOffset?0:1)){let v=m.parent.textBetween(m.parentOffset,g.parentOffset),D=()=>S(t.state.tr.insertText(v,w,M));t.someProp("handleTextInput",z=>z(t,w,M,v,D))||t.dispatch(D())}else t.dispatch(S());else t.dispatch(S())}function Ac(t,e,n){return Math.max(n.anchor,n.head)>e.content.size?null:ho(t,e.resolve(n.anchor),e.resolve(n.head))}function Cm(t,e){let n=t.firstChild.marks,r=e.firstChild.marks,s=n,i=r,o,l,a;for(let u=0;uu.mark(l.addToSet(u.marks));else if(s.length==0&&i.length==1)l=i[0],o="remove",a=u=>u.mark(l.removeFromSet(u.marks));else return null;let c=[];for(let u=0;un||ji(o,!0,!1)0&&(e||t.indexAfter(r)==t.node(r).childCount);)r--,s++,e=!1;if(n){let i=t.node(r).maybeChild(t.indexAfter(r));for(;i&&!i.isLeaf;)i=i.firstChild,s++}return s}function Tm(t,e,n,r,s){let i=t.findDiffStart(e,n);if(i==null)return null;let{a:o,b:l}=t.findDiffEnd(e,n+t.size,n+e.size);if(s=="end"){let a=Math.max(0,i-Math.min(o,l));r-=o+a-i}if(o=o?i-r:0;i-=a,i&&i=l?i-r:0;i-=a,i&&i=56320&&e<=57343&&n>=55296&&n<=56319}var Pn=class{constructor(e,n){this._root=null,this.focused=!1,this.trackWrites=null,this.mounted=!1,this.markCursor=null,this.cursorWrapper=null,this.lastSelectedViewDesc=void 0,this.input=new ro,this.prevDirectPlugins=[],this.pluginViews=[],this.requiresGeckoHackNode=!1,this.dragging=null,this._props=n,this.state=n.state,this.directPlugins=n.plugins||[],this.directPlugins.forEach(Dc),this.dispatch=this.dispatch.bind(this),this.dom=e&&e.mount||document.createElement("div"),e&&(e.appendChild?e.appendChild(this.dom):typeof e=="function"?e(this.dom):e.mount&&(this.mounted=!0)),this.editable=Oc(this),Rc(this),this.nodeViews=Ic(this),this.docView=cc(this.state.doc,Nc(this),Wi(this),this.dom,this),this.domObserver=new ao(this,(r,s,i,o)=>Sm(this,r,s,i,o)),this.domObserver.start(),Kp(this),this.updatePluginViews()}get composing(){return this.input.composing}get props(){if(this._props.state!=this.state){let e=this._props;this._props={};for(let n in e)this._props[n]=e[n];this._props.state=this.state}return this._props}update(e){e.handleDOMEvents!=this._props.handleDOMEvents&&so(this);let n=this._props;this._props=e,e.plugins&&(e.plugins.forEach(Dc),this.directPlugins=e.plugins),this.updateStateInner(e.state,n)}setProps(e){let n={};for(let r in this._props)n[r]=this._props[r];n.state=this.state;for(let r in e)n[r]=e[r];this.update(n)}updateState(e){this.updateStateInner(e,this._props)}updateStateInner(e,n){var r;let s=this.state,i=!1,o=!1;e.storedMarks&&this.composing&&(iu(this),o=!0),this.state=e;let l=s.plugins!=e.plugins||this._props.plugins!=n.plugins;if(l||this._props.plugins!=n.plugins||this._props.nodeViews!=n.nodeViews){let f=Ic(this);Am(f,this.nodeViews)&&(this.nodeViews=f,i=!0)}(l||n.handleDOMEvents!=this._props.handleDOMEvents)&&so(this),this.editable=Oc(this),Rc(this);let a=Wi(this),c=Nc(this),u=s.plugins!=e.plugins&&!s.doc.eq(e.doc)?"reset":e.scrollToSelection>s.scrollToSelection?"to selection":"preserve",d=i||!this.docView.matchesNode(e.doc,c,a);(d||!e.selection.eq(s.selection))&&(o=!0);let h=u=="preserve"&&o&&this.dom.style.overflowAnchor==null&&cp(this);if(o){this.domObserver.stop();let f=d&&(ge||ne)&&!this.composing&&!s.selection.empty&&!e.selection.empty&&vm(s.selection,e.selection);if(d){let p=ne?this.trackWrites=this.domSelectionRange().focusNode:null;this.composing&&(this.input.compositionNode=sm(this)),(i||!this.docView.update(e.doc,c,a,this))&&(this.docView.updateOuterDeco(c),this.docView.destroy(),this.docView=cc(e.doc,c,a,this.dom,this)),p&&(!this.trackWrites||!this.dom.contains(this.trackWrites))&&(f=!0)}f||!(this.input.mouseDown&&this.domObserver.currentSelection.eq(this.domSelectionRange())&&Rp(this))?et(this,f):(qc(this,e.selection),this.domObserver.setCurSelection()),this.domObserver.start()}this.updatePluginViews(s),!((r=this.dragging)===null||r===void 0)&&r.node&&!s.doc.eq(e.doc)&&this.updateDraggedNode(this.dragging,s),u=="reset"?this.dom.scrollTop=0:u=="to selection"?this.scrollToSelection():h&&up(h)}scrollToSelection(){let e=this.domSelectionRange().focusNode;if(!(!e||!this.dom.contains(e.nodeType==1?e:e.parentNode))){if(!this.someProp("handleScrollToSelection",n=>n(this)))if(this.state.selection instanceof E){let n=this.docView.domAfterPos(this.state.selection.from);n.nodeType==1&&rc(this,n.getBoundingClientRect(),e)}else rc(this,this.coordsAtPos(this.state.selection.head,1),e)}}destroyPluginViews(){let e;for(;e=this.pluginViews.pop();)e.destroy&&e.destroy()}updatePluginViews(e){if(!e||e.plugins!=this.state.plugins||this.directPlugins!=this.prevDirectPlugins){this.prevDirectPlugins=this.directPlugins,this.destroyPluginViews();for(let n=0;n0&&this.state.doc.nodeAt(i))==r.node&&(s=i)}this.dragging=new Nr(e.slice,e.move,s<0?void 0:E.create(this.state.doc,s))}someProp(e,n){let r=this._props&&this._props[e],s;if(r!=null&&(s=n?n(r):r))return s;for(let o=0;on.ownerDocument.getSelection()),this._root=n}return e||document}updateRoot(){this._root=null}posAtCoords(e){return gp(this,e)}coordsAtPos(e,n=1){return _c(this,e,n)}domAtPos(e,n=0){return this.docView.domFromPos(e,n)}nodeDOM(e){let n=this.docView.descAt(e);return n?n.nodeDOM:null}posAtDOM(e,n,r=-1){let s=this.docView.posFromDOM(e,n,r);if(s==null)throw new RangeError("DOM position not inside the editor");return s}endOfTextblock(e,n){return wp(this,n||this.state,e)}pasteHTML(e,n){return In(this,"",e,!1,n||new ClipboardEvent("paste"))}pasteText(e,n){return In(this,e,null,!0,n||new ClipboardEvent("paste"))}serializeForClipboard(e){return fo(this,e)}destroy(){this.docView&&(Up(this),this.destroyPluginViews(),this.mounted?(this.docView.update(this.state.doc,[],Wi(this),this),this.dom.textContent=""):this.dom.parentNode&&this.dom.parentNode.removeChild(this.dom),this.docView.destroy(),this.docView=null,Zf())}get isDestroyed(){return this.docView==null}dispatchEvent(e){return Jp(this,e)}domSelectionRange(){let e=this.domSelection();return e?le&&this.root.nodeType===11&&sp(this.dom.ownerDocument)==this.dom&&gm(this,e)||e:{focusNode:null,focusOffset:0,anchorNode:null,anchorOffset:0}}domSelection(){return this.root.getSelection()}};Pn.prototype.dispatch=function(t){let e=this._props.dispatchTransaction;e?e.call(this,t):this.updateState(this.state.apply(t))};function Nc(t){let e=Object.create(null);return e.class="ProseMirror",e.contenteditable=String(t.editable),t.someProp("attributes",n=>{if(typeof n=="function"&&(n=n(t.state)),n)for(let r in n)r=="class"?e.class+=" "+n[r]:r=="style"?e.style=(e.style?e.style+";":"")+n[r]:!e[r]&&r!="contenteditable"&&r!="nodeName"&&(e[r]=String(n[r]))}),e.translate||(e.translate="no"),[Q.node(0,t.state.doc.content.size,e)]}function Rc(t){if(t.markCursor){let e=document.createElement("img");e.className="ProseMirror-separator",e.setAttribute("mark-placeholder","true"),e.setAttribute("alt",""),t.cursorWrapper={dom:e,deco:Q.widget(t.state.selection.from,e,{raw:!0,marks:t.markCursor})}}else t.cursorWrapper=null}function Oc(t){return!t.someProp("editable",e=>e(t.state)===!1)}function vm(t,e){let n=Math.min(t.$anchor.sharedDepth(t.head),e.$anchor.sharedDepth(e.head));return t.$anchor.start(n)!=e.$anchor.start(n)}function Ic(t){let e=Object.create(null);function n(r){for(let s in r)Object.prototype.hasOwnProperty.call(e,s)||(e[s]=r[s])}return t.someProp("nodeViews",n),t.someProp("markViews",n),e}function Am(t,e){let n=0,r=0;for(let s in t){if(t[s]!=e[s])return!0;n++}for(let s in e)r++;return n!=r}function Dc(t){if(t.spec.state||t.spec.filterTransaction||t.spec.appendTransaction)throw new RangeError("Plugins passed directly to the view must not have a state component")}var tt={8:"Backspace",9:"Tab",10:"Enter",12:"NumLock",13:"Enter",16:"Shift",17:"Control",18:"Alt",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",44:"PrintScreen",45:"Insert",46:"Delete",59:";",61:"=",91:"Meta",92:"Meta",106:"*",107:"+",108:",",109:"-",110:".",111:"/",144:"NumLock",145:"ScrollLock",160:"Shift",161:"Shift",162:"Control",163:"Control",164:"Alt",165:"Alt",173:"-",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},zr={48:")",49:"!",50:"@",51:"#",52:"$",53:"%",54:"^",55:"&",56:"*",57:"(",59:":",61:"+",173:"_",186:":",187:"+",188:"<",189:"_",190:">",191:"?",192:"~",219:"{",220:"|",221:"}",222:'"'},Em=typeof navigator<"u"&&/Mac/.test(navigator.platform),Nm=typeof navigator<"u"&&/MSIE \d|Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);for(Y=0;Y<10;Y++)tt[48+Y]=tt[96+Y]=String(Y);var Y;for(Y=1;Y<=24;Y++)tt[Y+111]="F"+Y;var Y;for(Y=65;Y<=90;Y++)tt[Y]=String.fromCharCode(Y+32),zr[Y]=String.fromCharCode(Y);var Y;for(Lr in tt)zr.hasOwnProperty(Lr)||(zr[Lr]=tt[Lr]);var Lr;function du(t){var e=Em&&t.metaKey&&t.shiftKey&&!t.ctrlKey&&!t.altKey||Nm&&t.shiftKey&&t.key&&t.key.length==1||t.key=="Unidentified",n=!e&&t.key||(t.shiftKey?zr:tt)[t.keyCode]||t.key||"Unidentified";return n=="Esc"&&(n="Escape"),n=="Del"&&(n="Delete"),n=="Left"&&(n="ArrowLeft"),n=="Up"&&(n="ArrowUp"),n=="Right"&&(n="ArrowRight"),n=="Down"&&(n="ArrowDown"),n}var Rm=typeof navigator<"u"&&/Mac|iP(hone|[oa]d)/.test(navigator.platform),Om=typeof navigator<"u"&&/Win/.test(navigator.platform);function Im(t){let e=t.split(/-(?!$)/),n=e[e.length-1];n=="Space"&&(n=" ");let r,s,i,o;for(let l=0;l{for(var n in e)Pm(t,n,{get:e[n],enumerable:!0})};function Wr(t){let{state:e,transaction:n}=t,{selection:r}=n,{doc:s}=n,{storedMarks:i}=n;return{...e,apply:e.apply.bind(e),applyTransaction:e.applyTransaction.bind(e),plugins:e.plugins,schema:e.schema,reconfigure:e.reconfigure.bind(e),toJSON:e.toJSON.bind(e),get storedMarks(){return i},get selection(){return r},get doc(){return s},get tr(){return r=n.selection,s=n.doc,i=n.storedMarks,n}}}var jr=class{constructor(t){this.editor=t.editor,this.rawCommands=this.editor.extensionManager.commands,this.customState=t.state}get hasCustomState(){return!!this.customState}get state(){return this.customState||this.editor.state}get commands(){let{rawCommands:t,editor:e,state:n}=this,{view:r}=e,{tr:s}=n,i=this.buildProps(s);return Object.fromEntries(Object.entries(t).map(([o,l])=>[o,(...c)=>{let u=l(...c)(i);return!s.getMeta("preventDispatch")&&!this.hasCustomState&&r.dispatch(s),u}]))}get chain(){return()=>this.createChain()}get can(){return()=>this.createCan()}createChain(t,e=!0){let{rawCommands:n,editor:r,state:s}=this,{view:i}=r,o=[],l=!!t,a=t||s.tr,c=()=>(!l&&e&&!a.getMeta("preventDispatch")&&!this.hasCustomState&&i.dispatch(a),o.every(d=>d===!0)),u={...Object.fromEntries(Object.entries(n).map(([d,h])=>[d,(...p)=>{let m=this.buildProps(a,e),g=h(...p)(m);return o.push(g),u}])),run:c};return u}createCan(t){let{rawCommands:e,state:n}=this,r=!1,s=t||n.tr,i=this.buildProps(s,r);return{...Object.fromEntries(Object.entries(e).map(([l,a])=>[l,(...c)=>a(...c)({...i,dispatch:void 0})])),chain:()=>this.createChain(s,r)}}buildProps(t,e=!0){let{rawCommands:n,editor:r,state:s}=this,{view:i}=r,o={tr:t,editor:r,view:i,state:Wr({state:s,transaction:t}),dispatch:e?()=>{}:void 0,chain:()=>this.createChain(t,e),can:()=>this.createCan(t),get commands(){return Object.fromEntries(Object.entries(n).map(([l,a])=>[l,(...c)=>a(...c)(o)]))}};return o}},Ee={};Co(Ee,{blur:()=>Lm,clearContent:()=>zm,clearNodes:()=>Bm,command:()=>$m,createParagraphNear:()=>Hm,cut:()=>Fm,deleteCurrentNode:()=>_m,deleteNode:()=>Vm,deleteRange:()=>Wm,deleteSelection:()=>jm,enter:()=>Km,exitCode:()=>Um,extendMarkRange:()=>qm,first:()=>Jm,focus:()=>Xm,forEach:()=>Qm,insertContent:()=>Ym,insertContentAt:()=>tg,joinBackward:()=>sg,joinDown:()=>rg,joinForward:()=>ig,joinItemBackward:()=>og,joinItemForward:()=>lg,joinTextblockBackward:()=>ag,joinTextblockForward:()=>cg,joinUp:()=>ng,keyboardShortcut:()=>dg,lift:()=>hg,liftEmptyBlock:()=>fg,liftListItem:()=>pg,newlineInCode:()=>mg,resetAttributes:()=>gg,scrollIntoView:()=>yg,selectAll:()=>kg,selectNodeBackward:()=>bg,selectNodeForward:()=>xg,selectParentNode:()=>wg,selectTextblockEnd:()=>Sg,selectTextblockStart:()=>Cg,setContent:()=>Mg,setMark:()=>Hg,setMeta:()=>Fg,setNode:()=>_g,setNodeSelection:()=>Vg,setTextDirection:()=>Wg,setTextSelection:()=>jg,sinkListItem:()=>Kg,splitBlock:()=>Ug,splitListItem:()=>qg,toggleList:()=>Jg,toggleMark:()=>Gg,toggleNode:()=>Xg,toggleWrap:()=>Qg,undoInputRule:()=>Yg,unsetAllMarks:()=>Zg,unsetMark:()=>ey,unsetTextDirection:()=>ty,updateAttributes:()=>ny,wrapIn:()=>ry,wrapInList:()=>sy});var Lm=()=>({editor:t,view:e})=>(requestAnimationFrame(()=>{var n;t.isDestroyed||(e.dom.blur(),(n=window?.getSelection())==null||n.removeAllRanges())}),!0),zm=(t=!0)=>({commands:e})=>e.setContent("",{emitUpdate:t}),Bm=()=>({state:t,tr:e,dispatch:n})=>{let{selection:r}=e,{ranges:s}=r;return n&&s.forEach(({$from:i,$to:o})=>{t.doc.nodesBetween(i.pos,o.pos,(l,a)=>{if(l.type.isText)return;let{doc:c,mapping:u}=e,d=c.resolve(u.map(a)),h=c.resolve(u.map(a+l.nodeSize)),f=d.blockRange(h);if(!f)return;let p=Xe(f);if(l.type.isTextblock){let{defaultType:m}=d.parent.contentMatchAt(d.index());e.setNodeMarkup(f.start,m)}(p||p===0)&&e.lift(f,p)})}),!0},$m=t=>e=>t(e),Hm=()=>({state:t,dispatch:e})=>Pi(t,e),Fm=(t,e)=>({editor:n,tr:r})=>{let{state:s}=n,i=s.doc.slice(t.from,t.to);r.deleteRange(t.from,t.to);let o=r.mapping.map(e);return r.insert(o,i.content),r.setSelection(new A(r.doc.resolve(Math.max(o-1,0)))),!0},_m=()=>({tr:t,dispatch:e})=>{let{selection:n}=t,r=n.$anchor.node();if(r.content.size>0)return!1;let s=t.selection.$anchor;for(let i=s.depth;i>0;i-=1)if(s.node(i).type===r.type){if(e){let l=s.before(i),a=s.after(i);t.delete(l,a).scrollIntoView()}return!0}return!1};function J(t,e){if(typeof t=="string"){if(!e.nodes[t])throw Error(`There is no node type named '${t}'. Maybe you forgot to add the extension?`);return e.nodes[t]}return t}var Vm=t=>({tr:e,state:n,dispatch:r})=>{let s=J(t,n.schema),i=e.selection.$anchor;for(let o=i.depth;o>0;o-=1)if(i.node(o).type===s){if(r){let a=i.before(o),c=i.after(o);e.delete(a,c).scrollIntoView()}return!0}return!1},Wm=t=>({tr:e,dispatch:n})=>{let{from:r,to:s}=t;return n&&e.delete(r,s),!0},jm=()=>({state:t,dispatch:e})=>Sr(t,e),Km=()=>({commands:t})=>t.keyboardShortcut("Enter"),Um=()=>({state:t,dispatch:e})=>Di(t,e);function Mo(t){return Object.prototype.toString.call(t)==="[object RegExp]"}function _r(t,e,n={strict:!0}){let r=Object.keys(e);return r.length?r.every(s=>n.strict?e[s]===t[s]:Mo(e[s])?e[s].test(t[s]):e[s]===t[s]):!0}function Su(t,e,n={}){return t.find(r=>r.type===e&&_r(Object.fromEntries(Object.keys(n).map(s=>[s,r.attrs[s]])),n))}function fu(t,e,n={}){return!!Su(t,e,n)}function To(t,e,n){var r;if(!t||!e)return;let s=t.parent.childAfter(t.parentOffset);if((!s.node||!s.node.marks.some(u=>u.type===e))&&(s=t.parent.childBefore(t.parentOffset)),!s.node||!s.node.marks.some(u=>u.type===e)||(n=n||((r=s.node.marks[0])==null?void 0:r.attrs),!Su([...s.node.marks],e,n)))return;let o=s.index,l=t.start()+s.offset,a=o+1,c=l+s.node.nodeSize;for(;o>0&&fu([...t.parent.child(o-1).marks],e,n);)o-=1,l-=t.parent.child(o).nodeSize;for(;a({tr:n,state:r,dispatch:s})=>{let i=nt(t,r.schema),{doc:o,selection:l}=n,{$from:a,from:c,to:u}=l;if(s){let d=To(a,i,e);if(d&&d.from<=c&&d.to>=u){let h=A.create(o,d.from,d.to);n.setSelection(h)}}return!0},Jm=t=>e=>{let n=typeof t=="function"?t(e):t;for(let r=0;r({editor:n,view:r,tr:s,dispatch:i})=>{e={scrollIntoView:!0,...e};let o=()=>{(Vr()||pu())&&r.dom.focus(),Gm()&&!Vr()&&!pu()&&r.dom.focus({preventScroll:!0}),requestAnimationFrame(()=>{n.isDestroyed||(r.focus(),e?.scrollIntoView&&n.commands.scrollIntoView())})};try{if(r.hasFocus()&&t===null||t===!1)return!0}catch{return!1}if(i&&t===null&&!Cu(n.state.selection))return o(),!0;let l=Mu(s.doc,t)||n.state.selection,a=n.state.selection.eq(l);return i&&(a||s.setSelection(l),a&&s.storedMarks&&s.setStoredMarks(s.storedMarks),o()),!0},Qm=(t,e)=>n=>t.every((r,s)=>e(r,{...n,index:s})),Ym=(t,e)=>({tr:n,commands:r})=>r.insertContentAt({from:n.selection.from,to:n.selection.to},t,e),Tu=t=>{let e=t.childNodes;for(let n=e.length-1;n>=0;n-=1){let r=e[n];r.nodeType===3&&r.nodeValue&&/^(\n\s\s|\n)$/.test(r.nodeValue)?t.removeChild(r):r.nodeType===1&&Tu(r)}return t};function Hn(t){if(typeof window>"u")throw new Error("[tiptap error]: there is no window object available, so this function cannot be used");let e=`${t}`,n=new window.DOMParser().parseFromString(e,"text/html").body;return Tu(n)}function Vn(t,e,n){if(t instanceof me||t instanceof b)return t;n={slice:!0,parseOptions:{},...n};let r=typeof t=="object"&&t!==null,s=typeof t=="string";if(r)try{if(Array.isArray(t)&&t.length>0)return b.fromArray(t.map(l=>e.nodeFromJSON(l)));let o=e.nodeFromJSON(t);return n.errorOnInvalidContent&&o.check(),o}catch(i){if(n.errorOnInvalidContent)throw new Error("[tiptap error]: Invalid JSON content",{cause:i});return console.warn("[tiptap warn]: Invalid content.","Passed value:",t,"Error:",i),Vn("",e,n)}if(s){if(n.errorOnInvalidContent){let o=!1,l="",a=new Gt({topNode:e.spec.topNode,marks:e.spec.marks,nodes:e.spec.nodes.append({__tiptap__private__unknown__catch__all__node:{content:"inline*",group:"block",parseDOM:[{tag:"*",getAttrs:c=>(o=!0,l=typeof c=="string"?c:c.outerHTML,null)}]}})});if(n.slice?Ie.fromSchema(a).parseSlice(Hn(t),n.parseOptions):Ie.fromSchema(a).parse(Hn(t),n.parseOptions),n.errorOnInvalidContent&&o)throw new Error("[tiptap error]: Invalid HTML content",{cause:new Error(`Invalid element found: ${l}`)})}let i=Ie.fromSchema(e);return n.slice?i.parseSlice(Hn(t),n.parseOptions).content:i.parse(Hn(t),n.parseOptions)}return Vn("",e,n)}function Zm(t,e,n){let r=t.steps.length-1;if(r{o===0&&(o=u)}),t.setSelection(R.near(t.doc.resolve(o),n))}var eg=t=>!("type"in t),tg=(t,e,n)=>({tr:r,dispatch:s,editor:i})=>{var o;if(s){n={parseOptions:i.options.parseOptions,updateSelection:!0,applyInputRules:!1,applyPasteRules:!1,...n};let l,a=g=>{i.emit("contentError",{editor:i,error:g,disableCollaboration:()=>{"collaboration"in i.storage&&typeof i.storage.collaboration=="object"&&i.storage.collaboration&&(i.storage.collaboration.isDisabled=!0)}})},c={preserveWhitespace:"full",...n.parseOptions};if(!n.errorOnInvalidContent&&!i.options.enableContentCheck&&i.options.emitContentError)try{Vn(e,i.schema,{parseOptions:c,errorOnInvalidContent:!0})}catch(g){a(g)}try{l=Vn(e,i.schema,{parseOptions:c,errorOnInvalidContent:(o=n.errorOnInvalidContent)!=null?o:i.options.enableContentCheck})}catch(g){return a(g),!1}let{from:u,to:d}=typeof t=="number"?{from:t,to:t}:{from:t.from,to:t.to},h=!0,f=!0;if((eg(l)?l:[l]).forEach(g=>{g.check(),h=h?g.isText&&g.marks.length===0:!1,f=f?g.isBlock:!1}),u===d&&f){let{parent:g}=r.doc.resolve(u);g.isTextblock&&!g.type.spec.code&&!g.childCount&&(u-=1,d+=1)}let m;if(h){if(Array.isArray(e))m=e.map(g=>g.text||"").join("");else if(e instanceof b){let g="";e.forEach(y=>{y.text&&(g+=y.text)}),m=g}else typeof e=="object"&&e&&e.text?m=e.text:m=e;r.insertText(m,u,d)}else{m=l;let g=r.doc.resolve(u),y=g.node(),k=g.parentOffset===0,w=y.isText||y.isTextblock,M=y.content.size>0;k&&w&&M&&(u=Math.max(0,u-1)),r.replaceWith(u,d,m)}n.updateSelection&&Zm(r,r.steps.length-1,-1),n.applyInputRules&&r.setMeta("applyInputRules",{from:u,text:m}),n.applyPasteRules&&r.setMeta("applyPasteRules",{from:u,text:m})}return!0},ng=()=>({state:t,dispatch:e})=>Ka(t,e),rg=()=>({state:t,dispatch:e})=>Ua(t,e),sg=()=>({state:t,dispatch:e})=>Ti(t,e),ig=()=>({state:t,dispatch:e})=>Ei(t,e),og=()=>({state:t,dispatch:e,tr:n})=>{try{let r=Tt(t.doc,t.selection.$from.pos,-1);return r==null?!1:(n.join(r,2),e&&e(n),!0)}catch{return!1}},lg=()=>({state:t,dispatch:e,tr:n})=>{try{let r=Tt(t.doc,t.selection.$from.pos,1);return r==null?!1:(n.join(r,2),e&&e(n),!0)}catch{return!1}},ag=()=>({state:t,dispatch:e})=>_a(t,e),cg=()=>({state:t,dispatch:e})=>Va(t,e);function vu(){return typeof navigator<"u"?/Mac/.test(navigator.platform):!1}function ug(t){let e=t.split(/-(?!$)/),n=e[e.length-1];n==="Space"&&(n=" ");let r,s,i,o;for(let l=0;l({editor:e,view:n,tr:r,dispatch:s})=>{let i=ug(t).split(/-(?!$)/),o=i.find(c=>!["Alt","Ctrl","Meta","Shift"].includes(c)),l=new KeyboardEvent("keydown",{key:o==="Space"?" ":o,altKey:i.includes("Alt"),ctrlKey:i.includes("Ctrl"),metaKey:i.includes("Meta"),shiftKey:i.includes("Shift"),bubbles:!0,cancelable:!0}),a=e.captureTransaction(()=>{n.someProp("handleKeyDown",c=>c(n,l))});return a?.steps.forEach(c=>{let u=c.map(r.mapping);u&&s&&r.maybeStep(u)}),!0};function $e(t,e,n={}){let{from:r,to:s,empty:i}=t.selection,o=e?J(e,t.schema):null,l=[];t.doc.nodesBetween(r,s,(d,h)=>{if(d.isText)return;let f=Math.max(r,h),p=Math.min(s,h+d.nodeSize);l.push({node:d,from:f,to:p})});let a=s-r,c=l.filter(d=>o?o.name===d.node.type.name:!0).filter(d=>_r(d.node.attrs,n,{strict:!1}));return i?!!c.length:c.reduce((d,h)=>d+h.to-h.from,0)>=a}var hg=(t,e={})=>({state:n,dispatch:r})=>{let s=J(t,n.schema);return $e(n,s,e)?qa(n,r):!1},fg=()=>({state:t,dispatch:e})=>Li(t,e),pg=t=>({state:e,dispatch:n})=>{let r=J(t,e.schema);return Za(r)(e,n)},mg=()=>({state:t,dispatch:e})=>Oi(t,e);function Kr(t,e){return e.nodes[t]?"node":e.marks[t]?"mark":null}function mu(t,e){let n=typeof e=="string"?[e]:e;return Object.keys(t).reduce((r,s)=>(n.includes(s)||(r[s]=t[s]),r),{})}var gg=(t,e)=>({tr:n,state:r,dispatch:s})=>{let i=null,o=null,l=Kr(typeof t=="string"?t:t.name,r.schema);if(!l)return!1;l==="node"&&(i=J(t,r.schema)),l==="mark"&&(o=nt(t,r.schema));let a=!1;return n.selection.ranges.forEach(c=>{r.doc.nodesBetween(c.$from.pos,c.$to.pos,(u,d)=>{i&&i===u.type&&(a=!0,s&&n.setNodeMarkup(d,void 0,mu(u.attrs,e))),o&&u.marks.length&&u.marks.forEach(h=>{o===h.type&&(a=!0,s&&n.addMark(d,d+u.nodeSize,o.create(mu(h.attrs,e))))})})}),a},yg=()=>({tr:t,dispatch:e})=>(e&&t.scrollIntoView(),!0),kg=()=>({tr:t,dispatch:e})=>{if(e){let n=new ue(t.doc);t.setSelection(n)}return!0},bg=()=>({state:t,dispatch:e})=>vi(t,e),xg=()=>({state:t,dispatch:e})=>Ni(t,e),wg=()=>({state:t,dispatch:e})=>Ja(t,e),Sg=()=>({state:t,dispatch:e})=>Bi(t,e),Cg=()=>({state:t,dispatch:e})=>zi(t,e);function wo(t,e,n={},r={}){return Vn(t,e,{slice:!1,parseOptions:n,errorOnInvalidContent:r.errorOnInvalidContent})}var Mg=(t,{errorOnInvalidContent:e,emitUpdate:n=!0,parseOptions:r={}}={})=>({editor:s,tr:i,dispatch:o,commands:l})=>{let{doc:a}=i;if(r.preserveWhitespace!=="full"){let c=wo(t,s.schema,r,{errorOnInvalidContent:e??s.options.enableContentCheck});return o&&i.replaceWith(0,a.content.size,c).setMeta("preventUpdate",!n),!0}return o&&i.setMeta("preventUpdate",!n),l.insertContentAt({from:0,to:a.content.size},t,{parseOptions:r,errorOnInvalidContent:e??s.options.enableContentCheck})};function Au(t,e){let n=nt(e,t.schema),{from:r,to:s,empty:i}=t.selection,o=[];i?(t.storedMarks&&o.push(...t.storedMarks),o.push(...t.selection.$head.marks())):t.doc.nodesBetween(r,s,a=>{o.push(...a.marks)});let l=o.find(a=>a.type.name===n.name);return l?{...l.attrs}:{}}function vo(t,e){let n=new at(t);return e.forEach(r=>{r.steps.forEach(s=>{n.step(s)})}),n}function Tg(t){for(let e=0;e{n(s)&&r.push({node:s,pos:i})}),r}function Ao(t,e){for(let n=t.depth;n>0;n-=1){let r=t.node(n);if(e(r))return{pos:n>0?t.before(n):0,start:t.start(n),depth:n,node:r}}}function Ur(t){return e=>Ao(e.$from,t)}function T(t,e,n){return t.config[e]===void 0&&t.parent?T(t.parent,e,n):typeof t.config[e]=="function"?t.config[e].bind({...n,parent:t.parent?T(t.parent,e,n):null}):t.config[e]}function Wn(t){return t.map(e=>{let n={name:e.name,options:e.options,storage:e.storage},r=T(e,"addExtensions",n);return r?[e,...Wn(r())]:e}).flat(10)}function Eo(t,e){let n=qe.fromSchema(e).serializeFragment(t),s=document.implementation.createHTMLDocument().createElement("div");return s.appendChild(n),s.innerHTML}function Nu(t){return typeof t=="function"}function _(t,e=void 0,...n){return Nu(t)?e?t.bind(e)(...n):t(...n):t}function vg(t={}){return Object.keys(t).length===0&&t.constructor===Object}function un(t){let e=t.filter(s=>s.type==="extension"),n=t.filter(s=>s.type==="node"),r=t.filter(s=>s.type==="mark");return{baseExtensions:e,nodeExtensions:n,markExtensions:r}}function Ru(t){let e=[],{nodeExtensions:n,markExtensions:r}=un(t),s=[...n,...r],i={default:null,validate:void 0,rendered:!0,renderHTML:null,parseHTML:null,keepOnSplit:!0,isRequired:!1},o=n.filter(c=>c.name!=="text").map(c=>c.name),l=r.map(c=>c.name),a=[...o,...l];return t.forEach(c=>{let u={name:c.name,options:c.options,storage:c.storage,extensions:s},d=T(c,"addGlobalAttributes",u);if(!d)return;d().forEach(f=>{let p;Array.isArray(f.types)?p=f.types:f.types==="*"?p=a:f.types==="nodes"?p=o:f.types==="marks"?p=l:p=[],p.forEach(m=>{Object.entries(f.attributes).forEach(([g,y])=>{e.push({type:m,name:g,attribute:{...i,...y}})})})})}),s.forEach(c=>{let u={name:c.name,options:c.options,storage:c.storage},d=T(c,"addAttributes",u);if(!d)return;let h=d();Object.entries(h).forEach(([f,p])=>{let m={...i,...p};typeof m?.default=="function"&&(m.default=m.default()),m?.isRequired&&m?.default===void 0&&delete m.default,e.push({type:c.name,name:f,attribute:m})})}),e}function I(...t){return t.filter(e=>!!e).reduce((e,n)=>{let r={...e};return Object.entries(n).forEach(([s,i])=>{if(!r[s]){r[s]=i;return}if(s==="class"){let l=i?String(i).split(" "):[],a=r[s]?r[s].split(" "):[],c=l.filter(u=>!a.includes(u));r[s]=[...a,...c].join(" ")}else if(s==="style"){let l=i?i.split(";").map(u=>u.trim()).filter(Boolean):[],a=r[s]?r[s].split(";").map(u=>u.trim()).filter(Boolean):[],c=new Map;a.forEach(u=>{let[d,h]=u.split(":").map(f=>f.trim());c.set(d,h)}),l.forEach(u=>{let[d,h]=u.split(":").map(f=>f.trim());c.set(d,h)}),r[s]=Array.from(c.entries()).map(([u,d])=>`${u}: ${d}`).join("; ")}else r[s]=i}),r},{})}function dn(t,e){return e.filter(n=>n.type===t.type.name).filter(n=>n.attribute.rendered).map(n=>n.attribute.renderHTML?n.attribute.renderHTML(t.attrs)||{}:{[n.name]:t.attrs[n.name]}).reduce((n,r)=>I(n,r),{})}function Ag(t){return typeof t!="string"?t:t.match(/^[+-]?(?:\d*\.)?\d+$/)?Number(t):t==="true"?!0:t==="false"?!1:t}function gu(t,e){return"style"in t?t:{...t,getAttrs:n=>{let r=t.getAttrs?t.getAttrs(n):t.attrs;if(r===!1)return!1;let s=e.reduce((i,o)=>{let l=o.attribute.parseHTML?o.attribute.parseHTML(n):Ag(n.getAttribute(o.name));return l==null?i:{...i,[o.name]:l}},{});return{...r,...s}}}}function yu(t){return Object.fromEntries(Object.entries(t).filter(([e,n])=>e==="attrs"&&vg(n)?!1:n!=null))}function ku(t){var e,n;let r={};return!((e=t?.attribute)!=null&&e.isRequired)&&"default"in(t?.attribute||{})&&(r.default=t.attribute.default),((n=t?.attribute)==null?void 0:n.validate)!==void 0&&(r.validate=t.attribute.validate),[t.name,r]}function Ou(t,e){var n;let r=Ru(t),{nodeExtensions:s,markExtensions:i}=un(t),o=(n=s.find(c=>T(c,"topNode")))==null?void 0:n.name,l=Object.fromEntries(s.map(c=>{let u=r.filter(y=>y.type===c.name),d={name:c.name,options:c.options,storage:c.storage,editor:e},h=t.reduce((y,k)=>{let w=T(k,"extendNodeSchema",d);return{...y,...w?w(c):{}}},{}),f=yu({...h,content:_(T(c,"content",d)),marks:_(T(c,"marks",d)),group:_(T(c,"group",d)),inline:_(T(c,"inline",d)),atom:_(T(c,"atom",d)),selectable:_(T(c,"selectable",d)),draggable:_(T(c,"draggable",d)),code:_(T(c,"code",d)),whitespace:_(T(c,"whitespace",d)),linebreakReplacement:_(T(c,"linebreakReplacement",d)),defining:_(T(c,"defining",d)),isolating:_(T(c,"isolating",d)),attrs:Object.fromEntries(u.map(ku))}),p=_(T(c,"parseHTML",d));p&&(f.parseDOM=p.map(y=>gu(y,u)));let m=T(c,"renderHTML",d);m&&(f.toDOM=y=>m({node:y,HTMLAttributes:dn(y,u)}));let g=T(c,"renderText",d);return g&&(f.toText=g),[c.name,f]})),a=Object.fromEntries(i.map(c=>{let u=r.filter(g=>g.type===c.name),d={name:c.name,options:c.options,storage:c.storage,editor:e},h=t.reduce((g,y)=>{let k=T(y,"extendMarkSchema",d);return{...g,...k?k(c):{}}},{}),f=yu({...h,inclusive:_(T(c,"inclusive",d)),excludes:_(T(c,"excludes",d)),group:_(T(c,"group",d)),spanning:_(T(c,"spanning",d)),code:_(T(c,"code",d)),attrs:Object.fromEntries(u.map(ku))}),p=_(T(c,"parseHTML",d));p&&(f.parseDOM=p.map(g=>gu(g,u)));let m=T(c,"renderHTML",d);return m&&(f.toDOM=g=>m({mark:g,HTMLAttributes:dn(g,u)})),[c.name,f]}));return new Gt({topNode:o,nodes:l,marks:a})}function Eg(t){let e=t.filter((n,r)=>t.indexOf(n)!==r);return Array.from(new Set(e))}function _n(t){return t.sort((n,r)=>{let s=T(n,"priority")||100,i=T(r,"priority")||100;return s>i?-1:sr.name));return n.length&&console.warn(`[tiptap warn]: Duplicate extension names found: [${n.map(r=>`'${r}'`).join(", ")}]. This can lead to issues.`),e}function Ng(t,e){let n=No(t);return Ou(n,e)}function Iu(t,e){let n=Ng(e),r=Hn(t);return Ie.fromSchema(n).parse(r).toJSON()}function Du(t,e,n){let{from:r,to:s}=e,{blockSeparator:i=` - -`,textSerializers:o={}}=n||{},l="";return t.nodesBetween(r,s,(a,c,u,d)=>{var h;a.isBlock&&c>r&&(l+=i);let f=o?.[a.type.name];if(f)return u&&(l+=f({node:a,pos:c,parent:u,index:d,range:e})),!1;a.isText&&(l+=(h=a?.text)==null?void 0:h.slice(Math.max(r,c)-c,s-c))}),l}function Rg(t,e){let n={from:0,to:t.content.size};return Du(t,n,e)}function Pu(t){return Object.fromEntries(Object.entries(t.nodes).filter(([,e])=>e.spec.toText).map(([e,n])=>[e,n.spec.toText]))}function Og(t,e){let n=J(e,t.schema),{from:r,to:s}=t.selection,i=[];t.doc.nodesBetween(r,s,l=>{i.push(l)});let o=i.reverse().find(l=>l.type.name===n.name);return o?{...o.attrs}:{}}function Ro(t,e){let n=Kr(typeof e=="string"?e:e.name,t.schema);return n==="node"?Og(t,e):n==="mark"?Au(t,e):{}}function Ig(t,e=JSON.stringify){let n={};return t.filter(r=>{let s=e(r);return Object.prototype.hasOwnProperty.call(n,s)?!1:n[s]=!0})}function Dg(t){let e=Ig(t);return e.length===1?e:e.filter((n,r)=>!e.filter((i,o)=>o!==r).some(i=>n.oldRange.from>=i.oldRange.from&&n.oldRange.to<=i.oldRange.to&&n.newRange.from>=i.newRange.from&&n.newRange.to<=i.newRange.to))}function Oo(t){let{mapping:e,steps:n}=t,r=[];return e.maps.forEach((s,i)=>{let o=[];if(s.ranges.length)s.forEach((l,a)=>{o.push({from:l,to:a})});else{let{from:l,to:a}=n[i];if(l===void 0||a===void 0)return;o.push({from:l,to:a})}o.forEach(({from:l,to:a})=>{let c=e.slice(i).map(l,-1),u=e.slice(i).map(a),d=e.invert().map(c,-1),h=e.invert().map(u);r.push({oldRange:{from:d,to:h},newRange:{from:c,to:u}})})}),Dg(r)}function qr(t,e,n){let r=[];return t===e?n.resolve(t).marks().forEach(s=>{let i=n.resolve(t),o=To(i,s.type);o&&r.push({mark:s,...o})}):n.nodesBetween(t,e,(s,i)=>{!s||s?.nodeSize===void 0||r.push(...s.marks.map(o=>({from:i,to:i+s.nodeSize,mark:o})))}),r}var Lu=(t,e,n,r=20)=>{let s=t.doc.resolve(n),i=r,o=null;for(;i>0&&o===null;){let l=s.node(i);l?.type.name===e?o=l:i-=1}return[o,i]};function $n(t,e){return e.nodes[t]||e.marks[t]||null}function Fr(t,e,n){return Object.fromEntries(Object.entries(n).filter(([r])=>{let s=t.find(i=>i.type===e&&i.name===r);return s?s.attribute.keepOnSplit:!1}))}var Pg=(t,e=500)=>{let n="",r=t.parentOffset;return t.parent.nodesBetween(Math.max(0,r-e),r,(s,i,o,l)=>{var a,c;let u=((c=(a=s.type.spec).toText)==null?void 0:c.call(a,{node:s,pos:i,parent:o,index:l}))||s.textContent||"%leaf%";n+=s.isAtom&&!s.isText?u:u.slice(0,Math.max(0,r-i))}),n};function So(t,e,n={}){let{empty:r,ranges:s}=t.selection,i=e?nt(e,t.schema):null;if(r)return!!(t.storedMarks||t.selection.$from.marks()).filter(d=>i?i.name===d.type.name:!0).find(d=>_r(d.attrs,n,{strict:!1}));let o=0,l=[];if(s.forEach(({$from:d,$to:h})=>{let f=d.pos,p=h.pos;t.doc.nodesBetween(f,p,(m,g)=>{if(i&&m.inlineContent&&!m.type.allowsMarkType(i))return!1;if(!m.isText&&!m.marks.length)return;let y=Math.max(f,g),k=Math.min(p,g+m.nodeSize),w=k-y;o+=w,l.push(...m.marks.map(M=>({mark:M,from:y,to:k})))})}),o===0)return!1;let a=l.filter(d=>i?i.name===d.mark.type.name:!0).filter(d=>_r(d.mark.attrs,n,{strict:!1})).reduce((d,h)=>d+h.to-h.from,0),c=l.filter(d=>i?d.mark.type!==i&&d.mark.type.excludes(i):!0).reduce((d,h)=>d+h.to-h.from,0);return(a>0?a+c:a)>=o}function Lg(t,e,n={}){if(!e)return $e(t,null,n)||So(t,null,n);let r=Kr(e,t.schema);return r==="node"?$e(t,e,n):r==="mark"?So(t,e,n):!1}var zu=(t,e)=>{let{$from:n,$to:r,$anchor:s}=t.selection;if(e){let i=Ur(l=>l.type.name===e)(t.selection);if(!i)return!1;let o=t.doc.resolve(i.pos+1);return s.pos+1===o.end()}return!(r.parentOffset{let{$from:e,$to:n}=t.selection;return!(e.parentOffset>0||e.pos!==n.pos)};function bu(t,e){return Array.isArray(e)?e.some(n=>(typeof n=="string"?n:n.name)===t.name):e}function xu(t,e){let{nodeExtensions:n}=un(e),r=n.find(o=>o.name===t);if(!r)return!1;let s={name:r.name,options:r.options,storage:r.storage},i=_(T(r,"group",s));return typeof i!="string"?!1:i.split(" ").includes("list")}function jn(t,{checkChildren:e=!0,ignoreWhitespace:n=!1}={}){var r;if(n){if(t.type.name==="hardBreak")return!0;if(t.isText)return/^\s*$/m.test((r=t.text)!=null?r:"")}if(t.isText)return!t.text;if(t.isAtom||t.isLeaf)return!1;if(t.content.childCount===0)return!0;if(e){let s=!0;return t.content.forEach(i=>{s!==!1&&(jn(i,{ignoreWhitespace:n,checkChildren:e})||(s=!1))}),s}return!1}function Jr(t){return t instanceof E}var $u=class Hu{constructor(e){this.position=e}static fromJSON(e){return new Hu(e.position)}toJSON(){return{position:this.position}}};function zg(t,e){let n=e.mapping.mapResult(t.position);return{position:new $u(n.pos),mapResult:n}}function Bg(t){return new $u(t)}function $g(t,e,n){var r;let{selection:s}=e,i=null;if(Cu(s)&&(i=s.$cursor),i){let l=(r=t.storedMarks)!=null?r:i.marks();return i.parent.type.allowsMarkType(n)&&(!!n.isInSet(l)||!l.some(c=>c.type.excludes(n)))}let{ranges:o}=s;return o.some(({$from:l,$to:a})=>{let c=l.depth===0?t.doc.inlineContent&&t.doc.type.allowsMarkType(n):!1;return t.doc.nodesBetween(l.pos,a.pos,(u,d,h)=>{if(c)return!1;if(u.isInline){let f=!h||h.type.allowsMarkType(n),p=!!n.isInSet(u.marks)||!u.marks.some(m=>m.type.excludes(n));c=f&&p}return!c}),c})}var Hg=(t,e={})=>({tr:n,state:r,dispatch:s})=>{let{selection:i}=n,{empty:o,ranges:l}=i,a=nt(t,r.schema);if(s)if(o){let c=Au(r,a);n.addStoredMark(a.create({...c,...e}))}else l.forEach(c=>{let u=c.$from.pos,d=c.$to.pos;r.doc.nodesBetween(u,d,(h,f)=>{let p=Math.max(f,u),m=Math.min(f+h.nodeSize,d);h.marks.find(y=>y.type===a)?h.marks.forEach(y=>{a===y.type&&n.addMark(p,m,a.create({...y.attrs,...e}))}):n.addMark(p,m,a.create(e))})});return $g(r,n,a)},Fg=(t,e)=>({tr:n})=>(n.setMeta(t,e),!0),_g=(t,e={})=>({state:n,dispatch:r,chain:s})=>{let i=J(t,n.schema),o;return n.selection.$anchor.sameParent(n.selection.$head)&&(o=n.selection.$anchor.parent.attrs),i.isTextblock?s().command(({commands:l})=>$i(i,{...o,...e})(n)?!0:l.clearNodes()).command(({state:l})=>$i(i,{...o,...e})(l,r)).run():(console.warn('[tiptap warn]: Currently "setNode()" only supports text block nodes.'),!1)},Vg=t=>({tr:e,dispatch:n})=>{if(n){let{doc:r}=e,s=Lt(t,0,r.content.size),i=E.create(r,s);e.setSelection(i)}return!0},Wg=(t,e)=>({tr:n,state:r,dispatch:s})=>{let{selection:i}=r,o,l;return typeof e=="number"?(o=e,l=e):e&&"from"in e&&"to"in e?(o=e.from,l=e.to):(o=i.from,l=i.to),s&&n.doc.nodesBetween(o,l,(a,c)=>{a.isText||n.setNodeMarkup(c,void 0,{...a.attrs,dir:t})}),!0},jg=t=>({tr:e,dispatch:n})=>{if(n){let{doc:r}=e,{from:s,to:i}=typeof t=="number"?{from:t,to:t}:t,o=A.atStart(r).from,l=A.atEnd(r).to,a=Lt(s,o,l),c=Lt(i,o,l),u=A.create(r,a,c);e.setSelection(u)}return!0},Kg=t=>({state:e,dispatch:n})=>{let r=J(t,e.schema);return ec(r)(e,n)};function wu(t,e){let n=t.storedMarks||t.selection.$to.parentOffset&&t.selection.$from.marks();if(n){let r=n.filter(s=>e?.includes(s.type.name));t.tr.ensureMarks(r)}}var Ug=({keepMarks:t=!0}={})=>({tr:e,state:n,dispatch:r,editor:s})=>{let{selection:i,doc:o}=e,{$from:l,$to:a}=i,c=s.extensionManager.attributes,u=Fr(c,l.node().type.name,l.node().attrs);if(i instanceof E&&i.node.isBlock)return!l.parentOffset||!be(o,l.pos)?!1:(r&&(t&&wu(n,s.extensionManager.splittableMarks),e.split(l.pos).scrollIntoView()),!0);if(!l.parent.isBlock)return!1;let d=a.parentOffset===a.parent.content.size,h=l.depth===0?void 0:Tg(l.node(-1).contentMatchAt(l.indexAfter(-1))),f=d&&h?[{type:h,attrs:u}]:void 0,p=be(e.doc,e.mapping.map(l.pos),1,f);if(!f&&!p&&be(e.doc,e.mapping.map(l.pos),1,h?[{type:h}]:void 0)&&(p=!0,f=h?[{type:h,attrs:u}]:void 0),r){if(p&&(i instanceof A&&e.deleteSelection(),e.split(e.mapping.map(l.pos),1,f),h&&!d&&!l.parentOffset&&l.parent.type!==h)){let m=e.mapping.map(l.before()),g=e.doc.resolve(m);l.node(-1).canReplaceWith(g.index(),g.index()+1,h)&&e.setNodeMarkup(e.mapping.map(l.before()),h)}t&&wu(n,s.extensionManager.splittableMarks),e.scrollIntoView()}return p},qg=(t,e={})=>({tr:n,state:r,dispatch:s,editor:i})=>{var o;let l=J(t,r.schema),{$from:a,$to:c}=r.selection,u=r.selection.node;if(u&&u.isBlock||a.depth<2||!a.sameParent(c))return!1;let d=a.node(-1);if(d.type!==l)return!1;let h=i.extensionManager.attributes;if(a.parent.content.size===0&&a.node(-1).childCount===a.indexAfter(-1)){if(a.depth===2||a.node(-3).type!==l||a.index(-2)!==a.node(-2).childCount-1)return!1;if(s){let y=b.empty,k=a.index(-1)?1:a.index(-2)?2:3;for(let D=a.depth-k;D>=a.depth-3;D-=1)y=b.from(a.node(D).copy(y));let w=a.indexAfter(-1){if(v>-1)return!1;D.isTextblock&&D.content.size===0&&(v=z+1)}),v>-1&&n.setSelection(A.near(n.doc.resolve(v))),n.scrollIntoView()}return!0}let f=c.pos===a.end()?d.contentMatchAt(0).defaultType:null,p={...Fr(h,d.type.name,d.attrs),...e},m={...Fr(h,a.node().type.name,a.node().attrs),...e};n.delete(a.pos,c.pos);let g=f?[{type:l,attrs:p},{type:f,attrs:m}]:[{type:l,attrs:p}];if(!be(n.doc,a.pos,2))return!1;if(s){let{selection:y,storedMarks:k}=r,{splittableMarks:w}=i.extensionManager,M=k||y.$to.parentOffset&&y.$from.marks();if(n.split(a.pos,2,g).scrollIntoView(),!M||!s)return!0;let S=M.filter(N=>w.includes(N.type.name));n.ensureMarks(S)}return!0},bo=(t,e)=>{let n=Ur(o=>o.type===e)(t.selection);if(!n)return!0;let r=t.doc.resolve(Math.max(0,n.pos-1)).before(n.depth);if(r===void 0)return!0;let s=t.doc.nodeAt(r);return n.node.type===s?.type&&Ce(t.doc,n.pos)&&t.join(n.pos),!0},xo=(t,e)=>{let n=Ur(o=>o.type===e)(t.selection);if(!n)return!0;let r=t.doc.resolve(n.start).after(n.depth);if(r===void 0)return!0;let s=t.doc.nodeAt(r);return n.node.type===s?.type&&Ce(t.doc,r)&&t.join(r),!0},Jg=(t,e,n,r={})=>({editor:s,tr:i,state:o,dispatch:l,chain:a,commands:c,can:u})=>{let{extensions:d,splittableMarks:h}=s.extensionManager,f=J(t,o.schema),p=J(e,o.schema),{selection:m,storedMarks:g}=o,{$from:y,$to:k}=m,w=y.blockRange(k),M=g||m.$to.parentOffset&&m.$from.marks();if(!w)return!1;let S=Ur(N=>xu(N.type.name,d))(m);if(w.depth>=1&&S&&w.depth-S.depth<=1){if(S.node.type===f)return c.liftListItem(p);if(xu(S.node.type.name,d)&&f.validContent(S.node.content)&&l)return a().command(()=>(i.setNodeMarkup(S.pos,f),!0)).command(()=>bo(i,f)).command(()=>xo(i,f)).run()}return!n||!M||!l?a().command(()=>u().wrapInList(f,r)?!0:c.clearNodes()).wrapInList(f,r).command(()=>bo(i,f)).command(()=>xo(i,f)).run():a().command(()=>{let N=u().wrapInList(f,r),v=M.filter(D=>h.includes(D.type.name));return i.ensureMarks(v),N?!0:c.clearNodes()}).wrapInList(f,r).command(()=>bo(i,f)).command(()=>xo(i,f)).run()},Gg=(t,e={},n={})=>({state:r,commands:s})=>{let{extendEmptyMarkRange:i=!1}=n,o=nt(t,r.schema);return So(r,o,e)?s.unsetMark(o,{extendEmptyMarkRange:i}):s.setMark(o,e)},Xg=(t,e,n={})=>({state:r,commands:s})=>{let i=J(t,r.schema),o=J(e,r.schema),l=$e(r,i,n),a;return r.selection.$anchor.sameParent(r.selection.$head)&&(a=r.selection.$anchor.parent.attrs),l?s.setNode(o,a):s.setNode(i,{...a,...n})},Qg=(t,e={})=>({state:n,commands:r})=>{let s=J(t,n.schema);return $e(n,s,e)?r.lift(s):r.wrapIn(s,e)},Yg=()=>({state:t,dispatch:e})=>{let n=t.plugins;for(let r=0;r=0;a-=1)o.step(l.steps[a].invert(l.docs[a]));if(i.text){let a=o.doc.resolve(i.from).marks();o.replaceWith(i.from,i.to,t.schema.text(i.text,a))}else o.delete(i.from,i.to)}return!0}}return!1},Zg=()=>({tr:t,dispatch:e})=>{let{selection:n}=t,{empty:r,ranges:s}=n;return r||e&&s.forEach(i=>{t.removeMark(i.$from.pos,i.$to.pos)}),!0},ey=(t,e={})=>({tr:n,state:r,dispatch:s})=>{var i;let{extendEmptyMarkRange:o=!1}=e,{selection:l}=n,a=nt(t,r.schema),{$from:c,empty:u,ranges:d}=l;if(!s)return!0;if(u&&o){let{from:h,to:f}=l,p=(i=c.marks().find(g=>g.type===a))==null?void 0:i.attrs,m=To(c,a,p);m&&(h=m.from,f=m.to),n.removeMark(h,f,a)}else d.forEach(h=>{n.removeMark(h.$from.pos,h.$to.pos,a)});return n.removeStoredMark(a),!0},ty=t=>({tr:e,state:n,dispatch:r})=>{let{selection:s}=n,i,o;return typeof t=="number"?(i=t,o=t):t&&"from"in t&&"to"in t?(i=t.from,o=t.to):(i=s.from,o=s.to),r&&e.doc.nodesBetween(i,o,(l,a)=>{if(l.isText)return;let c={...l.attrs};delete c.dir,e.setNodeMarkup(a,void 0,c)}),!0},ny=(t,e={})=>({tr:n,state:r,dispatch:s})=>{let i=null,o=null,l=Kr(typeof t=="string"?t:t.name,r.schema);if(!l)return!1;l==="node"&&(i=J(t,r.schema)),l==="mark"&&(o=nt(t,r.schema));let a=!1;return n.selection.ranges.forEach(c=>{let u=c.$from.pos,d=c.$to.pos,h,f,p,m;n.selection.empty?r.doc.nodesBetween(u,d,(g,y)=>{i&&i===g.type&&(a=!0,p=Math.max(y,u),m=Math.min(y+g.nodeSize,d),h=y,f=g)}):r.doc.nodesBetween(u,d,(g,y)=>{y=u&&y<=d&&(i&&i===g.type&&(a=!0,s&&n.setNodeMarkup(y,void 0,{...g.attrs,...e})),o&&g.marks.length&&g.marks.forEach(k=>{if(o===k.type&&(a=!0,s)){let w=Math.max(y,u),M=Math.min(y+g.nodeSize,d);n.addMark(w,M,o.create({...k.attrs,...e}))}}))}),f&&(h!==void 0&&s&&n.setNodeMarkup(h,void 0,{...f.attrs,...e}),o&&f.marks.length&&f.marks.forEach(g=>{o===g.type&&s&&n.addMark(p,m,o.create({...g.attrs,...e}))}))}),a},ry=(t,e={})=>({state:n,dispatch:r})=>{let s=J(t,n.schema);return Qa(s,e)(n,r)},sy=(t,e={})=>({state:n,dispatch:r})=>{let s=J(t,n.schema);return Ya(s,e)(n,r)},iy=class{constructor(){this.callbacks={}}on(t,e){return this.callbacks[t]||(this.callbacks[t]=[]),this.callbacks[t].push(e),this}emit(t,...e){let n=this.callbacks[t];return n&&n.forEach(r=>r.apply(this,e)),this}off(t,e){let n=this.callbacks[t];return n&&(e?this.callbacks[t]=n.filter(r=>r!==e):delete this.callbacks[t]),this}once(t,e){let n=(...r)=>{this.off(t,n),e.apply(this,r)};return this.on(t,n)}removeAllListeners(){this.callbacks={}}},Gr=class{constructor(t){var e;this.find=t.find,this.handler=t.handler,this.undoable=(e=t.undoable)!=null?e:!0}},oy=(t,e)=>{if(Mo(e))return e.exec(t);let n=e(t);if(!n)return null;let r=[n.text];return r.index=n.index,r.input=t,r.data=n.data,n.replaceWith&&(n.text.includes(n.replaceWith)||console.warn('[tiptap warn]: "inputRuleMatch.replaceWith" must be part of "inputRuleMatch.text".'),r.push(n.replaceWith)),r};function Br(t){var e;let{editor:n,from:r,to:s,text:i,rules:o,plugin:l}=t,{view:a}=n;if(a.composing)return!1;let c=a.state.doc.resolve(r);if(c.parent.type.spec.code||(e=c.nodeBefore||c.nodeAfter)!=null&&e.marks.find(h=>h.type.spec.code))return!1;let u=!1,d=Pg(c)+i;return o.forEach(h=>{if(u)return;let f=oy(d,h.find);if(!f)return;let p=a.state.tr,m=Wr({state:a.state,transaction:p}),g={from:r-(f[0].length-i.length),to:s},{commands:y,chain:k,can:w}=new jr({editor:n,state:m});h.handler({state:m,range:g,match:f,commands:y,chain:k,can:w})===null||!p.steps.length||(h.undoable&&p.setMeta(l,{transform:p,from:r,to:s,text:i}),a.dispatch(p),u=!0)}),u}function ly(t){let{editor:e,rules:n}=t,r=new O({state:{init(){return null},apply(s,i,o){let l=s.getMeta(r);if(l)return l;let a=s.getMeta("applyInputRules");return!!a&&setTimeout(()=>{let{text:u}=a;typeof u=="string"?u=u:u=Eo(b.from(u),o.schema);let{from:d}=a,h=d+u.length;Br({editor:e,from:d,to:h,text:u,rules:n,plugin:r})}),s.selectionSet||s.docChanged?null:i}},props:{handleTextInput(s,i,o,l){return Br({editor:e,from:i,to:o,text:l,rules:n,plugin:r})},handleDOMEvents:{compositionend:s=>(setTimeout(()=>{let{$cursor:i}=s.state.selection;i&&Br({editor:e,from:i.pos,to:i.pos,text:"",rules:n,plugin:r})}),!1)},handleKeyDown(s,i){if(i.key!=="Enter")return!1;let{$cursor:o}=s.state.selection;return o?Br({editor:e,from:o.pos,to:o.pos,text:` -`,rules:n,plugin:r}):!1}},isInputRules:!0});return r}function ay(t){return Object.prototype.toString.call(t).slice(8,-1)}function $r(t){return ay(t)!=="Object"?!1:t.constructor===Object&&Object.getPrototypeOf(t)===Object.prototype}function Fu(t,e){let n={...t};return $r(t)&&$r(e)&&Object.keys(e).forEach(r=>{$r(e[r])&&$r(t[r])?n[r]=Fu(t[r],e[r]):n[r]=e[r]}),n}var Io=class{constructor(t={}){this.type="extendable",this.parent=null,this.child=null,this.name="",this.config={name:this.name},this.config={...this.config,...t},this.name=this.config.name}get options(){return{..._(T(this,"addOptions",{name:this.name}))||{}}}get storage(){return{..._(T(this,"addStorage",{name:this.name,options:this.options}))||{}}}configure(t={}){let e=this.extend({...this.config,addOptions:()=>Fu(this.options,t)});return e.name=this.name,e.parent=this.parent,e}extend(t={}){let e=new this.constructor({...this.config,...t});return e.parent=this,this.child=e,e.name="name"in t?t.name:e.parent.name,e}},xe=class _u extends Io{constructor(){super(...arguments),this.type="mark"}static create(e={}){let n=typeof e=="function"?e():e;return new _u(n)}static handleExit({editor:e,mark:n}){let{tr:r}=e.state,s=e.state.selection.$from;if(s.pos===s.end()){let o=s.marks();if(!!!o.find(c=>c?.type.name===n.name))return!1;let a=o.find(c=>c?.type.name===n.name);return a&&r.removeStoredMark(a),r.insertText(" ",s.pos),e.view.dispatch(r),!0}return!1}configure(e){return super.configure(e)}extend(e){let n=typeof e=="function"?e():e;return super.extend(n)}};function cy(t){return typeof t=="number"}var uy=class{constructor(t){this.find=t.find,this.handler=t.handler}},dy=(t,e,n)=>{if(Mo(e))return[...t.matchAll(e)];let r=e(t,n);return r?r.map(s=>{let i=[s.text];return i.index=s.index,i.input=t,i.data=s.data,s.replaceWith&&(s.text.includes(s.replaceWith)||console.warn('[tiptap warn]: "pasteRuleMatch.replaceWith" must be part of "pasteRuleMatch.text".'),i.push(s.replaceWith)),i}):[]};function hy(t){let{editor:e,state:n,from:r,to:s,rule:i,pasteEvent:o,dropEvent:l}=t,{commands:a,chain:c,can:u}=new jr({editor:e,state:n}),d=[];return n.doc.nodesBetween(r,s,(f,p)=>{var m,g,y,k,w;if((g=(m=f.type)==null?void 0:m.spec)!=null&&g.code||!(f.isText||f.isTextblock||f.isInline))return;let M=(w=(k=(y=f.content)==null?void 0:y.size)!=null?k:f.nodeSize)!=null?w:0,S=Math.max(r,p),N=Math.min(s,p+M);if(S>=N)return;let v=f.isText?f.text||"":f.textBetween(S-p,N-p,void 0,"\uFFFC");dy(v,i.find,o).forEach(z=>{if(z.index===void 0)return;let ke=S+z.index+1,Kt=ke+z[0].length,lt={from:n.tr.mapping.map(ke),to:n.tr.mapping.map(Kt)},Ut=i.handler({state:n,range:lt,match:z,commands:a,chain:c,can:u,pasteEvent:o,dropEvent:l});d.push(Ut)})}),d.every(f=>f!==null)}var Hr=null,fy=t=>{var e;let n=new ClipboardEvent("paste",{clipboardData:new DataTransfer});return(e=n.clipboardData)==null||e.setData("text/html",t),n};function py(t){let{editor:e,rules:n}=t,r=null,s=!1,i=!1,o=typeof ClipboardEvent<"u"?new ClipboardEvent("paste"):null,l;try{l=typeof DragEvent<"u"?new DragEvent("drop"):null}catch{l=null}let a=({state:u,from:d,to:h,rule:f,pasteEvt:p})=>{let m=u.tr,g=Wr({state:u,transaction:m});if(!(!hy({editor:e,state:g,from:Math.max(d-1,0),to:h.b-1,rule:f,pasteEvent:p,dropEvent:l})||!m.steps.length)){try{l=typeof DragEvent<"u"?new DragEvent("drop"):null}catch{l=null}return o=typeof ClipboardEvent<"u"?new ClipboardEvent("paste"):null,m}};return n.map(u=>new O({view(d){let h=p=>{var m;r=(m=d.dom.parentElement)!=null&&m.contains(p.target)?d.dom.parentElement:null,r&&(Hr=e)},f=()=>{Hr&&(Hr=null)};return window.addEventListener("dragstart",h),window.addEventListener("dragend",f),{destroy(){window.removeEventListener("dragstart",h),window.removeEventListener("dragend",f)}}},props:{handleDOMEvents:{drop:(d,h)=>{if(i=r===d.dom.parentElement,l=h,!i){let f=Hr;f?.isEditable&&setTimeout(()=>{let p=f.state.selection;p&&f.commands.deleteRange({from:p.from,to:p.to})},10)}return!1},paste:(d,h)=>{var f;let p=(f=h.clipboardData)==null?void 0:f.getData("text/html");return o=h,s=!!p?.includes("data-pm-slice"),!1}}},appendTransaction:(d,h,f)=>{let p=d[0],m=p.getMeta("uiEvent")==="paste"&&!s,g=p.getMeta("uiEvent")==="drop"&&!i,y=p.getMeta("applyPasteRules"),k=!!y;if(!m&&!g&&!k)return;if(k){let{text:S}=y;typeof S=="string"?S=S:S=Eo(b.from(S),f.schema);let{from:N}=y,v=N+S.length,D=fy(S);return a({rule:u,state:f,from:N,to:{b:v},pasteEvt:D})}let w=h.doc.content.findDiffStart(f.doc.content),M=h.doc.content.findDiffEnd(f.doc.content);if(!(!cy(w)||!M||w===M.b))return a({rule:u,state:f,from:w,to:M,pasteEvt:o})}}))}var Xr=class{constructor(t,e){this.splittableMarks=[],this.editor=e,this.baseExtensions=t,this.extensions=No(t),this.schema=Ou(this.extensions,e),this.setupExtensions()}get commands(){return this.extensions.reduce((t,e)=>{let n={name:e.name,options:e.options,storage:this.editor.extensionStorage[e.name],editor:this.editor,type:$n(e.name,this.schema)},r=T(e,"addCommands",n);return r?{...t,...r()}:t},{})}get plugins(){let{editor:t}=this;return _n([...this.extensions].reverse()).flatMap(r=>{let s={name:r.name,options:r.options,storage:this.editor.extensionStorage[r.name],editor:t,type:$n(r.name,this.schema)},i=[],o=T(r,"addKeyboardShortcuts",s),l={};if(r.type==="mark"&&T(r,"exitable",s)&&(l.ArrowRight=()=>xe.handleExit({editor:t,mark:r})),o){let h=Object.fromEntries(Object.entries(o()).map(([f,p])=>[f,()=>p({editor:t})]));l={...l,...h}}let a=hu(l);i.push(a);let c=T(r,"addInputRules",s);if(bu(r,t.options.enableInputRules)&&c){let h=c();if(h&&h.length){let f=ly({editor:t,rules:h}),p=Array.isArray(f)?f:[f];i.push(...p)}}let u=T(r,"addPasteRules",s);if(bu(r,t.options.enablePasteRules)&&u){let h=u();if(h&&h.length){let f=py({editor:t,rules:h});i.push(...f)}}let d=T(r,"addProseMirrorPlugins",s);if(d){let h=d();i.push(...h)}return i})}get attributes(){return Ru(this.extensions)}get nodeViews(){let{editor:t}=this,{nodeExtensions:e}=un(this.extensions);return Object.fromEntries(e.filter(n=>!!T(n,"addNodeView")).map(n=>{let r=this.attributes.filter(a=>a.type===n.name),s={name:n.name,options:n.options,storage:this.editor.extensionStorage[n.name],editor:t,type:J(n.name,this.schema)},i=T(n,"addNodeView",s);if(!i)return[];let o=i();if(!o)return[];let l=(a,c,u,d,h)=>{let f=dn(a,r);return o({node:a,view:c,getPos:u,decorations:d,innerDecorations:h,editor:t,extension:n,HTMLAttributes:f})};return[n.name,l]}))}dispatchTransaction(t){let{editor:e}=this;return _n([...this.extensions].reverse()).reduceRight((r,s)=>{let i={name:s.name,options:s.options,storage:this.editor.extensionStorage[s.name],editor:e,type:$n(s.name,this.schema)},o=T(s,"dispatchTransaction",i);return o?l=>{o.call(i,{transaction:l,next:r})}:r},t)}transformPastedHTML(t){let{editor:e}=this;return _n([...this.extensions]).reduce((r,s)=>{let i={name:s.name,options:s.options,storage:this.editor.extensionStorage[s.name],editor:e,type:$n(s.name,this.schema)},o=T(s,"transformPastedHTML",i);return o?(l,a)=>{let c=r(l,a);return o.call(i,c)}:r},t||(r=>r))}get markViews(){let{editor:t}=this,{markExtensions:e}=un(this.extensions);return Object.fromEntries(e.filter(n=>!!T(n,"addMarkView")).map(n=>{let r=this.attributes.filter(l=>l.type===n.name),s={name:n.name,options:n.options,storage:this.editor.extensionStorage[n.name],editor:t,type:nt(n.name,this.schema)},i=T(n,"addMarkView",s);if(!i)return[];let o=(l,a,c)=>{let u=dn(l,r);return i()({mark:l,view:a,inline:c,editor:t,extension:n,HTMLAttributes:u,updateAttributes:d=>{Ay(l,t,d)}})};return[n.name,o]}))}setupExtensions(){let t=this.extensions;this.editor.extensionStorage=Object.fromEntries(t.map(e=>[e.name,e.storage])),t.forEach(e=>{var n;let r={name:e.name,options:e.options,storage:this.editor.extensionStorage[e.name],editor:this.editor,type:$n(e.name,this.schema)};e.type==="mark"&&((n=_(T(e,"keepOnSplit",r)))==null||n)&&this.splittableMarks.push(e.name);let s=T(e,"onBeforeCreate",r),i=T(e,"onCreate",r),o=T(e,"onUpdate",r),l=T(e,"onSelectionUpdate",r),a=T(e,"onTransaction",r),c=T(e,"onFocus",r),u=T(e,"onBlur",r),d=T(e,"onDestroy",r);s&&this.editor.on("beforeCreate",s),i&&this.editor.on("create",i),o&&this.editor.on("update",o),l&&this.editor.on("selectionUpdate",l),a&&this.editor.on("transaction",a),c&&this.editor.on("focus",c),u&&this.editor.on("blur",u),d&&this.editor.on("destroy",d)})}};Xr.resolve=No;Xr.sort=_n;Xr.flatten=Wn;var my={};Co(my,{ClipboardTextSerializer:()=>Wu,Commands:()=>ju,Delete:()=>Ku,Drop:()=>Uu,Editable:()=>qu,FocusEvents:()=>Gu,Keymap:()=>Xu,Paste:()=>Qu,Tabindex:()=>Yu,TextDirection:()=>Zu,focusEventsPluginKey:()=>Ju});var B=class Vu extends Io{constructor(){super(...arguments),this.type="extension"}static create(e={}){let n=typeof e=="function"?e():e;return new Vu(n)}configure(e){return super.configure(e)}extend(e){let n=typeof e=="function"?e():e;return super.extend(n)}},Wu=B.create({name:"clipboardTextSerializer",addOptions(){return{blockSeparator:void 0}},addProseMirrorPlugins(){return[new O({key:new P("clipboardTextSerializer"),props:{clipboardTextSerializer:()=>{let{editor:t}=this,{state:e,schema:n}=t,{doc:r,selection:s}=e,{ranges:i}=s,o=Math.min(...i.map(u=>u.$from.pos)),l=Math.max(...i.map(u=>u.$to.pos)),a=Pu(n);return Du(r,{from:o,to:l},{...this.options.blockSeparator!==void 0?{blockSeparator:this.options.blockSeparator}:{},textSerializers:a})}}})]}}),ju=B.create({name:"commands",addCommands(){return{...Ee}}}),Ku=B.create({name:"delete",onUpdate({transaction:t,appendedTransactions:e}){var n,r,s;let i=()=>{var o,l,a,c;if((c=(a=(l=(o=this.editor.options.coreExtensionOptions)==null?void 0:o.delete)==null?void 0:l.filterTransaction)==null?void 0:a.call(l,t))!=null?c:t.getMeta("y-sync$"))return;let u=vo(t.before,[t,...e]);Oo(u).forEach(f=>{u.mapping.mapResult(f.oldRange.from).deletedAfter&&u.mapping.mapResult(f.oldRange.to).deletedBefore&&u.before.nodesBetween(f.oldRange.from,f.oldRange.to,(p,m)=>{let g=m+p.nodeSize-2,y=f.oldRange.from<=m&&g<=f.oldRange.to;this.editor.emit("delete",{type:"node",node:p,from:m,to:g,newFrom:u.mapping.map(m),newTo:u.mapping.map(g),deletedRange:f.oldRange,newRange:f.newRange,partial:!y,editor:this.editor,transaction:t,combinedTransform:u})})});let h=u.mapping;u.steps.forEach((f,p)=>{var m,g;if(f instanceof Ge){let y=h.slice(p).map(f.from,-1),k=h.slice(p).map(f.to),w=h.invert().map(y,-1),M=h.invert().map(k),S=(m=u.doc.nodeAt(y-1))==null?void 0:m.marks.some(v=>v.eq(f.mark)),N=(g=u.doc.nodeAt(k))==null?void 0:g.marks.some(v=>v.eq(f.mark));this.editor.emit("delete",{type:"mark",mark:f.mark,from:f.from,to:f.to,deletedRange:{from:w,to:M},newRange:{from:y,to:k},partial:!!(N||S),editor:this.editor,transaction:t,combinedTransform:u})}})};(s=(r=(n=this.editor.options.coreExtensionOptions)==null?void 0:n.delete)==null?void 0:r.async)==null||s?setTimeout(i,0):i()}}),Uu=B.create({name:"drop",addProseMirrorPlugins(){return[new O({key:new P("tiptapDrop"),props:{handleDrop:(t,e,n,r)=>{this.editor.emit("drop",{editor:this.editor,event:e,slice:n,moved:r})}}})]}}),qu=B.create({name:"editable",addProseMirrorPlugins(){return[new O({key:new P("editable"),props:{editable:()=>this.editor.options.editable}})]}}),Ju=new P("focusEvents"),Gu=B.create({name:"focusEvents",addProseMirrorPlugins(){let{editor:t}=this;return[new O({key:Ju,props:{handleDOMEvents:{focus:(e,n)=>{t.isFocused=!0;let r=t.state.tr.setMeta("focus",{event:n}).setMeta("addToHistory",!1);return e.dispatch(r),!1},blur:(e,n)=>{t.isFocused=!1;let r=t.state.tr.setMeta("blur",{event:n}).setMeta("addToHistory",!1);return e.dispatch(r),!1}}}})]}}),Xu=B.create({name:"keymap",addKeyboardShortcuts(){let t=()=>this.editor.commands.first(({commands:o})=>[()=>o.undoInputRule(),()=>o.command(({tr:l})=>{let{selection:a,doc:c}=l,{empty:u,$anchor:d}=a,{pos:h,parent:f}=d,p=d.parent.isTextblock&&h>0?l.doc.resolve(h-1):d,m=p.parent.type.spec.isolating,g=d.pos-d.parentOffset,y=m&&p.parent.childCount===1?g===d.pos:R.atStart(c).from===h;return!u||!f.type.isTextblock||f.textContent.length||!y||y&&d.parent.type.name==="paragraph"?!1:o.clearNodes()}),()=>o.deleteSelection(),()=>o.joinBackward(),()=>o.selectNodeBackward()]),e=()=>this.editor.commands.first(({commands:o})=>[()=>o.deleteSelection(),()=>o.deleteCurrentNode(),()=>o.joinForward(),()=>o.selectNodeForward()]),r={Enter:()=>this.editor.commands.first(({commands:o})=>[()=>o.newlineInCode(),()=>o.createParagraphNear(),()=>o.liftEmptyBlock(),()=>o.splitBlock()]),"Mod-Enter":()=>this.editor.commands.exitCode(),Backspace:t,"Mod-Backspace":t,"Shift-Backspace":t,Delete:e,"Mod-Delete":e,"Mod-a":()=>this.editor.commands.selectAll()},s={...r},i={...r,"Ctrl-h":t,"Alt-Backspace":t,"Ctrl-d":e,"Ctrl-Alt-Backspace":e,"Alt-Delete":e,"Alt-d":e,"Ctrl-a":()=>this.editor.commands.selectTextblockStart(),"Ctrl-e":()=>this.editor.commands.selectTextblockEnd()};return Vr()||vu()?i:s},addProseMirrorPlugins(){return[new O({key:new P("clearDocument"),appendTransaction:(t,e,n)=>{if(t.some(m=>m.getMeta("composition")))return;let r=t.some(m=>m.docChanged)&&!e.doc.eq(n.doc),s=t.some(m=>m.getMeta("preventClearDocument"));if(!r||s)return;let{empty:i,from:o,to:l}=e.selection,a=R.atStart(e.doc).from,c=R.atEnd(e.doc).to;if(i||!(o===a&&l===c)||!jn(n.doc))return;let h=n.tr,f=Wr({state:n,transaction:h}),{commands:p}=new jr({editor:this.editor,state:f});if(p.clearNodes(),!!h.steps.length)return h}})]}}),Qu=B.create({name:"paste",addProseMirrorPlugins(){return[new O({key:new P("tiptapPaste"),props:{handlePaste:(t,e,n)=>{this.editor.emit("paste",{editor:this.editor,event:e,slice:n})}}})]}}),Yu=B.create({name:"tabindex",addProseMirrorPlugins(){return[new O({key:new P("tabindex"),props:{attributes:()=>this.editor.isEditable?{tabindex:"0"}:{}}})]}}),Zu=B.create({name:"textDirection",addOptions(){return{direction:void 0}},addGlobalAttributes(){if(!this.options.direction)return[];let{nodeExtensions:t}=un(this.extensions);return[{types:t.filter(e=>e.name!=="text").map(e=>e.name),attributes:{dir:{default:this.options.direction,parseHTML:e=>{let n=e.getAttribute("dir");return n&&(n==="ltr"||n==="rtl"||n==="auto")?n:this.options.direction},renderHTML:e=>e.dir?{dir:e.dir}:{}}}}]},addProseMirrorPlugins(){return[new O({key:new P("textDirection"),props:{attributes:()=>{let t=this.options.direction;return t?{dir:t}:{}}}})]}}),gy=class Fn{constructor(e,n,r=!1,s=null){this.currentNode=null,this.actualDepth=null,this.isBlock=r,this.resolvedPos=e,this.editor=n,this.currentNode=s}get name(){return this.node.type.name}get node(){return this.currentNode||this.resolvedPos.node()}get element(){return this.editor.view.domAtPos(this.pos).node}get depth(){var e;return(e=this.actualDepth)!=null?e:this.resolvedPos.depth}get pos(){return this.resolvedPos.pos}get content(){return this.node.content}set content(e){let n=this.from,r=this.to;if(this.isBlock){if(this.content.size===0){console.error(`You can\u2019t set content on a block node. Tried to set content on ${this.name} at ${this.pos}`);return}n=this.from+1,r=this.to-1}this.editor.commands.insertContentAt({from:n,to:r},e)}get attributes(){return this.node.attrs}get textContent(){return this.node.textContent}get size(){return this.node.nodeSize}get from(){return this.isBlock?this.pos:this.resolvedPos.start(this.resolvedPos.depth)}get range(){return{from:this.from,to:this.to}}get to(){return this.isBlock?this.pos+this.size:this.resolvedPos.end(this.resolvedPos.depth)+(this.node.isText?0:1)}get parent(){if(this.depth===0)return null;let e=this.resolvedPos.start(this.resolvedPos.depth-1),n=this.resolvedPos.doc.resolve(e);return new Fn(n,this.editor)}get before(){let e=this.resolvedPos.doc.resolve(this.from-(this.isBlock?1:2));return e.depth!==this.depth&&(e=this.resolvedPos.doc.resolve(this.from-3)),new Fn(e,this.editor)}get after(){let e=this.resolvedPos.doc.resolve(this.to+(this.isBlock?2:1));return e.depth!==this.depth&&(e=this.resolvedPos.doc.resolve(this.to+3)),new Fn(e,this.editor)}get children(){let e=[];return this.node.content.forEach((n,r)=>{let s=n.isBlock&&!n.isTextblock,i=n.isAtom&&!n.isText,o=n.isInline,l=this.pos+r+(i?0:1);if(l<0||l>this.resolvedPos.doc.nodeSize-2)return;let a=this.resolvedPos.doc.resolve(l);if(!s&&!o&&a.depth<=this.depth)return;let c=new Fn(a,this.editor,s,s||o?n:null);s&&(c.actualDepth=this.depth+1),e.push(c)}),e}get firstChild(){return this.children[0]||null}get lastChild(){let e=this.children;return e[e.length-1]||null}closest(e,n={}){let r=null,s=this.parent;for(;s&&!r;){if(s.node.type.name===e)if(Object.keys(n).length>0){let i=s.node.attrs,o=Object.keys(n);for(let l=0;l{r&&s.length>0||(o.node.type.name===e&&i.every(a=>n[a]===o.node.attrs[a])&&s.push(o),!(r&&s.length>0)&&(s=s.concat(o.querySelectorAll(e,n,r))))}),s}setAttribute(e){let{tr:n}=this.editor.state;n.setNodeMarkup(this.from,void 0,{...this.node.attrs,...e}),this.editor.view.dispatch(n)}},yy=`.ProseMirror { +var Hp=Object.defineProperty;var _p=(n,e,t)=>e in n?Hp(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t;var j=(n,e,t)=>_p(n,typeof e!="symbol"?e+"":e,t);function de(n){this.content=n}de.prototype={constructor:de,find:function(n){for(var e=0;e>1}};de.from=function(n){if(n instanceof de)return n;var e=[];if(n)for(var t in n)e.push(t,n[t]);return new de(e)};var Wi=de;function sc(n,e,t){for(let r=0;;r++){if(r==n.childCount||r==e.childCount)return n.childCount==e.childCount?null:t;let s=n.child(r),i=e.child(r);if(s==i){t+=s.nodeSize;continue}if(!s.sameMarkup(i))return t;if(s.isText&&s.text!=i.text){let o=s.text,l=i.text,a=0;for(;o[a]==l[a];a++)t++;return a&&a0&&h>0&&c[d-1]==u[h-1];)d--,h--,t--,r--;return d&&h&&d=56320&&n<57344}function lc(n){return n>=55296&&n<56320}var b=class n{constructor(e,t){if(this.content=e,this.size=t||0,t==null)for(let r=0;re&&r(a,s+l,i||null,o)!==!1&&a.content.size){let u=l+1;a.nodesBetween(Math.max(0,e-u),Math.min(a.content.size,t-u),r,s+u)}l=c}}descendants(e){this.nodesBetween(0,this.size,e)}textBetween(e,t,r,s){let i="",o=!0;return this.nodesBetween(e,t,(l,a)=>{let c=l.isText?l.text.slice(Math.max(e,a)-a,t-a):l.isLeaf?s?typeof s=="function"?s(l):s:l.type.spec.leafText?l.type.spec.leafText(l):"":"";l.isBlock&&(l.isLeaf&&c||l.isTextblock)&&r&&(o?o=!1:i+=r),i+=c},0),i}append(e){if(!e.size)return this;if(!this.size)return e;let t=this.lastChild,r=e.firstChild,s=this.content.slice(),i=0;for(t.isText&&t.sameMarkup(r)&&(s[s.length-1]=t.withText(t.text+r.text),i=1);ie)for(let i=0,o=0;oe&&((ot)&&(l.isText?l=l.cut(Math.max(0,e-o),Math.min(l.text.length,t-o)):l=l.cut(Math.max(0,e-o-1),Math.min(l.content.size,t-o-1))),r.push(l),s+=l.nodeSize),o=a}return new n(r,s)}cutByIndex(e,t){return e==t?n.empty:e==0&&t==this.content.length?this:new n(this.content.slice(e,t))}replaceChild(e,t){let r=this.content[e];if(r==t)return this;let s=this.content.slice(),i=this.size+t.nodeSize-r.nodeSize;return s[e]=t,new n(s,i)}addToStart(e){return new n([e].concat(this.content),this.size+e.nodeSize)}addToEnd(e){return new n(this.content.concat(e),this.size+e.nodeSize)}eq(e){if(this.content.length!=e.content.length)return!1;for(let t=0;tthis.size||e<0)throw new RangeError(`Position ${e} outside of fragment (${this})`);for(let t=0,r=0;;t++){let s=this.child(t),i=r+s.nodeSize;if(i>=e)return i==e?Lr(t+1,i):Lr(t,r);r=i}}toString(){return"<"+this.toStringInner()+">"}toStringInner(){return this.content.join(", ")}toJSON(){return this.content.length?this.content.map(e=>e.toJSON()):null}static fromJSON(e,t){if(!t)return n.empty;if(!Array.isArray(t))throw new RangeError("Invalid input for Fragment.fromJSON");return n.fromArray(t.map(e.nodeFromJSON))}static fromArray(e){if(!e.length)return n.empty;let t,r=0;for(let s=0;sthis.type.rank&&(t||(t=e.slice(0,s)),t.push(this),r=!0),t&&t.push(i)}}return t||(t=e.slice()),r||t.push(this),t}removeFromSet(e){for(let t=0;tr.type.rank-s.type.rank),t}};_.none=[];var yt=class extends Error{},v=class n{constructor(e,t,r){this.content=e,this.openStart=t,this.openEnd=r}get size(){return this.content.size-this.openStart-this.openEnd}insertAt(e,t){let r=cc(this.content,e+this.openStart,t,this.openStart+1,this.openEnd+1);return r&&new n(r,this.openStart,this.openEnd)}removeBetween(e,t){return new n(ac(this.content,e+this.openStart,t+this.openStart),this.openStart,this.openEnd)}eq(e){return this.content.eq(e.content)&&this.openStart==e.openStart&&this.openEnd==e.openEnd}toString(){return this.content+"("+this.openStart+","+this.openEnd+")"}toJSON(){if(!this.content.size)return null;let e={content:this.content.toJSON()};return this.openStart>0&&(e.openStart=this.openStart),this.openEnd>0&&(e.openEnd=this.openEnd),e}static fromJSON(e,t){if(!t)return n.empty;let r=t.openStart||0,s=t.openEnd||0;if(typeof r!="number"||typeof s!="number")throw new RangeError("Invalid input for Slice.fromJSON");return new n(b.fromJSON(e,t.content),r,s)}static maxOpen(e,t=!0){let r=0,s=0;for(let i=e.firstChild;i&&!i.isLeaf&&(t||!i.type.spec.isolating);i=i.firstChild)r++;for(let i=e.lastChild;i&&!i.isLeaf&&(t||!i.type.spec.isolating);i=i.lastChild)s++;return new n(e,r,s)}};v.empty=new v(b.empty,0,0);function ac(n,e,t){let{index:r,offset:s}=n.findIndex(e),i=n.maybeChild(r),{index:o,offset:l}=n.findIndex(t);if(s==e||i.isText){if(l!=t&&!n.child(o).isText)throw new RangeError("Removing non-flat range");return n.cut(0,e).append(n.cut(t))}if(r!=o)throw new RangeError("Removing non-flat range");return n.replaceChild(r,i.copy(ac(i.content,e-s-1,t-s-1)))}function cc(n,e,t,r,s,i){let{index:o,offset:l}=n.findIndex(e),a=n.maybeChild(o);if(l==e||a.isText)return i&&r<=0&&s<=0&&!i.canReplace(o,o,t)?null:n.cut(0,e).append(t).append(n.cut(e));let c=cc(a.content,e-l-1,t,o==0?r-1:0,o==n.childCount-1?s-1:0,a);return c&&n.replaceChild(o,a.copy(c))}function Vp(n,e,t){if(t.openStart>n.depth)throw new yt("Inserted content deeper than insertion position");if(n.depth-t.openStart!=e.depth-t.openEnd)throw new yt("Inconsistent open depths");return uc(n,e,t,0)}function uc(n,e,t,r){let s=n.index(r),i=n.node(r);if(s==e.index(r)&&r=0&&n.isText&&n.sameMarkup(e[t])?e[t]=n.withText(e[t].text+n.text):e.push(n)}function Pn(n,e,t,r){let s=(e||n).node(t),i=0,o=e?e.index(t):s.childCount;n&&(i=n.index(t),n.depth>t?i++:n.textOffset&&(Rt(n.nodeAfter,r),i++));for(let l=i;ls&&Ji(n,e,s+1),o=r.depth>s&&Ji(t,r,s+1),l=[];return Pn(null,n,s,l),i&&o&&e.index(s)==t.index(s)?(dc(i,o),Rt(Ot(i,hc(n,e,t,r,s+1)),l)):(i&&Rt(Ot(i,Fr(n,e,s+1)),l),Pn(e,t,s,l),o&&Rt(Ot(o,Fr(t,r,s+1)),l)),Pn(r,null,s,l),new b(l)}function Fr(n,e,t){let r=[];if(Pn(null,n,t,r),n.depth>t){let s=Ji(n,e,t+1);Rt(Ot(s,Fr(n,e,t+1)),r)}return Pn(e,null,t,r),new b(r)}function Wp(n,e){let t=e.depth-n.openStart,s=e.node(t).copy(n.content);for(let i=t-1;i>=0;i--)s=e.node(i).copy(b.from(s));return{start:s.resolveNoCache(n.openStart+t),end:s.resolveNoCache(s.content.size-n.openEnd-t)}}var Hr=class n{constructor(e,t,r){this.pos=e,this.path=t,this.parentOffset=r,this.depth=t.length/3-1}resolveDepth(e){return e==null?this.depth:e<0?this.depth+e:e}get parent(){return this.node(this.depth)}get doc(){return this.node(0)}node(e){return this.path[this.resolveDepth(e)*3]}index(e){return this.path[this.resolveDepth(e)*3+1]}indexAfter(e){return e=this.resolveDepth(e),this.index(e)+(e==this.depth&&!this.textOffset?0:1)}start(e){return e=this.resolveDepth(e),e==0?0:this.path[e*3-1]+1}end(e){return e=this.resolveDepth(e),this.start(e)+this.node(e).content.size}before(e){if(e=this.resolveDepth(e),!e)throw new RangeError("There is no position before the top-level node");return e==this.depth+1?this.pos:this.path[e*3-1]}after(e){if(e=this.resolveDepth(e),!e)throw new RangeError("There is no position after the top-level node");return e==this.depth+1?this.pos:this.path[e*3-1]+this.path[e*3].nodeSize}get textOffset(){return this.pos-this.path[this.path.length-1]}get nodeAfter(){let e=this.parent,t=this.index(this.depth);if(t==e.childCount)return null;let r=this.pos-this.path[this.path.length-1],s=e.child(t);return r?e.child(t).cut(r):s}get nodeBefore(){let e=this.index(this.depth),t=this.pos-this.path[this.path.length-1];return t?this.parent.child(e).cut(0,t):e==0?null:this.parent.child(e-1)}posAtIndex(e,t){t=this.resolveDepth(t);let r=this.path[t*3],s=t==0?0:this.path[t*3-1]+1;for(let i=0;i0;t--)if(this.start(t)<=e&&this.end(t)>=e)return t;return 0}blockRange(e=this,t){if(e.pos=0;r--)if(e.pos<=this.end(r)&&(!t||t(this.node(r))))return new It(this,e,r);return null}sameParent(e){return this.pos-this.parentOffset==e.pos-e.parentOffset}max(e){return e.pos>this.pos?e:this}min(e){return e.pos=0&&t<=e.content.size))throw new RangeError("Position "+t+" out of range");let r=[],s=0,i=t;for(let o=e;;){let{index:l,offset:a}=o.content.findIndex(i),c=i-a;if(r.push(o,l,s+a),!c||(o=o.child(l),o.isText))break;i=c-1,s+=a+1}return new n(t,r,i)}static resolveCached(e,t){let r=Ga.get(e);if(r)for(let i=0;ie&&this.nodesBetween(e,t,i=>(r.isInSet(i.marks)&&(s=!0),!s)),s}get isBlock(){return this.type.isBlock}get isTextblock(){return this.type.isTextblock}get inlineContent(){return this.type.inlineContent}get isInline(){return this.type.isInline}get isText(){return this.type.isText}get isLeaf(){return this.type.isLeaf}get isAtom(){return this.type.isAtom}toString(){if(this.type.spec.toDebugString)return this.type.spec.toDebugString(this);let e=this.type.name;return this.content.size&&(e+="("+this.content.toStringInner()+")"),fc(this.marks,e)}contentMatchAt(e){let t=this.type.contentMatch.matchFragment(this.content,0,e);if(!t)throw new Error("Called contentMatchAt on a node with invalid content");return t}canReplace(e,t,r=b.empty,s=0,i=r.childCount){let o=this.contentMatchAt(e).matchFragment(r,s,i),l=o&&o.matchFragment(this.content,t);if(!l||!l.validEnd)return!1;for(let a=s;at.type.name)}`);this.content.forEach(t=>t.check())}toJSON(){let e={type:this.type.name};for(let t in this.attrs){e.attrs=this.attrs;break}return this.content.size&&(e.content=this.content.toJSON()),this.marks.length&&(e.marks=this.marks.map(t=>t.toJSON())),e}static fromJSON(e,t){if(!t)throw new RangeError("Invalid input for Node.fromJSON");let r;if(t.marks){if(!Array.isArray(t.marks))throw new RangeError("Invalid mark data for Node.fromJSON");r=t.marks.map(e.markFromJSON)}if(t.type=="text"){if(typeof t.text!="string")throw new RangeError("Invalid text node in JSON");return e.text(t.text,r)}let s=b.fromJSON(e,t.content),i=e.nodeType(t.type).create(t.attrs,s,r);return i.type.checkAttrs(i.attrs),i}};ve.prototype.text=void 0;var qi=class n extends ve{constructor(e,t,r,s){if(super(e,t,null,s),!r)throw new RangeError("Empty text nodes are not allowed");this.text=r}toString(){return this.type.spec.toDebugString?this.type.spec.toDebugString(this):fc(this.marks,JSON.stringify(this.text))}get textContent(){return this.text}textBetween(e,t){return this.text.slice(e,t)}get nodeSize(){return this.text.length}mark(e){return e==this.marks?this:new n(this.type,this.attrs,this.text,e)}withText(e){return e==this.text?this:new n(this.type,this.attrs,e,this.marks)}cut(e=0,t=this.text.length){return e==0&&t==this.text.length?this:this.withText(this.text.slice(e,t))}eq(e){return this.sameMarkup(e)&&this.text==e.text}toJSON(){let e=super.toJSON();return e.text=this.text,e}};function fc(n,e){for(let t=n.length-1;t>=0;t--)e=n[t].type.name+"("+e+")";return e}var Dt=class n{constructor(e){this.validEnd=e,this.next=[],this.wrapCache=[]}static parse(e,t){let r=new Ui(e,t);if(r.next==null)return n.empty;let s=pc(r);r.next&&r.err("Unexpected trailing text");let i=Yp(Qp(s));return Zp(i,r),i}matchType(e){for(let t=0;tc.createAndFill()));for(let c=0;c=this.next.length)throw new RangeError(`There's no ${e}th edge in this content match`);return this.next[e]}toString(){let e=[];function t(r){e.push(r);for(let s=0;s{let i=s+(r.validEnd?"*":" ")+" ";for(let o=0;o"+e.indexOf(r.next[o].next);return i}).join(` +`)}};Dt.empty=new Dt(!0);var Ui=class{constructor(e,t){this.string=e,this.nodeTypes=t,this.inline=null,this.pos=0,this.tokens=e.split(/\s*(?=\b|\W|$)/),this.tokens[this.tokens.length-1]==""&&this.tokens.pop(),this.tokens[0]==""&&this.tokens.shift()}get next(){return this.tokens[this.pos]}eat(e){return this.next==e&&(this.pos++||!0)}err(e){throw new SyntaxError(e+" (in content expression '"+this.string+"')")}};function pc(n){let e=[];do e.push(Kp(n));while(n.eat("|"));return e.length==1?e[0]:{type:"choice",exprs:e}}function Kp(n){let e=[];do e.push(qp(n));while(n.next&&n.next!=")"&&n.next!="|");return e.length==1?e[0]:{type:"seq",exprs:e}}function qp(n){let e=Xp(n);for(;;)if(n.eat("+"))e={type:"plus",expr:e};else if(n.eat("*"))e={type:"star",expr:e};else if(n.eat("?"))e={type:"opt",expr:e};else if(n.eat("{"))e=Up(n,e);else break;return e}function Xa(n){/\D/.test(n.next)&&n.err("Expected number, got '"+n.next+"'");let e=Number(n.next);return n.pos++,e}function Up(n,e){let t=Xa(n),r=t;return n.eat(",")&&(n.next!="}"?r=Xa(n):r=-1),n.eat("}")||n.err("Unclosed braced range"),{type:"range",min:t,max:r,expr:e}}function Gp(n,e){let t=n.nodeTypes,r=t[e];if(r)return[r];let s=[];for(let i in t){let o=t[i];o.isInGroup(e)&&s.push(o)}return s.length==0&&n.err("No node type or group '"+e+"' found"),s}function Xp(n){if(n.eat("(")){let e=pc(n);return n.eat(")")||n.err("Missing closing paren"),e}else if(/\W/.test(n.next))n.err("Unexpected token '"+n.next+"'");else{let e=Gp(n,n.next).map(t=>(n.inline==null?n.inline=t.isInline:n.inline!=t.isInline&&n.err("Mixing inline and block content"),{type:"name",value:t}));return n.pos++,e.length==1?e[0]:{type:"choice",exprs:e}}}function Qp(n){let e=[[]];return s(i(n,0),t()),e;function t(){return e.push([])-1}function r(o,l,a){let c={term:a,to:l};return e[o].push(c),c}function s(o,l){o.forEach(a=>a.to=l)}function i(o,l){if(o.type=="choice")return o.exprs.reduce((a,c)=>a.concat(i(c,l)),[]);if(o.type=="seq")for(let a=0;;a++){let c=i(o.exprs[a],l);if(a==o.exprs.length-1)return c;s(c,l=t())}else if(o.type=="star"){let a=t();return r(l,a),s(i(o.expr,a),a),[r(a)]}else if(o.type=="plus"){let a=t();return s(i(o.expr,l),a),s(i(o.expr,a),a),[r(a)]}else{if(o.type=="opt")return[r(l)].concat(i(o.expr,l));if(o.type=="range"){let a=l;for(let c=0;c{n[o].forEach(({term:l,to:a})=>{if(!l)return;let c;for(let u=0;u{c||s.push([l,c=[]]),c.indexOf(u)==-1&&c.push(u)})})});let i=e[r.join(",")]=new Dt(r.indexOf(n.length-1)>-1);for(let o=0;o-1}get whitespace(){return this.spec.whitespace||(this.spec.code?"pre":"normal")}hasRequiredAttrs(){for(let e in this.attrs)if(this.attrs[e].isRequired)return!0;return!1}compatibleContent(e){return this==e||this.contentMatch.compatible(e.contentMatch)}computeAttrs(e){return!e&&this.defaultAttrs?this.defaultAttrs:yc(this.attrs,e)}create(e=null,t,r){if(this.isText)throw new Error("NodeType.create can't construct text nodes");return new ve(this,this.computeAttrs(e),b.from(t),_.setFrom(r))}createChecked(e=null,t,r){return t=b.from(t),this.checkContent(t),new ve(this,this.computeAttrs(e),t,_.setFrom(r))}createAndFill(e=null,t,r){if(e=this.computeAttrs(e),t=b.from(t),t.size){let o=this.contentMatch.fillBefore(t);if(!o)return null;t=o.append(t)}let s=this.contentMatch.matchFragment(t),i=s&&s.fillBefore(b.empty,!0);return i?new ve(this,e,t.append(i),_.setFrom(r)):null}validContent(e){let t=this.contentMatch.matchFragment(e);if(!t||!t.validEnd)return!1;for(let r=0;r-1}allowsMarks(e){if(this.markSet==null)return!0;for(let t=0;tr[i]=new n(i,t,o));let s=t.spec.topNode||"doc";if(!r[s])throw new RangeError("Schema is missing its top node type ('"+s+"')");if(!r.text)throw new RangeError("Every schema needs a 'text' type");for(let i in r.text.attrs)throw new RangeError("The text node type should not have attributes");return r}};function em(n,e,t){let r=t.split("|");return s=>{let i=s===null?"null":typeof s;if(r.indexOf(i)<0)throw new RangeError(`Expected value of type ${r} for attribute ${e} on type ${n}, got ${i}`)}}var Gi=class{constructor(e,t,r){this.hasDefault=Object.prototype.hasOwnProperty.call(r,"default"),this.default=r.default,this.validate=typeof r.validate=="string"?em(e,t,r.validate):r.validate}get isRequired(){return!this.hasDefault}},zn=class n{constructor(e,t,r,s){this.name=e,this.rank=t,this.schema=r,this.spec=s,this.attrs=bc(e,s.attrs),this.excluded=null;let i=gc(this.attrs);this.instance=i?new _(this,i):null}create(e=null){return!e&&this.instance?this.instance:new _(this,yc(this.attrs,e))}static compile(e,t){let r=Object.create(null),s=0;return e.forEach((i,o)=>r[i]=new n(i,s++,t,o)),r}removeFromSet(e){for(var t=0;t-1}},cn=class{constructor(e){this.linebreakReplacement=null,this.cached=Object.create(null);let t=this.spec={};for(let s in e)t[s]=e[s];t.nodes=Wi.from(e.nodes),t.marks=Wi.from(e.marks||{}),this.nodes=_r.compile(this.spec.nodes,this),this.marks=zn.compile(this.spec.marks,this);let r=Object.create(null);for(let s in this.nodes){if(s in this.marks)throw new RangeError(s+" can not be both a node and a mark");let i=this.nodes[s],o=i.spec.content||"",l=i.spec.marks;if(i.contentMatch=r[o]||(r[o]=Dt.parse(o,this.nodes)),i.inlineContent=i.contentMatch.inlineContent,i.spec.linebreakReplacement){if(this.linebreakReplacement)throw new RangeError("Multiple linebreak nodes defined");if(!i.isInline||!i.isLeaf)throw new RangeError("Linebreak replacement nodes must be inline leaf nodes");this.linebreakReplacement=i}i.markSet=l=="_"?null:l?Ya(this,l.split(" ")):l==""||!i.inlineContent?[]:null}for(let s in this.marks){let i=this.marks[s],o=i.spec.excludes;i.excluded=o==null?[i]:o==""?[]:Ya(this,o.split(" "))}this.nodeFromJSON=s=>ve.fromJSON(this,s),this.markFromJSON=s=>_.fromJSON(this,s),this.topNodeType=this.nodes[this.spec.topNode||"doc"],this.cached.wrappings=Object.create(null)}node(e,t=null,r,s){if(typeof e=="string")e=this.nodeType(e);else if(e instanceof _r){if(e.schema!=this)throw new RangeError("Node type from different schema used ("+e.name+")")}else throw new RangeError("Invalid node type: "+e);return e.createChecked(t,r,s)}text(e,t){let r=this.nodes.text;return new qi(r,r.defaultAttrs,e,_.setFrom(t))}mark(e,t){return typeof e=="string"&&(e=this.marks[e]),e.create(t)}nodeType(e){let t=this.nodes[e];if(!t)throw new RangeError("Unknown node type: "+e);return t}};function Ya(n,e){let t=[];for(let r=0;r-1)&&t.push(o=a)}if(!o)throw new SyntaxError("Unknown mark type: '"+e[r]+"'")}return t}function tm(n){return n.tag!=null}function nm(n){return n.style!=null}var Fe=class n{constructor(e,t){this.schema=e,this.rules=t,this.tags=[],this.styles=[];let r=this.matchedStyles=[];t.forEach(s=>{if(tm(s))this.tags.push(s);else if(nm(s)){let i=/[^=]*/.exec(s.style)[0];r.indexOf(i)<0&&r.push(i),this.styles.push(s)}}),this.normalizeLists=!this.tags.some(s=>{if(!/^(ul|ol)\b/.test(s.tag)||!s.node)return!1;let i=e.nodes[s.node];return i.contentMatch.matchType(i)})}parse(e,t={}){let r=new Vr(this,t,!1);return r.addAll(e,_.none,t.from,t.to),r.finish()}parseSlice(e,t={}){let r=new Vr(this,t,!0);return r.addAll(e,_.none,t.from,t.to),v.maxOpen(r.finish())}matchTag(e,t,r){for(let s=r?this.tags.indexOf(r)+1:0;se.length&&(l.charCodeAt(e.length)!=61||l.slice(e.length+1)!=t))){if(o.getAttrs){let a=o.getAttrs(t);if(a===!1)continue;o.attrs=a||void 0}return o}}}static schemaRules(e){let t=[];function r(s){let i=s.priority==null?50:s.priority,o=0;for(;o{r(o=ec(o)),o.mark||o.ignore||o.clearMark||(o.mark=s)})}for(let s in e.nodes){let i=e.nodes[s].spec.parseDOM;i&&i.forEach(o=>{r(o=ec(o)),o.node||o.ignore||o.mark||(o.node=s)})}return t}static fromSchema(e){return e.cached.domParser||(e.cached.domParser=new n(e,n.schemaRules(e)))}},xc={address:!0,article:!0,aside:!0,blockquote:!0,canvas:!0,dd:!0,div:!0,dl:!0,fieldset:!0,figcaption:!0,figure:!0,footer:!0,form:!0,h1:!0,h2:!0,h3:!0,h4:!0,h5:!0,h6:!0,header:!0,hgroup:!0,hr:!0,li:!0,noscript:!0,ol:!0,output:!0,p:!0,pre:!0,section:!0,table:!0,tfoot:!0,ul:!0},rm={head:!0,noscript:!0,object:!0,script:!0,style:!0,title:!0},wc={ol:!0,ul:!0},Bn=1,Xi=2,Ln=4;function Za(n,e,t){return e!=null?(e?Bn:0)|(e==="full"?Xi:0):n&&n.whitespace=="pre"?Bn|Xi:t&~Ln}var an=class{constructor(e,t,r,s,i,o){this.type=e,this.attrs=t,this.marks=r,this.solid=s,this.options=o,this.content=[],this.activeMarks=_.none,this.match=i||(o&Ln?null:e.contentMatch)}findWrapping(e){if(!this.match){if(!this.type)return[];let t=this.type.contentMatch.fillBefore(b.from(e));if(t)this.match=this.type.contentMatch.matchFragment(t);else{let r=this.type.contentMatch,s;return(s=r.findWrapping(e.type))?(this.match=r,s):null}}return this.match.findWrapping(e.type)}finish(e){if(!(this.options&Bn)){let r=this.content[this.content.length-1],s;if(r&&r.isText&&(s=/[ \t\r\n\u000c]+$/.exec(r.text))){let i=r;r.text.length==s[0].length?this.content.pop():this.content[this.content.length-1]=i.withText(i.text.slice(0,i.text.length-s[0].length))}}let t=b.from(this.content);return!e&&this.match&&(t=t.append(this.match.fillBefore(b.empty,!0))),this.type?this.type.create(this.attrs,t,this.marks):t}inlineContext(e){return this.type?this.type.inlineContent:this.content.length?this.content[0].isInline:e.parentNode&&!xc.hasOwnProperty(e.parentNode.nodeName.toLowerCase())}},Vr=class{constructor(e,t,r){this.parser=e,this.options=t,this.isOpen=r,this.open=0,this.localPreserveWS=!1;let s=t.topNode,i,o=Za(null,t.preserveWhitespace,0)|(r?Ln:0);s?i=new an(s.type,s.attrs,_.none,!0,t.topMatch||s.type.contentMatch,o):r?i=new an(null,null,_.none,!0,null,o):i=new an(e.schema.topNodeType,null,_.none,!0,null,o),this.nodes=[i],this.find=t.findPositions,this.needsBlock=!1}get top(){return this.nodes[this.open]}addDOM(e,t){e.nodeType==3?this.addTextNode(e,t):e.nodeType==1&&this.addElement(e,t)}addTextNode(e,t){let r=e.nodeValue,s=this.top,i=s.options&Xi?"full":this.localPreserveWS||(s.options&Bn)>0,{schema:o}=this.parser;if(i==="full"||s.inlineContext(e)||/[^ \t\r\n\u000c]/.test(r)){if(i)if(i==="full")r=r.replace(/\r\n?/g,` +`);else if(o.linebreakReplacement&&/[\r\n]/.test(r)&&this.top.findWrapping(o.linebreakReplacement.create())){let l=r.split(/\r?\n|\r/);for(let a=0;a!a.clearMark(c)):t=t.concat(this.parser.schema.marks[a.mark].create(a.attrs)),a.consuming===!1)l=a;else break}}return t}addElementByRule(e,t,r,s){let i,o;if(t.node)if(o=this.parser.schema.nodes[t.node],o.isLeaf)this.insertNode(o.create(t.attrs),r,e.nodeName=="BR")||this.leafFallback(e,r);else{let a=this.enter(o,t.attrs||null,r,t.preserveWhitespace);a&&(i=!0,r=a)}else{let a=this.parser.schema.marks[t.mark];r=r.concat(a.create(t.attrs))}let l=this.top;if(o&&o.isLeaf)this.findInside(e);else if(s)this.addElement(e,r,s);else if(t.getContent)this.findInside(e),t.getContent(e,this.parser.schema).forEach(a=>this.insertNode(a,r,!1));else{let a=e;typeof t.contentElement=="string"?a=e.querySelector(t.contentElement):typeof t.contentElement=="function"?a=t.contentElement(e):t.contentElement&&(a=t.contentElement),this.findAround(e,a,!0),this.addAll(a,r),this.findAround(e,a,!1)}i&&this.sync(l)&&this.open--}addAll(e,t,r,s){let i=r||0;for(let o=r?e.childNodes[r]:e.firstChild,l=s==null?null:e.childNodes[s];o!=l;o=o.nextSibling,++i)this.findAtPoint(e,i),this.addDOM(o,t);this.findAtPoint(e,i)}findPlace(e,t,r){let s,i;for(let o=this.open,l=0;o>=0;o--){let a=this.nodes[o],c=a.findWrapping(e);if(c&&(!s||s.length>c.length+l)&&(s=c,i=a,!c.length))break;if(a.solid){if(r)break;l+=2}}if(!s)return null;this.sync(i);for(let o=0;o(o.type?o.type.allowsMarkType(c.type):tc(c.type,e))?(a=c.addToSet(a),!1):!0),this.nodes.push(new an(e,t,a,s,null,l)),this.open++,r}closeExtra(e=!1){let t=this.nodes.length-1;if(t>this.open){for(;t>this.open;t--)this.nodes[t-1].content.push(this.nodes[t].finish(e));this.nodes.length=this.open+1}}finish(){return this.open=0,this.closeExtra(this.isOpen),this.nodes[0].finish(!!(this.isOpen||this.options.topOpen))}sync(e){for(let t=this.open;t>=0;t--){if(this.nodes[t]==e)return this.open=t,!0;this.localPreserveWS&&(this.nodes[t].options|=Bn)}return!1}get currentPos(){this.closeExtra();let e=0;for(let t=this.open;t>=0;t--){let r=this.nodes[t].content;for(let s=r.length-1;s>=0;s--)e+=r[s].nodeSize;t&&e++}return e}findAtPoint(e,t){if(this.find)for(let r=0;r-1)return e.split(/\s*\|\s*/).some(this.matchesContext,this);let t=e.split("/"),r=this.options.context,s=!this.isOpen&&(!r||r.parent.type==this.nodes[0].type),i=-(r?r.depth+1:0)+(s?0:1),o=(l,a)=>{for(;l>=0;l--){let c=t[l];if(c==""){if(l==t.length-1||l==0)continue;for(;a>=i;a--)if(o(l-1,a))return!0;return!1}else{let u=a>0||a==0&&s?this.nodes[a].type:r&&a>=i?r.node(a-i).type:null;if(!u||u.name!=c&&!u.isInGroup(c))return!1;a--}}return!0};return o(t.length-1,this.open)}textblockFromContext(){let e=this.options.context;if(e)for(let t=e.depth;t>=0;t--){let r=e.node(t).contentMatchAt(e.indexAfter(t)).defaultType;if(r&&r.isTextblock&&r.defaultAttrs)return r}for(let t in this.parser.schema.nodes){let r=this.parser.schema.nodes[t];if(r.isTextblock&&r.defaultAttrs)return r}}};function sm(n){for(let e=n.firstChild,t=null;e;e=e.nextSibling){let r=e.nodeType==1?e.nodeName.toLowerCase():null;r&&wc.hasOwnProperty(r)&&t?(t.appendChild(e),e=t):r=="li"?t=e:r&&(t=null)}}function im(n,e){return(n.matches||n.msMatchesSelector||n.webkitMatchesSelector||n.mozMatchesSelector).call(n,e)}function ec(n){let e={};for(let t in n)e[t]=n[t];return e}function tc(n,e){let t=e.schema.nodes;for(let r in t){let s=t[r];if(!s.allowsMarkType(n))continue;let i=[],o=l=>{i.push(l);for(let a=0;a{if(i.length||o.marks.length){let l=0,a=0;for(;l=0;s--){let i=this.serializeMark(e.marks[s],e.isInline,t);i&&((i.contentDOM||i.dom).appendChild(r),r=i.dom)}return r}serializeMark(e,t,r={}){let s=this.marks[e.type.name];return s&&Br(zr(r),s(e,t),null,e.attrs)}static renderSpec(e,t,r=null,s){return typeof t=="string"?{dom:e.createTextNode(t)}:Br(e,t,r,s)}static fromSchema(e){return e.cached.domSerializer||(e.cached.domSerializer=new n(this.nodesFromSchema(e),this.marksFromSchema(e)))}static nodesFromSchema(e){let t=nc(e.nodes);return t.text||(t.text=r=>r.text),t}static marksFromSchema(e){return nc(e.marks)}};function nc(n){let e={};for(let t in n){let r=n[t].spec.toDOM;r&&(e[t]=r)}return e}function zr(n){return n.document||window.document}var rc=new WeakMap;function om(n){let e=rc.get(n);return e===void 0&&rc.set(n,e=lm(n)),e}function lm(n){let e=null;function t(r){if(r&&typeof r=="object")if(Array.isArray(r))if(typeof r[0]=="string")e||(e=[]),e.push(r);else for(let s=0;s-1)throw new RangeError("Using an array from an attribute object as a DOM spec. This may be an attempted cross site scripting attack.");let o=s.indexOf(" ");o>0&&(t=s.slice(0,o),s=s.slice(o+1));let l,a=t?n.createElementNS(t,s):n.createElement(s),c=e[1],u=1;if(c&&typeof c=="object"&&c.nodeType==null&&!Array.isArray(c)){u=2;for(let d in c)if(c[d]!=null){let h=d.indexOf(" ");h>0?a.setAttributeNS(d.slice(0,h),d.slice(h+1),c[d]):d=="style"&&a.style?a.style.cssText=c[d]:a.setAttribute(d,c[d])}}for(let d=u;du)throw new RangeError("Content hole must be the only child of its parent node");return{dom:a,contentDOM:a}}else if(typeof h=="string")a.appendChild(n.createTextNode(h));else{let{dom:f,contentDOM:p}=Br(n,h,t,r);if(a.appendChild(f),p){if(l)throw new RangeError("Multiple content holes");l=p}}}return{dom:a,contentDOM:l}}var vc=65535,Tc=Math.pow(2,16);function am(n,e){return n+e*Tc}function Sc(n){return n&vc}function cm(n){return(n-(n&vc))/Tc}var Mc=1,Ac=2,Wr=4,Ec=8,Hn=class{constructor(e,t,r){this.pos=e,this.delInfo=t,this.recover=r}get deleted(){return(this.delInfo&Ec)>0}get deletedBefore(){return(this.delInfo&(Mc|Wr))>0}get deletedAfter(){return(this.delInfo&(Ac|Wr))>0}get deletedAcross(){return(this.delInfo&Wr)>0}},rt=class n{constructor(e,t=!1){if(this.ranges=e,this.inverted=t,!e.length&&n.empty)return n.empty}recover(e){let t=0,r=Sc(e);if(!this.inverted)for(let s=0;se)break;let c=this.ranges[l+i],u=this.ranges[l+o],d=a+c;if(e<=d){let h=c?e==a?-1:e==d?1:t:t,f=a+s+(h<0?0:u);if(r)return f;let p=e==(t<0?a:d)?null:am(l/3,e-a),m=e==a?Ac:e==d?Mc:Wr;return(t<0?e!=a:e!=d)&&(m|=Ec),new Hn(f,m,p)}s+=u-c}return r?e+s:new Hn(e+s,0,null)}touches(e,t){let r=0,s=Sc(t),i=this.inverted?2:1,o=this.inverted?1:2;for(let l=0;le)break;let c=this.ranges[l+i],u=a+c;if(e<=u&&l==s*3)return!0;r+=this.ranges[l+o]-c}return!1}forEach(e){let t=this.inverted?2:1,r=this.inverted?1:2;for(let s=0,i=0;s=0;t--){let s=e.getMirror(t);this.appendMap(e._maps[t].invert(),s!=null&&s>t?r-s-1:void 0)}}invert(){let e=new n;return e.appendMappingInverted(this),e}map(e,t=1){if(this.mirror)return this._map(e,t,!0);for(let r=this.from;ri&&a!o.isAtom||!l.type.allowsMarkType(this.mark.type)?o:o.mark(this.mark.addToSet(o.marks)),s),t.openStart,t.openEnd);return oe.fromReplace(e,this.from,this.to,i)}invert(){return new st(this.from,this.to,this.mark)}map(e){let t=e.mapResult(this.from,1),r=e.mapResult(this.to,-1);return t.deleted&&r.deleted||t.pos>=r.pos?null:new n(t.pos,r.pos,this.mark)}merge(e){return e instanceof n&&e.mark.eq(this.mark)&&this.from<=e.to&&this.to>=e.from?new n(Math.min(this.from,e.from),Math.max(this.to,e.to),this.mark):null}toJSON(){return{stepType:"addMark",mark:this.mark.toJSON(),from:this.from,to:this.to}}static fromJSON(e,t){if(typeof t.from!="number"||typeof t.to!="number")throw new RangeError("Invalid input for AddMarkStep.fromJSON");return new n(t.from,t.to,e.markFromJSON(t.mark))}};re.jsonID("addMark",Vn);var st=class n extends re{constructor(e,t,r){super(),this.from=e,this.to=t,this.mark=r}apply(e){let t=e.slice(this.from,this.to),r=new v(no(t.content,s=>s.mark(this.mark.removeFromSet(s.marks)),e),t.openStart,t.openEnd);return oe.fromReplace(e,this.from,this.to,r)}invert(){return new Vn(this.from,this.to,this.mark)}map(e){let t=e.mapResult(this.from,1),r=e.mapResult(this.to,-1);return t.deleted&&r.deleted||t.pos>=r.pos?null:new n(t.pos,r.pos,this.mark)}merge(e){return e instanceof n&&e.mark.eq(this.mark)&&this.from<=e.to&&this.to>=e.from?new n(Math.min(this.from,e.from),Math.max(this.to,e.to),this.mark):null}toJSON(){return{stepType:"removeMark",mark:this.mark.toJSON(),from:this.from,to:this.to}}static fromJSON(e,t){if(typeof t.from!="number"||typeof t.to!="number")throw new RangeError("Invalid input for RemoveMarkStep.fromJSON");return new n(t.from,t.to,e.markFromJSON(t.mark))}};re.jsonID("removeMark",st);var Wn=class n extends re{constructor(e,t){super(),this.pos=e,this.mark=t}apply(e){let t=e.nodeAt(this.pos);if(!t)return oe.fail("No node at mark step's position");let r=t.type.create(t.attrs,null,this.mark.addToSet(t.marks));return oe.fromReplace(e,this.pos,this.pos+1,new v(b.from(r),0,t.isLeaf?0:1))}invert(e){let t=e.nodeAt(this.pos);if(t){let r=this.mark.addToSet(t.marks);if(r.length==t.marks.length){for(let s=0;sr.pos?null:new n(t.pos,r.pos,s,i,this.slice,this.insert,this.structure)}toJSON(){let e={stepType:"replaceAround",from:this.from,to:this.to,gapFrom:this.gapFrom,gapTo:this.gapTo,insert:this.insert};return this.slice.size&&(e.slice=this.slice.toJSON()),this.structure&&(e.structure=!0),e}static fromJSON(e,t){if(typeof t.from!="number"||typeof t.to!="number"||typeof t.gapFrom!="number"||typeof t.gapTo!="number"||typeof t.insert!="number")throw new RangeError("Invalid input for ReplaceAroundStep.fromJSON");return new n(t.from,t.to,t.gapFrom,t.gapTo,v.fromJSON(e,t.slice),t.insert,!!t.structure)}};re.jsonID("replaceAround",Y);function eo(n,e,t){let r=n.resolve(e),s=t-e,i=r.depth;for(;s>0&&i>0&&r.indexAfter(i)==r.node(i).childCount;)i--,s--;if(s>0){let o=r.node(i).maybeChild(r.indexAfter(i));for(;s>0;){if(!o||o.isLeaf)return!0;o=o.firstChild,s--}}return!1}function um(n,e,t,r){let s=[],i=[],o,l;n.doc.nodesBetween(e,t,(a,c,u)=>{if(!a.isInline)return;let d=a.marks;if(!r.isInSet(d)&&u.type.allowsMarkType(r.type)){let h=Math.max(c,e),f=Math.min(c+a.nodeSize,t),p=r.addToSet(d);for(let m=0;mn.step(a)),i.forEach(a=>n.step(a))}function dm(n,e,t,r){let s=[],i=0;n.doc.nodesBetween(e,t,(o,l)=>{if(!o.isInline)return;i++;let a=null;if(r instanceof zn){let c=o.marks,u;for(;u=r.isInSet(c);)(a||(a=[])).push(u),c=u.removeFromSet(c)}else r?r.isInSet(o.marks)&&(a=[r]):a=o.marks;if(a&&a.length){let c=Math.min(l+o.nodeSize,t);for(let u=0;un.step(new st(o.from,o.to,o.style)))}function ro(n,e,t,r=t.contentMatch,s=!0){let i=n.doc.nodeAt(e),o=[],l=e+1;for(let a=0;a=0;a--)n.step(o[a])}function hm(n,e,t){return(e==0||n.canReplace(e,n.childCount))&&(t==n.childCount||n.canReplace(0,t))}function it(n){let t=n.parent.content.cutByIndex(n.startIndex,n.endIndex);for(let r=n.depth,s=0,i=0;;--r){let o=n.$from.node(r),l=n.$from.index(r)+s,a=n.$to.indexAfter(r)-i;if(rt;p--)m||r.index(p)>0?(m=!0,u=b.from(r.node(p).copy(u)),d++):a--;let h=b.empty,f=0;for(let p=i,m=!1;p>t;p--)m||s.after(p+1)=0;o--){if(r.size){let l=t[o].type.contentMatch.matchFragment(r);if(!l||!l.validEnd)throw new RangeError("Wrapper type given to Transform.wrap does not form valid content of its parent wrapper")}r=b.from(t[o].type.create(t[o].attrs,r))}let s=e.start,i=e.end;n.step(new Y(s,i,s,i,new v(r,0,0),t.length,!0))}function ym(n,e,t,r,s){if(!r.isTextblock)throw new RangeError("Type given to setBlockType should be a textblock");let i=n.steps.length;n.doc.nodesBetween(e,t,(o,l)=>{let a=typeof s=="function"?s(o):s;if(o.isTextblock&&!o.hasMarkup(r,a)&&km(n.doc,n.mapping.slice(i).map(l),r)){let c=null;if(r.schema.linebreakReplacement){let f=r.whitespace=="pre",p=!!r.contentMatch.matchType(r.schema.linebreakReplacement);f&&!p?c=!1:!f&&p&&(c=!0)}c===!1&&Rc(n,o,l,i),ro(n,n.mapping.slice(i).map(l,1),r,void 0,c===null);let u=n.mapping.slice(i),d=u.map(l,1),h=u.map(l+o.nodeSize,1);return n.step(new Y(d,h,d+1,h-1,new v(b.from(r.create(a,null,o.marks)),0,0),1,!0)),c===!0&&Nc(n,o,l,i),!1}})}function Nc(n,e,t,r){e.forEach((s,i)=>{if(s.isText){let o,l=/\r?\n|\r/g;for(;o=l.exec(s.text);){let a=n.mapping.slice(r).map(t+1+i+o.index);n.replaceWith(a,a+1,e.type.schema.linebreakReplacement.create())}}})}function Rc(n,e,t,r){e.forEach((s,i)=>{if(s.type==s.type.schema.linebreakReplacement){let o=n.mapping.slice(r).map(t+1+i);n.replaceWith(o,o+1,e.type.schema.text(` +`))}})}function km(n,e,t){let r=n.resolve(e),s=r.index();return r.parent.canReplaceWith(s,s+1,t)}function bm(n,e,t,r,s){let i=n.doc.nodeAt(e);if(!i)throw new RangeError("No node at given position");t||(t=i.type);let o=t.create(r,null,s||i.marks);if(i.isLeaf)return n.replaceWith(e,e+i.nodeSize,o);if(!t.validContent(i.content))throw new RangeError("Invalid content for node type "+t.name);n.step(new Y(e,e+i.nodeSize,e+1,e+i.nodeSize-1,new v(b.from(o),0,0),1,!0))}function Ae(n,e,t=1,r){let s=n.resolve(e),i=s.depth-t,o=r&&r[r.length-1]||s.parent;if(i<0||s.parent.type.spec.isolating||!s.parent.canReplace(s.index(),s.parent.childCount)||!o.type.validContent(s.parent.content.cutByIndex(s.index(),s.parent.childCount)))return!1;for(let c=s.depth-1,u=t-2;c>i;c--,u--){let d=s.node(c),h=s.index(c);if(d.type.spec.isolating)return!1;let f=d.content.cutByIndex(h,d.childCount),p=r&&r[u+1];p&&(f=f.replaceChild(0,p.type.create(p.attrs)));let m=r&&r[u]||d;if(!d.canReplace(h+1,d.childCount)||!m.type.validContent(f))return!1}let l=s.indexAfter(i),a=r&&r[0];return s.node(i).canReplaceWith(l,l,a?a.type:s.node(i+1).type)}function xm(n,e,t=1,r){let s=n.doc.resolve(e),i=b.empty,o=b.empty;for(let l=s.depth,a=s.depth-t,c=t-1;l>a;l--,c--){i=b.from(s.node(l).copy(i));let u=r&&r[c];o=b.from(u?u.type.create(u.attrs,o):s.node(l).copy(o))}n.step(new le(e,e,new v(i.append(o),t,t),!0))}function Re(n,e){let t=n.resolve(e),r=t.index();return Oc(t.nodeBefore,t.nodeAfter)&&t.parent.canReplace(r,r+1)}function wm(n,e){e.content.size||n.type.compatibleContent(e.type);let t=n.contentMatchAt(n.childCount),{linebreakReplacement:r}=n.type.schema;for(let s=0;s0?(i=r.node(s+1),l++,o=r.node(s).maybeChild(l)):(i=r.node(s).maybeChild(l-1),o=r.node(s+1)),i&&!i.isTextblock&&Oc(i,o)&&r.node(s).canReplace(l,l+1))return e;if(s==0)break;e=t<0?r.before(s):r.after(s)}}function Sm(n,e,t){let r=null,{linebreakReplacement:s}=n.doc.type.schema,i=n.doc.resolve(e-t),o=i.node().type;if(s&&o.inlineContent){let u=o.whitespace=="pre",d=!!o.contentMatch.matchType(s);u&&!d?r=!1:!u&&d&&(r=!0)}let l=n.steps.length;if(r===!1){let u=n.doc.resolve(e+t);Rc(n,u.node(),u.before(),l)}o.inlineContent&&ro(n,e+t-1,o,i.node().contentMatchAt(i.index()),r==null);let a=n.mapping.slice(l),c=a.map(e-t);if(n.step(new le(c,a.map(e+t,-1),v.empty,!0)),r===!0){let u=n.doc.resolve(c);Nc(n,u.node(),u.before(),n.steps.length)}return n}function Cm(n,e,t){let r=n.resolve(e);if(r.parent.canReplaceWith(r.index(),r.index(),t))return e;if(r.parentOffset==0)for(let s=r.depth-1;s>=0;s--){let i=r.index(s);if(r.node(s).canReplaceWith(i,i,t))return r.before(s+1);if(i>0)return null}if(r.parentOffset==r.parent.content.size)for(let s=r.depth-1;s>=0;s--){let i=r.indexAfter(s);if(r.node(s).canReplaceWith(i,i,t))return r.after(s+1);if(i=0;o--){let l=o==r.depth?0:r.pos<=(r.start(o+1)+r.end(o+1))/2?-1:1,a=r.index(o)+(l>0?1:0),c=r.node(o),u=!1;if(i==1)u=c.canReplace(a,a,s);else{let d=c.contentMatchAt(a).findWrapping(s.firstChild.type);u=d&&c.canReplaceWith(a,a,d[0])}if(u)return l==0?r.pos:l<0?r.before(o+1):r.after(o+1)}return null}function jn(n,e,t=e,r=v.empty){if(e==t&&!r.size)return null;let s=n.resolve(e),i=n.resolve(t);return Ic(s,i,r)?new le(e,t,r):new to(s,i,r).fit()}function Ic(n,e,t){return!t.openStart&&!t.openEnd&&n.start()==e.start()&&n.parent.canReplace(n.index(),e.index(),t.content)}var to=class{constructor(e,t,r){this.$from=e,this.$to=t,this.unplaced=r,this.frontier=[],this.placed=b.empty;for(let s=0;s<=e.depth;s++){let i=e.node(s);this.frontier.push({type:i.type,match:i.contentMatchAt(e.indexAfter(s))})}for(let s=e.depth;s>0;s--)this.placed=b.from(e.node(s).copy(this.placed))}get depth(){return this.frontier.length-1}fit(){for(;this.unplaced.size;){let c=this.findFittable();c?this.placeNodes(c):this.openMore()||this.dropNode()}let e=this.mustMoveInline(),t=this.placed.size-this.depth-this.$from.depth,r=this.$from,s=this.close(e<0?this.$to:r.doc.resolve(e));if(!s)return null;let i=this.placed,o=r.depth,l=s.depth;for(;o&&l&&i.childCount==1;)i=i.firstChild.content,o--,l--;let a=new v(i,o,l);return e>-1?new Y(r.pos,e,this.$to.pos,this.$to.end(),a,t):a.size||r.pos!=this.$to.pos?new le(r.pos,s.pos,a):null}findFittable(){let e=this.unplaced.openStart;for(let t=this.unplaced.content,r=0,s=this.unplaced.openEnd;r1&&(s=0),i.type.spec.isolating&&s<=r){e=r;break}t=i.content}for(let t=1;t<=2;t++)for(let r=t==1?e:this.unplaced.openStart;r>=0;r--){let s,i=null;r?(i=Yi(this.unplaced.content,r-1).firstChild,s=i.content):s=this.unplaced.content;let o=s.firstChild;for(let l=this.depth;l>=0;l--){let{type:a,match:c}=this.frontier[l],u,d=null;if(t==1&&(o?c.matchType(o.type)||(d=c.fillBefore(b.from(o),!1)):i&&a.compatibleContent(i.type)))return{sliceDepth:r,frontierDepth:l,parent:i,inject:d};if(t==2&&o&&(u=c.findWrapping(o.type)))return{sliceDepth:r,frontierDepth:l,parent:i,wrap:u};if(i&&c.matchType(i.type))break}}}openMore(){let{content:e,openStart:t,openEnd:r}=this.unplaced,s=Yi(e,t);return!s.childCount||s.firstChild.isLeaf?!1:(this.unplaced=new v(e,t+1,Math.max(r,s.size+t>=e.size-r?t+1:0)),!0)}dropNode(){let{content:e,openStart:t,openEnd:r}=this.unplaced,s=Yi(e,t);if(s.childCount<=1&&t>0){let i=e.size-t<=t+s.size;this.unplaced=new v($n(e,t-1,1),t-1,i?t-1:r)}else this.unplaced=new v($n(e,t,1),t,r)}placeNodes({sliceDepth:e,frontierDepth:t,parent:r,inject:s,wrap:i}){for(;this.depth>t;)this.closeFrontierNode();if(i)for(let m=0;m1||a==0||m.content.size)&&(d=g,u.push(Dc(m.mark(h.allowedMarks(m.marks)),c==1?a:0,c==l.childCount?f:-1)))}let p=c==l.childCount;p||(f=-1),this.placed=Fn(this.placed,t,b.from(u)),this.frontier[t].match=d,p&&f<0&&r&&r.type==this.frontier[this.depth].type&&this.frontier.length>1&&this.closeFrontierNode();for(let m=0,g=l;m1&&s==this.$to.end(--r);)++s;return s}findCloseLevel(e){e:for(let t=Math.min(this.depth,e.depth);t>=0;t--){let{match:r,type:s}=this.frontier[t],i=t=0;l--){let{match:a,type:c}=this.frontier[l],u=Zi(e,l,c,a,!0);if(!u||u.childCount)continue e}return{depth:t,fit:o,move:i?e.doc.resolve(e.after(t+1)):e}}}}close(e){let t=this.findCloseLevel(e);if(!t)return null;for(;this.depth>t.depth;)this.closeFrontierNode();t.fit.childCount&&(this.placed=Fn(this.placed,t.depth,t.fit)),e=t.move;for(let r=t.depth+1;r<=e.depth;r++){let s=e.node(r),i=s.type.contentMatch.fillBefore(s.content,!0,e.index(r));this.openFrontierNode(s.type,s.attrs,i)}return e}openFrontierNode(e,t=null,r){let s=this.frontier[this.depth];s.match=s.match.matchType(e),this.placed=Fn(this.placed,this.depth,b.from(e.create(t,r))),this.frontier.push({type:e,match:e.contentMatch})}closeFrontierNode(){let t=this.frontier.pop().match.fillBefore(b.empty,!0);t.childCount&&(this.placed=Fn(this.placed,this.frontier.length,t))}};function $n(n,e,t){return e==0?n.cutByIndex(t,n.childCount):n.replaceChild(0,n.firstChild.copy($n(n.firstChild.content,e-1,t)))}function Fn(n,e,t){return e==0?n.append(t):n.replaceChild(n.childCount-1,n.lastChild.copy(Fn(n.lastChild.content,e-1,t)))}function Yi(n,e){for(let t=0;t1&&(r=r.replaceChild(0,Dc(r.firstChild,e-1,r.childCount==1?t-1:0))),e>0&&(r=n.type.contentMatch.fillBefore(r).append(r),t<=0&&(r=r.append(n.type.contentMatch.matchFragment(r).fillBefore(b.empty,!0)))),n.copy(r)}function Zi(n,e,t,r,s){let i=n.node(e),o=s?n.indexAfter(e):n.index(e);if(o==i.childCount&&!t.compatibleContent(i.type))return null;let l=r.fillBefore(i.content,!0,o);return l&&!vm(t,i.content,o)?l:null}function vm(n,e,t){for(let r=t;r0;h--,f--){let p=s.node(h).type.spec;if(p.defining||p.definingAsContext||p.isolating)break;o.indexOf(h)>-1?l=h:s.before(h)==f&&o.splice(1,0,-h)}let a=o.indexOf(l),c=[],u=r.openStart;for(let h=r.content,f=0;;f++){let p=h.firstChild;if(c.push(p),f==r.openStart)break;h=p.content}for(let h=u-1;h>=0;h--){let f=c[h],p=Tm(f.type);if(p&&!f.sameMarkup(s.node(Math.abs(l)-1)))u=h;else if(p||!f.type.isTextblock)break}for(let h=r.openStart;h>=0;h--){let f=(h+u+1)%(r.openStart+1),p=c[f];if(p)for(let m=0;m=0&&(n.replace(e,t,r),!(n.steps.length>d));h--){let f=o[h];f<0||(e=s.before(f),t=i.after(f))}}function Pc(n,e,t,r,s){if(er){let i=s.contentMatchAt(0),o=i.fillBefore(n).append(n);n=o.append(i.matchFragment(o).fillBefore(b.empty,!0))}return n}function Am(n,e,t,r){if(!r.isInline&&e==t&&n.doc.resolve(e).parent.content.size){let s=Cm(n.doc,e,r.type);s!=null&&(e=t=s)}n.replaceRange(e,t,new v(b.from(r),0,0))}function Em(n,e,t){let r=n.doc.resolve(e),s=n.doc.resolve(t);if(r.parent.isTextblock&&s.parent.isTextblock&&r.start()!=s.start()&&r.parentOffset==0&&s.parentOffset==0){let o=r.sharedDepth(t),l=!1;for(let a=r.depth;a>o;a--)r.node(a).type.spec.isolating&&(l=!0);for(let a=s.depth;a>o;a--)s.node(a).type.spec.isolating&&(l=!0);if(!l){for(let a=r.depth;a>0&&e==r.start(a);a--)e=r.before(a);for(let a=s.depth;a>0&&t==s.start(a);a--)t=s.before(a);r=n.doc.resolve(e),s=n.doc.resolve(t)}}let i=Lc(r,s);for(let o=0;o0&&(a||r.node(l-1).canReplace(r.index(l-1),s.indexAfter(l-1))))return n.delete(r.before(l),s.after(l))}for(let o=1;o<=r.depth&&o<=s.depth;o++)if(e-r.start(o)==r.depth-o&&t>r.end(o)&&s.end(o)-t!=s.depth-o&&r.start(o-1)==s.start(o-1)&&r.node(o-1).canReplace(r.index(o-1),s.index(o-1)))return n.delete(r.before(o),t);n.delete(e,t)}function Lc(n,e){let t=[],r=Math.min(n.depth,e.depth);for(let s=r;s>=0;s--){let i=n.start(s);if(ie.pos+(e.depth-s)||n.node(s).type.spec.isolating||e.node(s).type.spec.isolating)break;(i==e.start(s)||s==n.depth&&s==e.depth&&n.parent.inlineContent&&e.parent.inlineContent&&s&&e.start(s-1)==i-1)&&t.push(s)}return t}var jr=class n extends re{constructor(e,t,r){super(),this.pos=e,this.attr=t,this.value=r}apply(e){let t=e.nodeAt(this.pos);if(!t)return oe.fail("No node at attribute step's position");let r=Object.create(null);for(let i in t.attrs)r[i]=t.attrs[i];r[this.attr]=this.value;let s=t.type.create(r,null,t.marks);return oe.fromReplace(e,this.pos,this.pos+1,new v(b.from(s),0,t.isLeaf?0:1))}getMap(){return rt.empty}invert(e){return new n(this.pos,this.attr,e.nodeAt(this.pos).attrs[this.attr])}map(e){let t=e.mapResult(this.pos,1);return t.deletedAfter?null:new n(t.pos,this.attr,this.value)}toJSON(){return{stepType:"attr",pos:this.pos,attr:this.attr,value:this.value}}static fromJSON(e,t){if(typeof t.pos!="number"||typeof t.attr!="string")throw new RangeError("Invalid input for AttrStep.fromJSON");return new n(t.pos,t.attr,t.value)}};re.jsonID("attr",jr);var Jr=class n extends re{constructor(e,t){super(),this.attr=e,this.value=t}apply(e){let t=Object.create(null);for(let s in e.attrs)t[s]=e.attrs[s];t[this.attr]=this.value;let r=e.type.create(t,e.content,e.marks);return oe.ok(r)}getMap(){return rt.empty}invert(e){return new n(this.attr,e.attrs[this.attr])}map(e){return this}toJSON(){return{stepType:"docAttr",attr:this.attr,value:this.value}}static fromJSON(e,t){if(typeof t.attr!="string")throw new RangeError("Invalid input for DocAttrStep.fromJSON");return new n(t.attr,t.value)}};re.jsonID("docAttr",Jr);var dn=class extends Error{};dn=function n(e){let t=Error.call(this,e);return t.__proto__=n.prototype,t};dn.prototype=Object.create(Error.prototype);dn.prototype.constructor=dn;dn.prototype.name="TransformError";var kt=class{constructor(e){this.doc=e,this.steps=[],this.docs=[],this.mapping=new _n}get before(){return this.docs.length?this.docs[0]:this.doc}step(e){let t=this.maybeStep(e);if(t.failed)throw new dn(t.failed);return this}maybeStep(e){let t=e.apply(this.doc);return t.failed||this.addStep(e,t.doc),t}get docChanged(){return this.steps.length>0}changedRange(){let e=1e9,t=-1e9;for(let r=0;r{e=Math.min(e,l),t=Math.max(t,a)})}return e==1e9?null:{from:e,to:t}}addStep(e,t){this.docs.push(this.doc),this.steps.push(e),this.mapping.appendMap(e.getMap()),this.doc=t}replace(e,t=e,r=v.empty){let s=jn(this.doc,e,t,r);return s&&this.step(s),this}replaceWith(e,t,r){return this.replace(e,t,new v(b.from(r),0,0))}delete(e,t){return this.replace(e,t,v.empty)}insert(e,t){return this.replaceWith(e,e,t)}replaceRange(e,t,r){return Mm(this,e,t,r),this}replaceRangeWith(e,t,r){return Am(this,e,t,r),this}deleteRange(e,t){return Em(this,e,t),this}lift(e,t){return fm(this,e,t),this}join(e,t=1){return Sm(this,e,t),this}wrap(e,t){return gm(this,e,t),this}setBlockType(e,t=e,r,s=null){return ym(this,e,t,r,s),this}setNodeMarkup(e,t,r=null,s){return bm(this,e,t,r,s),this}setNodeAttribute(e,t,r){return this.step(new jr(e,t,r)),this}setDocAttribute(e,t){return this.step(new Jr(e,t)),this}addNodeMark(e,t){return this.step(new Wn(e,t)),this}removeNodeMark(e,t){let r=this.doc.nodeAt(e);if(!r)throw new RangeError("No node at position "+e);if(t instanceof _)t.isInSet(r.marks)&&this.step(new un(e,t));else{let s=r.marks,i,o=[];for(;i=t.isInSet(s);)o.push(new un(e,i)),s=i.removeFromSet(s);for(let l=o.length-1;l>=0;l--)this.step(o[l])}return this}split(e,t=1,r){return xm(this,e,t,r),this}addMark(e,t,r){return um(this,e,t,r),this}removeMark(e,t,r){return dm(this,e,t,r),this}clearIncompatible(e,t,r){return ro(this,e,t,r),this}};var so=Object.create(null),O=class{constructor(e,t,r){this.$anchor=e,this.$head=t,this.ranges=r||[new pn(e.min(t),e.max(t))]}get anchor(){return this.$anchor.pos}get head(){return this.$head.pos}get from(){return this.$from.pos}get to(){return this.$to.pos}get $from(){return this.ranges[0].$from}get $to(){return this.ranges[0].$to}get empty(){let e=this.ranges;for(let t=0;t=0;i--){let o=t<0?fn(e.node(0),e.node(i),e.before(i+1),e.index(i),t,r):fn(e.node(0),e.node(i),e.after(i+1),e.index(i)+1,t,r);if(o)return o}return null}static near(e,t=1){return this.findFrom(e,t)||this.findFrom(e,-t)||new ke(e.node(0))}static atStart(e){return fn(e,e,0,0,1)||new ke(e)}static atEnd(e){return fn(e,e,e.content.size,e.childCount,-1)||new ke(e)}static fromJSON(e,t){if(!t||!t.type)throw new RangeError("Invalid input for Selection.fromJSON");let r=so[t.type];if(!r)throw new RangeError(`No selection type ${t.type} defined`);return r.fromJSON(e,t)}static jsonID(e,t){if(e in so)throw new RangeError("Duplicate use of selection JSON ID "+e);return so[e]=t,t.prototype.jsonID=e,t}getBookmark(){return A.between(this.$anchor,this.$head).getBookmark()}};O.prototype.visible=!0;var pn=class{constructor(e,t){this.$from=e,this.$to=t}},zc=!1;function Bc(n){!zc&&!n.parent.inlineContent&&(zc=!0,console.warn("TextSelection endpoint not pointing into a node with inline content ("+n.parent.type.name+")"))}var A=class n extends O{constructor(e,t=e){Bc(e),Bc(t),super(e,t)}get $cursor(){return this.$anchor.pos==this.$head.pos?this.$head:null}map(e,t){let r=e.resolve(t.map(this.head));if(!r.parent.inlineContent)return O.near(r);let s=e.resolve(t.map(this.anchor));return new n(s.parent.inlineContent?s:r,r)}replace(e,t=v.empty){if(super.replace(e,t),t==v.empty){let r=this.$from.marksAcross(this.$to);r&&e.ensureMarks(r)}}eq(e){return e instanceof n&&e.anchor==this.anchor&&e.head==this.head}getBookmark(){return new Ur(this.anchor,this.head)}toJSON(){return{type:"text",anchor:this.anchor,head:this.head}}static fromJSON(e,t){if(typeof t.anchor!="number"||typeof t.head!="number")throw new RangeError("Invalid input for TextSelection.fromJSON");return new n(e.resolve(t.anchor),e.resolve(t.head))}static create(e,t,r=t){let s=e.resolve(t);return new this(s,r==t?s:e.resolve(r))}static between(e,t,r){let s=e.pos-t.pos;if((!r||s)&&(r=s>=0?1:-1),!t.parent.inlineContent){let i=O.findFrom(t,r,!0)||O.findFrom(t,-r,!0);if(i)t=i.$head;else return O.near(t,r)}return e.parent.inlineContent||(s==0?e=t:(e=(O.findFrom(e,-r,!0)||O.findFrom(e,r,!0)).$anchor,e.pos0?0:1);s>0?o=0;o+=s){let l=e.child(o);if(l.isAtom){if(!i&&N.isSelectable(l))return N.create(n,t-(s<0?l.nodeSize:0))}else{let a=fn(n,l,t+s,s<0?l.childCount:0,s,i);if(a)return a}t+=l.nodeSize*s}return null}function $c(n,e,t){let r=n.steps.length-1;if(r{o==null&&(o=u)}),n.setSelection(O.near(n.doc.resolve(o),t))}var Fc=1,qr=2,Hc=4,lo=class extends kt{constructor(e){super(e.doc),this.curSelectionFor=0,this.updated=0,this.meta=Object.create(null),this.time=Date.now(),this.curSelection=e.selection,this.storedMarks=e.storedMarks}get selection(){return this.curSelectionFor0}setStoredMarks(e){return this.storedMarks=e,this.updated|=qr,this}ensureMarks(e){return _.sameSet(this.storedMarks||this.selection.$from.marks(),e)||this.setStoredMarks(e),this}addStoredMark(e){return this.ensureMarks(e.addToSet(this.storedMarks||this.selection.$head.marks()))}removeStoredMark(e){return this.ensureMarks(e.removeFromSet(this.storedMarks||this.selection.$head.marks()))}get storedMarksSet(){return(this.updated&qr)>0}addStep(e,t){super.addStep(e,t),this.updated=this.updated&~qr,this.storedMarks=null}setTime(e){return this.time=e,this}replaceSelection(e){return this.selection.replace(this,e),this}replaceSelectionWith(e,t=!0){let r=this.selection;return t&&(e=e.mark(this.storedMarks||(r.empty?r.$from.marks():r.$from.marksAcross(r.$to)||_.none))),r.replaceWith(this,e),this}deleteSelection(){return this.selection.replace(this),this}insertText(e,t,r){let s=this.doc.type.schema;if(t==null)return e?this.replaceSelectionWith(s.text(e),!0):this.deleteSelection();{if(r==null&&(r=t),!e)return this.deleteRange(t,r);let i=this.storedMarks;if(!i){let o=this.doc.resolve(t);i=r==t?o.marks():o.marksAcross(this.doc.resolve(r))}return this.replaceRangeWith(t,r,s.text(e,i)),!this.selection.empty&&this.selection.to==t+e.length&&this.setSelection(O.near(this.selection.$to)),this}}setMeta(e,t){return this.meta[typeof e=="string"?e:e.key]=t,this}getMeta(e){return this.meta[typeof e=="string"?e:e.key]}get isGeneric(){for(let e in this.meta)return!1;return!0}scrollIntoView(){return this.updated|=Hc,this}get scrolledIntoView(){return(this.updated&Hc)>0}};function _c(n,e){return!e||!n?n:n.bind(e)}var Lt=class{constructor(e,t,r){this.name=e,this.init=_c(t.init,r),this.apply=_c(t.apply,r)}},Rm=[new Lt("doc",{init(n){return n.doc||n.schema.topNodeType.createAndFill()},apply(n){return n.doc}}),new Lt("selection",{init(n,e){return n.selection||O.atStart(e.doc)},apply(n){return n.selection}}),new Lt("storedMarks",{init(n){return n.storedMarks||null},apply(n,e,t,r){return r.selection.$cursor?n.storedMarks:null}}),new Lt("scrollToSelection",{init(){return 0},apply(n,e){return n.scrolledIntoView?e+1:e}})],Jn=class{constructor(e,t){this.schema=e,this.plugins=[],this.pluginsByKey=Object.create(null),this.fields=Rm.slice(),t&&t.forEach(r=>{if(this.pluginsByKey[r.key])throw new RangeError("Adding different instances of a keyed plugin ("+r.key+")");this.plugins.push(r),this.pluginsByKey[r.key]=r,r.spec.state&&this.fields.push(new Lt(r.key,r.spec.state,r))})}},Gr=class n{constructor(e){this.config=e}get schema(){return this.config.schema}get plugins(){return this.config.plugins}apply(e){return this.applyTransaction(e).state}filterTransaction(e,t=-1){for(let r=0;rr.toJSON())),e&&typeof e=="object")for(let r in e){if(r=="doc"||r=="selection")throw new RangeError("The JSON fields `doc` and `selection` are reserved");let s=e[r],i=s.spec.state;i&&i.toJSON&&(t[r]=i.toJSON.call(s,this[s.key]))}return t}static fromJSON(e,t,r){if(!t)throw new RangeError("Invalid input for EditorState.fromJSON");if(!e.schema)throw new RangeError("Required config field 'schema' missing");let s=new Jn(e.schema,e.plugins),i=new n(s);return s.fields.forEach(o=>{if(o.name=="doc")i.doc=ve.fromJSON(e.schema,t.doc);else if(o.name=="selection")i.selection=O.fromJSON(i.doc,t.selection);else if(o.name=="storedMarks")t.storedMarks&&(i.storedMarks=t.storedMarks.map(e.schema.markFromJSON));else{if(r)for(let l in r){let a=r[l],c=a.spec.state;if(a.key==o.name&&c&&c.fromJSON&&Object.prototype.hasOwnProperty.call(t,l)){i[o.name]=c.fromJSON.call(a,e,t[l],i);return}}i[o.name]=o.init(e,i)}}),i}};function Vc(n,e,t){for(let r in n){let s=n[r];s instanceof Function?s=s.bind(e):r=="handleDOMEvents"&&(s=Vc(s,e,{})),t[r]=s}return t}var I=class{constructor(e){this.spec=e,this.props={},e.props&&Vc(e.props,this,this.props),this.key=e.key?e.key.key:Wc("plugin")}getState(e){return e[this.key]}},io=Object.create(null);function Wc(n){return n in io?n+"$"+ ++io[n]:(io[n]=0,n+"$")}var L=class{constructor(e="key"){this.key=Wc(e)}get(e){return e.config.pluginsByKey[this.key]}getState(e){return e[this.key]}};var Jc=(n,e)=>n.selection.empty?!1:(e&&e(n.tr.deleteSelection().scrollIntoView()),!0);function Kc(n,e){let{$cursor:t}=n.selection;return!t||(e?!e.endOfTextblock("backward",n):t.parentOffset>0)?null:t}var co=(n,e,t)=>{let r=Kc(n,t);if(!r)return!1;let s=ho(r);if(!s){let o=r.blockRange(),l=o&&it(o);return l==null?!1:(e&&e(n.tr.lift(o,l).scrollIntoView()),!0)}let i=s.nodeBefore;if(tu(n,s,e,-1))return!0;if(r.parent.content.size==0&&(mn(i,"end")||N.isSelectable(i)))for(let o=r.depth;;o--){let l=jn(n.doc,r.before(o),r.after(o),v.empty);if(l&&l.slice.size1)break}return i.isAtom&&s.depth==r.depth-1?(e&&e(n.tr.delete(s.pos-i.nodeSize,s.pos).scrollIntoView()),!0):!1},qc=(n,e,t)=>{let r=Kc(n,t);if(!r)return!1;let s=ho(r);return s?Gc(n,s,e):!1},Uc=(n,e,t)=>{let r=Xc(n,t);if(!r)return!1;let s=mo(r);return s?Gc(n,s,e):!1};function Gc(n,e,t){let r=e.nodeBefore,s=r,i=e.pos-1;for(;!s.isTextblock;i--){if(s.type.spec.isolating)return!1;let u=s.lastChild;if(!u)return!1;s=u}let o=e.nodeAfter,l=o,a=e.pos+1;for(;!l.isTextblock;a++){if(l.type.spec.isolating)return!1;let u=l.firstChild;if(!u)return!1;l=u}let c=jn(n.doc,i,a,v.empty);if(!c||c.from!=i||c instanceof le&&c.slice.size>=a-i)return!1;if(t){let u=n.tr.step(c);u.setSelection(A.create(u.doc,i)),t(u.scrollIntoView())}return!0}function mn(n,e,t=!1){for(let r=n;r;r=e=="start"?r.firstChild:r.lastChild){if(r.isTextblock)return!0;if(t&&r.childCount!=1)return!1}return!1}var uo=(n,e,t)=>{let{$head:r,empty:s}=n.selection,i=r;if(!s)return!1;if(r.parent.isTextblock){if(t?!t.endOfTextblock("backward",n):r.parentOffset>0)return!1;i=ho(r)}let o=i&&i.nodeBefore;return!o||!N.isSelectable(o)?!1:(e&&e(n.tr.setSelection(N.create(n.doc,i.pos-o.nodeSize)).scrollIntoView()),!0)};function ho(n){if(!n.parent.type.spec.isolating)for(let e=n.depth-1;e>=0;e--){if(n.index(e)>0)return n.doc.resolve(n.before(e+1));if(n.node(e).type.spec.isolating)break}return null}function Xc(n,e){let{$cursor:t}=n.selection;return!t||(e?!e.endOfTextblock("forward",n):t.parentOffset{let r=Xc(n,t);if(!r)return!1;let s=mo(r);if(!s)return!1;let i=s.nodeAfter;if(tu(n,s,e,1))return!0;if(r.parent.content.size==0&&(mn(i,"start")||N.isSelectable(i))){let o=jn(n.doc,r.before(),r.after(),v.empty);if(o&&o.slice.size{let{$head:r,empty:s}=n.selection,i=r;if(!s)return!1;if(r.parent.isTextblock){if(t?!t.endOfTextblock("forward",n):r.parentOffset=0;e--){let t=n.node(e);if(n.index(e)+1{let t=n.selection,r=t instanceof N,s;if(r){if(t.node.isTextblock||!Re(n.doc,t.from))return!1;s=t.from}else if(s=Pt(n.doc,t.from,-1),s==null)return!1;if(e){let i=n.tr.join(s);r&&i.setSelection(N.create(i.doc,s-n.doc.resolve(s).nodeBefore.nodeSize)),e(i.scrollIntoView())}return!0},Yc=(n,e)=>{let t=n.selection,r;if(t instanceof N){if(t.node.isTextblock||!Re(n.doc,t.to))return!1;r=t.to}else if(r=Pt(n.doc,t.to,1),r==null)return!1;return e&&e(n.tr.join(r).scrollIntoView()),!0},Zc=(n,e)=>{let{$from:t,$to:r}=n.selection,s=t.blockRange(r),i=s&&it(s);return i==null?!1:(e&&e(n.tr.lift(s,i).scrollIntoView()),!0)},go=(n,e)=>{let{$head:t,$anchor:r}=n.selection;return!t.parent.type.spec.code||!t.sameParent(r)?!1:(e&&e(n.tr.insertText(` +`).scrollIntoView()),!0)};function yo(n){for(let e=0;e{let{$head:t,$anchor:r}=n.selection;if(!t.parent.type.spec.code||!t.sameParent(r))return!1;let s=t.node(-1),i=t.indexAfter(-1),o=yo(s.contentMatchAt(i));if(!o||!s.canReplaceWith(i,i,o))return!1;if(e){let l=t.after(),a=n.tr.replaceWith(l,l,o.createAndFill());a.setSelection(O.near(a.doc.resolve(l),1)),e(a.scrollIntoView())}return!0},bo=(n,e)=>{let t=n.selection,{$from:r,$to:s}=t;if(t instanceof ke||r.parent.inlineContent||s.parent.inlineContent)return!1;let i=yo(s.parent.contentMatchAt(s.indexAfter()));if(!i||!i.isTextblock)return!1;if(e){let o=(!r.parentOffset&&s.index(){let{$cursor:t}=n.selection;if(!t||t.parent.content.size)return!1;if(t.depth>1&&t.after()!=t.end(-1)){let i=t.before();if(Ae(n.doc,i))return e&&e(n.tr.split(i).scrollIntoView()),!0}let r=t.blockRange(),s=r&&it(r);return s==null?!1:(e&&e(n.tr.lift(r,s).scrollIntoView()),!0)};function Om(n){return(e,t)=>{let{$from:r,$to:s}=e.selection;if(e.selection instanceof N&&e.selection.node.isBlock)return!r.parentOffset||!Ae(e.doc,r.pos)?!1:(t&&t(e.tr.split(r.pos).scrollIntoView()),!0);if(!r.depth)return!1;let i=[],o,l,a=!1,c=!1;for(let f=r.depth;;f--)if(r.node(f).isBlock){a=r.end(f)==r.pos+(r.depth-f),c=r.start(f)==r.pos-(r.depth-f),l=yo(r.node(f-1).contentMatchAt(r.indexAfter(f-1)));let m=n&&n(s.parent,a,r);i.unshift(m||(a&&l?{type:l}:null)),o=f;break}else{if(f==1)return!1;i.unshift(null)}let u=e.tr;(e.selection instanceof A||e.selection instanceof ke)&&u.deleteSelection();let d=u.mapping.map(r.pos),h=Ae(u.doc,d,i.length,i);if(h||(i[0]=l?{type:l}:null,h=Ae(u.doc,d,i.length,i)),!h)return!1;if(u.split(d,i.length,i),!a&&c&&r.node(o).type!=l){let f=u.mapping.map(r.before(o)),p=u.doc.resolve(f);l&&r.node(o-1).canReplaceWith(p.index(),p.index()+1,l)&&u.setNodeMarkup(u.mapping.map(r.before(o)),l)}return t&&t(u.scrollIntoView()),!0}}var Im=Om();var eu=(n,e)=>{let{$from:t,to:r}=n.selection,s,i=t.sharedDepth(r);return i==0?!1:(s=t.before(i),e&&e(n.tr.setSelection(N.create(n.doc,s))),!0)},Dm=(n,e)=>(e&&e(n.tr.setSelection(new ke(n.doc))),!0);function Pm(n,e,t){let r=e.nodeBefore,s=e.nodeAfter,i=e.index();return!r||!s||!r.type.compatibleContent(s.type)?!1:!r.content.size&&e.parent.canReplace(i-1,i)?(t&&t(n.tr.delete(e.pos-r.nodeSize,e.pos).scrollIntoView()),!0):!e.parent.canReplace(i,i+1)||!(s.isTextblock||Re(n.doc,e.pos))?!1:(t&&t(n.tr.join(e.pos).scrollIntoView()),!0)}function tu(n,e,t,r){let s=e.nodeBefore,i=e.nodeAfter,o,l,a=s.type.spec.isolating||i.type.spec.isolating;if(!a&&Pm(n,e,t))return!0;let c=!a&&e.parent.canReplace(e.index(),e.index()+1);if(c&&(o=(l=s.contentMatchAt(s.childCount)).findWrapping(i.type))&&l.matchType(o[0]||i.type).validEnd){if(t){let f=e.pos+i.nodeSize,p=b.empty;for(let y=o.length-1;y>=0;y--)p=b.from(o[y].create(null,p));p=b.from(s.copy(p));let m=n.tr.step(new Y(e.pos-1,f,e.pos,f,new v(p,1,0),o.length,!0)),g=m.doc.resolve(f+2*o.length);g.nodeAfter&&g.nodeAfter.type==s.type&&Re(m.doc,g.pos)&&m.join(g.pos),t(m.scrollIntoView())}return!0}let u=i.type.spec.isolating||r>0&&a?null:O.findFrom(e,1),d=u&&u.$from.blockRange(u.$to),h=d&&it(d);if(h!=null&&h>=e.depth)return t&&t(n.tr.lift(d,h).scrollIntoView()),!0;if(c&&mn(i,"start",!0)&&mn(s,"end")){let f=s,p=[];for(;p.push(f),!f.isTextblock;)f=f.lastChild;let m=i,g=1;for(;!m.isTextblock;m=m.firstChild)g++;if(f.canReplace(f.childCount,f.childCount,m.content)){if(t){let y=b.empty;for(let w=p.length-1;w>=0;w--)y=b.from(p[w].copy(y));let k=n.tr.step(new Y(e.pos-p.length,e.pos+i.nodeSize,e.pos+g,e.pos+i.nodeSize-g,new v(y,p.length,0),0,!0));t(k.scrollIntoView())}return!0}}return!1}function nu(n){return function(e,t){let r=e.selection,s=n<0?r.$from:r.$to,i=s.depth;for(;s.node(i).isInline;){if(!i)return!1;i--}return s.node(i).isTextblock?(t&&t(e.tr.setSelection(A.create(e.doc,n<0?s.start(i):s.end(i)))),!0):!1}}var wo=nu(-1),So=nu(1);function ru(n,e=null){return function(t,r){let{$from:s,$to:i}=t.selection,o=s.blockRange(i),l=o&&hn(o,n,e);return l?(r&&r(t.tr.wrap(o,l).scrollIntoView()),!0):!1}}function Co(n,e=null){return function(t,r){let s=!1;for(let i=0;i{if(s)return!1;if(!(!a.isTextblock||a.hasMarkup(n,e)))if(a.type==n)s=!0;else{let u=t.doc.resolve(c),d=u.index();s=u.parent.canReplaceWith(d,d+1,n)}})}if(!s)return!1;if(r){let i=t.tr;for(let o=0;o=2&&e.$from.node(e.depth-1).type.compatibleContent(t)&&e.startIndex==0){if(e.$from.index(e.depth-1)==0)return!1;let a=o.resolve(e.start-2);i=new It(a,a,e.depth),e.endIndex=0;u--)i=b.from(t[u].type.create(t[u].attrs,i));n.step(new Y(e.start-(r?2:0),e.end,e.start,e.end,new v(i,0,0),t.length,!0));let o=0;for(let u=0;uo.childCount>0&&o.firstChild.type==n);return i?t?r.node(i.depth-1).type==n?$m(e,t,n,i):Fm(e,t,i):!0:!1}}function $m(n,e,t,r){let s=n.tr,i=r.end,o=r.$to.end(r.depth);im;p--)f-=s.child(p).nodeSize,r.delete(f-1,f+1);let i=r.doc.resolve(t.start),o=i.nodeAfter;if(r.mapping.map(t.end)!=t.start+i.nodeAfter.nodeSize)return!1;let l=t.startIndex==0,a=t.endIndex==s.childCount,c=i.node(-1),u=i.index(-1);if(!c.canReplace(u+(l?0:1),u+1,o.content.append(a?b.empty:b.from(s))))return!1;let d=i.pos,h=d+o.nodeSize;return r.step(new Y(d-(l?1:0),h+(a?1:0),d+1,h-1,new v((l?b.empty:b.from(s.copy(b.empty))).append(a?b.empty:b.from(s.copy(b.empty))),l?0:1,a?0:1),l?0:1)),e(r.scrollIntoView()),!0}function ou(n){return function(e,t){let{$from:r,$to:s}=e.selection,i=r.blockRange(s,c=>c.childCount>0&&c.firstChild.type==n);if(!i)return!1;let o=i.startIndex;if(o==0)return!1;let l=i.parent,a=l.child(o-1);if(a.type!=n)return!1;if(t){let c=a.lastChild&&a.lastChild.type==l.type,u=b.from(c?n.create():null),d=new v(b.from(n.create(null,b.from(l.type.create(null,u)))),c?3:1,0),h=i.start,f=i.end;t(e.tr.step(new Y(h-(c?3:1),f,h,f,d,1,!0)).scrollIntoView())}return!0}}var ae=function(n){for(var e=0;;e++)if(n=n.previousSibling,!n)return e},bn=function(n){let e=n.assignedSlot||n.parentNode;return e&&e.nodeType==11?e.host:e},Ro=null,lt=function(n,e,t){let r=Ro||(Ro=document.createRange());return r.setEnd(n,t??n.nodeValue.length),r.setStart(n,e||0),r},Hm=function(){Ro=null},Vt=function(n,e,t,r){return t&&(lu(n,e,t,r,-1)||lu(n,e,t,r,1))},_m=/^(img|br|input|textarea|hr)$/i;function lu(n,e,t,r,s){for(var i;;){if(n==t&&e==r)return!0;if(e==(s<0?0:Ie(n))){let o=n.parentNode;if(!o||o.nodeType!=1||Zn(n)||_m.test(n.nodeName)||n.contentEditable=="false")return!1;e=ae(n)+(s<0?0:1),n=o}else if(n.nodeType==1){let o=n.childNodes[e+(s<0?-1:0)];if(o.nodeType==1&&o.contentEditable=="false")if(!((i=o.pmViewDesc)===null||i===void 0)&&i.ignoreForSelection)e+=s;else return!1;else n=o,e=s<0?Ie(n):0}else return!1}}function Ie(n){return n.nodeType==3?n.nodeValue.length:n.childNodes.length}function Vm(n,e){for(;;){if(n.nodeType==3&&e)return n;if(n.nodeType==1&&e>0){if(n.contentEditable=="false")return null;n=n.childNodes[e-1],e=Ie(n)}else if(n.parentNode&&!Zn(n))e=ae(n),n=n.parentNode;else return null}}function Wm(n,e){for(;;){if(n.nodeType==3&&e2),Oe=xn||(je?/Mac/.test(je.platform):!1),_u=je?/Win/.test(je.platform):!1,at=/Android \d/.test(vt),er=!!au&&"webkitFontSmoothing"in au.documentElement.style,qm=er?+(/\bAppleWebKit\/(\d+)/.exec(navigator.userAgent)||[0,0])[1]:0;function Um(n){let e=n.defaultView&&n.defaultView.visualViewport;return e?{left:0,right:e.width,top:0,bottom:e.height}:{left:0,right:n.documentElement.clientWidth,top:0,bottom:n.documentElement.clientHeight}}function ot(n,e){return typeof n=="number"?n:n[e]}function Gm(n){let e=n.getBoundingClientRect(),t=e.width/n.offsetWidth||1,r=e.height/n.offsetHeight||1;return{left:e.left,right:e.left+n.clientWidth*t,top:e.top,bottom:e.top+n.clientHeight*r}}function cu(n,e,t){let r=n.someProp("scrollThreshold")||0,s=n.someProp("scrollMargin")||5,i=n.dom.ownerDocument;for(let o=t||n.dom;o;){if(o.nodeType!=1){o=bn(o);continue}let l=o,a=l==i.body,c=a?Um(i):Gm(l),u=0,d=0;if(e.topc.bottom-ot(r,"bottom")&&(d=e.bottom-e.top>c.bottom-c.top?e.top+ot(s,"top")-c.top:e.bottom-c.bottom+ot(s,"bottom")),e.leftc.right-ot(r,"right")&&(u=e.right-c.right+ot(s,"right")),u||d)if(a)i.defaultView.scrollBy(u,d);else{let f=l.scrollLeft,p=l.scrollTop;d&&(l.scrollTop+=d),u&&(l.scrollLeft+=u);let m=l.scrollLeft-f,g=l.scrollTop-p;e={left:e.left-m,top:e.top-g,right:e.right-m,bottom:e.bottom-g}}let h=a?"fixed":getComputedStyle(o).position;if(/^(fixed|sticky)$/.test(h))break;o=h=="absolute"?o.offsetParent:bn(o)}}function Xm(n){let e=n.dom.getBoundingClientRect(),t=Math.max(0,e.top),r,s;for(let i=(e.left+e.right)/2,o=t+1;o=t-20){r=l,s=a.top;break}}return{refDOM:r,refTop:s,stack:Vu(n.dom)}}function Vu(n){let e=[],t=n.ownerDocument;for(let r=n;r&&(e.push({dom:r,top:r.scrollTop,left:r.scrollLeft}),n!=t);r=bn(r));return e}function Qm({refDOM:n,refTop:e,stack:t}){let r=n?n.getBoundingClientRect().top:0;Wu(t,r==0?0:r-e)}function Wu(n,e){for(let t=0;t=l){o=Math.max(p.bottom,o),l=Math.min(p.top,l);let m=p.left>e.left?p.left-e.left:p.right=(p.left+p.right)/2?1:0));continue}}else p.top>e.top&&!a&&p.left<=e.left&&p.right>=e.left&&(a=u,c={left:Math.max(p.left,Math.min(p.right,e.left)),top:p.top});!t&&(e.left>=p.right&&e.top>=p.top||e.left>=p.left&&e.top>=p.bottom)&&(i=d+1)}}return!t&&a&&(t=a,s=c,r=0),t&&t.nodeType==3?Zm(t,s):!t||r&&t.nodeType==1?{node:n,offset:i}:ju(t,s)}function Zm(n,e){let t=n.nodeValue.length,r=document.createRange(),s;for(let i=0;i=(o.left+o.right)/2?1:0)};break}}return r.detach(),s||{node:n,offset:0}}function Go(n,e){return n.left>=e.left-1&&n.left<=e.right+1&&n.top>=e.top-1&&n.top<=e.bottom+1}function eg(n,e){let t=n.parentNode;return t&&/^li$/i.test(t.nodeName)&&e.left(o.left+o.right)/2?1:-1}return n.docView.posFromDOM(r,s,i)}function ng(n,e,t,r){let s=-1;for(let i=e,o=!1;i!=n.dom;){let l=n.docView.nearestDesc(i,!0),a;if(!l)return null;if(l.dom.nodeType==1&&(l.node.isBlock&&l.parent||!l.contentDOM)&&((a=l.dom.getBoundingClientRect()).width||a.height)&&(l.node.isBlock&&l.parent&&!/^T(R|BODY|HEAD|FOOT)$/.test(l.dom.nodeName)&&(!o&&a.left>r.left||a.top>r.top?s=l.posBefore:(!o&&a.right-1?s:n.docView.posFromDOM(e,t,-1)}function Ju(n,e,t){let r=n.childNodes.length;if(r&&t.tope.top&&s++}let c;er&&s&&r.nodeType==1&&(c=r.childNodes[s-1]).nodeType==1&&c.contentEditable=="false"&&c.getBoundingClientRect().top>=e.top&&s--,r==n.dom&&s==r.childNodes.length-1&&r.lastChild.nodeType==1&&e.top>r.lastChild.getBoundingClientRect().bottom?l=n.state.doc.content.size:(s==0||r.nodeType!=1||r.childNodes[s-1].nodeName!="BR")&&(l=ng(n,r,s,e))}l==null&&(l=tg(n,o,e));let a=n.docView.nearestDesc(o,!0);return{pos:l,inside:a?a.posAtStart-a.border:-1}}function uu(n){return n.top=0&&s==r.nodeValue.length?(a--,u=1):t<0?a--:c++,Kn(xt(lt(r,a,c),u),u<0)}if(!n.state.doc.resolve(e-(i||0)).parent.inlineContent){if(i==null&&s&&(t<0||s==Ie(r))){let a=r.childNodes[s-1];if(a.nodeType==1)return To(a.getBoundingClientRect(),!1)}if(i==null&&s=0)}if(i==null&&s&&(t<0||s==Ie(r))){let a=r.childNodes[s-1],c=a.nodeType==3?lt(a,Ie(a)-(o?0:1)):a.nodeType==1&&(a.nodeName!="BR"||!a.nextSibling)?a:null;if(c)return Kn(xt(c,1),!1)}if(i==null&&s=0)}function Kn(n,e){if(n.width==0)return n;let t=e?n.left:n.right;return{top:n.top,bottom:n.bottom,left:t,right:t}}function To(n,e){if(n.height==0)return n;let t=e?n.top:n.bottom;return{top:t,bottom:t,left:n.left,right:n.right}}function qu(n,e,t){let r=n.state,s=n.root.activeElement;r!=e&&n.updateState(e),s!=n.dom&&n.focus();try{return t()}finally{r!=e&&n.updateState(r),s!=n.dom&&s&&s.focus()}}function ig(n,e,t){let r=e.selection,s=t=="up"?r.$from:r.$to;return qu(n,e,()=>{let{node:i}=n.docView.domFromPos(s.pos,t=="up"?-1:1);for(;;){let l=n.docView.nearestDesc(i,!0);if(!l)break;if(l.node.isBlock){i=l.contentDOM||l.dom;break}i=l.dom.parentNode}let o=Ku(n,s.pos,1);for(let l=i.firstChild;l;l=l.nextSibling){let a;if(l.nodeType==1)a=l.getClientRects();else if(l.nodeType==3)a=lt(l,0,l.nodeValue.length).getClientRects();else continue;for(let c=0;cu.top+1&&(t=="up"?o.top-u.top>(u.bottom-o.top)*2:u.bottom-o.bottom>(o.bottom-u.top)*2))return!1}}return!0})}var og=/[\u0590-\u08ac]/;function lg(n,e,t){let{$head:r}=e.selection;if(!r.parent.isTextblock)return!1;let s=r.parentOffset,i=!s,o=s==r.parent.content.size,l=n.domSelection();return l?!og.test(r.parent.textContent)||!l.modify?t=="left"||t=="backward"?i:o:qu(n,e,()=>{let{focusNode:a,focusOffset:c,anchorNode:u,anchorOffset:d}=n.domSelectionRange(),h=l.caretBidiLevel;l.modify("move",t,"character");let f=r.depth?n.docView.domAfterPos(r.before()):n.dom,{focusNode:p,focusOffset:m}=n.domSelectionRange(),g=p&&!f.contains(p.nodeType==1?p:p.parentNode)||a==p&&c==m;try{l.collapse(u,d),a&&(a!=u||c!=d)&&l.extend&&l.extend(a,c)}catch{}return h!=null&&(l.caretBidiLevel=h),g}):r.pos==r.start()||r.pos==r.end()}var du=null,hu=null,fu=!1;function ag(n,e,t){return du==e&&hu==t?fu:(du=e,hu=t,fu=t=="up"||t=="down"?ig(n,e,t):lg(n,e,t))}var Pe=0,pu=1,Bt=2,Je=3,Wt=class{constructor(e,t,r,s){this.parent=e,this.children=t,this.dom=r,this.contentDOM=s,this.dirty=Pe,r.pmViewDesc=this}matchesWidget(e){return!1}matchesMark(e){return!1}matchesNode(e,t,r){return!1}matchesHack(e){return!1}parseRule(){return null}stopEvent(e){return!1}get size(){let e=0;for(let t=0;tae(this.contentDOM);else if(this.contentDOM&&this.contentDOM!=this.dom&&this.dom.contains(this.contentDOM))s=e.compareDocumentPosition(this.contentDOM)&2;else if(this.dom.firstChild){if(t==0)for(let i=e;;i=i.parentNode){if(i==this.dom){s=!1;break}if(i.previousSibling)break}if(s==null&&t==e.childNodes.length)for(let i=e;;i=i.parentNode){if(i==this.dom){s=!0;break}if(i.nextSibling)break}}return s??r>0?this.posAtEnd:this.posAtStart}nearestDesc(e,t=!1){for(let r=!0,s=e;s;s=s.parentNode){let i=this.getDesc(s),o;if(i&&(!t||i.node))if(r&&(o=i.nodeDOM)&&!(o.nodeType==1?o.contains(e.nodeType==1?e:e.parentNode):o==e))r=!1;else return i}}getDesc(e){let t=e.pmViewDesc;for(let r=t;r;r=r.parent)if(r==this)return t}posFromDOM(e,t,r){for(let s=e;s;s=s.parentNode){let i=this.getDesc(s);if(i)return i.localPosFromDOM(e,t,r)}return-1}descAt(e){for(let t=0,r=0;te||o instanceof Yr){s=e-i;break}i=l}if(s)return this.children[r].domFromPos(s-this.children[r].border,t);for(let i;r&&!(i=this.children[r-1]).size&&i instanceof Xr&&i.side>=0;r--);if(t<=0){let i,o=!0;for(;i=r?this.children[r-1]:null,!(!i||i.dom.parentNode==this.contentDOM);r--,o=!1);return i&&t&&o&&!i.border&&!i.domAtom?i.domFromPos(i.size,t):{node:this.contentDOM,offset:i?ae(i.dom)+1:0}}else{let i,o=!0;for(;i=r=u&&t<=c-a.border&&a.node&&a.contentDOM&&this.contentDOM.contains(a.contentDOM))return a.parseRange(e,t,u);e=o;for(let d=l;d>0;d--){let h=this.children[d-1];if(h.size&&h.dom.parentNode==this.contentDOM&&!h.emptyChildAt(1)){s=ae(h.dom)+1;break}e-=h.size}s==-1&&(s=0)}if(s>-1&&(c>t||l==this.children.length-1)){t=c;for(let u=l+1;up&&ot){let p=l;l=a,a=p}let f=document.createRange();f.setEnd(a.node,a.offset),f.setStart(l.node,l.offset),c.removeAllRanges(),c.addRange(f)}}ignoreMutation(e){return!this.contentDOM&&e.type!="selection"}get contentLost(){return this.contentDOM&&this.contentDOM!=this.dom&&!this.dom.contains(this.contentDOM)}markDirty(e,t){for(let r=0,s=0;s=r:er){let l=r+i.border,a=o-i.border;if(e>=l&&t<=a){this.dirty=e==r||t==o?Bt:pu,e==l&&t==a&&(i.contentLost||i.dom.parentNode!=this.contentDOM)?i.dirty=Je:i.markDirty(e-l,t-l);return}else i.dirty=i.dom==i.contentDOM&&i.dom.parentNode==this.contentDOM&&!i.children.length?Bt:Je}r=o}this.dirty=Bt}markParentsDirty(){let e=1;for(let t=this.parent;t;t=t.parent,e++){let r=e==1?Bt:pu;t.dirty{if(!i)return s;if(i.parent)return i.parent.posBeforeChild(i)})),!t.type.spec.raw){if(o.nodeType!=1){let l=document.createElement("span");l.appendChild(o),o=l}o.contentEditable="false",o.classList.add("ProseMirror-widget")}super(e,[],o,null),this.widget=t,this.widget=t,i=this}matchesWidget(e){return this.dirty==Pe&&e.type.eq(this.widget.type)}parseRule(){return{ignore:!0}}stopEvent(e){let t=this.widget.spec.stopEvent;return t?t(e):!1}ignoreMutation(e){return e.type!="selection"||this.widget.spec.ignoreSelection}destroy(){this.widget.type.destroy(this.dom),super.destroy()}get domAtom(){return!0}get ignoreForSelection(){return!!this.widget.type.spec.relaxedSide}get side(){return this.widget.type.side}},Po=class extends Wt{constructor(e,t,r,s){super(e,[],t,null),this.textDOM=r,this.text=s}get size(){return this.text.length}localPosFromDOM(e,t){return e!=this.textDOM?this.posAtStart+(t?this.size:0):this.posAtStart+t}domFromPos(e){return{node:this.textDOM,offset:e}}ignoreMutation(e){return e.type==="characterData"&&e.target.nodeValue==e.oldValue}},wn=class n extends Wt{constructor(e,t,r,s,i){super(e,[],r,s),this.mark=t,this.spec=i}static create(e,t,r,s){let i=s.nodeViews[t.type.name],o=i&&i(t,s,r);return(!o||!o.dom)&&(o=nt.renderSpec(document,t.type.spec.toDOM(t,r),null,t.attrs)),new n(e,t,o.dom,o.contentDOM||o.dom,o)}parseRule(){return this.dirty&Je||this.mark.type.spec.reparseInView?null:{mark:this.mark.type.name,attrs:this.mark.attrs,contentElement:this.contentDOM}}matchesMark(e){return this.dirty!=Je&&this.mark.eq(e)}markDirty(e,t){if(super.markDirty(e,t),this.dirty!=Pe){let r=this.parent;for(;!r.node;)r=r.parent;r.dirty0&&(i=$o(i,0,e,r));for(let l=0;l{if(!a)return o;if(a.parent)return a.parent.posBeforeChild(a)},r,s),u=c&&c.dom,d=c&&c.contentDOM;if(t.isText){if(!u)u=document.createTextNode(t.text);else if(u.nodeType!=3)throw new RangeError("Text must be rendered as a DOM text node")}else u||({dom:u,contentDOM:d}=nt.renderSpec(document,t.type.spec.toDOM(t),null,t.attrs));!d&&!t.isText&&u.nodeName!="BR"&&(u.hasAttribute("contenteditable")||(u.contentEditable="false"),t.type.spec.draggable&&(u.draggable=!0));let h=u;return u=Xu(u,r,t),c?a=new Lo(e,t,r,s,u,d||null,h,c,i,o+1):t.isText?new Qr(e,t,r,s,u,h,i):new n(e,t,r,s,u,d||null,h,i,o+1)}parseRule(){if(this.node.type.spec.reparseInView)return null;let e={node:this.node.type.name,attrs:this.node.attrs};if(this.node.type.whitespace=="pre"&&(e.preserveWhitespace="full"),!this.contentDOM)e.getContent=()=>this.node.content;else if(!this.contentLost)e.contentElement=this.contentDOM;else{for(let t=this.children.length-1;t>=0;t--){let r=this.children[t];if(this.dom.contains(r.dom.parentNode)){e.contentElement=r.dom.parentNode;break}}e.contentElement||(e.getContent=()=>b.empty)}return e}matchesNode(e,t,r){return this.dirty==Pe&&e.eq(this.node)&&Zr(t,this.outerDeco)&&r.eq(this.innerDeco)}get size(){return this.node.nodeSize}get border(){return this.node.isLeaf?0:1}updateChildren(e,t){let r=this.node.inlineContent,s=t,i=e.composing?this.localCompositionInfo(e,t):null,o=i&&i.pos>-1?i:null,l=i&&i.pos<0,a=new Bo(this,o&&o.node,e);hg(this.node,this.innerDeco,(c,u,d)=>{c.spec.marks?a.syncToMarks(c.spec.marks,r,e,u):c.type.side>=0&&!d&&a.syncToMarks(u==this.node.childCount?_.none:this.node.child(u).marks,r,e,u),a.placeWidget(c,e,s)},(c,u,d,h)=>{a.syncToMarks(c.marks,r,e,h);let f;a.findNodeMatch(c,u,d,h)||l&&e.state.selection.from>s&&e.state.selection.to-1&&a.updateNodeAt(c,u,d,f,e)||a.updateNextNode(c,u,d,e,h,s)||a.addNode(c,u,d,e,s),s+=c.nodeSize}),a.syncToMarks([],r,e,0),this.node.isTextblock&&a.addTextblockHacks(),a.destroyRest(),(a.changed||this.dirty==Bt)&&(o&&this.protectLocalComposition(e,o),Uu(this.contentDOM,this.children,e),xn&&fg(this.dom))}localCompositionInfo(e,t){let{from:r,to:s}=e.state.selection;if(!(e.state.selection instanceof A)||rt+this.node.content.size)return null;let i=e.input.compositionNode;if(!i||!this.dom.contains(i.parentNode))return null;if(this.node.inlineContent){let o=i.nodeValue,l=pg(this.node.content,o,r-t,s-t);return l<0?null:{node:i,pos:l,text:o}}else return{node:i,pos:-1,text:""}}protectLocalComposition(e,{node:t,pos:r,text:s}){if(this.getDesc(t))return;let i=t;for(;i.parentNode!=this.contentDOM;i=i.parentNode){for(;i.previousSibling;)i.parentNode.removeChild(i.previousSibling);for(;i.nextSibling;)i.parentNode.removeChild(i.nextSibling);i.pmViewDesc&&(i.pmViewDesc=void 0)}let o=new Po(this,i,t,s);e.input.compositionNodes.push(o),this.children=$o(this.children,r,r+s.length,e,o)}update(e,t,r,s){return this.dirty==Je||!e.sameMarkup(this.node)?!1:(this.updateInner(e,t,r,s),!0)}updateInner(e,t,r,s){this.updateOuterDeco(t),this.node=e,this.innerDeco=r,this.contentDOM&&this.updateChildren(s,this.posAtStart),this.dirty=Pe}updateOuterDeco(e){if(Zr(e,this.outerDeco))return;let t=this.nodeDOM.nodeType!=1,r=this.dom;this.dom=Gu(this.dom,this.nodeDOM,zo(this.outerDeco,this.node,t),zo(e,this.node,t)),this.dom!=r&&(r.pmViewDesc=void 0,this.dom.pmViewDesc=this),this.outerDeco=e}selectNode(){this.nodeDOM.nodeType==1&&(this.nodeDOM.classList.add("ProseMirror-selectednode"),(this.contentDOM||!this.node.type.spec.draggable)&&(this.nodeDOM.draggable=!0))}deselectNode(){this.nodeDOM.nodeType==1&&(this.nodeDOM.classList.remove("ProseMirror-selectednode"),(this.contentDOM||!this.node.type.spec.draggable)&&this.nodeDOM.removeAttribute("draggable"))}get domAtom(){return this.node.isAtom}};function mu(n,e,t,r,s){Xu(r,e,n);let i=new Ct(void 0,n,e,t,r,r,r,s,0);return i.contentDOM&&i.updateChildren(s,0),i}var Qr=class n extends Ct{constructor(e,t,r,s,i,o,l){super(e,t,r,s,i,null,o,l,0)}parseRule(){let e=this.nodeDOM.parentNode;for(;e&&e!=this.dom&&!e.pmIsDeco;)e=e.parentNode;return{skip:e||!0}}update(e,t,r,s){return this.dirty==Je||this.dirty!=Pe&&!this.inParent()||!e.sameMarkup(this.node)?!1:(this.updateOuterDeco(t),(this.dirty!=Pe||e.text!=this.node.text)&&e.text!=this.nodeDOM.nodeValue&&(this.nodeDOM.nodeValue=e.text,s.trackWrites==this.nodeDOM&&(s.trackWrites=null)),this.node=e,this.dirty=Pe,!0)}inParent(){let e=this.parent.contentDOM;for(let t=this.nodeDOM;t;t=t.parentNode)if(t==e)return!0;return!1}domFromPos(e){return{node:this.nodeDOM,offset:e}}localPosFromDOM(e,t,r){return e==this.nodeDOM?this.posAtStart+Math.min(t,this.node.text.length):super.localPosFromDOM(e,t,r)}ignoreMutation(e){return e.type!="characterData"&&e.type!="selection"}slice(e,t,r){let s=this.node.cut(e,t),i=document.createTextNode(s.text);return new n(this.parent,s,this.outerDeco,this.innerDeco,i,i,r)}markDirty(e,t){super.markDirty(e,t),this.dom!=this.nodeDOM&&(e==0||t==this.nodeDOM.nodeValue.length)&&(this.dirty=Je)}get domAtom(){return!1}isText(e){return this.node.text==e}},Yr=class extends Wt{parseRule(){return{ignore:!0}}matchesHack(e){return this.dirty==Pe&&this.dom.nodeName==e}get domAtom(){return!0}get ignoreForCoords(){return this.dom.nodeName=="IMG"}},Lo=class extends Ct{constructor(e,t,r,s,i,o,l,a,c,u){super(e,t,r,s,i,o,l,c,u),this.spec=a}update(e,t,r,s){if(this.dirty==Je)return!1;if(this.spec.update&&(this.node.type==e.type||this.spec.multiType)){let i=this.spec.update(e,t,r);return i&&this.updateInner(e,t,r,s),i}else return!this.contentDOM&&!e.isLeaf?!1:super.update(e,t,r,s)}selectNode(){this.spec.selectNode?this.spec.selectNode():super.selectNode()}deselectNode(){this.spec.deselectNode?this.spec.deselectNode():super.deselectNode()}setSelection(e,t,r,s){this.spec.setSelection?this.spec.setSelection(e,t,r.root):super.setSelection(e,t,r,s)}destroy(){this.spec.destroy&&this.spec.destroy(),super.destroy()}stopEvent(e){return this.spec.stopEvent?this.spec.stopEvent(e):!1}ignoreMutation(e){return this.spec.ignoreMutation?this.spec.ignoreMutation(e):super.ignoreMutation(e)}};function Uu(n,e,t){let r=n.firstChild,s=!1;for(let i=0;i>1,l=Math.min(o,e.length);for(;i-1)a>this.index&&(this.changed=!0,this.destroyBetween(this.index,a)),this.top=this.top.children[this.index];else{let u=wn.create(this.top,e[o],t,r);this.top.children.splice(this.index,0,u),this.top=u,this.changed=!0}this.index=0,o++}}findNodeMatch(e,t,r,s){let i=-1,o;if(s>=this.preMatch.index&&(o=this.preMatch.matches[s-this.preMatch.index]).parent==this.top&&o.matchesNode(e,t,r))i=this.top.children.indexOf(o,this.index);else for(let l=this.index,a=Math.min(this.top.children.length,l+5);l0;){let l;for(;;)if(r){let c=t.children[r-1];if(c instanceof wn)t=c,r=c.children.length;else{l=c,r--;break}}else{if(t==e)break e;r=t.parent.children.indexOf(t),t=t.parent}let a=l.node;if(a){if(a!=n.child(s-1))break;--s,i.set(l,s),o.push(l)}}return{index:s,matched:i,matches:o.reverse()}}function dg(n,e){return n.type.side-e.type.side}function hg(n,e,t,r){let s=e.locals(n),i=0;if(s.length==0){for(let c=0;ci;)l.push(s[o++]);let p=i+h.nodeSize;if(h.isText){let g=p;o!g.inline):l.slice();r(h,m,e.forChild(i,h),f),i=p}}function fg(n){if(n.nodeName=="UL"||n.nodeName=="OL"){let e=n.style.cssText;n.style.cssText=e+"; list-style: square !important",window.getComputedStyle(n).listStyle,n.style.cssText=e}}function pg(n,e,t,r){for(let s=0,i=0;s=t){if(i>=r&&a.slice(r-e.length-l,r-l)==e)return r-e.length;let c=l=0&&c+e.length+l>=t)return l+c;if(t==r&&a.length>=r+e.length-l&&a.slice(r-l,r-l+e.length)==e)return r}}return-1}function $o(n,e,t,r,s){let i=[];for(let o=0,l=0;o=t||u<=e?i.push(a):(ct&&i.push(a.slice(t-c,a.size,r)))}return i}function Xo(n,e=null){let t=n.domSelectionRange(),r=n.state.doc;if(!t.focusNode)return null;let s=n.docView.nearestDesc(t.focusNode),i=s&&s.size==0,o=n.docView.posFromDOM(t.focusNode,t.focusOffset,1);if(o<0)return null;let l=r.resolve(o),a,c;if(as(t)){for(a=o;s&&!s.node;)s=s.parent;let d=s.node;if(s&&d.isAtom&&N.isSelectable(d)&&s.parent&&!(d.isInline&&jm(t.focusNode,t.focusOffset,s.dom))){let h=s.posBefore;c=new N(o==h?l:r.resolve(h))}}else{if(t instanceof n.dom.ownerDocument.defaultView.Selection&&t.rangeCount>1){let d=o,h=o;for(let f=0;f{(t.anchorNode!=r||t.anchorOffset!=s)&&(e.removeEventListener("selectionchange",n.input.hideSelectionGuard),setTimeout(()=>{(!Qu(n)||n.state.selection.visible)&&n.dom.classList.remove("ProseMirror-hideselection")},20))})}function gg(n){let e=n.domSelection();if(!e)return;let t=n.cursorWrapper.dom,r=t.nodeName=="IMG";r?e.collapse(t.parentNode,ae(t)+1):e.collapse(t,0),!r&&!n.state.selection.visible&&Te&&St<=11&&(t.disabled=!0,t.disabled=!1)}function Yu(n,e){if(e instanceof N){let t=n.docView.descAt(e.from);t!=n.lastSelectedViewDesc&&(xu(n),t&&t.selectNode(),n.lastSelectedViewDesc=t)}else xu(n)}function xu(n){n.lastSelectedViewDesc&&(n.lastSelectedViewDesc.parent&&n.lastSelectedViewDesc.deselectNode(),n.lastSelectedViewDesc=void 0)}function Qo(n,e,t,r){return n.someProp("createSelectionBetween",s=>s(n,e,t))||A.between(e,t,r)}function wu(n){return n.editable&&!n.hasFocus()?!1:Zu(n)}function Zu(n){let e=n.domSelectionRange();if(!e.anchorNode)return!1;try{return n.dom.contains(e.anchorNode.nodeType==3?e.anchorNode.parentNode:e.anchorNode)&&(n.editable||n.dom.contains(e.focusNode.nodeType==3?e.focusNode.parentNode:e.focusNode))}catch{return!1}}function yg(n){let e=n.docView.domFromPos(n.state.selection.anchor,0),t=n.domSelectionRange();return Vt(e.node,e.offset,t.anchorNode,t.anchorOffset)}function Fo(n,e){let{$anchor:t,$head:r}=n.selection,s=e>0?t.max(r):t.min(r),i=s.parent.inlineContent?s.depth?n.doc.resolve(e>0?s.after():s.before()):null:s;return i&&O.findFrom(i,e)}function wt(n,e){return n.dispatch(n.state.tr.setSelection(e).scrollIntoView()),!0}function Su(n,e,t){let r=n.state.selection;if(r instanceof A)if(t.indexOf("s")>-1){let{$head:s}=r,i=s.textOffset?null:e<0?s.nodeBefore:s.nodeAfter;if(!i||i.isText||!i.isLeaf)return!1;let o=n.state.doc.resolve(s.pos+i.nodeSize*(e<0?-1:1));return wt(n,new A(r.$anchor,o))}else if(r.empty){if(n.endOfTextblock(e>0?"forward":"backward")){let s=Fo(n.state,e);return s&&s instanceof N?wt(n,s):!1}else if(!(Oe&&t.indexOf("m")>-1)){let s=r.$head,i=s.textOffset?null:e<0?s.nodeBefore:s.nodeAfter,o;if(!i||i.isText)return!1;let l=e<0?s.pos-i.nodeSize:s.pos;return i.isAtom||(o=n.docView.descAt(l))&&!o.contentDOM?N.isSelectable(i)?wt(n,new N(e<0?n.state.doc.resolve(s.pos-i.nodeSize):s)):er?wt(n,new A(n.state.doc.resolve(e<0?l:l+i.nodeSize))):!1:!1}}else return!1;else{if(r instanceof N&&r.node.isInline)return wt(n,new A(e>0?r.$to:r.$from));{let s=Fo(n.state,e);return s?wt(n,s):!1}}}function es(n){return n.nodeType==3?n.nodeValue.length:n.childNodes.length}function Un(n,e){let t=n.pmViewDesc;return t&&t.size==0&&(e<0||n.nextSibling||n.nodeName!="BR")}function yn(n,e){return e<0?kg(n):bg(n)}function kg(n){let e=n.domSelectionRange(),t=e.focusNode,r=e.focusOffset;if(!t)return;let s,i,o=!1;for(De&&t.nodeType==1&&r0){if(t.nodeType!=1)break;{let l=t.childNodes[r-1];if(Un(l,-1))s=t,i=--r;else if(l.nodeType==3)t=l,r=t.nodeValue.length;else break}}else{if(ed(t))break;{let l=t.previousSibling;for(;l&&Un(l,-1);)s=t.parentNode,i=ae(l),l=l.previousSibling;if(l)t=l,r=es(t);else{if(t=t.parentNode,t==n.dom)break;r=0}}}o?Ho(n,t,r):s&&Ho(n,s,i)}function bg(n){let e=n.domSelectionRange(),t=e.focusNode,r=e.focusOffset;if(!t)return;let s=es(t),i,o;for(;;)if(r{n.state==s&&ut(n)},50)}function Cu(n,e){let t=n.state.doc.resolve(e);if(!(se||_u)&&t.parent.inlineContent){let s=n.coordsAtPos(e);if(e>t.start()){let i=n.coordsAtPos(e-1),o=(i.top+i.bottom)/2;if(o>s.top&&o1)return i.lefts.top&&o1)return i.left>s.left?"ltr":"rtl"}}return getComputedStyle(n.dom).direction=="rtl"?"rtl":"ltr"}function vu(n,e,t){let r=n.state.selection;if(r instanceof A&&!r.empty||t.indexOf("s")>-1||Oe&&t.indexOf("m")>-1)return!1;let{$from:s,$to:i}=r;if(!s.parent.inlineContent||n.endOfTextblock(e<0?"up":"down")){let o=Fo(n.state,e);if(o&&o instanceof N)return wt(n,o)}if(!s.parent.inlineContent){let o=e<0?s:i,l=r instanceof ke?O.near(o,e):O.findFrom(o,e);return l?wt(n,l):!1}return!1}function Tu(n,e){if(!(n.state.selection instanceof A))return!0;let{$head:t,$anchor:r,empty:s}=n.state.selection;if(!t.sameParent(r))return!0;if(!s)return!1;if(n.endOfTextblock(e>0?"forward":"backward"))return!0;let i=!t.textOffset&&(e<0?t.nodeBefore:t.nodeAfter);if(i&&!i.isText){let o=n.state.tr;return e<0?o.delete(t.pos-i.nodeSize,t.pos):o.delete(t.pos,t.pos+i.nodeSize),n.dispatch(o),!0}return!1}function Mu(n,e,t){n.domObserver.stop(),e.contentEditable=t,n.domObserver.start()}function Sg(n){if(!fe||n.state.selection.$head.parentOffset>0)return!1;let{focusNode:e,focusOffset:t}=n.domSelectionRange();if(e&&e.nodeType==1&&t==0&&e.firstChild&&e.firstChild.contentEditable=="false"){let r=e.firstChild;Mu(n,r,"true"),setTimeout(()=>Mu(n,r,"false"),20)}return!1}function Cg(n){let e="";return n.ctrlKey&&(e+="c"),n.metaKey&&(e+="m"),n.altKey&&(e+="a"),n.shiftKey&&(e+="s"),e}function vg(n,e){let t=e.keyCode,r=Cg(e);if(t==8||Oe&&t==72&&r=="c")return Tu(n,-1)||yn(n,-1);if(t==46&&!e.shiftKey||Oe&&t==68&&r=="c")return Tu(n,1)||yn(n,1);if(t==13||t==27)return!0;if(t==37||Oe&&t==66&&r=="c"){let s=t==37?Cu(n,n.state.selection.from)=="ltr"?-1:1:-1;return Su(n,s,r)||yn(n,s)}else if(t==39||Oe&&t==70&&r=="c"){let s=t==39?Cu(n,n.state.selection.from)=="ltr"?1:-1:1;return Su(n,s,r)||yn(n,s)}else{if(t==38||Oe&&t==80&&r=="c")return vu(n,-1,r)||yn(n,-1);if(t==40||Oe&&t==78&&r=="c")return Sg(n)||vu(n,1,r)||yn(n,1);if(r==(Oe?"m":"c")&&(t==66||t==73||t==89||t==90))return!0}return!1}function Yo(n,e){n.someProp("transformCopied",f=>{e=f(e,n)});let t=[],{content:r,openStart:s,openEnd:i}=e;for(;s>1&&i>1&&r.childCount==1&&r.firstChild.childCount==1;){s--,i--;let f=r.firstChild;t.push(f.type.name,f.attrs!=f.type.defaultAttrs?f.attrs:null),r=f.content}let o=n.someProp("clipboardSerializer")||nt.fromSchema(n.state.schema),l=od(),a=l.createElement("div");a.appendChild(o.serializeFragment(r,{document:l}));let c=a.firstChild,u,d=0;for(;c&&c.nodeType==1&&(u=id[c.nodeName.toLowerCase()]);){for(let f=u.length-1;f>=0;f--){let p=l.createElement(u[f]);for(;a.firstChild;)p.appendChild(a.firstChild);a.appendChild(p),d++}c=a.firstChild}c&&c.nodeType==1&&c.setAttribute("data-pm-slice",`${s} ${i}${d?` -${d}`:""} ${JSON.stringify(t)}`);let h=n.someProp("clipboardTextSerializer",f=>f(e,n))||e.content.textBetween(0,e.content.size,` + +`);return{dom:a,text:h,slice:e}}function td(n,e,t,r,s){let i=s.parent.type.spec.code,o,l;if(!t&&!e)return null;let a=!!e&&(r||i||!t);if(a){if(n.someProp("transformPastedText",h=>{e=h(e,i||r,n)}),i)return l=new v(b.from(n.state.schema.text(e.replace(/\r\n?/g,` +`))),0,0),n.someProp("transformPasted",h=>{l=h(l,n,!0)}),l;let d=n.someProp("clipboardTextParser",h=>h(e,s,r,n));if(d)l=d;else{let h=s.marks(),{schema:f}=n.state,p=nt.fromSchema(f);o=document.createElement("div"),e.split(/(?:\r\n?|\n)+/).forEach(m=>{let g=o.appendChild(document.createElement("p"));m&&g.appendChild(p.serializeNode(f.text(m,h)))})}}else n.someProp("transformPastedHTML",d=>{t=d(t,n)}),o=Eg(t),er&&Ng(o);let c=o&&o.querySelector("[data-pm-slice]"),u=c&&/^(\d+) (\d+)(?: -(\d+))? (.*)/.exec(c.getAttribute("data-pm-slice")||"");if(u&&u[3])for(let d=+u[3];d>0;d--){let h=o.firstChild;for(;h&&h.nodeType!=1;)h=h.nextSibling;if(!h)break;o=h}if(l||(l=(n.someProp("clipboardParser")||n.someProp("domParser")||Fe.fromSchema(n.state.schema)).parseSlice(o,{preserveWhitespace:!!(a||u),context:s,ruleFromNode(h){return h.nodeName=="BR"&&!h.nextSibling&&h.parentNode&&!Tg.test(h.parentNode.nodeName)?{ignore:!0}:null}})),u)l=Rg(Au(l,+u[1],+u[2]),u[4]);else if(l=v.maxOpen(Mg(l.content,s),!0),l.openStart||l.openEnd){let d=0,h=0;for(let f=l.content.firstChild;d{l=d(l,n,a)}),l}var Tg=/^(a|abbr|acronym|b|cite|code|del|em|i|ins|kbd|label|output|q|ruby|s|samp|span|strong|sub|sup|time|u|tt|var)$/i;function Mg(n,e){if(n.childCount<2)return n;for(let t=e.depth;t>=0;t--){let s=e.node(t).contentMatchAt(e.index(t)),i,o=[];if(n.forEach(l=>{if(!o)return;let a=s.findWrapping(l.type),c;if(!a)return o=null;if(c=o.length&&i.length&&rd(a,i,l,o[o.length-1],0))o[o.length-1]=c;else{o.length&&(o[o.length-1]=sd(o[o.length-1],i.length));let u=nd(l,a);o.push(u),s=s.matchType(u.type),i=a}}),o)return b.from(o)}return n}function nd(n,e,t=0){for(let r=e.length-1;r>=t;r--)n=e[r].create(null,b.from(n));return n}function rd(n,e,t,r,s){if(s1&&(i=0),s=t&&(l=e<0?o.contentMatchAt(0).fillBefore(l,i<=s).append(l):l.append(o.contentMatchAt(o.childCount).fillBefore(b.empty,!0))),n.replaceChild(e<0?0:n.childCount-1,o.copy(l))}function Au(n,e,t){return et})),Ao.createHTML(n)):n}function Eg(n){let e=/^(\s*]*>)*/.exec(n);e&&(n=n.slice(e[0].length));let t=od().createElement("div"),r=/<([a-z][^>\s]+)/i.exec(n),s;if((s=r&&id[r[1].toLowerCase()])&&(n=s.map(i=>"<"+i+">").join("")+n+s.map(i=>"").reverse().join("")),t.innerHTML=Ag(n),s)for(let i=0;i=0;l-=2){let a=t.nodes[r[l]];if(!a||a.hasRequiredAttrs())break;s=b.from(a.create(r[l+1],s)),i++,o++}return new v(s,i,o)}var be={},xe={},Og={touchstart:!0,touchmove:!0},Vo=class{constructor(){this.shiftKey=!1,this.mouseDown=null,this.lastKeyCode=null,this.lastKeyCodeTime=0,this.lastClick={time:0,x:0,y:0,type:"",button:0},this.lastSelectionOrigin=null,this.lastSelectionTime=0,this.lastIOSEnter=0,this.lastIOSEnterFallbackTimeout=-1,this.lastFocus=0,this.lastTouch=0,this.lastChromeDelete=0,this.composing=!1,this.compositionNode=null,this.composingTimeout=-1,this.compositionNodes=[],this.compositionEndedAt=-2e8,this.compositionID=1,this.badSafariComposition=!1,this.compositionPendingChanges=0,this.domChangeCount=0,this.eventHandlers=Object.create(null),this.hideSelectionGuard=null}};function Ig(n){for(let e in be){let t=be[e];n.dom.addEventListener(e,n.input.eventHandlers[e]=r=>{Pg(n,r)&&!Zo(n,r)&&(n.editable||!(r.type in xe))&&t(n,r)},Og[e]?{passive:!0}:void 0)}fe&&n.dom.addEventListener("input",()=>null),Wo(n)}function ct(n,e){n.input.lastSelectionOrigin=e,n.input.lastSelectionTime=Date.now()}function Dg(n){n.input.mouseDown&&n.input.mouseDown.done(),n.domObserver.stop();for(let e in n.input.eventHandlers)n.dom.removeEventListener(e,n.input.eventHandlers[e]);clearTimeout(n.input.composingTimeout),clearTimeout(n.input.lastIOSEnterFallbackTimeout)}function Wo(n){n.someProp("handleDOMEvents",e=>{for(let t in e)n.input.eventHandlers[t]||n.dom.addEventListener(t,n.input.eventHandlers[t]=r=>Zo(n,r))})}function Zo(n,e){return n.someProp("handleDOMEvents",t=>{let r=t[e.type];return r?r(n,e)||e.defaultPrevented:!1})}function Pg(n,e){if(!e.bubbles)return!0;if(e.defaultPrevented)return!1;for(let t=e.target;t!=n.dom;t=t.parentNode)if(!t||t.nodeType==11||t.pmViewDesc&&t.pmViewDesc.stopEvent(e))return!1;return!0}function Lg(n,e){!Zo(n,e)&&be[e.type]&&(n.editable||!(e.type in xe))&&be[e.type](n,e)}xe.keydown=(n,e)=>{let t=e;if(n.input.shiftKey=t.keyCode==16||t.shiftKey,!cd(n)&&(n.input.lastKeyCode=t.keyCode,n.input.lastKeyCodeTime=Date.now(),!(at&&se&&t.keyCode==13)))if(t.keyCode!=229&&n.domObserver.forceFlush(),xn&&t.keyCode==13&&!t.ctrlKey&&!t.altKey&&!t.metaKey){let r=Date.now();n.input.lastIOSEnter=r,n.input.lastIOSEnterFallbackTimeout=setTimeout(()=>{n.input.lastIOSEnter==r&&(n.someProp("handleKeyDown",s=>s(n,zt(13,"Enter"))),n.input.lastIOSEnter=0)},200)}else n.someProp("handleKeyDown",r=>r(n,t))||vg(n,t)?t.preventDefault():ct(n,"key")};xe.keyup=(n,e)=>{e.keyCode==16&&(n.input.shiftKey=!1)};xe.keypress=(n,e)=>{let t=e;if(cd(n)||!t.charCode||t.ctrlKey&&!t.altKey||Oe&&t.metaKey)return;if(n.someProp("handleKeyPress",s=>s(n,t))){t.preventDefault();return}let r=n.state.selection;if(!(r instanceof A)||!r.$from.sameParent(r.$to)){let s=String.fromCharCode(t.charCode),i=()=>n.state.tr.insertText(s).scrollIntoView();!/[\r\n]/.test(s)&&!n.someProp("handleTextInput",o=>o(n,r.$from.pos,r.$to.pos,s,i))&&n.dispatch(i()),t.preventDefault()}};function tr(n){return{left:n.clientX,top:n.clientY}}function zg(n,e){let t=e.x-n.clientX,r=e.y-n.clientY;return t*t+r*r<100}function el(n,e,t,r,s){if(r==-1)return!1;let i=n.state.doc.resolve(r);for(let o=i.depth+1;o>0;o--)if(n.someProp(e,l=>o>i.depth?l(n,t,i.nodeAfter,i.before(o),s,!0):l(n,t,i.node(o),i.before(o),s,!1)))return!0;return!1}function nr(n,e,t){if(n.focused||n.focus(),n.state.selection.eq(e))return;let r=n.state.tr.setSelection(e);t=="pointer"&&r.setMeta("pointer",!0),n.dispatch(r)}function Bg(n,e){if(e==-1)return!1;let t=n.state.doc.resolve(e),r=t.nodeAfter;return r&&r.isAtom&&N.isSelectable(r)?(nr(n,new N(t),"pointer"),!0):!1}function $g(n,e){if(e==-1)return!1;let t=n.state.selection,r,s;t instanceof N&&(r=t.node);let i=n.state.doc.resolve(e);for(let o=i.depth+1;o>0;o--){let l=o>i.depth?i.nodeAfter:i.node(o);if(N.isSelectable(l)){r&&t.$from.depth>0&&o>=t.$from.depth&&i.before(t.$from.depth+1)==t.$from.pos?s=i.before(t.$from.depth):s=i.before(o);break}}return s!=null?(nr(n,N.create(n.state.doc,s),"pointer"),!0):!1}function Fg(n,e,t,r,s){return el(n,"handleClickOn",e,t,r)||n.someProp("handleClick",i=>i(n,e,r))||(s?$g(n,t):Bg(n,t))}function Hg(n,e,t,r){return el(n,"handleDoubleClickOn",e,t,r)||n.someProp("handleDoubleClick",s=>s(n,e,r))}function _g(n,e,t,r){return el(n,"handleTripleClickOn",e,t,r)||n.someProp("handleTripleClick",s=>s(n,e,r))||Vg(n,t,r)}function Vg(n,e,t){if(t.button!=0)return!1;let r=ld(n,e,!0),s=n.state.doc;return r?(nr(n,r,"pointer"),r instanceof A&&s.eq(n.state.doc)&&(n.input.mouseDown=new Jo(n,r)),!0):!1}function ld(n,e,t){let r=n.state.doc;if(e==-1)return r.inlineContent?A.create(r,0,r.content.size):null;let s=r.resolve(e);for(let i=s.depth+1;i>0;i--){let o=i>s.depth?s.nodeAfter:s.node(i),l=s.before(i);if(o.inlineContent)return A.create(r,l+1,l+1+o.content.size);if(t&&N.isSelectable(o))return N.create(r,l)}return null}function tl(n){return ns(n)}var ad=Oe?"metaKey":"ctrlKey";be.mousedown=(n,e)=>{let t=e;n.input.shiftKey=t.shiftKey;let r=tl(n),s=Date.now(),i="singleClick";s-n.input.lastClick.time<500&&zg(t,n.input.lastClick)&&!t[ad]&&n.input.lastClick.button==t.button&&(n.input.lastClick.type=="singleClick"?i="doubleClick":n.input.lastClick.type=="doubleClick"&&(i="tripleClick")),n.input.lastClick={time:s,x:t.clientX,y:t.clientY,type:i,button:t.button},n.input.mouseDown&&n.input.mouseDown.done();let o=n.posAtCoords(tr(t));o&&(i=="singleClick"?n.input.mouseDown=new jo(n,o,t,!!r):(i=="doubleClick"?Hg:_g)(n,o.pos,o.inside,t)?t.preventDefault():ct(n,"pointer"))};var ts=class{constructor(e){this.view=e,this.mightDrag=null,e.root.addEventListener("mouseup",this.up=this.up.bind(this)),e.root.addEventListener("mousemove",this.move=this.move.bind(this))}up(e){this.done()}move(e){e.buttons==0&&this.done()}done(){this.view.root.removeEventListener("mouseup",this.up),this.view.root.removeEventListener("mousemove",this.move),this.view.input.mouseDown==this&&(this.view.input.mouseDown=null)}delaySelUpdate(){return!1}},jo=class extends ts{constructor(e,t,r,s){super(e),this.pos=t,this.event=r,this.flushed=s,this.delayedSelectionSync=!1,this.startDoc=e.state.doc,this.selectNode=!!r[ad],this.allowDefault=r.shiftKey;let i,o;if(t.inside>-1)i=e.state.doc.nodeAt(t.inside),o=t.inside;else{let u=e.state.doc.resolve(t.pos);i=u.parent,o=u.depth?u.before():0}let l=s?null:r.target,a=l?e.docView.nearestDesc(l,!0):null;this.target=a&&a.nodeDOM.nodeType==1?a.nodeDOM:null;let{selection:c}=e.state;r.button==0&&(i.type.spec.draggable&&i.type.spec.selectable!==!1||c instanceof N&&c.from<=o&&c.to>o)&&(this.mightDrag={node:i,pos:o,addAttr:!!(this.target&&!this.target.draggable),setUneditable:!!(this.target&&De&&!this.target.hasAttribute("contentEditable"))}),this.target&&this.mightDrag&&(this.mightDrag.addAttr||this.mightDrag.setUneditable)&&(this.view.domObserver.stop(),this.mightDrag.addAttr&&(this.target.draggable=!0),this.mightDrag.setUneditable&&setTimeout(()=>{this.view.input.mouseDown==this&&this.target.setAttribute("contentEditable","false")},20),this.view.domObserver.start()),ct(e,"pointer")}done(){super.done(),this.mightDrag&&this.target&&(this.view.domObserver.stop(),this.mightDrag.addAttr&&this.target.removeAttribute("draggable"),this.mightDrag.setUneditable&&this.target.removeAttribute("contentEditable"),this.view.domObserver.start()),this.delayedSelectionSync&&setTimeout(()=>{this.view.isDestroyed||ut(this.view)})}up(e){if(this.done(),!this.view.dom.contains(e.target))return;let t=this.pos;this.view.state.doc!=this.startDoc&&(t=this.view.posAtCoords(tr(e))),this.updateAllowDefault(e),this.allowDefault||!t?ct(this.view,"pointer"):Fg(this.view,t.pos,t.inside,e,this.selectNode)?e.preventDefault():e.button==0&&(this.flushed||fe&&this.mightDrag&&!this.mightDrag.node.isAtom||se&&!this.view.state.selection.visible&&Math.min(Math.abs(t.pos-this.view.state.selection.from),Math.abs(t.pos-this.view.state.selection.to))<=2)?(nr(this.view,O.near(this.view.state.doc.resolve(t.pos)),"pointer"),e.preventDefault()):ct(this.view,"pointer")}move(e){this.updateAllowDefault(e),ct(this.view,"pointer"),super.move(e)}updateAllowDefault(e){!this.allowDefault&&(Math.abs(this.event.x-e.clientX)>4||Math.abs(this.event.y-e.clientY)>4)&&(this.allowDefault=!0)}delaySelUpdate(){return this.allowDefault?(this.delayedSelectionSync=!0,!0):!1}},Jo=class extends ts{constructor(e,t){super(e),this.startSelection=t,this.startDoc=e.state.doc}move(e){if(e.buttons==0||this.view.isDestroyed||!this.view.state.doc.eq(this.startDoc)){this.done();return}e.preventDefault(),ct(this.view,"pointer");let t=this.view.posAtCoords(tr(e)),r=t&&ld(this.view,t.inside,!1);if(!r)return;let{doc:s}=this.view.state,i=this.startSelection,[o,l]=r.from{n.input.lastTouch=Date.now(),tl(n),ct(n,"pointer")};be.touchmove=n=>{n.input.lastTouch=Date.now(),ct(n,"pointer")};be.contextmenu=n=>tl(n);function cd(n,e){return n.composing?!0:fe&&Math.abs(Date.now()-n.input.compositionEndedAt)<500?(n.input.compositionEndedAt=-2e8,!0):!1}var Wg=at?5e3:-1;xe.compositionstart=xe.compositionupdate=n=>{if(!n.composing){n.domObserver.flush();let{state:e}=n,t=e.selection.$to;if(e.selection instanceof A&&(e.storedMarks||!t.textOffset&&t.parentOffset&&t.nodeBefore.marks.some(r=>r.type.spec.inclusive===!1)||se&&_u&&jg(n)))n.markCursor=n.state.storedMarks||t.marks(),ns(n,!0),n.markCursor=null;else if(ns(n,!e.selection.empty),De&&e.selection.empty&&t.parentOffset&&!t.textOffset&&t.nodeBefore.marks.length){let r=n.domSelectionRange();for(let s=r.focusNode,i=r.focusOffset;s&&s.nodeType==1&&i!=0;){let o=i<0?s.lastChild:s.childNodes[i-1];if(!o)break;if(o.nodeType==3){let l=n.domSelection();l&&l.collapse(o,o.nodeValue.length);break}else s=o,i=-1}}n.input.composing=!0}ud(n,Wg)};function jg(n){let{focusNode:e,focusOffset:t}=n.domSelectionRange();if(!e||e.nodeType!=1||t>=e.childNodes.length)return!1;let r=e.childNodes[t];return r.nodeType==1&&r.contentEditable=="false"}xe.compositionend=(n,e)=>{n.composing&&(n.input.composing=!1,n.input.compositionEndedAt=Date.now(),n.input.compositionPendingChanges=n.domObserver.pendingRecords().length?n.input.compositionID:0,n.input.compositionNode=null,n.input.badSafariComposition?n.domObserver.forceFlush():n.input.compositionPendingChanges&&Promise.resolve().then(()=>n.domObserver.flush()),n.input.compositionID++,ud(n,20))};function ud(n,e){clearTimeout(n.input.composingTimeout),e>-1&&(n.input.composingTimeout=setTimeout(()=>ns(n),e))}function dd(n){for(n.composing&&(n.input.composing=!1,n.input.compositionEndedAt=Date.now());n.input.compositionNodes.length>0;)n.input.compositionNodes.pop().markParentsDirty()}function Jg(n){let e=n.domSelectionRange();if(!e.focusNode)return null;let t=Vm(e.focusNode,e.focusOffset),r=Wm(e.focusNode,e.focusOffset);if(t&&r&&t!=r){let s=r.pmViewDesc,i=n.domObserver.lastChangedTextNode;if(t==i||r==i)return i;if(!s||!s.isText(r.nodeValue))return r;if(n.input.compositionNode==r){let o=t.pmViewDesc;if(!(!o||!o.isText(t.nodeValue)))return r}}return t||r}function ns(n,e=!1){if(!(at&&n.domObserver.flushingSoon>=0)){if(n.domObserver.forceFlush(),dd(n),e||n.docView&&n.docView.dirty){let t=Xo(n),r=n.state.selection;return t&&!t.eq(r)?n.dispatch(n.state.tr.setSelection(t)):(n.markCursor||e)&&!r.$from.node(r.$from.sharedDepth(r.to)).inlineContent?n.dispatch(n.state.tr.deleteSelection()):n.updateState(n.state),!0}return!1}}function Kg(n,e){if(!n.dom.parentNode)return;let t=n.dom.parentNode.appendChild(document.createElement("div"));t.appendChild(e),t.style.cssText="position: fixed; left: -10000px; top: 10px";let r=getSelection(),s=document.createRange();s.selectNodeContents(e),n.dom.blur(),r.removeAllRanges(),r.addRange(s),setTimeout(()=>{t.parentNode&&t.parentNode.removeChild(t),n.focus()},50)}var Gn=Te&&St<15||xn&&qm<604;be.copy=xe.cut=(n,e)=>{let t=e,r=n.state.selection,s=t.type=="cut";if(r.empty)return;let i=Gn?null:t.clipboardData,o=r.content(),{dom:l,text:a}=Yo(n,o);i?(t.preventDefault(),i.clearData(),i.setData("text/html",l.innerHTML),i.setData("text/plain",a)):Kg(n,l),s&&n.dispatch(n.state.tr.deleteSelection().scrollIntoView().setMeta("uiEvent","cut"))};function qg(n){return n.openStart==0&&n.openEnd==0&&n.content.childCount==1?n.content.firstChild:null}function Ug(n,e){if(!n.dom.parentNode)return;let t=n.input.shiftKey||n.state.selection.$from.parent.type.spec.code,r=n.dom.parentNode.appendChild(document.createElement(t?"textarea":"div"));t||(r.contentEditable="true"),r.style.cssText="position: fixed; left: -10000px; top: 10px",r.focus();let s=n.input.shiftKey&&n.input.lastKeyCode!=45;setTimeout(()=>{n.focus(),r.parentNode&&r.parentNode.removeChild(r),t?Xn(n,r.value,null,s,e):Xn(n,r.textContent,r.innerHTML,s,e)},50)}function Xn(n,e,t,r,s){let i=td(n,e,t,r,n.state.selection.$from);if(n.someProp("handlePaste",a=>a(n,s,i||v.empty)))return!0;if(!i)return!1;let o=qg(i),l=o?n.state.tr.replaceSelectionWith(o,r):n.state.tr.replaceSelection(i);return n.dispatch(l.scrollIntoView().setMeta("paste",!0).setMeta("uiEvent","paste")),!0}function hd(n){let e=n.getData("text/plain")||n.getData("Text");if(e)return e;let t=n.getData("text/uri-list");return t?t.replace(/\r?\n/g," "):""}xe.paste=(n,e)=>{let t=e;if(n.composing&&!at)return;let r=Gn?null:t.clipboardData,s=n.input.shiftKey&&n.input.lastKeyCode!=45;r&&Xn(n,hd(r),r.getData("text/html"),s,t)?t.preventDefault():Ug(n,t)};var rs=class{constructor(e,t,r){this.slice=e,this.move=t,this.node=r}},Gg=Oe?"altKey":"ctrlKey";function fd(n,e){let t;return n.someProp("dragCopies",r=>{t=t||r(e)}),t!=null?!t:!e[Gg]}be.dragstart=(n,e)=>{let t=e,r=n.input.mouseDown;if(r&&r.done(),!t.dataTransfer)return;let s=n.state.selection,i=s.empty?null:n.posAtCoords(tr(t)),o;if(!(i&&i.pos>=s.from&&i.pos<=(s instanceof N?s.to-1:s.to))){if(r&&r.mightDrag)o=N.create(n.state.doc,r.mightDrag.pos);else if(t.target&&t.target.nodeType==1){let d=n.docView.nearestDesc(t.target,!0);d&&d.node.type.spec.draggable&&d!=n.docView&&(o=N.create(n.state.doc,d.posBefore))}}let l=(o||n.state.selection).content(),{dom:a,text:c,slice:u}=Yo(n,l);(!t.dataTransfer.files.length||!se||Hu>120)&&t.dataTransfer.clearData(),t.dataTransfer.setData(Gn?"Text":"text/html",a.innerHTML),t.dataTransfer.effectAllowed="copyMove",Gn||t.dataTransfer.setData("text/plain",c),n.dragging=new rs(u,fd(n,t),o)};be.dragend=n=>{let e=n.dragging;window.setTimeout(()=>{n.dragging==e&&(n.dragging=null)},50)};xe.dragover=xe.dragenter=(n,e)=>e.preventDefault();xe.drop=(n,e)=>{try{Xg(n,e,n.dragging)}finally{n.dragging=null}};function Xg(n,e,t){if(!e.dataTransfer)return;let r=n.posAtCoords(tr(e));if(!r)return;let s=n.state.doc.resolve(r.pos),i=t&&t.slice;i?n.someProp("transformPasted",f=>{i=f(i,n,!1)}):i=td(n,hd(e.dataTransfer),Gn?null:e.dataTransfer.getData("text/html"),!1,s);let o=!!(t&&fd(n,e));if(n.someProp("handleDrop",f=>f(n,e,i||v.empty,o))){e.preventDefault();return}if(!i)return;e.preventDefault();let l=i?Kr(n.state.doc,s.pos,i):s.pos;l==null&&(l=s.pos);let a=n.state.tr;if(o){let{node:f}=t;f?f.replace(a):a.deleteSelection()}let c=a.mapping.map(l),u=i.openStart==0&&i.openEnd==0&&i.content.childCount==1,d=a.doc;if(u?a.replaceRangeWith(c,c,i.content.firstChild):a.replaceRange(c,c,i),a.doc.eq(d))return;let h=a.doc.resolve(c);if(u&&N.isSelectable(i.content.firstChild)&&h.nodeAfter&&h.nodeAfter.sameMarkup(i.content.firstChild))a.setSelection(new N(h));else{let f=a.mapping.map(l);a.mapping.maps[a.mapping.maps.length-1].forEach((p,m,g,y)=>f=y),a.setSelection(Qo(n,h,a.doc.resolve(f)))}n.focus(),n.dispatch(a.setMeta("uiEvent","drop"))}be.focus=n=>{n.input.lastFocus=Date.now(),n.focused||(n.domObserver.stop(),n.dom.classList.add("ProseMirror-focused"),n.domObserver.start(),n.focused=!0,setTimeout(()=>{n.docView&&n.hasFocus()&&!n.domObserver.currentSelection.eq(n.domSelectionRange())&&ut(n)},20))};be.blur=(n,e)=>{let t=e;n.focused&&(n.domObserver.stop(),n.dom.classList.remove("ProseMirror-focused"),n.domObserver.start(),t.relatedTarget&&n.dom.contains(t.relatedTarget)&&n.domObserver.currentSelection.clear(),n.focused=!1)};be.beforeinput=(n,e)=>{if(se&&at&&e.inputType=="deleteContentBackward"){n.domObserver.flushSoon();let{domChangeCount:r}=n.input;setTimeout(()=>{if(n.input.domChangeCount!=r||(n.dom.blur(),n.focus(),n.someProp("handleKeyDown",i=>i(n,zt(8,"Backspace")))))return;let{$cursor:s}=n.state.selection;s&&s.pos>0&&n.dispatch(n.state.tr.delete(s.pos-1,s.pos).scrollIntoView())},50)}};for(let n in xe)be[n]=xe[n];function Qn(n,e){if(n==e)return!0;for(let t in n)if(n[t]!==e[t])return!1;for(let t in e)if(!(t in n))return!1;return!0}var ss=class n{constructor(e,t){this.toDOM=e,this.spec=t||Ht,this.side=this.spec.side||0}map(e,t,r,s){let{pos:i,deleted:o}=e.mapResult(t.from+s,this.side<0?-1:1);return o?null:new Z(i-r,i-r,this)}valid(){return!0}eq(e){return this==e||e instanceof n&&(this.spec.key&&this.spec.key==e.spec.key||this.toDOM==e.toDOM&&Qn(this.spec,e.spec))}destroy(e){this.spec.destroy&&this.spec.destroy(e)}},Ft=class n{constructor(e,t){this.attrs=e,this.spec=t||Ht}map(e,t,r,s){let i=e.map(t.from+s,this.spec.inclusiveStart?-1:1)-r,o=e.map(t.to+s,this.spec.inclusiveEnd?1:-1)-r;return i>=o?null:new Z(i,o,this)}valid(e,t){return t.from=e&&(!i||i(l.spec))&&r.push(l.copy(l.from+s,l.to+s))}for(let o=0;oe){let l=this.children[o]+1;this.children[o+2].findInner(e-l,t-l,r,s+l,i)}}map(e,t,r){return this==he||e.maps.length==0?this:this.mapInner(e,t,0,0,r||Ht)}mapInner(e,t,r,s,i){let o;for(let l=0;l{let c=a+r,u;if(u=md(t,l,c)){for(s||(s=this.children.slice());il&&d.to=e){this.children[l]==e&&(r=this.children[l+2]);break}let i=e+1,o=i+t.content.size;for(let l=0;li&&a.type instanceof Ft){let c=Math.max(i,a.from)-i,u=Math.min(o,a.to)-i;cs.map(e,t,Ht));return n.from(r)}forChild(e,t){if(t.isLeaf)return J.empty;let r=[];for(let s=0;st instanceof J)?e:e.reduce((t,r)=>t.concat(r instanceof J?r:r.members),[]))}}forEachSet(e){for(let t=0;t{let g=m-p-(f-h);for(let y=0;yk+u-d)continue;let w=l[y]+u-d;f>=w?l[y+1]=h<=w?-2:-1:h>=u&&g&&(l[y]+=g,l[y+1]+=g)}d+=g}),u=t.maps[c].map(u,-1)}let a=!1;for(let c=0;c=r.content.size){a=!0;continue}let h=t.map(n[c+1]+i,-1),f=h-s,{index:p,offset:m}=r.content.findIndex(d),g=r.maybeChild(p);if(g&&m==d&&m+g.nodeSize==f){let y=l[c+2].mapInner(t,g,u+1,n[c]+i+1,o);y!=he?(l[c]=d,l[c+1]=f,l[c+2]=y):(l[c+1]=-2,a=!0)}else a=!0}if(a){let c=Yg(l,n,e,t,s,i,o),u=ls(c,r,0,o);e=u.local;for(let d=0;dt&&o.to{let c=md(n,l,a+t);if(c){i=!0;let u=ls(c,l,t+a+1,r);u!=he&&s.push(a,a+l.nodeSize,u)}});let o=pd(i?gd(n):n,-t).sort(_t);for(let l=0;l0;)e++;n.splice(e,0,t)}function Eo(n){let e=[];return n.someProp("decorations",t=>{let r=t(n.state);r&&r!=he&&e.push(r)}),n.cursorWrapper&&e.push(J.create(n.state.doc,[n.cursorWrapper.deco])),is.from(e)}var Zg={childList:!0,characterData:!0,characterDataOldValue:!0,attributes:!0,attributeOldValue:!0,subtree:!0},ey=Te&&St<=11,qo=class{constructor(){this.anchorNode=null,this.anchorOffset=0,this.focusNode=null,this.focusOffset=0}set(e){this.anchorNode=e.anchorNode,this.anchorOffset=e.anchorOffset,this.focusNode=e.focusNode,this.focusOffset=e.focusOffset}clear(){this.anchorNode=this.focusNode=null}eq(e){return e.anchorNode==this.anchorNode&&e.anchorOffset==this.anchorOffset&&e.focusNode==this.focusNode&&e.focusOffset==this.focusOffset}},Uo=class{constructor(e,t){this.view=e,this.handleDOMChange=t,this.queue=[],this.flushingSoon=-1,this.observer=null,this.currentSelection=new qo,this.onCharData=null,this.suppressingSelectionUpdates=!1,this.lastChangedTextNode=null,this.observer=window.MutationObserver&&new window.MutationObserver(r=>{for(let s=0;ss.type=="childList"&&s.removedNodes.length||s.type=="characterData"&&s.oldValue.length>s.target.nodeValue.length)?this.flushSoon():fe&&e.composing&&r.some(s=>s.type=="childList"&&s.target.nodeName=="TR")?(e.input.badSafariComposition=!0,this.flushSoon()):this.flush()}),ey&&(this.onCharData=r=>{this.queue.push({target:r.target,type:"characterData",oldValue:r.prevValue}),this.flushSoon()}),this.onSelectionChange=this.onSelectionChange.bind(this)}flushSoon(){this.flushingSoon<0&&(this.flushingSoon=window.setTimeout(()=>{this.flushingSoon=-1,this.flush()},20))}forceFlush(){this.flushingSoon>-1&&(window.clearTimeout(this.flushingSoon),this.flushingSoon=-1,this.flush())}start(){this.observer&&(this.observer.takeRecords(),this.observer.observe(this.view.dom,Zg)),this.onCharData&&this.view.dom.addEventListener("DOMCharacterDataModified",this.onCharData),this.connectSelection()}stop(){if(this.observer){let e=this.observer.takeRecords();if(e.length){for(let t=0;tthis.flush(),20)}this.observer.disconnect()}this.onCharData&&this.view.dom.removeEventListener("DOMCharacterDataModified",this.onCharData),this.disconnectSelection()}connectSelection(){this.view.dom.ownerDocument.addEventListener("selectionchange",this.onSelectionChange)}disconnectSelection(){this.view.dom.ownerDocument.removeEventListener("selectionchange",this.onSelectionChange)}suppressSelectionUpdates(){this.suppressingSelectionUpdates=!0,setTimeout(()=>this.suppressingSelectionUpdates=!1,50)}onSelectionChange(){if(wu(this.view)){if(this.suppressingSelectionUpdates)return ut(this.view);if(Te&&St<=11&&!this.view.state.selection.empty){let e=this.view.domSelectionRange();if(e.focusNode&&Vt(e.focusNode,e.focusOffset,e.anchorNode,e.anchorOffset))return this.flushSoon()}this.flush()}}setCurSelection(){this.currentSelection.set(this.view.domSelectionRange())}ignoreSelectionChange(e){if(!e.focusNode)return!0;let t=new Set,r;for(let i=e.focusNode;i;i=bn(i))t.add(i);for(let i=e.anchorNode;i;i=bn(i))if(t.has(i)){r=i;break}let s=r&&this.view.docView.nearestDesc(r);if(s&&s.ignoreMutation({type:"selection",target:r.nodeType==3?r.parentNode:r}))return this.setCurSelection(),!0}pendingRecords(){if(this.observer)for(let e of this.observer.takeRecords())this.queue.push(e);return this.queue}flush(){let{view:e}=this;if(!e.docView||this.flushingSoon>-1)return;let t=this.pendingRecords();t.length&&(this.queue=[]);let r=e.domSelectionRange(),s=!this.suppressingSelectionUpdates&&!this.currentSelection.eq(r)&&wu(e)&&!this.ignoreSelectionChange(r),i=-1,o=-1,l=!1,a=[];if(e.editable)for(let u=0;uu.nodeName=="BR")&&(e.input.lastKeyCode==8||e.input.lastKeyCode==46||se&&(e.composing||e.input.compositionEndedAt>Date.now()-50)&&t.some(u=>u.type=="childList"&&u.removedNodes.length))){for(let u of a)if(u.nodeName=="BR"&&u.parentNode){let d=u.nextSibling;for(;d&&d.nodeType==1;){if(d.contentEditable=="false"){u.parentNode.removeChild(u);break}d=d.firstChild}}}else if(De&&a.length){let u=a.filter(d=>d.nodeName=="BR");if(u.length==2){let[d,h]=u;d.parentNode&&d.parentNode.parentNode==h.parentNode?h.remove():d.remove()}else{let{focusNode:d}=this.currentSelection;for(let h of u){let f=h.parentNode;f&&f.nodeName=="LI"&&(!d||ry(e,d)!=f)&&h.remove()}}}let c=null;i<0&&s&&e.input.lastFocus>Date.now()-200&&Math.max(e.input.lastTouch,e.input.lastClick.time)-1||s)&&(i>-1&&(e.docView.markDirty(i,o),ty(e)),e.input.badSafariComposition&&(e.input.badSafariComposition=!1,sy(e,a)),this.handleDOMChange(i,o,l,a),e.docView&&e.docView.dirty?e.updateState(e.state):this.currentSelection.eq(r)||ut(e),this.currentSelection.set(r))}registerMutation(e,t){if(t.indexOf(e.target)>-1)return null;let r=this.view.docView.nearestDesc(e.target);if(e.type=="attributes"&&(r==this.view.docView||e.attributeName=="contenteditable"||e.attributeName=="style"&&!e.oldValue&&!e.target.getAttribute("style"))||!r||r.ignoreMutation(e))return null;if(e.type=="childList"){for(let u=0;us;g--){let y=r.childNodes[g-1],k=y.pmViewDesc;if(y.nodeName=="BR"&&!k){i=g;break}if(!k||k.size)break}let d=n.state.doc,h=n.someProp("domParser")||Fe.fromSchema(n.state.schema),f=d.resolve(o),p=null,m=h.parse(r,{topNode:f.parent,topMatch:f.parent.contentMatchAt(f.index()),topOpen:!0,from:s,to:i,preserveWhitespace:f.parent.type.whitespace=="pre"?"full":!0,findPositions:c,ruleFromNode:oy,context:f});if(c&&c[0].pos!=null){let g=c[0].pos,y=c[1]&&c[1].pos;y==null&&(y=g),p={anchor:g+o,head:y+o}}return{doc:m,sel:p,from:o,to:l}}function oy(n){let e=n.pmViewDesc;if(e)return e.parseRule();if(n.nodeName=="BR"&&n.parentNode){if(fe&&/^(ul|ol)$/i.test(n.parentNode.nodeName)){let t=document.createElement("div");return t.appendChild(document.createElement("li")),{skip:t}}else if(n.parentNode.lastChild==n||fe&&/^(tr|table)$/i.test(n.parentNode.nodeName))return{ignore:!0}}else if(n.nodeName=="IMG"&&n.getAttribute("mark-placeholder"))return{ignore:!0};return null}var ly=/^(a|abbr|acronym|b|bd[io]|big|br|button|cite|code|data(list)?|del|dfn|em|i|img|ins|kbd|label|map|mark|meter|output|q|ruby|s|samp|small|span|strong|su[bp]|time|u|tt|var)$/i;function ay(n,e,t,r,s){let i=n.input.compositionPendingChanges||(n.composing?n.input.compositionID:0);if(n.input.compositionPendingChanges=0,e<0){let T=n.input.lastSelectionTime>Date.now()-50?n.input.lastSelectionOrigin:null,R=Xo(n,T);if(R&&!n.state.selection.eq(R)){if(se&&at&&n.input.lastKeyCode===13&&Date.now()-100te(n,zt(13,"Enter"))))return;let P=n.state.tr.setSelection(R);T=="pointer"?P.setMeta("pointer",!0):T=="key"&&P.scrollIntoView(),i&&P.setMeta("composition",i),n.dispatch(P)}return}let o=n.state.doc.resolve(e),l=o.sharedDepth(t);e=o.before(l+1),t=n.state.doc.resolve(t).after(l+1);let a=n.state.selection,c=iy(n,e,t),u=n.state.doc,d=u.slice(c.from,c.to),h,f;n.input.lastKeyCode===8&&Date.now()-100Date.now()-225||at)&&s.some(T=>T.nodeType==1&&!ly.test(T.nodeName))&&(!p||p.endA>=p.endB)&&n.someProp("handleKeyDown",T=>T(n,zt(13,"Enter")))){n.input.lastIOSEnter=0;return}if(!p)if(r&&a instanceof A&&!a.empty&&a.$head.sameParent(a.$anchor)&&!n.composing&&!(c.sel&&c.sel.anchor!=c.sel.head))p={start:a.from,endA:a.to,endB:a.to};else{if(c.sel){let T=Du(n,n.state.doc,c.sel);if(T&&!T.eq(n.state.selection)){let R=n.state.tr.setSelection(T);i&&R.setMeta("composition",i),n.dispatch(R)}}return}n.state.selection.fromn.state.selection.from&&p.start<=n.state.selection.from+2&&n.state.selection.from>=c.from?p.start=n.state.selection.from:p.endA=n.state.selection.to-2&&n.state.selection.to<=c.to&&(p.endB+=n.state.selection.to-p.endA,p.endA=n.state.selection.to)),Te&&St<=11&&p.endB==p.start+1&&p.endA==p.start&&p.start>c.from&&c.doc.textBetween(p.start-c.from-1,p.start-c.from+1)==" \xA0"&&(p.start--,p.endA--,p.endB--);let m=c.doc.resolveNoCache(p.start-c.from),g=c.doc.resolveNoCache(p.endB-c.from),y=u.resolve(p.start),k=m.sameParent(g)&&m.parent.inlineContent&&y.end()>=p.endA;if((xn&&n.input.lastIOSEnter>Date.now()-225&&(!k||s.some(T=>T.nodeName=="DIV"||T.nodeName=="P"))||!k&&m.posT(n,zt(13,"Enter")))){n.input.lastIOSEnter=0;return}if(n.state.selection.anchor>p.start&&uy(u,p.start,p.endA,m,g)&&n.someProp("handleKeyDown",T=>T(n,zt(8,"Backspace")))){at&&se&&n.domObserver.suppressSelectionUpdates();return}se&&p.endB==p.start&&(n.input.lastChromeDelete=Date.now()),at&&!k&&m.start()!=g.start()&&g.parentOffset==0&&m.depth==g.depth&&c.sel&&c.sel.anchor==c.sel.head&&c.sel.head==p.endA&&(p.endB-=2,g=c.doc.resolveNoCache(p.endB-c.from),setTimeout(()=>{n.someProp("handleKeyDown",function(T){return T(n,zt(13,"Enter"))})},20));let w=p.start,C=p.endA,x=T=>{let R=T||n.state.tr.replace(w,C,c.doc.slice(p.start-c.from,p.endB-c.from));if(c.sel){let P=Du(n,R.doc,c.sel);P&&!(se&&n.composing&&P.empty&&(p.start!=p.endB||n.input.lastChromeDeleteut(n),20));let T=x(n.state.tr.delete(w,C)),R=u.resolve(p.start).marksAcross(u.resolve(p.endA));R&&T.ensureMarks(R),n.dispatch(T)}else if(p.endA==p.endB&&(M=cy(m.parent.content.cut(m.parentOffset,g.parentOffset),y.parent.content.cut(y.parentOffset,p.endA-y.start())))){let T=x(n.state.tr);M.type=="add"?T.addMark(w,C,M.mark):T.removeMark(w,C,M.mark),n.dispatch(T)}else if(m.parent.child(m.index()).isText&&m.index()==g.index()-(g.textOffset?0:1)){let T=m.parent.textBetween(m.parentOffset,g.parentOffset),R=()=>x(n.state.tr.insertText(T,w,C));n.someProp("handleTextInput",P=>P(n,w,C,T,R))||n.dispatch(R())}else n.dispatch(x());else n.dispatch(x())}function Du(n,e,t){return Math.max(t.anchor,t.head)>e.content.size?null:Qo(n,e.resolve(t.anchor),e.resolve(t.head))}function cy(n,e){let t=n.firstChild.marks,r=e.firstChild.marks,s=t,i=r,o,l,a;for(let u=0;uu.mark(l.addToSet(u.marks));else if(s.length==0&&i.length==1)l=i[0],o="remove",a=u=>u.mark(l.removeFromSet(u.marks));else return null;let c=[];for(let u=0;ut||No(o,!0,!1)0&&(e||n.indexAfter(r)==n.node(r).childCount);)r--,s++,e=!1;if(t){let i=n.node(r).maybeChild(n.indexAfter(r));for(;i&&!i.isLeaf;)i=i.firstChild,s++}return s}function dy(n,e,t,r,s){let i=n.findDiffStart(e,t),o=t+n.size,l=t+e.size;if(i==null)return null;let{a,b:c}=n.findDiffEnd(e,o,l);if(s=="end"){let u=Math.max(0,i-Math.min(a,c));r-=a+u-i}if(a=a?i-r:0;i-=u,c=i+(c-a),a=i}else if(c=c?i-r:0;i-=u,a=i+(a-c),c=i}return{start:i,endA:a,endB:c}}var Yn=class{constructor(e,t){this._root=null,this.focused=!1,this.trackWrites=null,this.mounted=!1,this.markCursor=null,this.cursorWrapper=null,this.lastSelectedViewDesc=void 0,this.input=new Vo,this.prevDirectPlugins=[],this.pluginViews=[],this.requiresGeckoHackNode=!1,this.dragging=null,this._props=t,this.state=t.state,this.directPlugins=t.plugins||[],this.directPlugins.forEach($u),this.dispatch=this.dispatch.bind(this),this.dom=e&&e.mount||document.createElement("div"),e&&(e.appendChild?e.appendChild(this.dom):typeof e=="function"?e(this.dom):e.mount&&(this.mounted=!0)),this.editable=zu(this),Lu(this),this.nodeViews=Bu(this),this.docView=mu(this.state.doc,Pu(this),Eo(this),this.dom,this),this.domObserver=new Uo(this,(r,s,i,o)=>ay(this,r,s,i,o)),this.domObserver.start(),Ig(this),this.updatePluginViews()}get composing(){return this.input.composing}get props(){if(this._props.state!=this.state){let e=this._props;this._props={};for(let t in e)this._props[t]=e[t];this._props.state=this.state}return this._props}update(e){e.handleDOMEvents!=this._props.handleDOMEvents&&Wo(this);let t=this._props;this._props=e,e.plugins&&(e.plugins.forEach($u),this.directPlugins=e.plugins),this.updateStateInner(e.state,t)}setProps(e){let t={};for(let r in this._props)t[r]=this._props[r];t.state=this.state;for(let r in e)t[r]=e[r];this.update(t)}updateState(e){this.updateStateInner(e,this._props)}updateStateInner(e,t){var r;let s=this.state,i=!1,o=!1;e.storedMarks&&this.composing&&(dd(this),o=!0),this.state=e;let l=s.plugins!=e.plugins||this._props.plugins!=t.plugins;if(l||this._props.plugins!=t.plugins||this._props.nodeViews!=t.nodeViews){let f=Bu(this);fy(f,this.nodeViews)&&(this.nodeViews=f,i=!0)}(l||t.handleDOMEvents!=this._props.handleDOMEvents)&&Wo(this),this.editable=zu(this),Lu(this);let a=Eo(this),c=Pu(this),u=s.plugins!=e.plugins&&!s.doc.eq(e.doc)?"reset":e.scrollToSelection>s.scrollToSelection?"to selection":"preserve",d=i||!this.docView.matchesNode(e.doc,c,a);(d||!e.selection.eq(s.selection))&&(o=!0);let h=u=="preserve"&&o&&this.dom.style.overflowAnchor==null&&Xm(this);if(o){this.domObserver.stop();let f=d&&(Te||se)&&!this.composing&&!s.selection.empty&&!e.selection.empty&&hy(s.selection,e.selection);if(d){let m=se?this.trackWrites=this.domSelectionRange().focusNode:null;this.composing&&(this.input.compositionNode=Jg(this)),(i||!this.docView.update(e.doc,c,a,this))&&(this.docView.updateOuterDeco(c),this.docView.destroy(),this.docView=mu(e.doc,c,a,this.dom,this)),m&&(!this.trackWrites||!this.dom.contains(this.trackWrites))&&(f=!0)}let p=this.input.mouseDown;f||!(p&&this.domObserver.currentSelection.eq(this.domSelectionRange())&&yg(this)&&p.delaySelUpdate())?ut(this,f):(Yu(this,e.selection),this.domObserver.setCurSelection()),this.domObserver.start()}this.updatePluginViews(s),!((r=this.dragging)===null||r===void 0)&&r.node&&!s.doc.eq(e.doc)&&this.updateDraggedNode(this.dragging,s),u=="reset"?this.dom.scrollTop=0:u=="to selection"?this.scrollToSelection():h&&Qm(h)}scrollToSelection(){let e=this.domSelectionRange().focusNode;if(!(!e||!this.dom.contains(e.nodeType==1?e:e.parentNode))){if(!this.someProp("handleScrollToSelection",t=>t(this)))if(this.state.selection instanceof N){let t=this.docView.domAfterPos(this.state.selection.from);t.nodeType==1&&cu(this,t.getBoundingClientRect(),e)}else cu(this,this.coordsAtPos(this.state.selection.head,1),e)}}destroyPluginViews(){let e;for(;e=this.pluginViews.pop();)e.destroy&&e.destroy()}updatePluginViews(e){if(!e||e.plugins!=this.state.plugins||this.directPlugins!=this.prevDirectPlugins){this.prevDirectPlugins=this.directPlugins,this.destroyPluginViews();for(let t=0;t0&&it.ownerDocument.getSelection()),this._root=t}return e||document}updateRoot(){this._root=null}posAtCoords(e){return rg(this,e)}coordsAtPos(e,t=1){return Ku(this,e,t)}domAtPos(e,t=0){return this.docView.domFromPos(e,t)}nodeDOM(e){let t=this.docView.descAt(e);return t?t.nodeDOM:null}posAtDOM(e,t,r=-1){let s=this.docView.posFromDOM(e,t,r);if(s==null)throw new RangeError("DOM position not inside the editor");return s}endOfTextblock(e,t){return ag(this,t||this.state,e)}pasteHTML(e,t){return Xn(this,"",e,!1,t||new ClipboardEvent("paste"))}pasteText(e,t){return Xn(this,e,null,!0,t||new ClipboardEvent("paste"))}serializeForClipboard(e){return Yo(this,e)}destroy(){this.docView&&(Dg(this),this.destroyPluginViews(),this.mounted?(this.docView.update(this.state.doc,[],Eo(this),this),this.dom.textContent=""):this.dom.parentNode&&this.dom.parentNode.removeChild(this.dom),this.docView.destroy(),this.docView=null,Hm())}get isDestroyed(){return this.docView==null}dispatchEvent(e){return Lg(this,e)}domSelectionRange(){let e=this.domSelection();return e?fe&&this.root.nodeType===11&&Jm(this.dom.ownerDocument)==this.dom&&ny(this,e)||e:{focusNode:null,focusOffset:0,anchorNode:null,anchorOffset:0}}domSelection(){return this.root.getSelection()}};Yn.prototype.dispatch=function(n){let e=this._props.dispatchTransaction;e?e.call(this,n):this.updateState(this.state.apply(n))};function Pu(n){let e=Object.create(null);return e.class="ProseMirror",e.contenteditable=String(n.editable),n.someProp("attributes",t=>{if(typeof t=="function"&&(t=t(n.state)),t)for(let r in t)r=="class"?e.class+=" "+t[r]:r=="style"?e.style=(e.style?e.style+";":"")+t[r]:!e[r]&&r!="contenteditable"&&r!="nodeName"&&(e[r]=String(t[r]))}),e.translate||(e.translate="no"),[Z.node(0,n.state.doc.content.size,e)]}function Lu(n){if(n.markCursor){let e=document.createElement("img");e.className="ProseMirror-separator",e.setAttribute("mark-placeholder","true"),e.setAttribute("alt",""),n.cursorWrapper={dom:e,deco:Z.widget(n.state.selection.from,e,{raw:!0,marks:n.markCursor})}}else n.cursorWrapper=null}function zu(n){return!n.someProp("editable",e=>e(n.state)===!1)}function hy(n,e){let t=Math.min(n.$anchor.sharedDepth(n.head),e.$anchor.sharedDepth(e.head));return n.$anchor.start(t)!=e.$anchor.start(t)}function Bu(n){let e=Object.create(null);function t(r){for(let s in r)Object.prototype.hasOwnProperty.call(e,s)||(e[s]=r[s])}return n.someProp("nodeViews",t),n.someProp("markViews",t),e}function fy(n,e){let t=0,r=0;for(let s in n){if(n[s]!=e[s])return!0;t++}for(let s in e)r++;return t!=r}function $u(n){if(n.spec.state||n.spec.filterTransaction||n.spec.appendTransaction)throw new RangeError("Plugins passed directly to the view must not have a state component")}var dt={8:"Backspace",9:"Tab",10:"Enter",12:"NumLock",13:"Enter",16:"Shift",17:"Control",18:"Alt",20:"CapsLock",27:"Escape",32:" ",33:"PageUp",34:"PageDown",35:"End",36:"Home",37:"ArrowLeft",38:"ArrowUp",39:"ArrowRight",40:"ArrowDown",44:"PrintScreen",45:"Insert",46:"Delete",59:";",61:"=",91:"Meta",92:"Meta",106:"*",107:"+",108:",",109:"-",110:".",111:"/",144:"NumLock",145:"ScrollLock",160:"Shift",161:"Shift",162:"Control",163:"Control",164:"Alt",165:"Alt",173:"-",186:";",187:"=",188:",",189:"-",190:".",191:"/",192:"`",219:"[",220:"\\",221:"]",222:"'"},us={48:")",49:"!",50:"@",51:"#",52:"$",53:"%",54:"^",55:"&",56:"*",57:"(",59:":",61:"+",173:"_",186:":",187:"+",188:"<",189:"_",190:">",191:"?",192:"~",219:"{",220:"|",221:"}",222:'"'},py=typeof navigator<"u"&&/Mac/.test(navigator.platform),my=typeof navigator<"u"&&/MSIE \d|Trident\/(?:[7-9]|\d{2,})\..*rv:(\d+)/.exec(navigator.userAgent);for(ee=0;ee<10;ee++)dt[48+ee]=dt[96+ee]=String(ee);var ee;for(ee=1;ee<=24;ee++)dt[ee+111]="F"+ee;var ee;for(ee=65;ee<=90;ee++)dt[ee]=String.fromCharCode(ee+32),us[ee]=String.fromCharCode(ee);var ee;for(cs in dt)us.hasOwnProperty(cs)||(us[cs]=dt[cs]);var cs;function yd(n){var e=py&&n.metaKey&&n.shiftKey&&!n.ctrlKey&&!n.altKey||my&&n.shiftKey&&n.key&&n.key.length==1||n.key=="Unidentified",t=!e&&n.key||(n.shiftKey?us:dt)[n.keyCode]||n.key||"Unidentified";return t=="Esc"&&(t="Escape"),t=="Del"&&(t="Delete"),t=="Left"&&(t="ArrowLeft"),t=="Up"&&(t="ArrowUp"),t=="Right"&&(t="ArrowRight"),t=="Down"&&(t="ArrowDown"),t}var gy=typeof navigator<"u"&&/Mac|iP(hone|[oa]d)/.test(navigator.platform),yy=typeof navigator<"u"&&/Win/.test(navigator.platform);function ky(n){let e=n.split(/-(?!$)/),t=e[e.length-1];t=="Space"&&(t=" ");let r,s,i,o;for(let l=0;l{for(var t in e)xy(n,t,{get:e[t],enumerable:!0})};function ys(n){let{state:e,transaction:t}=n,{selection:r}=t,{doc:s}=t,{storedMarks:i}=t;return{...e,apply:e.apply.bind(e),applyTransaction:e.applyTransaction.bind(e),plugins:e.plugins,schema:e.schema,reconfigure:e.reconfigure.bind(e),toJSON:e.toJSON.bind(e),get storedMarks(){return i},get selection(){return r},get doc(){return s},get tr(){return r=t.selection,s=t.doc,i=t.storedMarks,t}}}var ks=class{constructor(n){this.editor=n.editor,this.rawCommands=this.editor.extensionManager.commands,this.customState=n.state}get hasCustomState(){return!!this.customState}get state(){return this.customState||this.editor.state}get commands(){let{rawCommands:n,editor:e,state:t}=this,{view:r}=e,{tr:s}=t,i=this.buildProps(s);return Object.fromEntries(Object.entries(n).map(([o,l])=>[o,(...c)=>{let u=l(...c)(i);return!s.getMeta("preventDispatch")&&!this.hasCustomState&&r.dispatch(s),u}]))}get chain(){return()=>this.createChain()}get can(){return()=>this.createCan()}createChain(n,e=!0){let{rawCommands:t,editor:r,state:s}=this,{view:i}=r,o=[],l=!!n,a=n||s.tr,c=()=>(!l&&e&&!a.getMeta("preventDispatch")&&!this.hasCustomState&&i.dispatch(a),o.every(d=>d===!0)),u={...Object.fromEntries(Object.entries(t).map(([d,h])=>[d,(...p)=>{let m=this.buildProps(a,e),g=h(...p)(m);return o.push(g),u}])),run:c};return u}createCan(n){let{rawCommands:e,state:t}=this,r=!1,s=n||t.tr,i=this.buildProps(s,r);return{...Object.fromEntries(Object.entries(e).map(([l,a])=>[l,(...c)=>a(...c)({...i,dispatch:void 0})])),chain:()=>this.createChain(s,r)}}buildProps(n,e=!0){let{rawCommands:t,editor:r,state:s}=this,{view:i}=r,o={tr:n,editor:r,view:i,state:ys({state:s,transaction:n}),dispatch:e?()=>{}:void 0,chain:()=>this.createChain(n,e),can:()=>this.createCan(n),get commands(){return Object.fromEntries(Object.entries(t).map(([l,a])=>[l,(...c)=>a(...c)(o)]))}};return o}},Le={};cl(Le,{blur:()=>wy,clearContent:()=>Sy,clearNodes:()=>Cy,command:()=>vy,createParagraphNear:()=>Ty,cut:()=>My,deleteCurrentNode:()=>Ay,deleteNode:()=>Ey,deleteRange:()=>Ny,deleteSelection:()=>Iy,enter:()=>Dy,exitCode:()=>Py,extendMarkRange:()=>Ly,first:()=>zy,focus:()=>$y,forEach:()=>Fy,insertContent:()=>Hy,insertContentAt:()=>Wy,joinBackward:()=>Ky,joinDown:()=>Jy,joinForward:()=>qy,joinItemBackward:()=>Uy,joinItemForward:()=>Gy,joinTextblockBackward:()=>Xy,joinTextblockForward:()=>Qy,joinUp:()=>jy,keyboardShortcut:()=>Zy,lift:()=>e0,liftEmptyBlock:()=>t0,liftListItem:()=>n0,newlineInCode:()=>r0,resetAttributes:()=>s0,scrollIntoView:()=>i0,selectAll:()=>o0,selectNodeBackward:()=>l0,selectNodeForward:()=>a0,selectParentNode:()=>c0,selectTextblockEnd:()=>u0,selectTextblockStart:()=>d0,setContent:()=>h0,setMark:()=>A0,setMeta:()=>E0,setNode:()=>N0,setNodeSelection:()=>R0,setTextDirection:()=>O0,setTextSelection:()=>I0,sinkListItem:()=>D0,splitBlock:()=>P0,splitListItem:()=>L0,toggleList:()=>B0,toggleMark:()=>$0,toggleNode:()=>F0,toggleWrap:()=>H0,undoInputRule:()=>_0,unsetAllMarks:()=>V0,unsetMark:()=>W0,unsetTextDirection:()=>j0,updateAttributes:()=>J0,wrapIn:()=>K0,wrapInList:()=>q0});var wy=()=>({editor:n,view:e})=>(requestAnimationFrame(()=>{var t;n.isDestroyed||(e.dom.blur(),(t=window?.getSelection())==null||t.removeAllRanges())}),!0),Sy=(n=!0)=>({commands:e})=>e.setContent("",{emitUpdate:n}),Cy=()=>({state:n,tr:e,dispatch:t})=>{let{selection:r}=e,{ranges:s}=r;return t&&s.forEach(({$from:i,$to:o})=>{n.doc.nodesBetween(i.pos,o.pos,(l,a)=>{if(l.type.isText)return;let{doc:c,mapping:u}=e,d=c.resolve(u.map(a)),h=c.resolve(u.map(a+l.nodeSize)),f=d.blockRange(h);if(!f)return;let p=it(f);if(l.type.isTextblock){let{defaultType:m}=d.parent.contentMatchAt(d.index());e.setNodeMarkup(f.start,m)}(p||p===0)&&e.lift(f,p)})}),!0},vy=n=>e=>n(e),Ty=()=>({state:n,dispatch:e})=>bo(n,e),My=(n,e)=>({editor:t,tr:r})=>{let{state:s}=t,i=s.doc.slice(n.from,n.to);r.deleteRange(n.from,n.to);let o=r.mapping.map(e);return r.insert(o,i.content),r.setSelection(new A(r.doc.resolve(Math.max(o-1,0)))),!0},Ay=()=>({tr:n,dispatch:e})=>{let{selection:t}=n,r=t.$anchor.node();if(r.content.size>0)return!1;let s=n.selection.$anchor;for(let i=s.depth;i>0;i-=1)if(s.node(i).type===r.type){if(e){let l=s.before(i),a=s.after(i);n.delete(l,a).scrollIntoView()}return!0}return!1};function U(n,e){if(typeof n=="string"){if(!e.nodes[n])throw Error(`There is no node type named '${n}'. Maybe you forgot to add the extension?`);return e.nodes[n]}return n}var Ey=n=>({tr:e,state:t,dispatch:r})=>{let s=U(n,t.schema),i=e.selection.$anchor;for(let o=i.depth;o>0;o-=1)if(i.node(o).type===s){if(r){let a=i.before(o),c=i.after(o);e.delete(a,c).scrollIntoView()}return!0}return!1},Ny=n=>({tr:e,dispatch:t})=>{let{from:r,to:s}=n;return t&&e.delete(r,s),!0},Ry=n=>n.content?/^text(\*|\+)/.test(n.content):!1,bd=(n,e,t)=>{if(!n.parent.isInline||t==="left"&&n.pos>n.start()||t==="right"&&n.pos{let r=bd(n,t,"left"),s=bd(e,t,"right");return{from:r,to:s}},Iy=()=>({state:n,dispatch:e})=>{let{$from:t,$to:r}=n.selection;if(n.selection.empty)return!1;let{from:s,to:i}=Oy(t,r,n.schema);return e&&(n.tr.deleteRange(s,i).scrollIntoView(),e(n.tr)),!0},Dy=()=>({commands:n})=>n.keyboardShortcut("Enter"),Py=()=>({state:n,dispatch:e})=>ko(n,e);function ul(n){return Object.prototype.toString.call(n)==="[object RegExp]"}function ms(n,e,t={strict:!0}){let r=Object.keys(e);return r.length?r.every(s=>t.strict?e[s]===n[s]:ul(e[s])?e[s].test(n[s]):e[s]===n[s]):!0}function Nd(n,e,t={}){return n.find(r=>r.type===e&&ms(Object.fromEntries(Object.keys(t).map(s=>[s,r.attrs[s]])),t))}function xd(n,e,t={}){return!!Nd(n,e,t)}function dl(n,e,t){if(!n||!e)return;let r=n.parent.childAfter(n.parentOffset);if((!r.node||!r.node.marks.some(c=>c.type===e))&&(r=n.parent.childBefore(n.parentOffset)),!r.node||!r.node.marks.some(c=>c.type===e))return;if(!t){let c=r.node.marks.find(u=>u.type===e);c&&(t=c.attrs)}if(!Nd([...r.node.marks],e,t))return;let i=r.index,o=n.start()+r.offset,l=i+1,a=o+r.node.nodeSize;for(;i>0&&xd([...n.parent.child(i-1).marks],e,t);)i-=1,o-=n.parent.child(i).nodeSize;for(;l({tr:t,state:r,dispatch:s})=>{let i=ht(n,r.schema),{doc:o,selection:l}=t,{$from:a,from:c,to:u}=l;if(s){let d=dl(a,i,e);if(d&&d.from<=c&&d.to>=u){let h=A.create(o,d.from,d.to);t.setSelection(h)}}return!0},zy=n=>e=>{let t=typeof n=="function"?n(e):n;for(let r=0;r({editor:t,view:r,tr:s,dispatch:i})=>{e={scrollIntoView:!0,...e};let o=()=>{(gs()||wd())&&r.dom.focus(),By()&&!gs()&&!wd()&&r.dom.focus({preventScroll:!0}),requestAnimationFrame(()=>{t.isDestroyed||(r.focus(),e?.scrollIntoView&&t.commands.scrollIntoView())})};try{if(r.hasFocus()&&n===null||n===!1)return!0}catch{return!1}if(i&&n===null&&!Rd(t.state.selection))return o(),!0;let l=Od(s.doc,n)||t.state.selection,a=t.state.selection.eq(l);return i&&(a||s.setSelection(l),a&&s.storedMarks&&s.setStoredMarks(s.storedMarks),o()),!0},Fy=(n,e)=>t=>n.every((r,s)=>e(r,{...t,index:s})),Hy=(n,e)=>({tr:t,commands:r})=>r.insertContentAt({from:t.selection.from,to:t.selection.to},n,e),Id=n=>{let e=n.childNodes;for(let t=e.length-1;t>=0;t-=1){let r=e[t];r.nodeType===3&&r.nodeValue&&/^(\n\s\s|\n)$/.test(r.nodeValue)?n.removeChild(r):r.nodeType===1&&Id(r)}return n};function ir(n){if(typeof window>"u")throw new Error("[tiptap error]: there is no window object available, so this function cannot be used");let e=`${n}`,t=new window.DOMParser().parseFromString(e,"text/html").body;return Id(t)}function lr(n,e,t){if(n instanceof ve||n instanceof b)return n;t={slice:!0,parseOptions:{},...t};let r=typeof n=="object"&&n!==null,s=typeof n=="string";if(r)try{if(Array.isArray(n)&&n.length>0)return b.fromArray(n.map(l=>e.nodeFromJSON(l)));let o=e.nodeFromJSON(n);return t.errorOnInvalidContent&&o.check(),o}catch(i){if(t.errorOnInvalidContent)throw new Error("[tiptap error]: Invalid JSON content",{cause:i});return console.warn("[tiptap warn]: Invalid content.","Passed value:",n,"Error:",i),lr("",e,t)}if(s){if(t.errorOnInvalidContent){let o=!1,l="",a=new cn({topNode:e.spec.topNode,marks:e.spec.marks,nodes:e.spec.nodes.append({__tiptap__private__unknown__catch__all__node:{content:"inline*",group:"block",parseDOM:[{tag:"*",getAttrs:c=>(o=!0,l=typeof c=="string"?c:c.outerHTML,null)}]}})});if(t.slice?Fe.fromSchema(a).parseSlice(ir(n),t.parseOptions):Fe.fromSchema(a).parse(ir(n),t.parseOptions),t.errorOnInvalidContent&&o)throw new Error("[tiptap error]: Invalid HTML content",{cause:new Error(`Invalid element found: ${l}`)})}let i=Fe.fromSchema(e);return t.slice?i.parseSlice(ir(n),t.parseOptions).content:i.parse(ir(n),t.parseOptions)}return lr("",e,t)}function _y(n,e,t){let r=n.steps.length-1;if(r{o===0&&(o=u)}),n.setSelection(O.near(n.doc.resolve(o),t))}var Vy=n=>!("type"in n),Wy=(n,e,t)=>({tr:r,dispatch:s,editor:i})=>{var o;if(s){t={parseOptions:i.options.parseOptions,updateSelection:!0,applyInputRules:!1,applyPasteRules:!1,...t};let l,a=g=>{i.emit("contentError",{editor:i,error:g,disableCollaboration:()=>{"collaboration"in i.storage&&typeof i.storage.collaboration=="object"&&i.storage.collaboration&&(i.storage.collaboration.isDisabled=!0)}})},c={preserveWhitespace:"full",...t.parseOptions};if(!t.errorOnInvalidContent&&!i.options.enableContentCheck&&i.options.emitContentError)try{lr(e,i.schema,{parseOptions:c,errorOnInvalidContent:!0})}catch(g){a(g)}try{l=lr(e,i.schema,{parseOptions:c,errorOnInvalidContent:(o=t.errorOnInvalidContent)!=null?o:i.options.enableContentCheck})}catch(g){return a(g),!1}let{from:u,to:d}=typeof n=="number"?{from:n,to:n}:{from:n.from,to:n.to},h=!0,f=!0;if((Vy(l)?l:[l]).forEach(g=>{g.check(),h=h?g.isText&&g.marks.length===0:!1,f=f?g.isBlock:!1}),u===d&&f){let{parent:g}=r.doc.resolve(u);g.isTextblock&&!g.type.spec.code&&!g.childCount&&(u-=1,d+=1)}let m;if(h){if(Array.isArray(e))m=e.map(g=>g.text||"").join("");else if(e instanceof b){let g="";e.forEach(y=>{y.text&&(g+=y.text)}),m=g}else typeof e=="object"&&e&&e.text?m=e.text:m=e;r.insertText(m,u,d)}else{m=l;let g=r.doc.resolve(u),y=g.node(),k=g.parentOffset===0,w=y.isText||y.isTextblock,C=y.content.size>0;k&&w&&C&&f&&(u=Math.max(0,u-1)),r.replaceWith(u,d,m)}t.updateSelection&&_y(r,r.steps.length-1,-1),t.applyInputRules&&r.setMeta("applyInputRules",{from:u,text:m}),t.applyPasteRules&&r.setMeta("applyPasteRules",{from:u,text:m})}return!0},jy=()=>({state:n,dispatch:e})=>Qc(n,e),Jy=()=>({state:n,dispatch:e})=>Yc(n,e),Ky=()=>({state:n,dispatch:e})=>co(n,e),qy=()=>({state:n,dispatch:e})=>fo(n,e),Uy=()=>({state:n,dispatch:e,tr:t})=>{try{let r=Pt(n.doc,n.selection.$from.pos,-1);return r==null?!1:(t.join(r,2),e&&e(t),!0)}catch{return!1}},Gy=()=>({state:n,dispatch:e,tr:t})=>{try{let r=Pt(n.doc,n.selection.$from.pos,1);return r==null?!1:(t.join(r,2),e&&e(t),!0)}catch{return!1}},Xy=()=>({state:n,dispatch:e})=>qc(n,e),Qy=()=>({state:n,dispatch:e})=>Uc(n,e);function Dd(){return typeof navigator<"u"?/Mac/.test(navigator.platform):!1}function Yy(n){let e=n.split(/-(?!$)/),t=e[e.length-1];t==="Space"&&(t=" ");let r,s,i,o;for(let l=0;l({editor:e,view:t,tr:r,dispatch:s})=>{let i=Yy(n).split(/-(?!$)/),o=i.find(c=>!["Alt","Ctrl","Meta","Shift"].includes(c)),l=new KeyboardEvent("keydown",{key:o==="Space"?" ":o,altKey:i.includes("Alt"),ctrlKey:i.includes("Ctrl"),metaKey:i.includes("Meta"),shiftKey:i.includes("Shift"),bubbles:!0,cancelable:!0}),a=e.captureTransaction(()=>{t.someProp("handleKeyDown",c=>c(t,l))});return a?.steps.forEach(c=>{let u=c.map(r.mapping);u&&s&&r.maybeStep(u)}),!0};function Ke(n,e,t={}){let{from:r,to:s,empty:i}=n.selection,o=e?U(e,n.schema):null,l=[];n.doc.nodesBetween(r,s,(d,h)=>{if(d.isText)return;let f=Math.max(r,h),p=Math.min(s,h+d.nodeSize);l.push({node:d,from:f,to:p})});let a=s-r,c=l.filter(d=>o?o.name===d.node.type.name:!0).filter(d=>ms(d.node.attrs,t,{strict:!1}));return i?!!c.length:c.reduce((d,h)=>d+h.to-h.from,0)>=a}var e0=(n,e={})=>({state:t,dispatch:r})=>{let s=U(n,t.schema);return Ke(t,s,e)?Zc(t,r):!1},t0=()=>({state:n,dispatch:e})=>xo(n,e),n0=n=>({state:e,dispatch:t})=>{let r=U(n,e.schema);return iu(r)(e,t)},r0=()=>({state:n,dispatch:e})=>go(n,e);function bs(n,e){return e.nodes[n]?"node":e.marks[n]?"mark":null}function Sd(n,e){let t=typeof e=="string"?[e]:e;return Object.keys(n).reduce((r,s)=>(t.includes(s)||(r[s]=n[s]),r),{})}var s0=(n,e)=>({tr:t,state:r,dispatch:s})=>{let i=null,o=null,l=bs(typeof n=="string"?n:n.name,r.schema);if(!l)return!1;l==="node"&&(i=U(n,r.schema)),l==="mark"&&(o=ht(n,r.schema));let a=!1;return t.selection.ranges.forEach(c=>{r.doc.nodesBetween(c.$from.pos,c.$to.pos,(u,d)=>{i&&i===u.type&&(a=!0,s&&t.setNodeMarkup(d,void 0,Sd(u.attrs,e))),o&&u.marks.length&&u.marks.forEach(h=>{o===h.type&&(a=!0,s&&t.addMark(d,d+u.nodeSize,o.create(Sd(h.attrs,e))))})})}),a},i0=()=>({tr:n,dispatch:e})=>(e&&n.scrollIntoView(),!0),o0=()=>({tr:n,dispatch:e})=>{if(e){let t=new ke(n.doc);n.setSelection(t)}return!0},l0=()=>({state:n,dispatch:e})=>uo(n,e),a0=()=>({state:n,dispatch:e})=>po(n,e),c0=()=>({state:n,dispatch:e})=>eu(n,e),u0=()=>({state:n,dispatch:e})=>So(n,e),d0=()=>({state:n,dispatch:e})=>wo(n,e);function ll(n,e,t={},r={}){return lr(n,e,{slice:!1,parseOptions:t,errorOnInvalidContent:r.errorOnInvalidContent})}var h0=(n,{errorOnInvalidContent:e,emitUpdate:t=!0,parseOptions:r={}}={})=>({editor:s,tr:i,dispatch:o,commands:l})=>{let{doc:a}=i;if(r.preserveWhitespace!=="full"){let c=ll(n,s.schema,r,{errorOnInvalidContent:e??s.options.enableContentCheck});return o&&i.replaceWith(0,a.content.size,c).setMeta("preventUpdate",!t),!0}return o&&i.setMeta("preventUpdate",!t),l.insertContentAt({from:0,to:a.content.size},n,{parseOptions:r,errorOnInvalidContent:e??s.options.enableContentCheck})};function Pd(n,e){let t=ht(e,n.schema),{from:r,to:s,empty:i}=n.selection,o=[];i?(n.storedMarks&&o.push(...n.storedMarks),o.push(...n.selection.$head.marks())):n.doc.nodesBetween(r,s,a=>{o.push(...a.marks)});let l=o.find(a=>a.type.name===t.name);return l?{...l.attrs}:{}}function hl(n,e){let t=new kt(n);return e.forEach(r=>{r.steps.forEach(s=>{t.step(s)})}),t}function f0(n){for(let e=0;e{t(s)&&r.push({node:s,pos:i})}),r}function fl(n,e){for(let t=n.depth;t>0;t-=1){let r=n.node(t);if(e(r))return{pos:t>0?n.before(t):0,start:n.start(t),depth:t,node:r}}}function xs(n){return e=>fl(e.$from,n)}function E(n,e,t){return n.config[e]===void 0&&n.parent?E(n.parent,e,t):typeof n.config[e]=="function"?n.config[e].bind({...t,parent:n.parent?E(n.parent,e,t):null}):n.config[e]}function ar(n){return n.map(e=>{let t={name:e.name,options:e.options,storage:e.storage},r=E(e,"addExtensions",t);return r?[e,...ar(r())]:e}).flat(10)}function pl(n,e){let t=nt.fromSchema(e).serializeFragment(n),s=document.implementation.createHTMLDocument().createElement("div");return s.appendChild(t),s.innerHTML}function zd(n){return typeof n=="function"}function F(n,e=void 0,...t){return zd(n)?e?n.bind(e)(...t):n(...t):n}function p0(n={}){return Object.keys(n).length===0&&n.constructor===Object}function Sn(n){let e=n.filter(s=>s.type==="extension"),t=n.filter(s=>s.type==="node"),r=n.filter(s=>s.type==="mark");return{baseExtensions:e,nodeExtensions:t,markExtensions:r}}function Bd(n){let e=[],{nodeExtensions:t,markExtensions:r}=Sn(n),s=[...t,...r],i={default:null,validate:void 0,rendered:!0,renderHTML:null,parseHTML:null,keepOnSplit:!0,isRequired:!1},o=t.filter(c=>c.name!=="text").map(c=>c.name),l=r.map(c=>c.name),a=[...o,...l];return n.forEach(c=>{let u={name:c.name,options:c.options,storage:c.storage,extensions:s},d=E(c,"addGlobalAttributes",u);if(!d)return;d().forEach(f=>{let p;Array.isArray(f.types)?p=f.types:f.types==="*"?p=a:f.types==="nodes"?p=o:f.types==="marks"?p=l:p=[],p.forEach(m=>{Object.entries(f.attributes).forEach(([g,y])=>{e.push({type:m,name:g,attribute:{...i,...y}})})})})}),s.forEach(c=>{let u={name:c.name,options:c.options,storage:c.storage},d=E(c,"addAttributes",u);if(!d)return;let h=d();Object.entries(h).forEach(([f,p])=>{let m={...i,...p};typeof m?.default=="function"&&(m.default=m.default()),m?.isRequired&&m?.default===void 0&&delete m.default,e.push({type:c.name,name:f,attribute:m})})}),e}function m0(n){let e=[],t="",r=!1,s=!1,i=0,o=n.length;for(let l=0;l0){i-=1,t+=a;continue}if(a===";"&&i===0){e.push(t),t="";continue}}t+=a}return t&&e.push(t),e}function Cd(n){let e=[],t=m0(n||""),r=t.length;for(let s=0;s!!e).reduce((e,t)=>{let r={...e};return Object.entries(t).forEach(([s,i])=>{if(!r[s]){r[s]=i;return}if(s==="class"){let l=i?String(i).split(" "):[],a=r[s]?r[s].split(" "):[],c=l.filter(u=>!a.includes(u));r[s]=[...a,...c].join(" ")}else if(s==="style"){let l=new Map([...Cd(r[s]),...Cd(i)]);r[s]=Array.from(l.entries()).map(([a,c])=>`${a}: ${c}`).join("; ")}else r[s]=i}),r},{})}function Cn(n,e){return e.filter(t=>t.type===n.type.name).filter(t=>t.attribute.rendered).map(t=>t.attribute.renderHTML?t.attribute.renderHTML(n.attrs)||{}:{[t.name]:n.attrs[t.name]}).reduce((t,r)=>D(t,r),{})}function g0(n){return typeof n!="string"?n:n.match(/^[+-]?(?:\d*\.)?\d+$/)?Number(n):n==="true"?!0:n==="false"?!1:n}function vd(n,e){return"style"in n?n:{...n,getAttrs:t=>{let r=n.getAttrs?n.getAttrs(t):n.attrs;if(r===!1)return!1;let s=e.reduce((i,o)=>{let l=o.attribute.parseHTML?o.attribute.parseHTML(t):g0(t.getAttribute(o.name));return l==null?i:{...i,[o.name]:l}},{});return{...r,...s}}}}function Td(n){return Object.fromEntries(Object.entries(n).filter(([e,t])=>e==="attrs"&&p0(t)?!1:t!=null))}function Md(n){var e,t;let r={};return!((e=n?.attribute)!=null&&e.isRequired)&&"default"in(n?.attribute||{})&&(r.default=n.attribute.default),((t=n?.attribute)==null?void 0:t.validate)!==void 0&&(r.validate=n.attribute.validate),[n.name,r]}function $d(n,e){var t;let r=Bd(n),{nodeExtensions:s,markExtensions:i}=Sn(n),o=(t=s.find(c=>E(c,"topNode")))==null?void 0:t.name,l=Object.fromEntries(s.map(c=>{let u=r.filter(y=>y.type===c.name),d={name:c.name,options:c.options,storage:c.storage,editor:e},h=n.reduce((y,k)=>{let w=E(k,"extendNodeSchema",d);return{...y,...w?w(c):{}}},{}),f=Td({...h,content:F(E(c,"content",d)),marks:F(E(c,"marks",d)),group:F(E(c,"group",d)),inline:F(E(c,"inline",d)),atom:F(E(c,"atom",d)),selectable:F(E(c,"selectable",d)),draggable:F(E(c,"draggable",d)),code:F(E(c,"code",d)),whitespace:F(E(c,"whitespace",d)),linebreakReplacement:F(E(c,"linebreakReplacement",d)),defining:F(E(c,"defining",d)),isolating:F(E(c,"isolating",d)),attrs:Object.fromEntries(u.map(Md))}),p=F(E(c,"parseHTML",d));p&&(f.parseDOM=p.map(y=>vd(y,u)));let m=E(c,"renderHTML",d);m&&(f.toDOM=y=>m({node:y,HTMLAttributes:Cn(y,u)}));let g=E(c,"renderText",d);return g&&(f.toText=g),[c.name,f]})),a=Object.fromEntries(i.map(c=>{let u=r.filter(g=>g.type===c.name),d={name:c.name,options:c.options,storage:c.storage,editor:e},h=n.reduce((g,y)=>{let k=E(y,"extendMarkSchema",d);return{...g,...k?k(c):{}}},{}),f=Td({...h,inclusive:F(E(c,"inclusive",d)),excludes:F(E(c,"excludes",d)),group:F(E(c,"group",d)),spanning:F(E(c,"spanning",d)),code:F(E(c,"code",d)),attrs:Object.fromEntries(u.map(Md))}),p=F(E(c,"parseHTML",d));p&&(f.parseDOM=p.map(g=>vd(g,u)));let m=E(c,"renderHTML",d);return m&&(f.toDOM=g=>m({mark:g,HTMLAttributes:Cn(g,u)})),[c.name,f]}));return new cn({topNode:o,nodes:l,marks:a})}function y0(n){let e=n.filter((t,r)=>n.indexOf(t)!==r);return Array.from(new Set(e))}function Jt(n){return n.sort((t,r)=>{let s=E(t,"priority")||100,i=E(r,"priority")||100;return s>i?-1:sr.name));return t.length&&console.warn(`[tiptap warn]: Duplicate extension names found: [${t.map(r=>`'${r}'`).join(", ")}]. This can lead to issues.`),e}function gl(n,e){let t=ml(n);return $d(t,e)}function Fd(n,e){let t=gl(e),r=ir(n);return Fe.fromSchema(t).parse(r).toJSON()}function Hd(n,e,t){let{from:r,to:s}=e,{blockSeparator:i=` + +`,textSerializers:o={}}=t||{},l="";return n.nodesBetween(r,s,(a,c,u,d)=>{var h;a.isBlock&&c>r&&(l+=i);let f=o?.[a.type.name];if(f)return u&&(l+=f({node:a,pos:c,parent:u,index:d,range:e})),!1;a.isText&&(l+=(h=a?.text)==null?void 0:h.slice(Math.max(r,c)-c,s-c))}),l}function k0(n,e){let t={from:0,to:n.content.size};return Hd(n,t,e)}function _d(n){return Object.fromEntries(Object.entries(n.nodes).filter(([,e])=>e.spec.toText).map(([e,t])=>[e,t.spec.toText]))}function b0(n,e){let t=U(e,n.schema),{from:r,to:s}=n.selection,i=[];n.doc.nodesBetween(r,s,l=>{i.push(l)});let o=i.reverse().find(l=>l.type.name===t.name);return o?{...o.attrs}:{}}function yl(n,e){let t=bs(typeof e=="string"?e:e.name,n.schema);return t==="node"?b0(n,e):t==="mark"?Pd(n,e):{}}function x0(n,e=JSON.stringify){let t={};return n.filter(r=>{let s=e(r);return Object.prototype.hasOwnProperty.call(t,s)?!1:t[s]=!0})}function w0(n){let e=x0(n);return e.length===1?e:e.filter((t,r)=>!e.filter((i,o)=>o!==r).some(i=>t.oldRange.from>=i.oldRange.from&&t.oldRange.to<=i.oldRange.to&&t.newRange.from>=i.newRange.from&&t.newRange.to<=i.newRange.to))}function kl(n){let{mapping:e,steps:t}=n,r=[];return e.maps.forEach((s,i)=>{let o=[];if(s.ranges.length)s.forEach((l,a)=>{o.push({from:l,to:a})});else{let{from:l,to:a}=t[i];if(l===void 0||a===void 0)return;o.push({from:l,to:a})}o.forEach(({from:l,to:a})=>{let c=e.slice(i).map(l,-1),u=e.slice(i).map(a),d=e.invert().map(c,-1),h=e.invert().map(u);r.push({oldRange:{from:d,to:h},newRange:{from:c,to:u}})})}),w0(r)}function ws(n,e,t){let r=[];return n===e?t.resolve(n).marks().forEach(s=>{let i=t.resolve(n),o=dl(i,s.type);o&&r.push({mark:s,...o})}):t.nodesBetween(n,e,(s,i)=>{!s||s?.nodeSize===void 0||r.push(...s.marks.map(o=>({from:i,to:i+s.nodeSize,mark:o})))}),r}var Vd=(n,e,t,r=20)=>{let s=n.doc.resolve(t),i=r,o=null;for(;i>0&&o===null;){let l=s.node(i);l?.type.name===e?o=l:i-=1}return[o,i]};function sr(n,e){return e.nodes[n]||e.marks[n]||null}function ps(n,e,t){return Object.fromEntries(Object.entries(t).filter(([r])=>{let s=n.find(i=>i.type===e&&i.name===r);return s?s.attribute.keepOnSplit:!1}))}var S0=(n,e=500)=>{let t="",r=n.parentOffset;return n.parent.nodesBetween(Math.max(0,r-e),r,(s,i,o,l)=>{var a,c;let u=((c=(a=s.type.spec).toText)==null?void 0:c.call(a,{node:s,pos:i,parent:o,index:l}))||s.textContent||"%leaf%";t+=s.isAtom&&!s.isText?u:u.slice(0,Math.max(0,r-i))}),t};function al(n,e,t={}){let{empty:r,ranges:s}=n.selection,i=e?ht(e,n.schema):null;if(r)return!!(n.storedMarks||n.selection.$from.marks()).filter(d=>i?i.name===d.type.name:!0).find(d=>ms(d.attrs,t,{strict:!1}));let o=0,l=[];if(s.forEach(({$from:d,$to:h})=>{let f=d.pos,p=h.pos;n.doc.nodesBetween(f,p,(m,g)=>{if(i&&m.inlineContent&&!m.type.allowsMarkType(i))return!1;if(!m.isText&&!m.marks.length)return;let y=Math.max(f,g),k=Math.min(p,g+m.nodeSize),w=k-y;o+=w,l.push(...m.marks.map(C=>({mark:C,from:y,to:k})))})}),o===0)return!1;let a=l.filter(d=>i?i.name===d.mark.type.name:!0).filter(d=>ms(d.mark.attrs,t,{strict:!1})).reduce((d,h)=>d+h.to-h.from,0),c=l.filter(d=>i?d.mark.type!==i&&d.mark.type.excludes(i):!0).reduce((d,h)=>d+h.to-h.from,0);return(a>0?a+c:a)>=o}function C0(n,e,t={}){if(!e)return Ke(n,null,t)||al(n,null,t);let r=bs(e,n.schema);return r==="node"?Ke(n,e,t):r==="mark"?al(n,e,t):!1}var Wd=(n,e)=>{let{$from:t,$to:r,$anchor:s}=n.selection;if(e){let i=xs(l=>l.type.name===e)(n.selection);if(!i)return!1;let o=n.doc.resolve(i.pos+1);return s.pos+1===o.end()}return!(r.parentOffset{let{$from:e,$to:t}=n.selection;return!(e.parentOffset>0||e.pos!==t.pos)};function Ad(n,e){return Array.isArray(e)?e.some(t=>(typeof t=="string"?t:t.name)===n.name):e}function sl(n,e){let{nodeExtensions:t}=Sn(e),r=t.find(o=>o.name===n);if(!r)return!1;let s={name:r.name,options:r.options,storage:r.storage},i=F(E(r,"group",s));return typeof i!="string"?!1:i.split(" ").includes("list")}function vn(n,{checkChildren:e=!0,ignoreWhitespace:t=!1}={}){var r;if(t){if(n.type.name==="hardBreak")return!0;if(n.isText)return!/\S/.test((r=n.text)!=null?r:"")}if(n.isText)return!n.text;if(n.isAtom||n.isLeaf)return!1;if(n.content.childCount===0)return!0;if(e){let s=!0;return n.content.forEach(i=>{s!==!1&&(vn(i,{ignoreWhitespace:t,checkChildren:e})||(s=!1))}),s}return!1}function Ss(n){return n instanceof N}var Jd=class Kd{constructor(e){this.position=e}static fromJSON(e){return new Kd(e.position)}toJSON(){return{position:this.position}}};function v0(n,e){let t=e.mapping.mapResult(n.position);return{position:new Jd(t.pos),mapResult:t}}function T0(n){return new Jd(n)}function M0(n,e,t){var r;let{selection:s}=e,i=null;if(Rd(s)&&(i=s.$cursor),i){let l=(r=n.storedMarks)!=null?r:i.marks();return i.parent.type.allowsMarkType(t)&&(!!t.isInSet(l)||!l.some(c=>c.type.excludes(t)))}let{ranges:o}=s;return o.some(({$from:l,$to:a})=>{let c=l.depth===0?n.doc.inlineContent&&n.doc.type.allowsMarkType(t):!1;return n.doc.nodesBetween(l.pos,a.pos,(u,d,h)=>{if(c)return!1;if(u.isInline){let f=!h||h.type.allowsMarkType(t),p=!!t.isInSet(u.marks)||!u.marks.some(m=>m.type.excludes(t));c=f&&p}return!c}),c})}var A0=(n,e={})=>({tr:t,state:r,dispatch:s})=>{let{selection:i}=t,{empty:o,ranges:l}=i,a=ht(n,r.schema);if(s)if(o){let c=Pd(r,a);t.addStoredMark(a.create({...c,...e}))}else l.forEach(c=>{let u=c.$from.pos,d=c.$to.pos;r.doc.nodesBetween(u,d,(h,f)=>{let p=Math.max(f,u),m=Math.min(f+h.nodeSize,d);h.marks.find(y=>y.type===a)?h.marks.forEach(y=>{a===y.type&&t.addMark(p,m,a.create({...y.attrs,...e}))}):t.addMark(p,m,a.create(e))})});return M0(r,t,a)},E0=(n,e)=>({tr:t})=>(t.setMeta(n,e),!0),N0=(n,e={})=>({state:t,dispatch:r,chain:s})=>{let i=U(n,t.schema),o;return t.selection.$anchor.sameParent(t.selection.$head)&&(o=t.selection.$anchor.parent.attrs),i.isTextblock?s().command(({commands:l})=>Co(i,{...o,...e})(t)?!0:l.clearNodes()).command(({state:l})=>Co(i,{...o,...e})(l,r)).run():(console.warn('[tiptap warn]: Currently "setNode()" only supports text block nodes.'),!1)},R0=n=>({tr:e,dispatch:t})=>{if(t){let{doc:r}=e,s=jt(n,0,r.content.size),i=N.create(r,s);e.setSelection(i)}return!0},O0=(n,e)=>({tr:t,state:r,dispatch:s})=>{let{selection:i}=r,o,l;return typeof e=="number"?(o=e,l=e):e&&"from"in e&&"to"in e?(o=e.from,l=e.to):(o=i.from,l=i.to),s&&t.doc.nodesBetween(o,l,(a,c)=>{a.isText||t.setNodeMarkup(c,void 0,{...a.attrs,dir:n})}),!0},I0=n=>({tr:e,dispatch:t})=>{if(t){let{doc:r}=e,{from:s,to:i}=typeof n=="number"?{from:n,to:n}:n,o=A.atStart(r).from,l=A.atEnd(r).to,a=jt(s,o,l),c=jt(i,o,l),u=A.create(r,a,c);e.setSelection(u)}return!0},D0=n=>({state:e,dispatch:t})=>{let r=U(n,e.schema);return ou(r)(e,t)};function Ed(n,e){let t=n.storedMarks||n.selection.$to.parentOffset&&n.selection.$from.marks();if(t){let r=t.filter(s=>e?.includes(s.type.name));n.tr.ensureMarks(r)}}var P0=({keepMarks:n=!0}={})=>({tr:e,state:t,dispatch:r,editor:s})=>{let{selection:i,doc:o}=e,{$from:l,$to:a}=i,c=s.extensionManager.attributes,u=ps(c,l.node().type.name,l.node().attrs);if(i instanceof N&&i.node.isBlock)return!l.parentOffset||!Ae(o,l.pos)?!1:(r&&(n&&Ed(t,s.extensionManager.splittableMarks),e.split(l.pos).scrollIntoView()),!0);if(!l.parent.isBlock)return!1;let d=a.parentOffset===a.parent.content.size,h=l.depth===0?void 0:f0(l.node(-1).contentMatchAt(l.indexAfter(-1))),f=d&&h?[{type:h,attrs:u}]:void 0,p=Ae(e.doc,e.mapping.map(l.pos),1,f);if(!f&&!p&&Ae(e.doc,e.mapping.map(l.pos),1,h?[{type:h}]:void 0)&&(p=!0,f=h?[{type:h,attrs:u}]:void 0),r){if(p&&(i instanceof A&&e.deleteSelection(),e.split(e.mapping.map(l.pos),1,f),h&&!d&&!l.parentOffset&&l.parent.type!==h)){let m=e.mapping.map(l.before()),g=e.doc.resolve(m);l.node(-1).canReplaceWith(g.index(),g.index()+1,h)&&e.setNodeMarkup(e.mapping.map(l.before()),h)}n&&Ed(t,s.extensionManager.splittableMarks),e.scrollIntoView()}return p},L0=(n,e={})=>({tr:t,state:r,dispatch:s,editor:i})=>{var o;let l=U(n,r.schema),{$from:a,$to:c}=r.selection,u=r.selection.node;if(u&&u.isBlock||a.depth<2||!a.sameParent(c))return!1;let d=a.node(-1);if(d.type!==l)return!1;let h=i.extensionManager.attributes;if(a.parent.content.size===0&&a.node(-1).childCount===a.indexAfter(-1)){if(a.depth===2||a.node(-3).type!==l||a.index(-2)!==a.node(-2).childCount-1)return!1;if(s){let y=b.empty,k=a.index(-1)?1:a.index(-2)?2:3;for(let R=a.depth-k;R>=a.depth-3;R-=1)y=b.from(a.node(R).copy(y));let w=a.indexAfter(-1){if(T>-1)return!1;R.isTextblock&&R.content.size===0&&(T=P+1)}),T>-1&&t.setSelection(A.near(t.doc.resolve(T))),t.scrollIntoView()}return!0}let f=c.pos===a.end()?d.contentMatchAt(0).defaultType:null,p={...ps(h,d.type.name,d.attrs),...e},m={...ps(h,a.node().type.name,a.node().attrs),...e};t.delete(a.pos,c.pos);let g=f?[{type:l,attrs:p},{type:f,attrs:m}]:[{type:l,attrs:p}];if(!Ae(t.doc,a.pos,2))return!1;if(s){let{selection:y,storedMarks:k}=r,{splittableMarks:w}=i.extensionManager,C=k||y.$to.parentOffset&&y.$from.marks();if(t.split(a.pos,2,g).scrollIntoView(),!C||!s)return!0;let x=C.filter(M=>w.includes(M.type.name));t.ensureMarks(x)}return!0},il=(n,e)=>{let t=xs(o=>o.type===e)(n.selection);if(!t)return!0;let r=n.doc.resolve(Math.max(0,t.pos-1)).before(t.depth);if(r===void 0)return!0;let s=n.doc.nodeAt(r);return t.node.type===s?.type&&Re(n.doc,t.pos)&&n.join(t.pos),!0},ol=(n,e)=>{let t=xs(o=>o.type===e)(n.selection);if(!t)return!0;let r=n.doc.resolve(t.start).after(t.depth);if(r===void 0)return!0;let s=n.doc.nodeAt(r);return t.node.type===s?.type&&Re(n.doc,r)&&n.join(r),!0};function z0(n){let e=n.doc,t=e.firstChild;if(!t)return null;let r=e.resolve(1),s=e.resolve(t.nodeSize-1);return A.between(r,s)}var B0=(n,e,t,r={})=>({editor:s,tr:i,state:o,dispatch:l,chain:a,commands:c,can:u})=>{let{extensions:d,splittableMarks:h}=s.extensionManager,f=U(n,o.schema),p=U(e,o.schema),{selection:m,storedMarks:g}=o,{$from:y,$to:k}=m,w=y.blockRange(k),C=g||m.$to.parentOffset&&m.$from.marks();if(!w)return!1;let x=xs(ue=>sl(ue.type.name,d))(m),M=m.from===0&&m.to===o.doc.content.size,T=o.doc.content.content,R=T.length===1?T[0]:null,P=M&&R&&sl(R.type.name,d)?{node:R,pos:0,depth:0}:null,te=x??P,tt=!!x&&w.depth>=1&&w.depth-x.depth<=1,We=!!P;if((tt||We)&&te){if(te.node.type===f)return M&&We?a().command(({tr:ue,dispatch:ne})=>{let Q=z0(ue);return Q?(ue.setSelection(Q),ne&&ne(ue),!0):!1}).liftListItem(p).run():c.liftListItem(p);if(sl(te.node.type.name,d)&&f.validContent(te.node.content))return a().command(()=>(i.setNodeMarkup(te.pos,f),!0)).command(()=>il(i,f)).command(()=>ol(i,f)).run()}return!t||!C||!l?a().command(()=>u().wrapInList(f,r)?!0:c.clearNodes()).wrapInList(f,r).command(()=>il(i,f)).command(()=>ol(i,f)).run():a().command(()=>{let ue=u().wrapInList(f,r),ne=C.filter(Q=>h.includes(Q.type.name));return i.ensureMarks(ne),ue?!0:c.clearNodes()}).wrapInList(f,r).command(()=>il(i,f)).command(()=>ol(i,f)).run()},$0=(n,e={},t={})=>({state:r,commands:s})=>{let{extendEmptyMarkRange:i=!1}=t,o=ht(n,r.schema);return al(r,o,e)?s.unsetMark(o,{extendEmptyMarkRange:i}):s.setMark(o,e)},F0=(n,e,t={})=>({state:r,commands:s})=>{let i=U(n,r.schema),o=U(e,r.schema),l=Ke(r,i,t),a;return r.selection.$anchor.sameParent(r.selection.$head)&&(a=r.selection.$anchor.parent.attrs),l?s.setNode(o,a):s.setNode(i,{...a,...t})},H0=(n,e={})=>({state:t,commands:r})=>{let s=U(n,t.schema);return Ke(t,s,e)?r.lift(s):r.wrapIn(s,e)},_0=()=>({state:n,dispatch:e})=>{let t=n.plugins;for(let r=0;r=0;a-=1)o.step(l.steps[a].invert(l.docs[a]));if(i.text){let a=o.doc.resolve(i.from).marks();o.replaceWith(i.from,i.to,n.schema.text(i.text,a))}else o.delete(i.from,i.to)}return!0}}return!1},V0=(n={})=>({tr:e,dispatch:t,editor:r})=>{let{ignoreClearable:s=!1}=n,{selection:i}=e,{empty:o,ranges:l}=i;if(o)return!0;let{nonClearableMarks:a}=r.extensionManager;if(t){let c=Object.values(r.schema.marks).filter(u=>s||!a.includes(u.name));l.forEach(u=>{for(let d of c)e.removeMark(u.$from.pos,u.$to.pos,d)})}return!0},W0=(n,e={})=>({tr:t,state:r,dispatch:s})=>{var i;let{extendEmptyMarkRange:o=!1}=e,{selection:l}=t,a=ht(n,r.schema),{$from:c,empty:u,ranges:d}=l;if(!s)return!0;if(u&&o){let{from:h,to:f}=l,p=(i=c.marks().find(g=>g.type===a))==null?void 0:i.attrs,m=dl(c,a,p);m&&(h=m.from,f=m.to),t.removeMark(h,f,a)}else d.forEach(h=>{t.removeMark(h.$from.pos,h.$to.pos,a)});return t.removeStoredMark(a),!0},j0=n=>({tr:e,state:t,dispatch:r})=>{let{selection:s}=t,i,o;return typeof n=="number"?(i=n,o=n):n&&"from"in n&&"to"in n?(i=n.from,o=n.to):(i=s.from,o=s.to),r&&e.doc.nodesBetween(i,o,(l,a)=>{if(l.isText)return;let c={...l.attrs};delete c.dir,e.setNodeMarkup(a,void 0,c)}),!0},J0=(n,e={})=>({tr:t,state:r,dispatch:s})=>{let i=null,o=null,l=bs(typeof n=="string"?n:n.name,r.schema);if(!l)return!1;l==="node"&&(i=U(n,r.schema)),l==="mark"&&(o=ht(n,r.schema));let a=!1;return t.selection.ranges.forEach(c=>{let u=c.$from.pos,d=c.$to.pos,h,f,p,m;t.selection.empty?r.doc.nodesBetween(u,d,(g,y)=>{i&&i===g.type&&(a=!0,p=Math.max(y,u),m=Math.min(y+g.nodeSize,d),h=y,f=g)}):r.doc.nodesBetween(u,d,(g,y)=>{y=u&&y<=d&&(i&&i===g.type&&(a=!0,s&&t.setNodeMarkup(y,void 0,{...g.attrs,...e})),o&&g.marks.length&&g.marks.forEach(k=>{if(o===k.type&&(a=!0,s)){let w=Math.max(y,u),C=Math.min(y+g.nodeSize,d);t.addMark(w,C,o.create({...k.attrs,...e}))}}))}),f&&(h!==void 0&&s&&t.setNodeMarkup(h,void 0,{...f.attrs,...e}),o&&f.marks.length&&f.marks.forEach(g=>{o===g.type&&s&&t.addMark(p,m,o.create({...g.attrs,...e}))}))}),a},K0=(n,e={})=>({state:t,dispatch:r})=>{let s=U(n,t.schema);return ru(s,e)(t,r)},q0=(n,e={})=>({state:t,dispatch:r})=>{let s=U(n,t.schema);return su(s,e)(t,r)},U0=class{constructor(){this.callbacks={}}on(n,e){return this.callbacks[n]||(this.callbacks[n]=[]),this.callbacks[n].push(e),this}emit(n,...e){let t=this.callbacks[n];return t&&t.forEach(r=>r.apply(this,e)),this}off(n,e){let t=this.callbacks[n];return t&&(e?this.callbacks[n]=t.filter(r=>r!==e):delete this.callbacks[n]),this}once(n,e){let t=(...r)=>{this.off(n,t),e.apply(this,r)};return this.on(n,t)}removeAllListeners(){this.callbacks={}}},Cs=class{constructor(n){var e;this.find=n.find,this.handler=n.handler,this.undoable=(e=n.undoable)!=null?e:!0}},G0=(n,e)=>{if(ul(e))return e.exec(n);let t=e(n);if(!t)return null;let r=[t.text];return r.index=t.index,r.input=n,r.data=t.data,t.replaceWith&&(t.text.includes(t.replaceWith)||console.warn('[tiptap warn]: "inputRuleMatch.replaceWith" must be part of "inputRuleMatch.text".'),r.push(t.replaceWith)),r};function ds(n){var e;let{editor:t,from:r,to:s,text:i,rules:o,plugin:l}=n,{view:a}=t;if(a.composing)return!1;let c=a.state.doc.resolve(r);if(c.parent.type.spec.code||(e=c.nodeBefore||c.nodeAfter)!=null&&e.marks.find(h=>h.type.spec.code))return!1;let u=!1,d=S0(c)+i;return o.forEach(h=>{if(u)return;let f=G0(d,h.find);if(!f)return;let p=a.state.tr,m=ys({state:a.state,transaction:p}),g={from:r-(f[0].length-i.length),to:s},{commands:y,chain:k,can:w}=new ks({editor:t,state:m});h.handler({state:m,range:g,match:f,commands:y,chain:k,can:w})===null||!p.steps.length||(h.undoable&&p.setMeta(l,{transform:p,from:r,to:s,text:i}),a.dispatch(p),u=!0)}),u}function X0(n){let{editor:e,rules:t}=n,r=new I({state:{init(){return null},apply(s,i,o){let l=s.getMeta(r);if(l)return l;let a=s.getMeta("applyInputRules");return a&&setTimeout(()=>{let{text:u}=a;typeof u=="string"?u=u:u=pl(b.from(u),o.schema);let{from:d}=a,h=d+u.length;ds({editor:e,from:d,to:h,text:u,rules:t,plugin:r})}),s.selectionSet||s.docChanged?null:i}},props:{handleTextInput(s,i,o,l){return ds({editor:e,from:i,to:o,text:l,rules:t,plugin:r})},handleDOMEvents:{compositionend:s=>(setTimeout(()=>{let{$cursor:i}=s.state.selection;i&&ds({editor:e,from:i.pos,to:i.pos,text:"",rules:t,plugin:r})}),!1)},handleKeyDown(s,i){if(i.key!=="Enter")return!1;let{$cursor:o}=s.state.selection;return o?ds({editor:e,from:o.pos,to:o.pos,text:` +`,rules:t,plugin:r}):!1}},isInputRules:!0});return r}function Q0(n){return Object.prototype.toString.call(n).slice(8,-1)}function hs(n){return Q0(n)!=="Object"?!1:n.constructor===Object&&Object.getPrototypeOf(n)===Object.prototype}function qd(n,e){let t={...n};return hs(n)&&hs(e)&&Object.keys(e).forEach(r=>{hs(e[r])&&hs(n[r])?t[r]=qd(n[r],e[r]):t[r]=e[r]}),t}var bl=class{constructor(n={}){this.type="extendable",this.parent=null,this.child=null,this.name="",this.config={name:this.name},this.config={...this.config,...n},this.name=this.config.name}get options(){return{...F(E(this,"addOptions",{name:this.name}))}}get storage(){return{...F(E(this,"addStorage",{name:this.name,options:this.options}))}}configure(n={}){let e=this.extend({...this.config,addOptions:()=>qd(this.options,n)});return e.name=this.name,e.parent=this.parent,this.child=null,e}extend(n={}){let e=new this.constructor({...this.config,...n});return e.parent=this,this.child=e,e.name="name"in n?n.name:e.parent.name,e}},Ee=class Ud extends bl{constructor(){super(...arguments),this.type="mark"}static create(e={}){let t=typeof e=="function"?e():e;return new Ud(t)}static handleExit({editor:e,mark:t}){let{tr:r}=e.state,s=e.state.selection.$from;if(s.pos===s.end()){let o=s.marks();if(!!!o.find(c=>c?.type.name===t.name))return!1;let a=o.find(c=>c?.type.name===t.name);return a&&r.removeStoredMark(a),r.insertText(" ",s.pos),e.view.dispatch(r),!0}return!1}configure(e){return super.configure(e)}extend(e){let t=typeof e=="function"?e():e;return super.extend(t)}};function Y0(n){return typeof n=="number"}var Z0=class{constructor(n){this.find=n.find,this.handler=n.handler}},ek=(n,e,t)=>{if(ul(e))return[...n.matchAll(e)];let r=e(n,t);return r?r.map(s=>{let i=[s.text];return i.index=s.index,i.input=n,i.data=s.data,s.replaceWith&&(s.text.includes(s.replaceWith)||console.warn('[tiptap warn]: "pasteRuleMatch.replaceWith" must be part of "pasteRuleMatch.text".'),i.push(s.replaceWith)),i}):[]};function tk(n){let{editor:e,state:t,from:r,to:s,rule:i,pasteEvent:o,dropEvent:l}=n,{commands:a,chain:c,can:u}=new ks({editor:e,state:t}),d=[];return t.doc.nodesBetween(r,s,(f,p)=>{var m,g,y,k,w;if((g=(m=f.type)==null?void 0:m.spec)!=null&&g.code||!(f.isText||f.isTextblock||f.isInline))return;let C=(w=(k=(y=f.content)==null?void 0:y.size)!=null?k:f.nodeSize)!=null?w:0,x=Math.max(r,p),M=Math.min(s,p+C);if(x>=M)return;let T=f.isText?f.text||"":f.textBetween(x-p,M-p,void 0,"\uFFFC");ek(T,i.find,o).forEach(P=>{if(P.index===void 0)return;let te=x+P.index+1,tt=te+P[0].length,We={from:t.tr.mapping.map(te),to:t.tr.mapping.map(tt)},ue=i.handler({state:t,range:We,match:P,commands:a,chain:c,can:u,pasteEvent:o,dropEvent:l});d.push(ue)})}),d.every(f=>f!==null)}var fs=null,nk=n=>{var e;let t=new ClipboardEvent("paste",{clipboardData:new DataTransfer});return(e=t.clipboardData)==null||e.setData("text/html",n),t};function rk(n){let{editor:e,rules:t}=n,r=null,s=!1,i=!1,o=typeof ClipboardEvent<"u"?new ClipboardEvent("paste"):null,l;try{l=typeof DragEvent<"u"?new DragEvent("drop"):null}catch{l=null}let a=({state:u,from:d,to:h,rule:f,pasteEvt:p})=>{let m=u.tr,g=ys({state:u,transaction:m});if(!(!tk({editor:e,state:g,from:Math.max(d-1,0),to:h.b-1,rule:f,pasteEvent:p,dropEvent:l})||!m.steps.length)){try{l=typeof DragEvent<"u"?new DragEvent("drop"):null}catch{l=null}return o=typeof ClipboardEvent<"u"?new ClipboardEvent("paste"):null,m}};return t.map(u=>new I({view(d){let h=p=>{var m;r=(m=d.dom.parentElement)!=null&&m.contains(p.target)?d.dom.parentElement:null,r&&(fs=e)},f=()=>{fs&&(fs=null)};return window.addEventListener("dragstart",h),window.addEventListener("dragend",f),{destroy(){window.removeEventListener("dragstart",h),window.removeEventListener("dragend",f)}}},props:{handleDOMEvents:{drop:(d,h)=>{if(i=r===d.dom.parentElement,l=h,!i){let f=fs;f?.isEditable&&setTimeout(()=>{let p=f.state.selection;p&&f.commands.deleteRange({from:p.from,to:p.to})},10)}return!1},paste:(d,h)=>{var f;let p=(f=h.clipboardData)==null?void 0:f.getData("text/html");return o=h,s=!!p?.includes("data-pm-slice"),!1}}},appendTransaction:(d,h,f)=>{let p=d[0],m=p.getMeta("uiEvent")==="paste"&&!s,g=p.getMeta("uiEvent")==="drop"&&!i,y=p.getMeta("applyPasteRules"),k=!!y;if(!m&&!g&&!k)return;if(k){let{text:x}=y;typeof x=="string"?x=x:x=pl(b.from(x),f.schema);let{from:M}=y,T=M+x.length,R=nk(x);return a({rule:u,state:f,from:M,to:{b:T},pasteEvt:R})}let w=h.doc.content.findDiffStart(f.doc.content),C=h.doc.content.findDiffEnd(f.doc.content);if(!(!Y0(w)||!C||w===C.b))return a({rule:u,state:f,from:w,to:C,pasteEvt:o})}}))}var vs=class{constructor(n,e){this.splittableMarks=[],this.nonClearableMarks=[],this.editor=e,this.baseExtensions=n,this.extensions=ml(n),this.schema=$d(this.extensions,e),this.setupExtensions()}get commands(){return this.extensions.reduce((n,e)=>{let t={name:e.name,options:e.options,storage:this.editor.extensionStorage[e.name],editor:this.editor,type:sr(e.name,this.schema)},r=E(e,"addCommands",t);return r?{...n,...r()}:n},{})}get plugins(){let{editor:n}=this;return Jt([...this.extensions].reverse()).flatMap(r=>{let s={name:r.name,options:r.options,storage:this.editor.extensionStorage[r.name],editor:n,type:sr(r.name,this.schema)},i=[],o=E(r,"addKeyboardShortcuts",s),l={};if(r.type==="mark"&&E(r,"exitable",s)&&(l.ArrowRight=()=>Ee.handleExit({editor:n,mark:r})),o){let h=Object.fromEntries(Object.entries(o()).map(([f,p])=>[f,()=>p({editor:n})]));l={...l,...h}}let a=kd(l);i.push(a);let c=E(r,"addInputRules",s);if(Ad(r,n.options.enableInputRules)&&c){let h=c();if(h&&h.length){let f=X0({editor:n,rules:h}),p=Array.isArray(f)?f:[f];i.push(...p)}}let u=E(r,"addPasteRules",s);if(Ad(r,n.options.enablePasteRules)&&u){let h=u();if(h&&h.length){let f=rk({editor:n,rules:h});i.push(...f)}}let d=E(r,"addProseMirrorPlugins",s);if(d){let h=d();i.push(...h)}return i})}get attributes(){return Bd(this.extensions)}get nodeViews(){let{editor:n}=this,{nodeExtensions:e}=Sn(this.extensions);return Object.fromEntries(e.filter(t=>!!E(t,"addNodeView")).map(t=>{let r=this.attributes.filter(a=>a.type===t.name),s={name:t.name,options:t.options,storage:this.editor.extensionStorage[t.name],editor:n,type:U(t.name,this.schema)},i=E(t,"addNodeView",s);if(!i)return[];let o=i();if(!o)return[];let l=(a,c,u,d,h)=>{let f=Cn(a,r);return o({node:a,view:c,getPos:u,decorations:d,innerDecorations:h,editor:n,extension:t,HTMLAttributes:f})};return[t.name,l]}))}dispatchTransaction(n){let{editor:e}=this;return Jt([...this.extensions].reverse()).reduceRight((r,s)=>{let i={name:s.name,options:s.options,storage:this.editor.extensionStorage[s.name],editor:e,type:sr(s.name,this.schema)},o=E(s,"dispatchTransaction",i);return o?l=>{o.call(i,{transaction:l,next:r})}:r},n)}transformPastedHTML(n){let{editor:e}=this;return Jt([...this.extensions]).reduce((r,s)=>{let i={name:s.name,options:s.options,storage:this.editor.extensionStorage[s.name],editor:e,type:sr(s.name,this.schema)},o=E(s,"transformPastedHTML",i);return o?(l,a)=>{let c=r(l,a);return o.call(i,c)}:r},n||(r=>r))}get markViews(){let{editor:n}=this,{markExtensions:e}=Sn(this.extensions);return Object.fromEntries(e.filter(t=>!!E(t,"addMarkView")).map(t=>{let r=this.attributes.filter(l=>l.type===t.name),s={name:t.name,options:t.options,storage:this.editor.extensionStorage[t.name],editor:n,type:ht(t.name,this.schema)},i=E(t,"addMarkView",s);if(!i)return[];let o=(l,a,c)=>{let u=Cn(l,r);return i()({mark:l,view:a,inline:c,editor:n,extension:t,HTMLAttributes:u,updateAttributes:d=>{mk(l,n,d)}})};return[t.name,o]}))}destroy(){this.extensions.forEach(n=>{let e=n;for(;e.parent;){let t=e.parent;t.child===e&&(t.child=null),e=t}}),this.extensions=[],this.baseExtensions=[],this.schema=null,this.editor=null}setupExtensions(){let n=this.extensions;this.editor.extensionStorage=Object.fromEntries(n.map(e=>[e.name,e.storage])),n.forEach(e=>{var t,r;let s={name:e.name,options:e.options,storage:this.editor.extensionStorage[e.name],editor:this.editor,type:sr(e.name,this.schema)};e.type==="mark"&&(((t=F(E(e,"keepOnSplit",s)))==null||t)&&this.splittableMarks.push(e.name),(r=F(E(e,"clearable",s)))==null||r||this.nonClearableMarks.push(e.name));let i=E(e,"onBeforeCreate",s),o=E(e,"onCreate",s),l=E(e,"onUpdate",s),a=E(e,"onSelectionUpdate",s),c=E(e,"onTransaction",s),u=E(e,"onFocus",s),d=E(e,"onBlur",s),h=E(e,"onDestroy",s);i&&this.editor.on("beforeCreate",i),o&&this.editor.on("create",o),l&&this.editor.on("update",l),a&&this.editor.on("selectionUpdate",a),c&&this.editor.on("transaction",c),u&&this.editor.on("focus",u),d&&this.editor.on("blur",d),h&&this.editor.on("destroy",h)})}};vs.resolve=ml;vs.sort=Jt;vs.flatten=ar;var sk={};cl(sk,{ClipboardTextSerializer:()=>Xd,Commands:()=>Qd,Delete:()=>Yd,Drop:()=>Zd,Editable:()=>eh,FocusEvents:()=>nh,Keymap:()=>rh,Paste:()=>sh,Tabindex:()=>ih,TextDirection:()=>oh,focusEventsPluginKey:()=>th});var B=class Gd extends bl{constructor(){super(...arguments),this.type="extension"}static create(e={}){let t=typeof e=="function"?e():e;return new Gd(t)}configure(e){return super.configure(e)}extend(e){let t=typeof e=="function"?e():e;return super.extend(t)}},Xd=B.create({name:"clipboardTextSerializer",addOptions(){return{blockSeparator:void 0}},addProseMirrorPlugins(){return[new I({key:new L("clipboardTextSerializer"),props:{clipboardTextSerializer:()=>{let{editor:n}=this,{state:e,schema:t}=n,{doc:r,selection:s}=e,i=_d(t),{blockSeparator:o}=this.options,l={...o!==void 0?{blockSeparator:o}:{},textSerializers:i};return[...s.ranges].sort((c,u)=>c.$from.pos-u.$from.pos).map(({$from:c,$to:u})=>Hd(r,{from:c.pos,to:u.pos},l)).join(o??` + +`)}}})]}}),Qd=B.create({name:"commands",addCommands(){return{...Le}}}),Yd=B.create({name:"delete",onUpdate({transaction:n,appendedTransactions:e}){var t,r,s;let i=()=>{var o,l,a,c;if((c=(a=(l=(o=this.editor.options.coreExtensionOptions)==null?void 0:o.delete)==null?void 0:l.filterTransaction)==null?void 0:a.call(l,n))!=null?c:n.getMeta("y-sync$"))return;let u=hl(n.before,[n,...e]);kl(u).forEach(f=>{u.mapping.mapResult(f.oldRange.from).deletedAfter&&u.mapping.mapResult(f.oldRange.to).deletedBefore&&u.before.nodesBetween(f.oldRange.from,f.oldRange.to,(p,m)=>{let g=m+p.nodeSize-2,y=f.oldRange.from<=m&&g<=f.oldRange.to;this.editor.emit("delete",{type:"node",node:p,from:m,to:g,newFrom:u.mapping.map(m),newTo:u.mapping.map(g),deletedRange:f.oldRange,newRange:f.newRange,partial:!y,editor:this.editor,transaction:n,combinedTransform:u})})});let h=u.mapping;u.steps.forEach((f,p)=>{var m,g;if(f instanceof st){let y=h.slice(p).map(f.from,-1),k=h.slice(p).map(f.to),w=h.invert().map(y,-1),C=h.invert().map(k),x=y>0?(m=u.doc.nodeAt(y-1))==null?void 0:m.marks.some(T=>T.eq(f.mark)):!1,M=(g=u.doc.nodeAt(k))==null?void 0:g.marks.some(T=>T.eq(f.mark));this.editor.emit("delete",{type:"mark",mark:f.mark,from:f.from,to:f.to,deletedRange:{from:w,to:C},newRange:{from:y,to:k},partial:!!(M||x),editor:this.editor,transaction:n,combinedTransform:u})}})};(s=(r=(t=this.editor.options.coreExtensionOptions)==null?void 0:t.delete)==null?void 0:r.async)==null||s?setTimeout(i,0):i()}}),Zd=B.create({name:"drop",addProseMirrorPlugins(){return[new I({key:new L("tiptapDrop"),props:{handleDrop:(n,e,t,r)=>{this.editor.emit("drop",{editor:this.editor,event:e,slice:t,moved:r})}}})]}}),eh=B.create({name:"editable",addProseMirrorPlugins(){return[new I({key:new L("editable"),props:{editable:()=>this.editor.options.editable}})]}}),th=new L("focusEvents"),nh=B.create({name:"focusEvents",addProseMirrorPlugins(){let{editor:n}=this;return[new I({key:th,props:{handleDOMEvents:{focus:(e,t)=>{n.isFocused=!0;let r=n.state.tr.setMeta("focus",{event:t}).setMeta("addToHistory",!1);return e.dispatch(r),!1},blur:(e,t)=>{n.isFocused=!1;let r=n.state.tr.setMeta("blur",{event:t}).setMeta("addToHistory",!1);return e.dispatch(r),!1}}}})]}}),rh=B.create({name:"keymap",addKeyboardShortcuts(){let n=()=>this.editor.commands.first(({commands:o})=>[()=>o.undoInputRule(),()=>o.command(({tr:l})=>{let{selection:a,doc:c}=l,{empty:u,$anchor:d}=a,{pos:h,parent:f}=d,p=d.parent.isTextblock&&h>0?l.doc.resolve(h-1):d,m=p.parent.type.spec.isolating,g=d.pos-d.parentOffset,y=m&&p.parent.childCount===1?g===d.pos:O.atStart(c).from===h;return!u||!f.type.isTextblock||f.textContent.length||!y||y&&d.parent.type.name==="paragraph"?!1:o.clearNodes()}),()=>o.deleteSelection(),()=>o.joinBackward(),()=>o.selectNodeBackward()]),e=()=>this.editor.commands.first(({commands:o})=>[()=>o.deleteSelection(),()=>o.deleteCurrentNode(),()=>o.joinForward(),()=>o.selectNodeForward()]),r={Enter:()=>this.editor.commands.first(({commands:o})=>[()=>o.newlineInCode(),()=>o.createParagraphNear(),()=>o.liftEmptyBlock(),()=>o.splitBlock()]),"Mod-Enter":()=>this.editor.commands.exitCode(),Backspace:n,"Mod-Backspace":n,"Shift-Backspace":n,Delete:e,"Mod-Delete":e,"Mod-a":()=>this.editor.commands.selectAll()},s={...r},i={...r,"Ctrl-h":n,"Alt-Backspace":n,"Ctrl-d":e,"Ctrl-Alt-Backspace":e,"Alt-Delete":e,"Alt-d":e,"Ctrl-a":()=>this.editor.commands.selectTextblockStart(),"Ctrl-e":()=>this.editor.commands.selectTextblockEnd()};return gs()||Dd()?i:s},addProseMirrorPlugins(){return[new I({key:new L("clearDocument"),appendTransaction:(n,e,t)=>{if(n.some(m=>m.getMeta("composition")))return;let r=n.some(m=>m.docChanged)&&!e.doc.eq(t.doc),s=n.some(m=>m.getMeta("preventClearDocument"));if(!r||s)return;let{empty:i,from:o,to:l}=e.selection,a=O.atStart(e.doc).from,c=O.atEnd(e.doc).to;if(i||!(o===a&&l===c)||!vn(t.doc))return;let h=t.tr,f=ys({state:t,transaction:h}),{commands:p}=new ks({editor:this.editor,state:f});if(p.clearNodes(),!!h.steps.length)return h}})]}}),sh=B.create({name:"paste",addProseMirrorPlugins(){return[new I({key:new L("tiptapPaste"),props:{handlePaste:(n,e,t)=>{this.editor.emit("paste",{editor:this.editor,event:e,slice:t})}}})]}}),ih=B.create({name:"tabindex",addOptions(){return{value:void 0}},addProseMirrorPlugins(){return[new I({key:new L("tabindex"),props:{attributes:()=>{var n;return!this.editor.isEditable&&this.options.value===void 0?{}:{tabindex:(n=this.options.value)!=null?n:"0"}}}})]}}),oh=B.create({name:"textDirection",addOptions(){return{direction:void 0}},addGlobalAttributes(){if(!this.options.direction)return[];let{nodeExtensions:n}=Sn(this.extensions);return[{types:n.filter(e=>e.name!=="text").map(e=>e.name),attributes:{dir:{default:this.options.direction,parseHTML:e=>{let t=e.getAttribute("dir");return t&&(t==="ltr"||t==="rtl"||t==="auto")?t:this.options.direction},renderHTML:e=>e.dir?{dir:e.dir}:{}}}}]},addProseMirrorPlugins(){return[new I({key:new L("textDirection"),props:{attributes:()=>{let n=this.options.direction;return n?{dir:n}:{}}}})]}}),ik=class or{constructor(e,t,r=!1,s=null){this.currentNode=null,this.actualDepth=null,this.isBlock=r,this.resolvedPos=e,this.editor=t,this.currentNode=s}get name(){return this.node.type.name}get node(){return this.currentNode||this.resolvedPos.node()}get element(){return this.editor.view.domAtPos(this.pos).node}get depth(){var e;return(e=this.actualDepth)!=null?e:this.resolvedPos.depth}get pos(){return this.resolvedPos.pos}get content(){return this.node.content}set content(e){let t=this.from,r=this.to;if(this.isBlock){if(this.content.size===0){console.error(`You can\u2019t set content on a block node. Tried to set content on ${this.name} at ${this.pos}`);return}t=this.from+1,r=this.to-1}this.editor.commands.insertContentAt({from:t,to:r},e)}get attributes(){return this.node.attrs}get textContent(){return this.node.textContent}get size(){return this.node.nodeSize}get from(){return this.isBlock?this.pos:this.resolvedPos.start(this.resolvedPos.depth)}get range(){return{from:this.from,to:this.to}}get to(){return this.isBlock?this.pos+this.size:this.resolvedPos.end(this.resolvedPos.depth)+(this.node.isText?0:1)}get parent(){if(this.depth===0)return null;let e=this.resolvedPos.start(this.resolvedPos.depth-1),t=this.resolvedPos.doc.resolve(e);return new or(t,this.editor)}get before(){let e=this.resolvedPos.doc.resolve(this.from-(this.isBlock?1:2));return e.depth!==this.depth&&(e=this.resolvedPos.doc.resolve(this.from-3)),new or(e,this.editor)}get after(){let e=this.resolvedPos.doc.resolve(this.to+(this.isBlock?2:1));return e.depth!==this.depth&&(e=this.resolvedPos.doc.resolve(this.to+3)),new or(e,this.editor)}get children(){let e=[];return this.node.content.forEach((t,r)=>{let s=t.isBlock&&!t.isTextblock,i=t.isAtom&&!t.isText,o=t.isInline,l=this.pos+r+(i?0:1);if(l<0||l>this.resolvedPos.doc.nodeSize-2)return;let a=this.resolvedPos.doc.resolve(l);if(!s&&!o&&a.depth<=this.depth)return;let c=new or(a,this.editor,s,s||o?t:null);s&&(c.actualDepth=this.depth+1),e.push(c)}),e}get firstChild(){return this.children[0]||null}get lastChild(){let e=this.children;return e[e.length-1]||null}closest(e,t={}){let r=null,s=this.parent;for(;s&&!r;){if(s.node.type.name===e)if(Object.keys(t).length>0){let i=s.node.attrs,o=Object.keys(t);for(let l=0;l{r&&s.length>0||(o.node.type.name===e&&i.every(a=>t[a]===o.node.attrs[a])&&s.push(o),!(r&&s.length>0)&&(s=s.concat(o.querySelectorAll(e,t,r))))}),s}setAttribute(e){let{tr:t}=this.editor.state;t.setNodeMarkup(this.from,void 0,{...this.node.attrs,...e}),this.editor.view.dispatch(t)}},ok=`.ProseMirror { position: relative; } @@ -80,129 +82,138 @@ img.ProseMirror-separator { .ProseMirror-focused .ProseMirror-gapcursor { display: block; -}`;function ky(t,e,n){let r=document.querySelector(`style[data-tiptap-style${n?`-${n}`:""}]`);if(r!==null)return r;let s=document.createElement("style");return e&&s.setAttribute("nonce",e),s.setAttribute(`data-tiptap-style${n?`-${n}`:""}`,""),s.innerHTML=t,document.getElementsByTagName("head")[0].appendChild(s),s}var by=class extends iy{constructor(t={}){super(),this.css=null,this.className="tiptap",this.editorView=null,this.isFocused=!1,this.isInitialized=!1,this.extensionStorage={},this.instanceId=Math.random().toString(36).slice(2,9),this.options={element:typeof document<"u"?document.createElement("div"):null,content:"",injectCSS:!0,injectNonce:void 0,extensions:[],autofocus:!1,editable:!0,textDirection:void 0,editorProps:{},parseOptions:{},coreExtensionOptions:{},enableInputRules:!0,enablePasteRules:!0,enableCoreExtensions:!0,enableContentCheck:!1,emitContentError:!1,onBeforeCreate:()=>null,onCreate:()=>null,onMount:()=>null,onUnmount:()=>null,onUpdate:()=>null,onSelectionUpdate:()=>null,onTransaction:()=>null,onFocus:()=>null,onBlur:()=>null,onDestroy:()=>null,onContentError:({error:r})=>{throw r},onPaste:()=>null,onDrop:()=>null,onDelete:()=>null,enableExtensionDispatchTransaction:!0},this.isCapturingTransaction=!1,this.capturedTransaction=null,this.utils={getUpdatedPosition:zg,createMappablePosition:Bg},this.setOptions(t),this.createExtensionManager(),this.createCommandManager(),this.createSchema(),this.on("beforeCreate",this.options.onBeforeCreate),this.emit("beforeCreate",{editor:this}),this.on("mount",this.options.onMount),this.on("unmount",this.options.onUnmount),this.on("contentError",this.options.onContentError),this.on("create",this.options.onCreate),this.on("update",this.options.onUpdate),this.on("selectionUpdate",this.options.onSelectionUpdate),this.on("transaction",this.options.onTransaction),this.on("focus",this.options.onFocus),this.on("blur",this.options.onBlur),this.on("destroy",this.options.onDestroy),this.on("drop",({event:r,slice:s,moved:i})=>this.options.onDrop(r,s,i)),this.on("paste",({event:r,slice:s})=>this.options.onPaste(r,s)),this.on("delete",this.options.onDelete);let e=this.createDoc(),n=Mu(e,this.options.autofocus);this.editorState=wr.create({doc:e,schema:this.schema,selection:n||void 0}),this.options.element&&this.mount(this.options.element)}mount(t){if(typeof document>"u")throw new Error("[tiptap error]: The editor cannot be mounted because there is no 'document' defined in this environment.");this.createView(t),this.emit("mount",{editor:this}),this.css&&!document.head.contains(this.css)&&document.head.appendChild(this.css),window.setTimeout(()=>{this.isDestroyed||(this.options.autofocus!==!1&&this.options.autofocus!==null&&this.commands.focus(this.options.autofocus),this.emit("create",{editor:this}),this.isInitialized=!0)},0)}unmount(){if(this.editorView){let t=this.editorView.dom;t?.editor&&delete t.editor,this.editorView.destroy()}if(this.editorView=null,this.isInitialized=!1,this.css&&!document.querySelectorAll(`.${this.className}`).length)try{typeof this.css.remove=="function"?this.css.remove():this.css.parentNode&&this.css.parentNode.removeChild(this.css)}catch(t){console.warn("Failed to remove CSS element:",t)}this.css=null,this.emit("unmount",{editor:this})}get storage(){return this.extensionStorage}get commands(){return this.commandManager.commands}chain(){return this.commandManager.chain()}can(){return this.commandManager.can()}injectCSS(){this.options.injectCSS&&typeof document<"u"&&(this.css=ky(yy,this.options.injectNonce))}setOptions(t={}){this.options={...this.options,...t},!(!this.editorView||!this.state||this.isDestroyed)&&(this.options.editorProps&&this.view.setProps(this.options.editorProps),this.view.updateState(this.state))}setEditable(t,e=!0){this.setOptions({editable:t}),e&&this.emit("update",{editor:this,transaction:this.state.tr,appendedTransactions:[]})}get isEditable(){return this.options.editable&&this.view&&this.view.editable}get view(){return this.editorView?this.editorView:new Proxy({state:this.editorState,updateState:t=>{this.editorState=t},dispatch:t=>{this.dispatchTransaction(t)},composing:!1,dragging:null,editable:!0,isDestroyed:!1},{get:(t,e)=>{if(this.editorView)return this.editorView[e];if(e==="state")return this.editorState;if(e in t)return Reflect.get(t,e);throw new Error(`[tiptap error]: The editor view is not available. Cannot access view['${e}']. The editor may not be mounted yet.`)}})}get state(){return this.editorView&&(this.editorState=this.view.state),this.editorState}registerPlugin(t,e){let n=Nu(e)?e(t,[...this.state.plugins]):[...this.state.plugins,t],r=this.state.reconfigure({plugins:n});return this.view.updateState(r),r}unregisterPlugin(t){if(this.isDestroyed)return;let e=this.state.plugins,n=e;if([].concat(t).forEach(s=>{let i=typeof s=="string"?`${s}$`:s.key;n=n.filter(o=>!o.key.startsWith(i))}),e.length===n.length)return;let r=this.state.reconfigure({plugins:n});return this.view.updateState(r),r}createExtensionManager(){var t,e;let r=[...this.options.enableCoreExtensions?[qu,Wu.configure({blockSeparator:(e=(t=this.options.coreExtensionOptions)==null?void 0:t.clipboardTextSerializer)==null?void 0:e.blockSeparator}),ju,Gu,Xu,Yu,Uu,Qu,Ku,Zu.configure({direction:this.options.textDirection})].filter(s=>typeof this.options.enableCoreExtensions=="object"?this.options.enableCoreExtensions[s.name]!==!1:!0):[],...this.options.extensions].filter(s=>["extension","node","mark"].includes(s?.type));this.extensionManager=new Xr(r,this)}createCommandManager(){this.commandManager=new jr({editor:this})}createSchema(){this.schema=this.extensionManager.schema}createDoc(){let t;try{t=wo(this.options.content,this.schema,this.options.parseOptions,{errorOnInvalidContent:this.options.enableContentCheck})}catch(e){if(!(e instanceof Error)||!["[tiptap error]: Invalid JSON content","[tiptap error]: Invalid HTML content"].includes(e.message))throw e;this.emit("contentError",{editor:this,error:e,disableCollaboration:()=>{"collaboration"in this.storage&&typeof this.storage.collaboration=="object"&&this.storage.collaboration&&(this.storage.collaboration.isDisabled=!0),this.options.extensions=this.options.extensions.filter(n=>n.name!=="collaboration"),this.createExtensionManager()}}),t=wo(this.options.content,this.schema,this.options.parseOptions,{errorOnInvalidContent:!1})}return t}createView(t){let{editorProps:e,enableExtensionDispatchTransaction:n}=this.options,r=e.dispatchTransaction||this.dispatchTransaction.bind(this),s=n?this.extensionManager.dispatchTransaction(r):r,i=e.transformPastedHTML,o=this.extensionManager.transformPastedHTML(i);this.editorView=new Pn(t,{...e,attributes:{role:"textbox",...e?.attributes},dispatchTransaction:s,transformPastedHTML:o,state:this.editorState,markViews:this.extensionManager.markViews,nodeViews:this.extensionManager.nodeViews});let l=this.state.reconfigure({plugins:this.extensionManager.plugins});this.view.updateState(l),this.prependClass(),this.injectCSS();let a=this.view.dom;a.editor=this}createNodeViews(){this.view.isDestroyed||this.view.setProps({markViews:this.extensionManager.markViews,nodeViews:this.extensionManager.nodeViews})}prependClass(){this.view.dom.className=`${this.className} ${this.view.dom.className}`}captureTransaction(t){this.isCapturingTransaction=!0,t(),this.isCapturingTransaction=!1;let e=this.capturedTransaction;return this.capturedTransaction=null,e}dispatchTransaction(t){if(this.view.isDestroyed)return;if(this.isCapturingTransaction){if(!this.capturedTransaction){this.capturedTransaction=t;return}t.steps.forEach(c=>{var u;return(u=this.capturedTransaction)==null?void 0:u.step(c)});return}let{state:e,transactions:n}=this.state.applyTransaction(t),r=!this.state.selection.eq(e.selection),s=n.includes(t),i=this.state;if(this.emit("beforeTransaction",{editor:this,transaction:t,nextState:e}),!s)return;this.view.updateState(e),this.emit("transaction",{editor:this,transaction:t,appendedTransactions:n.slice(1)}),r&&this.emit("selectionUpdate",{editor:this,transaction:t});let o=n.findLast(c=>c.getMeta("focus")||c.getMeta("blur")),l=o?.getMeta("focus"),a=o?.getMeta("blur");l&&this.emit("focus",{editor:this,event:l.event,transaction:o}),a&&this.emit("blur",{editor:this,event:a.event,transaction:o}),!(t.getMeta("preventUpdate")||!n.some(c=>c.docChanged)||i.doc.eq(e.doc))&&this.emit("update",{editor:this,transaction:t,appendedTransactions:n.slice(1)})}getAttributes(t){return Ro(this.state,t)}isActive(t,e){let n=typeof t=="string"?t:null,r=typeof t=="string"?e:t;return Lg(this.state,n,r)}getJSON(){return this.state.doc.toJSON()}getHTML(){return Eo(this.state.doc.content,this.schema)}getText(t){let{blockSeparator:e=` +}`;function xl(n,e,t){let r=document.querySelector(`style[data-tiptap-style${t?`-${t}`:""}]`);if(r!==null)return r;let s=document.createElement("style");return e&&s.setAttribute("nonce",e),s.setAttribute(`data-tiptap-style${t?`-${t}`:""}`,""),s.innerHTML=n,document.getElementsByTagName("head")[0].appendChild(s),s}var lk=class extends U0{constructor(n={}){super(),this.css=null,this.className="tiptap",this.editorView=null,this.isFocused=!1,this.destroyed=!1,this.isInitialized=!1,this.extensionStorage={},this.instanceId=Math.random().toString(36).slice(2,9),this.options={element:typeof document<"u"?document.createElement("div"):null,content:"",injectCSS:!0,injectNonce:void 0,extensions:[],autofocus:!1,editable:!0,textDirection:void 0,editorProps:{},parseOptions:{},coreExtensionOptions:{},enableInputRules:!0,enablePasteRules:!0,enableCoreExtensions:!0,enableContentCheck:!1,emitContentError:!1,onBeforeCreate:()=>null,onCreate:()=>null,onMount:()=>null,onUnmount:()=>null,onUpdate:()=>null,onSelectionUpdate:()=>null,onTransaction:()=>null,onFocus:()=>null,onBlur:()=>null,onDestroy:()=>null,onContentError:({error:r})=>{throw r},onPaste:()=>null,onDrop:()=>null,onDelete:()=>null,enableExtensionDispatchTransaction:!0},this.isCapturingTransaction=!1,this.capturedTransaction=null,this.utils={getUpdatedPosition:v0,createMappablePosition:T0},this.setOptions(n),this.createExtensionManager(),this.createCommandManager(),this.createSchema(),this.on("beforeCreate",this.options.onBeforeCreate),this.emit("beforeCreate",{editor:this}),this.on("mount",this.options.onMount),this.on("unmount",this.options.onUnmount),this.on("contentError",this.options.onContentError),this.on("create",this.options.onCreate),this.on("update",this.options.onUpdate),this.on("selectionUpdate",this.options.onSelectionUpdate),this.on("transaction",this.options.onTransaction),this.on("focus",this.options.onFocus),this.on("blur",this.options.onBlur),this.on("destroy",this.options.onDestroy),this.on("drop",({event:r,slice:s,moved:i})=>this.options.onDrop(r,s,i)),this.on("paste",({event:r,slice:s})=>this.options.onPaste(r,s)),this.on("delete",this.options.onDelete);let e=this.createDoc(),t=Od(e,this.options.autofocus);this.editorState=Gr.create({doc:e,schema:this.schema,selection:t||void 0}),this.options.element&&this.mount(this.options.element)}mount(n){if(typeof document>"u")throw new Error("[tiptap error]: The editor cannot be mounted because there is no 'document' defined in this environment.");this.createView(n),this.emit("mount",{editor:this}),this.css&&!document.head.contains(this.css)&&document.head.appendChild(this.css),window.setTimeout(()=>{this.isDestroyed||(this.options.autofocus!==!1&&this.options.autofocus!==null&&this.commands.focus(this.options.autofocus),this.emit("create",{editor:this}),this.isInitialized=!0)},0)}unmount(){if(this.editorView){let n=this.editorView.dom;n?.editor&&delete n.editor,this.editorView.destroy()}if(this.editorView=null,this.isInitialized=!1,this.css&&!document.querySelectorAll(`.${this.className}`).length)try{typeof this.css.remove=="function"?this.css.remove():this.css.parentNode&&this.css.parentNode.removeChild(this.css)}catch(n){console.warn("Failed to remove CSS element:",n)}this.css=null,this.emit("unmount",{editor:this})}get storage(){return this.extensionStorage}get commands(){return this.commandManager.commands}chain(){return this.commandManager.chain()}can(){return this.commandManager.can()}injectCSS(){this.options.injectCSS&&typeof document<"u"&&(this.css=xl(ok,this.options.injectNonce))}setOptions(n={}){this.options={...this.options,...n},!(!this.editorView||!this.state||this.isDestroyed)&&(this.options.editorProps&&this.view.setProps(this.options.editorProps),this.view.updateState(this.state))}setEditable(n,e=!0){this.setOptions({editable:n}),e&&this.emit("update",{editor:this,transaction:this.state.tr,appendedTransactions:[]})}get isEditable(){return this.options.editable&&this.view&&this.view.editable}get view(){return this.editorView?this.editorView:new Proxy({state:this.editorState,updateState:n=>{this.editorState=n},dispatch:n=>{this.dispatchTransaction(n)},composing:!1,dragging:null,editable:!0,isDestroyed:!1},{get:(n,e)=>{if(this.editorView)return this.editorView[e];if(e==="state")return this.editorState;if(e in n)return Reflect.get(n,e);throw new Error(`[tiptap error]: The editor view is not available. Cannot access view['${e}']. The editor may not be mounted yet.`)}})}get state(){return this.editorView&&(this.editorState=this.view.state),this.editorState}registerPlugin(n,e){let t=zd(e)?e(n,[...this.state.plugins]):[...this.state.plugins,n],r=this.state.reconfigure({plugins:t});return this.view.updateState(r),r}unregisterPlugin(n){if(this.isDestroyed)return;let e=this.state.plugins,t=e;if([].concat(n).forEach(s=>{let i=typeof s=="string"?`${s}$`:s.key;t=t.filter(o=>!o.key.startsWith(i))}),e.length===t.length)return;let r=this.state.reconfigure({plugins:t});return this.view.updateState(r),r}createExtensionManager(){var n,e,t,r;let i=[...this.options.enableCoreExtensions?[eh,Xd.configure({blockSeparator:(e=(n=this.options.coreExtensionOptions)==null?void 0:n.clipboardTextSerializer)==null?void 0:e.blockSeparator}),Qd,nh,rh,ih.configure({value:(r=(t=this.options.coreExtensionOptions)==null?void 0:t.tabindex)==null?void 0:r.value}),Zd,sh,Yd,oh.configure({direction:this.options.textDirection})].filter(o=>typeof this.options.enableCoreExtensions=="object"?this.options.enableCoreExtensions[o.name]!==!1:!0):[],...this.options.extensions].filter(o=>["extension","node","mark"].includes(o?.type));this.extensionManager=new vs(i,this)}createCommandManager(){this.commandManager=new ks({editor:this})}createSchema(){this.schema=this.extensionManager.schema}createDoc(){let n;try{n=ll(this.options.content,this.schema,this.options.parseOptions,{errorOnInvalidContent:this.options.enableContentCheck})}catch(e){if(!(e instanceof Error)||!["[tiptap error]: Invalid JSON content","[tiptap error]: Invalid HTML content"].includes(e.message))throw e;this.emit("contentError",{editor:this,error:e,disableCollaboration:()=>{"collaboration"in this.storage&&typeof this.storage.collaboration=="object"&&this.storage.collaboration&&(this.storage.collaboration.isDisabled=!0),this.options.extensions=this.options.extensions.filter(t=>t.name!=="collaboration"),this.createExtensionManager()}}),n=ll(this.options.content,this.schema,this.options.parseOptions,{errorOnInvalidContent:!1})}return n}createView(n){let{editorProps:e,enableExtensionDispatchTransaction:t}=this.options,r=e.dispatchTransaction||this.dispatchTransaction.bind(this),s=t?this.extensionManager.dispatchTransaction(r):r,i=e.transformPastedHTML,o=this.extensionManager.transformPastedHTML(i);this.editorView=new Yn(n,{...e,attributes:{role:"textbox",...e?.attributes},dispatchTransaction:s,transformPastedHTML:o,state:this.editorState,markViews:this.extensionManager.markViews,nodeViews:this.extensionManager.nodeViews});let l=this.state.reconfigure({plugins:this.extensionManager.plugins});this.view.updateState(l),this.prependClass(),this.injectCSS();let a=this.view.dom;a.editor=this}createNodeViews(){this.view.isDestroyed||this.view.setProps({markViews:this.extensionManager.markViews,nodeViews:this.extensionManager.nodeViews})}prependClass(){this.view.dom.className=`${this.className} ${this.view.dom.className}`}captureTransaction(n){this.isCapturingTransaction=!0,n(),this.isCapturingTransaction=!1;let e=this.capturedTransaction;return this.capturedTransaction=null,e}dispatchTransaction(n){if(this.view.isDestroyed)return;if(this.isCapturingTransaction){if(!this.capturedTransaction){this.capturedTransaction=n;return}n.steps.forEach(c=>{var u;return(u=this.capturedTransaction)==null?void 0:u.step(c)});return}let{state:e,transactions:t}=this.state.applyTransaction(n),r=!this.state.selection.eq(e.selection),s=t.includes(n),i=this.state;if(this.emit("beforeTransaction",{editor:this,transaction:n,nextState:e}),!s)return;this.view.updateState(e),this.emit("transaction",{editor:this,transaction:n,appendedTransactions:t.slice(1)}),r&&this.emit("selectionUpdate",{editor:this,transaction:n});let o=t.findLast(c=>c.getMeta("focus")||c.getMeta("blur")),l=o?.getMeta("focus"),a=o?.getMeta("blur");l&&this.emit("focus",{editor:this,event:l.event,transaction:o}),a&&this.emit("blur",{editor:this,event:a.event,transaction:o}),!(n.getMeta("preventUpdate")||!t.some(c=>c.docChanged)||i.doc.eq(e.doc))&&this.emit("update",{editor:this,transaction:n,appendedTransactions:t.slice(1)})}getAttributes(n){return yl(this.state,n)}isActive(n,e){let t=typeof n=="string"?n:null,r=typeof n=="string"?e:n;return C0(this.state,t,r)}getJSON(){return this.state.doc.toJSON()}getHTML(){return pl(this.state.doc.content,this.schema)}getText(n){let{blockSeparator:e=` -`,textSerializers:n={}}=t||{};return Rg(this.state.doc,{blockSeparator:e,textSerializers:{...Pu(this.schema),...n}})}get isEmpty(){return jn(this.state.doc)}destroy(){this.emit("destroy"),this.unmount(),this.removeAllListeners()}get isDestroyed(){var t,e;return(e=(t=this.editorView)==null?void 0:t.isDestroyed)!=null?e:!0}$node(t,e){var n;return((n=this.$doc)==null?void 0:n.querySelector(t,e))||null}$nodes(t,e){var n;return((n=this.$doc)==null?void 0:n.querySelectorAll(t,e))||null}$pos(t){let e=this.state.doc.resolve(t);return new gy(e,this)}get $doc(){return this.$pos(0)}};function He(t){return new Gr({find:t.find,handler:({state:e,range:n,match:r})=>{let s=_(t.getAttributes,void 0,r);if(s===!1||s===null)return null;let{tr:i}=e,o=r[r.length-1],l=r[0];if(o){let a=l.search(/\S/),c=n.from+l.indexOf(o),u=c+o.length;if(qr(n.from,n.to,e.doc).filter(f=>f.mark.type.excluded.find(m=>m===t.type&&m!==f.mark.type)).filter(f=>f.to>c).length)return null;un.from&&i.delete(n.from+a,c);let h=n.from+a+o.length;i.addMark(n.from+a,h,t.type.create(s||{})),i.removeStoredMark(t.type)}},undoable:t.undoable})}function Qr(t){return new Gr({find:t.find,handler:({state:e,range:n,match:r})=>{let s=_(t.getAttributes,void 0,r)||{},{tr:i}=e,o=n.from,l=n.to,a=t.type.create(s);if(r[1]){let c=r[0].lastIndexOf(r[1]),u=o+c;u>l?u=l:l=u+r[1].length;let d=r[0][r[0].length-1];i.insertText(d,o+r[0].length-1),i.replaceWith(u,l,a)}else if(r[0]){let c=t.type.isInline?o:o-1;i.insert(c,t.type.create(s)).delete(i.mapping.map(o),i.mapping.map(l))}i.scrollIntoView()},undoable:t.undoable})}function Kn(t){return new Gr({find:t.find,handler:({state:e,range:n,match:r})=>{let s=e.doc.resolve(n.from),i=_(t.getAttributes,void 0,r)||{};if(!s.node(-1).canReplaceWith(s.index(-1),s.indexAfter(-1),t.type))return null;e.tr.delete(n.from,n.to).setBlockType(n.from,n.from,t.type,i)},undoable:t.undoable})}function Fe(t){return new Gr({find:t.find,handler:({state:e,range:n,match:r,chain:s})=>{let i=_(t.getAttributes,void 0,r)||{},o=e.tr.delete(n.from,n.to),a=o.doc.resolve(n.from).blockRange(),c=a&&Yt(a,t.type,i);if(!c)return null;if(o.wrap(a,c),t.keepMarks&&t.editor){let{selection:d,storedMarks:h}=e,{splittableMarks:f}=t.editor.extensionManager,p=h||d.$to.parentOffset&&d.$from.marks();if(p){let m=p.filter(g=>f.includes(g.type.name));o.ensureMarks(m)}}if(t.keepAttributes){let d=t.type.name==="bulletList"||t.type.name==="orderedList"?"listItem":"taskList";s().updateAttributes(d,i).run()}let u=o.doc.resolve(n.from-1).nodeBefore;u&&u.type===t.type&&Ce(o.doc,n.from-1)&&(!t.joinPredicate||t.joinPredicate(r,u))&&o.join(n.from-1)},undoable:t.undoable})}var xy=t=>"touches"in t,ed=class{constructor(t){this.directions=["bottom-left","bottom-right","top-left","top-right"],this.minSize={height:8,width:8},this.preserveAspectRatio=!1,this.classNames={container:"",wrapper:"",handle:"",resizing:""},this.initialWidth=0,this.initialHeight=0,this.aspectRatio=1,this.isResizing=!1,this.activeHandle=null,this.startX=0,this.startY=0,this.startWidth=0,this.startHeight=0,this.isShiftKeyPressed=!1,this.lastEditableState=void 0,this.handleMap=new Map,this.handleMouseMove=l=>{if(!this.isResizing||!this.activeHandle)return;let a=l.clientX-this.startX,c=l.clientY-this.startY;this.handleResize(a,c)},this.handleTouchMove=l=>{if(!this.isResizing||!this.activeHandle)return;let a=l.touches[0];if(!a)return;let c=a.clientX-this.startX,u=a.clientY-this.startY;this.handleResize(c,u)},this.handleMouseUp=()=>{if(!this.isResizing)return;let l=this.element.offsetWidth,a=this.element.offsetHeight;this.onCommit(l,a),this.isResizing=!1,this.activeHandle=null,this.container.dataset.resizeState="false",this.classNames.resizing&&this.container.classList.remove(this.classNames.resizing),document.removeEventListener("mousemove",this.handleMouseMove),document.removeEventListener("mouseup",this.handleMouseUp),document.removeEventListener("keydown",this.handleKeyDown),document.removeEventListener("keyup",this.handleKeyUp)},this.handleKeyDown=l=>{l.key==="Shift"&&(this.isShiftKeyPressed=!0)},this.handleKeyUp=l=>{l.key==="Shift"&&(this.isShiftKeyPressed=!1)};var e,n,r,s,i,o;this.node=t.node,this.editor=t.editor,this.element=t.element,this.contentElement=t.contentElement,this.getPos=t.getPos,this.onResize=t.onResize,this.onCommit=t.onCommit,this.onUpdate=t.onUpdate,(e=t.options)!=null&&e.min&&(this.minSize={...this.minSize,...t.options.min}),(n=t.options)!=null&&n.max&&(this.maxSize=t.options.max),(r=t?.options)!=null&&r.directions&&(this.directions=t.options.directions),(s=t.options)!=null&&s.preserveAspectRatio&&(this.preserveAspectRatio=t.options.preserveAspectRatio),(i=t.options)!=null&&i.className&&(this.classNames={container:t.options.className.container||"",wrapper:t.options.className.wrapper||"",handle:t.options.className.handle||"",resizing:t.options.className.resizing||""}),(o=t.options)!=null&&o.createCustomHandle&&(this.createCustomHandle=t.options.createCustomHandle),this.wrapper=this.createWrapper(),this.container=this.createContainer(),this.applyInitialSize(),this.attachHandles(),this.editor.on("update",this.handleEditorUpdate.bind(this))}get dom(){return this.container}get contentDOM(){var t;return(t=this.contentElement)!=null?t:null}handleEditorUpdate(){let t=this.editor.isEditable;t!==this.lastEditableState&&(this.lastEditableState=t,t?t&&this.handleMap.size===0&&this.attachHandles():this.removeHandles())}update(t,e,n){return t.type!==this.node.type?!1:(this.node=t,this.onUpdate?this.onUpdate(t,e,n):!0)}destroy(){this.isResizing&&(this.container.dataset.resizeState="false",this.classNames.resizing&&this.container.classList.remove(this.classNames.resizing),document.removeEventListener("mousemove",this.handleMouseMove),document.removeEventListener("mouseup",this.handleMouseUp),document.removeEventListener("keydown",this.handleKeyDown),document.removeEventListener("keyup",this.handleKeyUp),this.isResizing=!1,this.activeHandle=null),this.editor.off("update",this.handleEditorUpdate.bind(this)),this.container.remove()}createContainer(){let t=document.createElement("div");return t.dataset.resizeContainer="",t.dataset.node=this.node.type.name,t.style.display="flex",this.classNames.container&&(t.className=this.classNames.container),t.appendChild(this.wrapper),t}createWrapper(){let t=document.createElement("div");return t.style.position="relative",t.style.display="block",t.dataset.resizeWrapper="",this.classNames.wrapper&&(t.className=this.classNames.wrapper),t.appendChild(this.element),t}createHandle(t){let e=document.createElement("div");return e.dataset.resizeHandle=t,e.style.position="absolute",this.classNames.handle&&(e.className=this.classNames.handle),e}positionHandle(t,e){let n=e.includes("top"),r=e.includes("bottom"),s=e.includes("left"),i=e.includes("right");n&&(t.style.top="0"),r&&(t.style.bottom="0"),s&&(t.style.left="0"),i&&(t.style.right="0"),(e==="top"||e==="bottom")&&(t.style.left="0",t.style.right="0"),(e==="left"||e==="right")&&(t.style.top="0",t.style.bottom="0")}attachHandles(){this.directions.forEach(t=>{let e;this.createCustomHandle?e=this.createCustomHandle(t):e=this.createHandle(t),e instanceof HTMLElement||(console.warn(`[ResizableNodeView] createCustomHandle("${t}") did not return an HTMLElement. Falling back to default handle.`),e=this.createHandle(t)),this.createCustomHandle||this.positionHandle(e,t),e.addEventListener("mousedown",n=>this.handleResizeStart(n,t)),e.addEventListener("touchstart",n=>this.handleResizeStart(n,t)),this.handleMap.set(t,e),this.wrapper.appendChild(e)})}removeHandles(){this.handleMap.forEach(t=>t.remove()),this.handleMap.clear()}applyInitialSize(){let t=this.node.attrs.width,e=this.node.attrs.height;t?(this.element.style.width=`${t}px`,this.initialWidth=t):this.initialWidth=this.element.offsetWidth,e?(this.element.style.height=`${e}px`,this.initialHeight=e):this.initialHeight=this.element.offsetHeight,this.initialWidth>0&&this.initialHeight>0&&(this.aspectRatio=this.initialWidth/this.initialHeight)}handleResizeStart(t,e){t.preventDefault(),t.stopPropagation(),this.isResizing=!0,this.activeHandle=e,xy(t)?(this.startX=t.touches[0].clientX,this.startY=t.touches[0].clientY):(this.startX=t.clientX,this.startY=t.clientY),this.startWidth=this.element.offsetWidth,this.startHeight=this.element.offsetHeight,this.startWidth>0&&this.startHeight>0&&(this.aspectRatio=this.startWidth/this.startHeight);let n=this.getPos();this.container.dataset.resizeState="true",this.classNames.resizing&&this.container.classList.add(this.classNames.resizing),document.addEventListener("mousemove",this.handleMouseMove),document.addEventListener("touchmove",this.handleTouchMove),document.addEventListener("mouseup",this.handleMouseUp),document.addEventListener("keydown",this.handleKeyDown),document.addEventListener("keyup",this.handleKeyUp)}handleResize(t,e){if(!this.activeHandle)return;let n=this.preserveAspectRatio||this.isShiftKeyPressed,{width:r,height:s}=this.calculateNewDimensions(this.activeHandle,t,e),i=this.applyConstraints(r,s,n);this.element.style.width=`${i.width}px`,this.element.style.height=`${i.height}px`,this.onResize&&this.onResize(i.width,i.height)}calculateNewDimensions(t,e,n){let r=this.startWidth,s=this.startHeight,i=t.includes("right"),o=t.includes("left"),l=t.includes("bottom"),a=t.includes("top");return i?r=this.startWidth+e:o&&(r=this.startWidth-e),l?s=this.startHeight+n:a&&(s=this.startHeight-n),(t==="right"||t==="left")&&(r=this.startWidth+(i?e:-e)),(t==="top"||t==="bottom")&&(s=this.startHeight+(l?n:-n)),this.preserveAspectRatio||this.isShiftKeyPressed?this.applyAspectRatio(r,s,t):{width:r,height:s}}applyConstraints(t,e,n){var r,s,i,o;if(!n){let c=Math.max(this.minSize.width,t),u=Math.max(this.minSize.height,e);return(r=this.maxSize)!=null&&r.width&&(c=Math.min(this.maxSize.width,c)),(s=this.maxSize)!=null&&s.height&&(u=Math.min(this.maxSize.height,u)),{width:c,height:u}}let l=t,a=e;return lthis.maxSize.width&&(l=this.maxSize.width,a=l/this.aspectRatio),(o=this.maxSize)!=null&&o.height&&a>this.maxSize.height&&(a=this.maxSize.height,l=a*this.aspectRatio),{width:l,height:a}}applyAspectRatio(t,e,n){let r=n==="left"||n==="right",s=n==="top"||n==="bottom";return r?{width:t,height:t/this.aspectRatio}:s?{width:e*this.aspectRatio,height:e}:{width:t,height:t/this.aspectRatio}}};function td(t,e){let{selection:n}=t,{$from:r}=n;if(n instanceof E){let i=r.index();return r.parent.canReplaceWith(i,i+1,e)}let s=r.depth;for(;s>=0;){let i=r.index(s);if(r.node(s).contentMatchAt(i).matchType(e))return!0;s-=1}return!1}var wy={};Co(wy,{createAtomBlockMarkdownSpec:()=>Sy,createBlockMarkdownSpec:()=>Cy,createInlineMarkdownSpec:()=>vy,parseAttributes:()=>Do,parseIndentedBlocks:()=>Yr,renderNestedMarkdownContent:()=>Un,serializeAttributes:()=>Po});function Do(t){if(!t?.trim())return{};let e={},n=[],r=t.replace(/["']([^"']*)["']/g,c=>(n.push(c),`__QUOTED_${n.length-1}__`)),s=r.match(/(?:^|\s)\.([a-zA-Z][\w-]*)/g);if(s){let c=s.map(u=>u.trim().slice(1));e.class=c.join(" ")}let i=r.match(/(?:^|\s)#([a-zA-Z][\w-]*)/);i&&(e.id=i[1]);let o=/([a-zA-Z][\w-]*)\s*=\s*(__QUOTED_\d+__)/g;Array.from(r.matchAll(o)).forEach(([,c,u])=>{var d;let h=parseInt(((d=u.match(/__QUOTED_(\d+)__/))==null?void 0:d[1])||"0",10),f=n[h];f&&(e[c]=f.slice(1,-1))});let a=r.replace(/(?:^|\s)\.([a-zA-Z][\w-]*)/g,"").replace(/(?:^|\s)#([a-zA-Z][\w-]*)/g,"").replace(/([a-zA-Z][\w-]*)\s*=\s*__QUOTED_\d+__/g,"").trim();return a&&a.split(/\s+/).filter(Boolean).forEach(u=>{u.match(/^[a-zA-Z][\w-]*$/)&&(e[u]=!0)}),e}function Po(t){if(!t||Object.keys(t).length===0)return"";let e=[];return t.class&&String(t.class).split(/\s+/).filter(Boolean).forEach(r=>e.push(`.${r}`)),t.id&&e.push(`#${t.id}`),Object.entries(t).forEach(([n,r])=>{n==="class"||n==="id"||(r===!0?e.push(n):r!==!1&&r!=null&&e.push(`${n}="${String(r)}"`))}),e.join(" ")}function Sy(t){let{nodeName:e,name:n,parseAttributes:r=Do,serializeAttributes:s=Po,defaultAttributes:i={},requiredAttributes:o=[],allowedAttributes:l}=t,a=n||e,c=u=>{if(!l)return u;let d={};return l.forEach(h=>{h in u&&(d[h]=u[h])}),d};return{parseMarkdown:(u,d)=>{let h={...i,...u.attributes};return d.createNode(e,h,[])},markdownTokenizer:{name:e,level:"block",start(u){var d;let h=new RegExp(`^:::${a}(?:\\s|$)`,"m"),f=(d=u.match(h))==null?void 0:d.index;return f!==void 0?f:-1},tokenize(u,d,h){let f=new RegExp(`^:::${a}(?:\\s+\\{([^}]*)\\})?\\s*:::(?:\\n|$)`),p=u.match(f);if(!p)return;let m=p[1]||"",g=r(m);if(!o.find(k=>!(k in g)))return{type:e,raw:p[0],attributes:g}}},renderMarkdown:u=>{let d=c(u.attrs||{}),h=s(d),f=h?` {${h}}`:"";return`:::${a}${f} :::`}}}function Cy(t){let{nodeName:e,name:n,getContent:r,parseAttributes:s=Do,serializeAttributes:i=Po,defaultAttributes:o={},content:l="block",allowedAttributes:a}=t,c=n||e,u=d=>{if(!a)return d;let h={};return a.forEach(f=>{f in d&&(h[f]=d[f])}),h};return{parseMarkdown:(d,h)=>{let f;if(r){let m=r(d);f=typeof m=="string"?[{type:"text",text:m}]:m}else l==="block"?f=h.parseChildren(d.tokens||[]):f=h.parseInline(d.tokens||[]);let p={...o,...d.attributes};return h.createNode(e,p,f)},markdownTokenizer:{name:e,level:"block",start(d){var h;let f=new RegExp(`^:::${c}`,"m"),p=(h=d.match(f))==null?void 0:h.index;return p!==void 0?p:-1},tokenize(d,h,f){var p;let m=new RegExp(`^:::${c}(?:\\s+\\{([^}]*)\\})?\\s*\\n`),g=d.match(m);if(!g)return;let[y,k=""]=g,w=s(k),M=1,S=y.length,N="",v=/^:::([\w-]*)(\s.*)?/gm,D=d.slice(S);for(v.lastIndex=0;;){let z=v.exec(D);if(z===null)break;let ke=z.index,Kt=z[1];if(!((p=z[2])!=null&&p.endsWith(":::"))){if(Kt)M+=1;else if(M-=1,M===0){let lt=D.slice(0,ke);N=lt.trim();let Ut=d.slice(0,S+ke+z[0].length),Se=[];if(N)if(l==="block")for(Se=f.blockTokens(lt),Se.forEach(pe=>{pe.text&&(!pe.tokens||pe.tokens.length===0)&&(pe.tokens=f.inlineTokens(pe.text))});Se.length>0;){let pe=Se[Se.length-1];if(pe.type==="paragraph"&&(!pe.text||pe.text.trim()===""))Se.pop();else break}else Se=f.inlineTokens(N);return{type:e,raw:Ut,attributes:w,content:N,tokens:Se}}}}}},renderMarkdown:(d,h)=>{let f=u(d.attrs||{}),p=i(f),m=p?` {${p}}`:"",g=h.renderChildren(d.content||[],` +`,textSerializers:t={}}=n||{};return k0(this.state.doc,{blockSeparator:e,textSerializers:{..._d(this.schema),...t}})}get isEmpty(){return vn(this.state.doc)}destroy(){this.destroyed||(this.destroyed=!0,this.emit("destroy"),this.unmount(),this.removeAllListeners(),this.extensionManager.destroy(),this.extensionManager=null,this.schema=null,this.commandManager=null,this.extensionStorage={})}get isDestroyed(){var n,e;return(e=(n=this.editorView)==null?void 0:n.isDestroyed)!=null?e:!0}$node(n,e){var t;return((t=this.$doc)==null?void 0:t.querySelector(n,e))||null}$nodes(n,e){var t;return((t=this.$doc)==null?void 0:t.querySelectorAll(n,e))||null}$pos(n){let e=this.state.doc.resolve(n),t=n>0&&e.nodeAfter&&!e.nodeAfter.isText?e.nodeAfter:null;return new ik(e,this,!1,t)}get $doc(){return this.$pos(0)}};function qe(n){return new Cs({find:n.find,handler:({state:e,range:t,match:r})=>{let s=F(n.getAttributes,void 0,r);if(s===!1||s===null)return null;let{tr:i}=e,o=r[r.length-1],l=r[0];if(o){let a=l.search(/\S/),c=t.from+l.indexOf(o),u=c+o.length;if(ws(t.from,t.to,e.doc).filter(f=>f.mark.type.excluded.find(m=>m===n.type&&m!==f.mark.type)).filter(f=>f.to>c).length)return null;ut.from&&i.delete(t.from+a,c);let h=t.from+a+o.length;i.addMark(t.from+a,h,n.type.create(s||{})),i.removeStoredMark(n.type)}},undoable:n.undoable})}function Ts(n){return new Cs({find:n.find,handler:({state:e,range:t,match:r})=>{let s=F(n.getAttributes,void 0,r)||{},{tr:i}=e,o=t.from,l=t.to,a=n.type.create(s);if(r[1]){let c=r[0].lastIndexOf(r[1]),u=o+c;u>l?u=l:l=u+r[1].length;let d=r[0][r[0].length-1];i.insertText(d,o+r[0].length-1),i.replaceWith(u,l,a)}else if(r[0]){let c=n.type.isInline?o:o-1;i.insert(c,n.type.create(s)).delete(i.mapping.map(o),i.mapping.map(l))}i.scrollIntoView()},undoable:n.undoable})}function cr(n){return new Cs({find:n.find,handler:({state:e,range:t,match:r})=>{let s=e.doc.resolve(t.from),i=F(n.getAttributes,void 0,r)||{};if(!s.node(-1).canReplaceWith(s.index(-1),s.indexAfter(-1),n.type))return null;e.tr.delete(t.from,t.to).setBlockType(t.from,t.from,n.type,i)},undoable:n.undoable})}function Ue(n){return new Cs({find:n.find,handler:({state:e,range:t,match:r,chain:s})=>{let i=F(n.getAttributes,void 0,r)||{},o=e.tr.delete(t.from,t.to),a=o.doc.resolve(t.from).blockRange(),c=a&&hn(a,n.type,i);if(!c)return null;if(o.wrap(a,c),n.keepMarks&&n.editor){let{selection:d,storedMarks:h}=e,{splittableMarks:f}=n.editor.extensionManager,p=h||d.$to.parentOffset&&d.$from.marks();if(p){let m=p.filter(g=>f.includes(g.type.name));o.ensureMarks(m)}}if(n.keepAttributes){let d=n.type.name==="bulletList"||n.type.name==="orderedList"?"listItem":"taskList";s().updateAttributes(d,i).run()}let u=o.doc.resolve(t.from-1).nodeBefore;u&&u.type===n.type&&Re(o.doc,t.from-1)&&(!n.joinPredicate||n.joinPredicate(r,u))&&o.join(t.from-1)},undoable:n.undoable})}var ak=n=>"touches"in n,lh=class{constructor(n){this.directions=["bottom-left","bottom-right","top-left","top-right"],this.minSize={height:8,width:8},this.preserveAspectRatio=!1,this.classNames={container:"",wrapper:"",handle:"",resizing:""},this.initialWidth=0,this.initialHeight=0,this.aspectRatio=1,this.isResizing=!1,this.activeHandle=null,this.startX=0,this.startY=0,this.startWidth=0,this.startHeight=0,this.isShiftKeyPressed=!1,this.lastEditableState=void 0,this.handleMap=new Map,this.handleMouseMove=l=>{if(!this.isResizing||!this.activeHandle)return;let a=l.clientX-this.startX,c=l.clientY-this.startY;this.handleResize(a,c)},this.handleTouchMove=l=>{if(!this.isResizing||!this.activeHandle)return;let a=l.touches[0];if(!a)return;let c=a.clientX-this.startX,u=a.clientY-this.startY;this.handleResize(c,u)},this.handleMouseUp=()=>{if(!this.isResizing)return;let l=this.element.offsetWidth,a=this.element.offsetHeight;this.onCommit(l,a),this.isResizing=!1,this.activeHandle=null,this.container.dataset.resizeState="false",this.classNames.resizing&&this.container.classList.remove(this.classNames.resizing),document.removeEventListener("mousemove",this.handleMouseMove),document.removeEventListener("mouseup",this.handleMouseUp),document.removeEventListener("keydown",this.handleKeyDown),document.removeEventListener("keyup",this.handleKeyUp)},this.handleKeyDown=l=>{l.key==="Shift"&&(this.isShiftKeyPressed=!0)},this.handleKeyUp=l=>{l.key==="Shift"&&(this.isShiftKeyPressed=!1)};var e,t,r,s,i,o;this.node=n.node,this.editor=n.editor,this.element=n.element,this.element.draggable=!1,this.contentElement=n.contentElement,this.getPos=n.getPos,this.onResize=n.onResize,this.onCommit=n.onCommit,this.onUpdate=n.onUpdate,(e=n.options)!=null&&e.min&&(this.minSize={...this.minSize,...n.options.min}),(t=n.options)!=null&&t.max&&(this.maxSize=n.options.max),(r=n?.options)!=null&&r.directions&&(this.directions=n.options.directions),(s=n.options)!=null&&s.preserveAspectRatio&&(this.preserveAspectRatio=n.options.preserveAspectRatio),(i=n.options)!=null&&i.className&&(this.classNames={container:n.options.className.container||"",wrapper:n.options.className.wrapper||"",handle:n.options.className.handle||"",resizing:n.options.className.resizing||""}),(o=n.options)!=null&&o.createCustomHandle&&(this.createCustomHandle=n.options.createCustomHandle),this.wrapper=this.createWrapper(),this.container=this.createContainer(),this.applyInitialSize(),this.attachHandles(),this.editor.on("update",this.handleEditorUpdate.bind(this))}get dom(){return this.container}get contentDOM(){var n;return(n=this.contentElement)!=null?n:null}handleEditorUpdate(){let n=this.editor.isEditable;n!==this.lastEditableState&&(this.lastEditableState=n,n?n&&this.handleMap.size===0&&this.attachHandles():this.removeHandles())}update(n,e,t){return n.type!==this.node.type?!1:(this.node=n,this.onUpdate?this.onUpdate(n,e,t):!0)}destroy(){this.isResizing&&(this.container.dataset.resizeState="false",this.classNames.resizing&&this.container.classList.remove(this.classNames.resizing),document.removeEventListener("mousemove",this.handleMouseMove),document.removeEventListener("mouseup",this.handleMouseUp),document.removeEventListener("keydown",this.handleKeyDown),document.removeEventListener("keyup",this.handleKeyUp),this.isResizing=!1,this.activeHandle=null),this.editor.off("update",this.handleEditorUpdate.bind(this)),this.container.remove()}createContainer(){let n=document.createElement("div");return n.dataset.resizeContainer="",n.dataset.node=this.node.type.name,n.style.display=this.node.type.isInline?"inline-flex":"flex",this.classNames.container&&(n.className=this.classNames.container),n.appendChild(this.wrapper),n}createWrapper(){let n=document.createElement("div");return n.style.position="relative",n.style.display="block",n.dataset.resizeWrapper="",this.classNames.wrapper&&(n.className=this.classNames.wrapper),n.appendChild(this.element),n}createHandle(n){let e=document.createElement("div");return e.dataset.resizeHandle=n,e.style.position="absolute",this.classNames.handle&&(e.className=this.classNames.handle),e}positionHandle(n,e){let t=e.includes("top"),r=e.includes("bottom"),s=e.includes("left"),i=e.includes("right");t&&(n.style.top="0"),r&&(n.style.bottom="0"),s&&(n.style.left="0"),i&&(n.style.right="0"),(e==="top"||e==="bottom")&&(n.style.left="0",n.style.right="0"),(e==="left"||e==="right")&&(n.style.top="0",n.style.bottom="0")}attachHandles(){this.directions.forEach(n=>{let e;this.createCustomHandle?e=this.createCustomHandle(n):e=this.createHandle(n),e instanceof HTMLElement||(console.warn(`[ResizableNodeView] createCustomHandle("${n}") did not return an HTMLElement. Falling back to default handle.`),e=this.createHandle(n)),this.createCustomHandle||this.positionHandle(e,n),e.addEventListener("mousedown",t=>this.handleResizeStart(t,n)),e.addEventListener("touchstart",t=>this.handleResizeStart(t,n)),this.handleMap.set(n,e),this.wrapper.appendChild(e)})}removeHandles(){this.handleMap.forEach(n=>n.remove()),this.handleMap.clear()}applyInitialSize(){let n=this.node.attrs.width,e=this.node.attrs.height;n?(this.element.style.width=`${n}px`,this.initialWidth=n):this.initialWidth=this.element.offsetWidth,e?(this.element.style.height=`${e}px`,this.initialHeight=e):this.initialHeight=this.element.offsetHeight,this.initialWidth>0&&this.initialHeight>0&&(this.aspectRatio=this.initialWidth/this.initialHeight)}handleResizeStart(n,e){n.preventDefault(),n.stopPropagation(),this.isResizing=!0,this.activeHandle=e,ak(n)?(this.startX=n.touches[0].clientX,this.startY=n.touches[0].clientY):(this.startX=n.clientX,this.startY=n.clientY),this.startWidth=this.element.offsetWidth,this.startHeight=this.element.offsetHeight,this.startWidth>0&&this.startHeight>0&&(this.aspectRatio=this.startWidth/this.startHeight);let t=this.getPos();this.container.dataset.resizeState="true",this.classNames.resizing&&this.container.classList.add(this.classNames.resizing),document.addEventListener("mousemove",this.handleMouseMove),document.addEventListener("touchmove",this.handleTouchMove),document.addEventListener("mouseup",this.handleMouseUp),document.addEventListener("keydown",this.handleKeyDown),document.addEventListener("keyup",this.handleKeyUp)}handleResize(n,e){if(!this.activeHandle)return;let t=this.preserveAspectRatio||this.isShiftKeyPressed,{width:r,height:s}=this.calculateNewDimensions(this.activeHandle,n,e),i=this.applyConstraints(r,s,t);this.element.style.width=`${i.width}px`,this.element.style.height=`${i.height}px`,this.onResize&&this.onResize(i.width,i.height)}calculateNewDimensions(n,e,t){let r=this.startWidth,s=this.startHeight,i=n.includes("right"),o=n.includes("left"),l=n.includes("bottom"),a=n.includes("top");return i?r=this.startWidth+e:o&&(r=this.startWidth-e),l?s=this.startHeight+t:a&&(s=this.startHeight-t),(n==="right"||n==="left")&&(r=this.startWidth+(i?e:-e)),(n==="top"||n==="bottom")&&(s=this.startHeight+(l?t:-t)),this.preserveAspectRatio||this.isShiftKeyPressed?this.applyAspectRatio(r,s,n):{width:r,height:s}}applyConstraints(n,e,t){var r,s,i,o;if(!t){let c=Math.max(this.minSize.width,n),u=Math.max(this.minSize.height,e);return(r=this.maxSize)!=null&&r.width&&(c=Math.min(this.maxSize.width,c)),(s=this.maxSize)!=null&&s.height&&(u=Math.min(this.maxSize.height,u)),{width:c,height:u}}let l=n,a=e;return lthis.maxSize.width&&(l=this.maxSize.width,a=l/this.aspectRatio),(o=this.maxSize)!=null&&o.height&&a>this.maxSize.height&&(a=this.maxSize.height,l=a*this.aspectRatio),{width:l,height:a}}applyAspectRatio(n,e,t){let r=t==="left"||t==="right",s=t==="top"||t==="bottom";return r?{width:n,height:n/this.aspectRatio}:s?{width:e*this.aspectRatio,height:e}:{width:n,height:n/this.aspectRatio}}};function Tt(n,e){if(n===e)return!0;if(!n||!e)return!1;let t=Object.keys(n),r=Object.keys(e);return t.length!==r.length?!1:t.every(s=>Object.prototype.hasOwnProperty.call(e,s)&&Object.is(n[s],e[s]))}function ah(n,e){let{selection:t}=n,{$from:r}=t;if(t instanceof N){let i=r.index();return r.parent.canReplaceWith(i,i+1,e)}let s=r.depth;for(;s>=0;){let i=r.index(s);if(r.node(s).contentMatchAt(i).matchType(e))return!0;s-=1}return!1}function wl(n){return n.replace(/</g,"<").replace(/>/g,">").replace(/"/g,'"').replace(/&/g,"&")}function ch(n){return n.replace(/&/g,"&").replace(//g,">")}var ck={};cl(ck,{createAtomBlockMarkdownSpec:()=>uk,createBlockMarkdownSpec:()=>dk,createInlineMarkdownSpec:()=>pk,parseAttributes:()=>Sl,parseIndentedBlocks:()=>Ms,renderNestedMarkdownContent:()=>ur,serializeAttributes:()=>Cl});function Sl(n){if(!n?.trim())return{};let e={},t=[],r=n.replace(/["']([^"']*)["']/g,c=>(t.push(c),`__QUOTED_${t.length-1}__`)),s=r.match(/(?:^|\s)\.([a-zA-Z][\w-]*)/g);if(s){let c=s.map(u=>u.trim().slice(1));e.class=c.join(" ")}let i=r.match(/(?:^|\s)#([a-zA-Z][\w-]*)/);i&&(e.id=i[1]);let o=/([a-zA-Z][\w-]*)\s*=\s*(__QUOTED_\d+__)/g;Array.from(r.matchAll(o)).forEach(([,c,u])=>{var d;let h=parseInt(((d=u.match(/__QUOTED_(\d+)__/))==null?void 0:d[1])||"0",10),f=t[h];f&&(e[c]=f.slice(1,-1))});let a=r.replace(/(?:^|\s)\.([a-zA-Z][\w-]*)/g,"").replace(/(?:^|\s)#([a-zA-Z][\w-]*)/g,"").replace(/([a-zA-Z][\w-]*)\s*=\s*__QUOTED_\d+__/g,"").trim();return a&&a.split(/\s+/).filter(Boolean).forEach(u=>{u.match(/^[a-zA-Z][\w-]*$/)&&(e[u]=!0)}),e}function Cl(n){if(!n||Object.keys(n).length===0)return"";let e=[];return n.class&&String(n.class).split(/\s+/).filter(Boolean).forEach(r=>e.push(`.${r}`)),n.id&&e.push(`#${n.id}`),Object.entries(n).forEach(([t,r])=>{t==="class"||t==="id"||(r===!0?e.push(t):r!==!1&&r!=null&&e.push(`${t}="${String(r)}"`))}),e.join(" ")}function uk(n){let{nodeName:e,name:t,parseAttributes:r=Sl,serializeAttributes:s=Cl,defaultAttributes:i={},requiredAttributes:o=[],allowedAttributes:l}=n,a=t||e,c=u=>{if(!l)return u;let d={};return l.forEach(h=>{h in u&&(d[h]=u[h])}),d};return{parseMarkdown:(u,d)=>{let h={...i,...u.attributes};return d.createNode(e,h,[])},markdownTokenizer:{name:e,level:"block",start(u){var d;let h=new RegExp(`^:::${a}(?:\\s|$)`,"m"),f=(d=u.match(h))==null?void 0:d.index;return f!==void 0?f:-1},tokenize(u,d,h){let f=new RegExp(`^:::${a}(?:\\s+\\{([^}]*)\\})?\\s*:::(?:\\n|$)`),p=u.match(f);if(!p)return;let m=p[1]||"",g=r(m);if(!o.find(k=>!(k in g)))return{type:e,raw:p[0],attributes:g}}},renderMarkdown:u=>{let d=c(u.attrs||{}),h=s(d),f=h?` {${h}}`:"";return`:::${a}${f} :::`}}}function dk(n){let{nodeName:e,name:t,getContent:r,parseAttributes:s=Sl,serializeAttributes:i=Cl,defaultAttributes:o={},content:l="block",allowedAttributes:a}=n,c=t||e,u=d=>{if(!a)return d;let h={};return a.forEach(f=>{f in d&&(h[f]=d[f])}),h};return{parseMarkdown:(d,h)=>{let f;if(r){let m=r(d);f=typeof m=="string"?[{type:"text",text:m}]:m}else l==="block"?f=h.parseChildren(d.tokens||[]):f=h.parseInline(d.tokens||[]);let p={...o,...d.attributes};return h.createNode(e,p,f)},markdownTokenizer:{name:e,level:"block",start(d){var h;let f=new RegExp(`^:::${c}`,"m"),p=(h=d.match(f))==null?void 0:h.index;return p!==void 0?p:-1},tokenize(d,h,f){var p;let m=new RegExp(`^:::${c}(?:\\s+\\{([^}]*)\\})?\\s*\\n`),g=d.match(m);if(!g)return;let[y,k=""]=g,w=s(k),C=1,x=y.length,M="",T=/^:::([\w-]*)(\s.*)?/gm,R=d.slice(x);for(T.lastIndex=0;;){let P=T.exec(R);if(P===null)break;let te=P.index,tt=P[1];if(!((p=P[2])!=null&&p.endsWith(":::"))){if(tt)C+=1;else if(C-=1,C===0){let We=R.slice(0,te);M=We.trim();let ue=d.slice(0,x+te+P[0].length),ne=[];if(M)if(l==="block")for(ne=f.blockTokens(We),ne.forEach(Q=>{Q.text&&(!Q.tokens||Q.tokens.length===0)&&(Q.tokens=f.inlineTokens(Q.text))});ne.length>0;){let Q=ne[ne.length-1];if(Q.type==="paragraph"&&(!Q.text||Q.text.trim()===""))ne.pop();else break}else ne=f.inlineTokens(M);return{type:e,raw:ue,attributes:w,content:M,tokens:ne}}}}}},renderMarkdown:(d,h)=>{let f=u(d.attrs||{}),p=i(f),m=p?` {${p}}`:"",g=h.renderChildren(d.content||[],` `);return`:::${c}${m} ${g} -:::`}}}function My(t){if(!t.trim())return{};let e={},n=/(\w+)=(?:"([^"]*)"|'([^']*)')/g,r=n.exec(t);for(;r!==null;){let[,s,i,o]=r;e[s]=i||o,r=n.exec(t)}return e}function Ty(t){return Object.entries(t).filter(([,e])=>e!=null).map(([e,n])=>`${e}="${n}"`).join(" ")}function vy(t){let{nodeName:e,name:n,getContent:r,parseAttributes:s=My,serializeAttributes:i=Ty,defaultAttributes:o={},selfClosing:l=!1,allowedAttributes:a}=t,c=n||e,u=h=>{if(!a)return h;let f={};return a.forEach(p=>{let m=typeof p=="string"?p:p.name,g=typeof p=="string"?void 0:p.skipIfDefault;if(m in h){let y=h[m];if(g!==void 0&&y===g)return;f[m]=y}}),f},d=c.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return{parseMarkdown:(h,f)=>{let p={...o,...h.attributes};if(l)return f.createNode(e,p);let m=r?r(h):h.content||"";return m?f.createNode(e,p,[f.createTextNode(m)]):f.createNode(e,p,[])},markdownTokenizer:{name:e,level:"inline",start(h){let f=l?new RegExp(`\\[${d}\\s*[^\\]]*\\]`):new RegExp(`\\[${d}\\s*[^\\]]*\\][\\s\\S]*?\\[\\/${d}\\]`),p=h.match(f),m=p?.index;return m!==void 0?m:-1},tokenize(h,f,p){let m=l?new RegExp(`^\\[${d}\\s*([^\\]]*)\\]`):new RegExp(`^\\[${d}\\s*([^\\]]*)\\]([\\s\\S]*?)\\[\\/${d}\\]`),g=h.match(m);if(!g)return;let y="",k="";if(l){let[,M]=g;k=M}else{let[,M,S]=g;k=M,y=S||""}let w=s(k.trim());return{type:e,raw:g[0],content:y.trim(),attributes:w}}},renderMarkdown:h=>{let f="";r?f=r(h):h.content&&h.content.length>0&&(f=h.content.filter(y=>y.type==="text").map(y=>y.text).join(""));let p=u(h.attrs||{}),m=i(p),g=m?` ${m}`:"";return l?`[${c}${g}]`:`[${c}${g}]${f}[/${c}]`}}}function Yr(t,e,n){var r,s,i,o;let l=t.split(` +:::`}}}function hk(n){if(!n.trim())return{};let e={},t=/(\w+)=(?:"([^"]*)"|'([^']*)')/g,r=t.exec(n);for(;r!==null;){let[,s,i,o]=r;e[s]=i||o,r=t.exec(n)}return e}function fk(n){return Object.entries(n).filter(([,e])=>e!=null).map(([e,t])=>`${e}="${t}"`).join(" ")}function pk(n){let{nodeName:e,name:t,getContent:r,parseAttributes:s=hk,serializeAttributes:i=fk,defaultAttributes:o={},selfClosing:l=!1,allowedAttributes:a}=n,c=t||e,u=h=>{if(!a)return h;let f={};return a.forEach(p=>{let m=typeof p=="string"?p:p.name,g=typeof p=="string"?void 0:p.skipIfDefault;if(m in h){let y=h[m];if(g!==void 0&&y===g)return;f[m]=y}}),f},d=c.replace(/[.*+?^${}()|[\]\\]/g,"\\$&");return{parseMarkdown:(h,f)=>{let p={...o,...h.attributes};if(l)return f.createNode(e,p);let m=r?r(h):h.content||"";return m?f.createNode(e,p,[f.createTextNode(m)]):f.createNode(e,p,[])},markdownTokenizer:{name:e,level:"inline",start(h){let f=l?new RegExp(`\\[${d}\\s*[^\\]]*\\]`):new RegExp(`\\[${d}\\s*[^\\]]*\\][\\s\\S]*?\\[\\/${d}\\]`),p=h.match(f),m=p?.index;return m!==void 0?m:-1},tokenize(h,f,p){let m=l?new RegExp(`^\\[${d}\\s*([^\\]]*)\\]`):new RegExp(`^\\[${d}\\s*([^\\]]*)\\]([\\s\\S]*?)\\[\\/${d}\\]`),g=h.match(m);if(!g)return;let y="",k="";if(l){let[,C]=g;k=C}else{let[,C,x]=g;k=C,y=x||""}let w=s(k.trim());return{type:e,raw:g[0],content:y.trim(),attributes:w}}},renderMarkdown:h=>{let f="";r?f=r(h):h.content&&h.content.length>0&&(f=h.content.filter(y=>y.type==="text").map(y=>y.text).join(""));let p=u(h.attrs||{}),m=i(p),g=m?` ${m}`:"";return l?`[${c}${g}]`:`[${c}${g}]${f}[/${c}]`}}}function Ms(n,e,t){var r,s,i,o;let l=n.split(` `),a=[],c="",u=0,d=e.baseIndentSize||2;for(;u0)break;if(h.trim()===""){u+=1,c=`${c}${h} `;continue}else return}let p=e.extractItemData(f),{indentLevel:m,mainContent:g}=p;c=`${c}${h} -`;let y=[g];for(u+=1;uke.trim()!=="");if(v===-1)break;if((((s=(r=l[u+1+v].match(/^(\s*)/))==null?void 0:r[1])==null?void 0:s.length)||0)>m){y.push(S),c=`${c}${S} -`,u+=1;continue}else break}if((((o=(i=S.match(/^(\s*)/))==null?void 0:i[1])==null?void 0:o.length)||0)>m)y.push(S),c=`${c}${S} -`,u+=1;else break}let k,w=y.slice(1);if(w.length>0){let S=w.map(N=>N.slice(m+d)).join(` -`);S.trim()&&(e.customNestedParser?k=e.customNestedParser(S):k=n.blockTokens(S))}let M=e.createToken(p,k);a.push(M)}if(a.length!==0)return{items:a,raw:c}}function Un(t,e,n,r){if(!t||!Array.isArray(t.content))return"";let s=typeof n=="function"?n(r):n,[i,...o]=t.content,l=e.renderChildren([i]),a=[`${s}${l}`];return o&&o.length>0&&o.forEach(c=>{let u=e.renderChildren([c]);if(u){let d=u.split(` -`).map(h=>h?e.indent(h):"").join(` -`);a.push(d)}}),a.join(` -`)}function Ay(t,e,n={}){let{state:r}=e,{doc:s,tr:i}=r,o=t;s.descendants((l,a)=>{let c=i.mapping.map(a),u=i.mapping.map(a)+l.nodeSize,d=null;if(l.marks.forEach(f=>{if(f!==o)return!1;d=f}),!d)return;let h=!1;if(Object.keys(n).forEach(f=>{n[f]!==d.attrs[f]&&(h=!0)}),h){let f=t.type.create({...t.attrs,...n});i.removeMark(c,u,t.type),i.addMark(c,u,f)}}),i.docChanged&&e.view.dispatch(i)}var $=class nd extends Io{constructor(){super(...arguments),this.type="node"}static create(e={}){let n=typeof e=="function"?e():e;return new nd(n)}configure(e){return super.configure(e)}extend(e){let n=typeof e=="function"?e():e;return super.extend(n)}};function Ne(t){return new uy({find:t.find,handler:({state:e,range:n,match:r,pasteEvent:s})=>{let i=_(t.getAttributes,void 0,r,s);if(i===!1||i===null)return null;let{tr:o}=e,l=r[r.length-1],a=r[0],c=n.to;if(l){let u=a.search(/\S/),d=n.from+a.indexOf(l),h=d+l.length;if(qr(n.from,n.to,e.doc).filter(p=>p.mark.type.excluded.find(g=>g===t.type&&g!==p.mark.type)).filter(p=>p.to>d).length)return null;hn.from&&o.delete(n.from+u,d),c=n.from+u+l.length,o.addMark(n.from+u,c,t.type.create(i||{})),o.removeStoredMark(t.type)}}})}var hn=(t,e)=>{if(t==="slot")return 0;if(t instanceof Function)return t(e);let{children:n,...r}=e??{};if(t==="svg")throw new Error("SVG elements are not supported in the JSX syntax, use the array syntax instead");return[t,r,n]};var Ey=/^\s*>\s$/,rd=$.create({name:"blockquote",addOptions(){return{HTMLAttributes:{}}},content:"block+",group:"block",defining:!0,parseHTML(){return[{tag:"blockquote"}]},renderHTML({HTMLAttributes:t}){return hn("blockquote",{...I(this.options.HTMLAttributes,t),children:hn("slot",{})})},parseMarkdown:(t,e)=>e.createNode("blockquote",void 0,e.parseChildren(t.tokens||[])),renderMarkdown:(t,e)=>{if(!t.content)return"";let n=">",r=[];return t.content.forEach(s=>{let l=e.renderChildren([s]).split(` -`).map(a=>a.trim()===""?n:`${n} ${a}`);r.push(l.join(` +`;let y=[g];for(u+=1;ute.trim()!=="");if(T===-1)break;if((((s=(r=l[u+1+T].match(/^(\s*)/))==null?void 0:r[1])==null?void 0:s.length)||0)>m){y.push(x),c=`${c}${x} +`,u+=1;continue}else break}if((((o=(i=x.match(/^(\s*)/))==null?void 0:i[1])==null?void 0:o.length)||0)>m)y.push(x),c=`${c}${x} +`,u+=1;else break}let k,w=y.slice(1);if(w.length>0){let x=w.map(M=>M.slice(m+d)).join(` +`);x.trim()&&(e.customNestedParser?k=e.customNestedParser(x):k=t.blockTokens(x))}let C=e.createToken(p,k);a.push(C)}if(a.length!==0)return{items:a,raw:c}}function ur(n,e,t,r){if(!n||!Array.isArray(n.content))return"";let s=typeof t=="function"?t(r):t,[i,...o]=n.content,l=e.renderChildren([i]),a=`${s}${l}`;return o&&o.length>0&&o.forEach((c,u)=>{var d,h;let f=(h=(d=e.renderChild)==null?void 0:d.call(e,c,u+1))!=null?h:e.renderChildren([c]);if(f!=null){let p=f.split(` +`).map(m=>m?e.indent(m):e.indent("")).join(` +`);a+=c.type==="paragraph"?` + +${p}`:` +${p}`}}),a}function uh(n,e){return n.length!==e.length?!1:n.every((t,r)=>{let s=e[r];return t.type===s.type&&Tt(t.attrs,s.attrs)})}function mk(n,e,t={}){let{state:r}=e,{doc:s,tr:i}=r,o=n;s.descendants((l,a)=>{let c=i.mapping.map(a),u=i.mapping.map(a)+l.nodeSize,d=null;if(l.marks.forEach(f=>{if(f!==o)return!1;d=f}),!d)return;let h=!1;if(Object.keys(t).forEach(f=>{t[f]!==d.attrs[f]&&(h=!0)}),h){let f=n.type.create({...n.attrs,...t});i.removeMark(c,u,n.type),i.addMark(c,u,f)}}),i.docChanged&&e.view.dispatch(i)}var $=class dh extends bl{constructor(){super(...arguments),this.type="node"}static create(e={}){let t=typeof e=="function"?e():e;return new dh(t)}configure(e){return super.configure(e)}extend(e){let t=typeof e=="function"?e():e;return super.extend(t)}};function ze(n){return new Z0({find:n.find,handler:({state:e,range:t,match:r,pasteEvent:s})=>{let i=F(n.getAttributes,void 0,r,s);if(i===!1||i===null)return null;let{tr:o}=e,l=r[r.length-1],a=r[0],c=t.to;if(l){let u=a.search(/\S/),d=t.from+a.indexOf(l),h=d+l.length;if(ws(t.from,t.to,e.doc).filter(m=>m.mark.type.excluded.find(y=>y===n.type&&y!==m.mark.type)).filter(m=>m.to>d).length)return null;ht.from&&o.delete(t.from+u,d),c=t.from+u+l.length,o.addMark(t.from+u,c,n.type.create(i||{})),r.index!==void 0&&r.input!==void 0&&r.index+r[0].length>=r.input.length||o.removeStoredMark(n.type)}}})}var Tn=(n,e)=>{if(n==="slot")return 0;if(n instanceof Function)return n(e);let{children:t,...r}=e??{};if(n==="svg")throw new Error("SVG elements are not supported in the JSX syntax, use the array syntax instead");return[n,r,t]};function Sh(n,e,t){for(let r=0;;r++){if(r==n.childCount||r==e.childCount)return n.childCount==e.childCount?null:t;let s=n.child(r),i=e.child(r);if(s==i){t+=s.nodeSize;continue}if(!s.sameMarkup(i))return t;if(s.isText&&s.text!=i.text){for(let o=0;s.text[o]==i.text[o];o++)t++;return t}if(s.content.size||i.content.size){let o=Sh(s.content,i.content,t+1);if(o!=null)return o}t+=s.nodeSize}}function Ch(n,e,t,r){for(let s=n.childCount,i=e.childCount;;){if(s==0||i==0)return s==i?null:{a:t,b:r};let o=n.child(--s),l=e.child(--i),a=o.nodeSize;if(o==l){t-=a,r-=a;continue}if(!o.sameMarkup(l))return{a:t,b:r};if(o.isText&&o.text!=l.text){let c=0,u=Math.min(o.text.length,l.text.length);for(;ce&&r(a,s+l,i||null,o)!==!1&&a.content.size){let u=l+1;a.nodesBetween(Math.max(0,e-u),Math.min(a.content.size,t-u),r,s+u)}l=c}}descendants(e){this.nodesBetween(0,this.size,e)}textBetween(e,t,r,s){let i="",o=!0;return this.nodesBetween(e,t,(l,a)=>{let c=l.isText?l.text.slice(Math.max(e,a)-a,t-a):l.isLeaf?s?typeof s=="function"?s(l):s:l.type.spec.leafText?l.type.spec.leafText(l):"":"";l.isBlock&&(l.isLeaf&&c||l.isTextblock)&&r&&(o?o=!1:i+=r),i+=c},0),i}append(e){if(!e.size)return this;if(!this.size)return e;let t=this.lastChild,r=e.firstChild,s=this.content.slice(),i=0;for(t.isText&&t.sameMarkup(r)&&(s[s.length-1]=t.withText(t.text+r.text),i=1);ie)for(let i=0,o=0;oe&&((ot)&&(l.isText?l=l.cut(Math.max(0,e-o),Math.min(l.text.length,t-o)):l=l.cut(Math.max(0,e-o-1),Math.min(l.content.size,t-o-1))),r.push(l),s+=l.nodeSize),o=a}return new we(r,s)}cutByIndex(e,t){return e==t?we.empty:e==0&&t==this.content.length?this:new we(this.content.slice(e,t))}replaceChild(e,t){let r=this.content[e];if(r==t)return this;let s=this.content.slice(),i=this.size+t.nodeSize-r.nodeSize;return s[e]=t,new we(s,i)}addToStart(e){return new we([e].concat(this.content),this.size+e.nodeSize)}addToEnd(e){return new we(this.content.concat(e),this.size+e.nodeSize)}eq(e){if(this.content.length!=e.content.length)return!1;for(let t=0;tthis.size||e<0)throw new RangeError(`Position ${e} outside of fragment (${this})`);for(let t=0,r=0;;t++){let s=this.child(t),i=r+s.nodeSize;if(i>=e)return i==e?As(t+1,i):As(t,r);r=i}}toString(){return"<"+this.toStringInner()+">"}toStringInner(){return this.content.join(", ")}toJSON(){return this.content.length?this.content.map(e=>e.toJSON()):null}static fromJSON(e,t){if(!t)return we.empty;if(!Array.isArray(t))throw new RangeError("Invalid input for Fragment.fromJSON");return we.fromArray(t.map(e.nodeFromJSON))}static fromArray(e){if(!e.length)return we.empty;let t,r=0;for(let s=0;sthis.type.rank&&(t||(t=e.slice(0,s)),t.push(this),r=!0),t&&t.push(i)}}return t||(t=e.slice()),r||t.push(this),t}removeFromSet(e){for(let t=0;tr.type.rank-s.type.rank),t}};Kt.none=[];var Ps=class extends Error{},G=class Mn{constructor(e,t,r){this.content=e,this.openStart=t,this.openEnd=r}get size(){return this.content.size-this.openStart-this.openEnd}insertAt(e,t){let r=Th(this.content,e+this.openStart,t,this.openStart+1,this.openEnd+1);return r&&new Mn(r,this.openStart,this.openEnd)}removeBetween(e,t){return new Mn(vh(this.content,e+this.openStart,t+this.openStart),this.openStart,this.openEnd)}eq(e){return this.content.eq(e.content)&&this.openStart==e.openStart&&this.openEnd==e.openEnd}toString(){return this.content+"("+this.openStart+","+this.openEnd+")"}toJSON(){if(!this.content.size)return null;let e={content:this.content.toJSON()};return this.openStart>0&&(e.openStart=this.openStart),this.openEnd>0&&(e.openEnd=this.openEnd),e}static fromJSON(e,t){if(!t)return Mn.empty;let r=t.openStart||0,s=t.openEnd||0;if(typeof r!="number"||typeof s!="number")throw new RangeError("Invalid input for Slice.fromJSON");return new Mn(pe.fromJSON(e,t.content),r,s)}static maxOpen(e,t=!0){let r=0,s=0;for(let i=e.firstChild;i&&!i.isLeaf&&(t||!i.type.spec.isolating);i=i.firstChild)r++;for(let i=e.lastChild;i&&!i.isLeaf&&(t||!i.type.spec.isolating);i=i.lastChild)s++;return new Mn(e,r,s)}};G.empty=new G(pe.empty,0,0);function vh(n,e,t){let{index:r,offset:s}=n.findIndex(e),i=n.maybeChild(r),{index:o,offset:l}=n.findIndex(t);if(s==e||i.isText){if(l!=t&&!n.child(o).isText)throw new RangeError("Removing non-flat range");return n.cut(0,e).append(n.cut(t))}if(r!=o)throw new RangeError("Removing non-flat range");return n.replaceChild(r,i.copy(vh(i.content,e-s-1,t-s-1)))}function Th(n,e,t,r,s,i){let{index:o,offset:l}=n.findIndex(e),a=n.maybeChild(o);if(l==e||a.isText)return i&&r<=0&&s<=0&&!i.canReplace(o,o,t)?null:n.cut(0,e).append(t).append(n.cut(e));let c=Th(a.content,e-l-1,t,o==0?r-1:0,o==n.childCount-1?s-1:0,a);return c&&n.replaceChild(o,a.copy(c))}function gk(n,e,t){if(t.openStart>n.depth)throw new Ps("Inserted content deeper than insertion position");if(n.depth-t.openStart!=e.depth-t.openEnd)throw new Ps("Inconsistent open depths");return Mh(n,e,t,0)}function Mh(n,e,t,r){let s=n.index(r),i=n.node(r);if(s==e.index(r)&&r=0&&n.isText&&n.sameMarkup(e[t])?e[t]=n.withText(e[t].text+n.text):e.push(n)}function yr(n,e,t,r){let s=(e||n).node(t),i=0,o=e?e.index(t):s.childCount;n&&(i=n.index(t),n.depth>t?i++:n.textOffset&&(qt(n.nodeAfter,r),i++));for(let l=i;ls&&El(n,e,s+1),o=r.depth>s&&El(t,r,s+1),l=[];return yr(null,n,s,l),i&&o&&e.index(s)==t.index(s)?(Ah(i,o),qt(Ut(i,Eh(n,e,t,r,s+1)),l)):(i&&qt(Ut(i,Ls(n,e,s+1)),l),yr(e,t,s,l),o&&qt(Ut(o,Ls(t,r,s+1)),l)),yr(r,null,s,l),new pe(l)}function Ls(n,e,t){let r=[];if(yr(null,n,t,r),n.depth>t){let s=El(n,e,t+1);qt(Ut(s,Ls(n,e,t+1)),r)}return yr(e,null,t,r),new pe(r)}function yk(n,e){let t=e.depth-n.openStart,s=e.node(t).copy(n.content);for(let i=t-1;i>=0;i--)s=e.node(i).copy(pe.from(s));return{start:s.resolveNoCache(n.openStart+t),end:s.resolveNoCache(s.content.size-n.openEnd-t)}}var hh=class Nl{constructor(e,t,r){this.pos=e,this.path=t,this.parentOffset=r,this.depth=t.length/3-1}resolveDepth(e){return e==null?this.depth:e<0?this.depth+e:e}get parent(){return this.node(this.depth)}get doc(){return this.node(0)}node(e){return this.path[this.resolveDepth(e)*3]}index(e){return this.path[this.resolveDepth(e)*3+1]}indexAfter(e){return e=this.resolveDepth(e),this.index(e)+(e==this.depth&&!this.textOffset?0:1)}start(e){return e=this.resolveDepth(e),e==0?0:this.path[e*3-1]+1}end(e){return e=this.resolveDepth(e),this.start(e)+this.node(e).content.size}before(e){if(e=this.resolveDepth(e),!e)throw new RangeError("There is no position before the top-level node");return e==this.depth+1?this.pos:this.path[e*3-1]}after(e){if(e=this.resolveDepth(e),!e)throw new RangeError("There is no position after the top-level node");return e==this.depth+1?this.pos:this.path[e*3-1]+this.path[e*3].nodeSize}get textOffset(){return this.pos-this.path[this.path.length-1]}get nodeAfter(){let e=this.parent,t=this.index(this.depth);if(t==e.childCount)return null;let r=this.pos-this.path[this.path.length-1],s=e.child(t);return r?e.child(t).cut(r):s}get nodeBefore(){let e=this.index(this.depth),t=this.pos-this.path[this.path.length-1];return t?this.parent.child(e).cut(0,t):e==0?null:this.parent.child(e-1)}posAtIndex(e,t){t=this.resolveDepth(t);let r=this.path[t*3],s=t==0?0:this.path[t*3-1]+1;for(let i=0;i0;t--)if(this.start(t)<=e&&this.end(t)>=e)return t;return 0}blockRange(e=this,t){if(e.pos=0;r--)if(e.pos<=this.end(r)&&(!t||t(this.node(r))))return new xk(this,e,r);return null}sameParent(e){return this.pos-this.parentOffset==e.pos-e.parentOffset}max(e){return e.pos>this.pos?e:this}min(e){return e.pos=0&&t<=e.content.size))throw new RangeError("Position "+t+" out of range");let r=[],s=0,i=t;for(let o=e;;){let{index:l,offset:a}=o.content.findIndex(i),c=i-a;if(r.push(o,l,s+a),!c||(o=o.child(l),o.isText))break;i=c-1,s+=a+1}return new Nl(t,r,i)}static resolveCached(e,t){let r=fh.get(e);if(r)for(let i=0;ie&&this.nodesBetween(e,t,i=>(r.isInSet(i.marks)&&(s=!0),!s)),s}get isBlock(){return this.type.isBlock}get isTextblock(){return this.type.isTextblock}get inlineContent(){return this.type.inlineContent}get isInline(){return this.type.isInline}get isText(){return this.type.isText}get isLeaf(){return this.type.isLeaf}get isAtom(){return this.type.isAtom}toString(){if(this.type.spec.toDebugString)return this.type.spec.toDebugString(this);let e=this.type.name;return this.content.size&&(e+="("+this.content.toStringInner()+")"),Ck(this.marks,e)}contentMatchAt(e){let t=this.type.contentMatch.matchFragment(this.content,0,e);if(!t)throw new Error("Called contentMatchAt on a node with invalid content");return t}canReplace(e,t,r=pe.empty,s=0,i=r.childCount){let o=this.contentMatchAt(e).matchFragment(r,s,i),l=o&&o.matchFragment(this.content,t);if(!l||!l.validEnd)return!1;for(let a=s;at.type.name)}`);this.content.forEach(t=>t.check())}toJSON(){let e={type:this.type.name};for(let t in this.attrs){e.attrs=this.attrs;break}return this.content.size&&(e.content=this.content.toJSON()),this.marks.length&&(e.marks=this.marks.map(t=>t.toJSON())),e}static fromJSON(e,t){if(!t)throw new RangeError("Invalid input for Node.fromJSON");let r;if(t.marks){if(!Array.isArray(t.marks))throw new RangeError("Invalid mark data for Node.fromJSON");r=t.marks.map(e.markFromJSON)}if(t.type=="text"){if(typeof t.text!="string")throw new RangeError("Invalid text node in JSON");return e.text(t.text,r)}let s=pe.fromJSON(e,t.content),i=e.nodeType(t.type).create(t.attrs,s,r);return i.type.checkAttrs(i.attrs),i}};Sk.prototype.text=void 0;function Ck(n,e){for(let t=n.length-1;t>=0;t--)e=n[t].type.name+"("+e+")";return e}var Ol=class Nh{constructor(e){this.validEnd=e,this.next=[],this.wrapCache=[]}static parse(e,t){let r=new vk(e,t);if(r.next==null)return Nh.empty;let s=Rh(r);r.next&&r.err("Unexpected trailing text");let i=Ok(Rk(s));return Ik(i,r),i}matchType(e){for(let t=0;tc.createAndFill()));for(let c=0;c=this.next.length)throw new RangeError(`There's no ${e}th edge in this content match`);return this.next[e]}toString(){let e=[];function t(r){e.push(r);for(let s=0;s{let i=s+(r.validEnd?"*":" ")+" ";for(let o=0;o"+e.indexOf(r.next[o].next);return i}).join(` +`)}};Ol.empty=new Ol(!0);var vk=class{constructor(n,e){this.string=n,this.nodeTypes=e,this.inline=null,this.pos=0,this.tokens=n.split(/\s*(?=\b|\W|$)/),this.tokens[this.tokens.length-1]==""&&this.tokens.pop(),this.tokens[0]==""&&this.tokens.shift()}get next(){return this.tokens[this.pos]}eat(n){return this.next==n&&(this.pos++||!0)}err(n){throw new SyntaxError(n+" (in content expression '"+this.string+"')")}};function Rh(n){let e=[];do e.push(Tk(n));while(n.eat("|"));return e.length==1?e[0]:{type:"choice",exprs:e}}function Tk(n){let e=[];do e.push(Mk(n));while(n.next&&n.next!=")"&&n.next!="|");return e.length==1?e[0]:{type:"seq",exprs:e}}function Mk(n){let e=Nk(n);for(;;)if(n.eat("+"))e={type:"plus",expr:e};else if(n.eat("*"))e={type:"star",expr:e};else if(n.eat("?"))e={type:"opt",expr:e};else if(n.eat("{"))e=Ak(n,e);else break;return e}function ph(n){/\D/.test(n.next)&&n.err("Expected number, got '"+n.next+"'");let e=Number(n.next);return n.pos++,e}function Ak(n,e){let t=ph(n),r=t;return n.eat(",")&&(n.next!="}"?r=ph(n):r=-1),n.eat("}")||n.err("Unclosed braced range"),{type:"range",min:t,max:r,expr:e}}function Ek(n,e){let t=n.nodeTypes,r=t[e];if(r)return[r];let s=[];for(let i in t){let o=t[i];o.isInGroup(e)&&s.push(o)}return s.length==0&&n.err("No node type or group '"+e+"' found"),s}function Nk(n){if(n.eat("(")){let e=Rh(n);return n.eat(")")||n.err("Missing closing paren"),e}else if(/\W/.test(n.next))n.err("Unexpected token '"+n.next+"'");else{let e=Ek(n,n.next).map(t=>(n.inline==null?n.inline=t.isInline:n.inline!=t.isInline&&n.err("Mixing inline and block content"),{type:"name",value:t}));return n.pos++,e.length==1?e[0]:{type:"choice",exprs:e}}}function Rk(n){let e=[[]];return s(i(n,0),t()),e;function t(){return e.push([])-1}function r(o,l,a){let c={term:a,to:l};return e[o].push(c),c}function s(o,l){o.forEach(a=>a.to=l)}function i(o,l){if(o.type=="choice")return o.exprs.reduce((a,c)=>a.concat(i(c,l)),[]);if(o.type=="seq")for(let a=0;;a++){let c=i(o.exprs[a],l);if(a==o.exprs.length-1)return c;s(c,l=t())}else if(o.type=="star"){let a=t();return r(l,a),s(i(o.expr,a),a),[r(a)]}else if(o.type=="plus"){let a=t();return s(i(o.expr,l),a),s(i(o.expr,a),a),[r(a)]}else{if(o.type=="opt")return[r(l)].concat(i(o.expr,l));if(o.type=="range"){let a=l;for(let c=0;c{n[o].forEach(({term:l,to:a})=>{if(!l)return;let c;for(let u=0;u{c||s.push([l,c=[]]),c.indexOf(u)==-1&&c.push(u)})})});let i=e[r.join(",")]=new Ol(r.indexOf(n.length-1)>-1);for(let o=0;o0}get deletedBefore(){return(this.delInfo&(Ph|Ns))>0}get deletedAfter(){return(this.delInfo&(Lh|Ns))>0}get deletedAcross(){return(this.delInfo&Ns)>0}},Gt=class An{constructor(e,t=!1){if(this.ranges=e,this.inverted=t,!e.length&&An.empty)return An.empty}recover(e){let t=0,r=gh(e);if(!this.inverted)for(let s=0;se)break;let c=this.ranges[l+i],u=this.ranges[l+o],d=a+c;if(e<=d){let h=c?e==a?-1:e==d?1:t:t,f=a+s+(h<0?0:u);if(r)return f;let p=e==(t<0?a:d)?null:Dk(l/3,e-a),m=e==a?Lh:e==d?Ph:Ns;return(t<0?e!=a:e!=d)&&(m|=zh),new yh(f,m,p)}s+=u-c}return r?e+s:new yh(e+s,0,null)}touches(e,t){let r=0,s=gh(t),i=this.inverted?2:1,o=this.inverted?1:2;for(let l=0;le)break;let c=this.ranges[l+i],u=a+c;if(e<=u&&l==s*3)return!0;r+=this.ranges[l+o]-c}return!1}forEach(e){let t=this.inverted?2:1,r=this.inverted?1:2;for(let s=0,i=0;s!o.isAtom||!l.type.allowsMarkType(this.mark.type)?o:o.mark(this.mark.addToSet(o.marks)),s),t.openStart,t.openEnd);return Se.fromReplace(e,this.from,this.to,i)}invert(){return new $h(this.from,this.to,this.mark)}map(e){let t=e.mapResult(this.from,1),r=e.mapResult(this.to,-1);return t.deleted&&r.deleted||t.pos>=r.pos?null:new hr(t.pos,r.pos,this.mark)}merge(e){return e instanceof hr&&e.mark.eq(this.mark)&&this.from<=e.to&&this.to>=e.from?new hr(Math.min(this.from,e.from),Math.max(this.to,e.to),this.mark):null}toJSON(){return{stepType:"addMark",mark:this.mark.toJSON(),from:this.from,to:this.to}}static fromJSON(e,t){if(typeof t.from!="number"||typeof t.to!="number")throw new RangeError("Invalid input for AddMarkStep.fromJSON");return new hr(t.from,t.to,e.markFromJSON(t.mark))}};me.jsonID("addMark",Bh);var $h=class fr extends me{constructor(e,t,r){super(),this.from=e,this.to=t,this.mark=r}apply(e){let t=e.slice(this.from,this.to),r=new G(Ll(t.content,s=>s.mark(this.mark.removeFromSet(s.marks)),e),t.openStart,t.openEnd);return Se.fromReplace(e,this.from,this.to,r)}invert(){return new Bh(this.from,this.to,this.mark)}map(e){let t=e.mapResult(this.from,1),r=e.mapResult(this.to,-1);return t.deleted&&r.deleted||t.pos>=r.pos?null:new fr(t.pos,r.pos,this.mark)}merge(e){return e instanceof fr&&e.mark.eq(this.mark)&&this.from<=e.to&&this.to>=e.from?new fr(Math.min(this.from,e.from),Math.max(this.to,e.to),this.mark):null}toJSON(){return{stepType:"removeMark",mark:this.mark.toJSON(),from:this.from,to:this.to}}static fromJSON(e,t){if(typeof t.from!="number"||typeof t.to!="number")throw new RangeError("Invalid input for RemoveMarkStep.fromJSON");return new fr(t.from,t.to,e.markFromJSON(t.mark))}};me.jsonID("removeMark",$h);var Fh=class pr extends me{constructor(e,t){super(),this.pos=e,this.mark=t}apply(e){let t=e.nodeAt(this.pos);if(!t)return Se.fail("No node at mark step's position");let r=t.type.create(t.attrs,null,this.mark.addToSet(t.marks));return Se.fromReplace(e,this.pos,this.pos+1,new G(pe.from(r),0,t.isLeaf?0:1))}invert(e){let t=e.nodeAt(this.pos);if(t){let r=this.mark.addToSet(t.marks);if(r.length==t.marks.length){for(let s=0;sr.pos?null:new Rs(t.pos,r.pos,s,i,this.slice,this.insert,this.structure)}toJSON(){let e={stepType:"replaceAround",from:this.from,to:this.to,gapFrom:this.gapFrom,gapTo:this.gapTo,insert:this.insert};return this.slice.size&&(e.slice=this.slice.toJSON()),this.structure&&(e.structure=!0),e}static fromJSON(e,t){if(typeof t.from!="number"||typeof t.to!="number"||typeof t.gapFrom!="number"||typeof t.gapTo!="number"||typeof t.insert!="number")throw new RangeError("Invalid input for ReplaceAroundStep.fromJSON");return new Rs(t.from,t.to,t.gapFrom,t.gapTo,G.fromJSON(e,t.slice),t.insert,!!t.structure)}};me.jsonID("replaceAround",_h);function Dl(n,e,t){let r=n.resolve(e),s=t-e,i=r.depth;for(;s>0&&i>0&&r.indexAfter(i)==r.node(i).childCount;)i--,s--;if(s>0){let o=r.node(i).maybeChild(r.indexAfter(i));for(;s>0;){if(!o||o.isLeaf)return!0;o=o.firstChild,s--}}return!1}var Lk=class Os extends me{constructor(e,t,r){super(),this.pos=e,this.attr=t,this.value=r}apply(e){let t=e.nodeAt(this.pos);if(!t)return Se.fail("No node at attribute step's position");let r=Object.create(null);for(let i in t.attrs)r[i]=t.attrs[i];r[this.attr]=this.value;let s=t.type.create(r,null,t.marks);return Se.fromReplace(e,this.pos,this.pos+1,new G(pe.from(s),0,t.isLeaf?0:1))}getMap(){return Gt.empty}invert(e){return new Os(this.pos,this.attr,e.nodeAt(this.pos).attrs[this.attr])}map(e){let t=e.mapResult(this.pos,1);return t.deletedAfter?null:new Os(t.pos,this.attr,this.value)}toJSON(){return{stepType:"attr",pos:this.pos,attr:this.attr,value:this.value}}static fromJSON(e,t){if(typeof t.pos!="number"||typeof t.attr!="string")throw new RangeError("Invalid input for AttrStep.fromJSON");return new Os(t.pos,t.attr,t.value)}};me.jsonID("attr",Lk);var zk=class Pl extends me{constructor(e,t){super(),this.attr=e,this.value=t}apply(e){let t=Object.create(null);for(let s in e.attrs)t[s]=e.attrs[s];t[this.attr]=this.value;let r=e.type.create(t,e.content,e.marks);return Se.ok(r)}getMap(){return Gt.empty}invert(e){return new Pl(this.attr,e.attrs[this.attr])}map(e){return this}toJSON(){return{stepType:"docAttr",attr:this.attr,value:this.value}}static fromJSON(e,t){if(typeof t.attr!="string")throw new RangeError("Invalid input for DocAttrStep.fromJSON");return new Pl(t.attr,t.value)}};me.jsonID("docAttr",zk);var br=class extends Error{};br=function n(e){let t=Error.call(this,e);return t.__proto__=n.prototype,t};br.prototype=Object.create(Error.prototype);br.prototype.constructor=br;br.prototype.name="TransformError";var Ml=Object.create(null),ie=class{constructor(n,e,t){this.$anchor=n,this.$head=e,this.ranges=t||[new Bk(n.min(e),n.max(e))]}get anchor(){return this.$anchor.pos}get head(){return this.$head.pos}get from(){return this.$from.pos}get to(){return this.$to.pos}get $from(){return this.ranges[0].$from}get $to(){return this.ranges[0].$to}get empty(){let n=this.ranges;for(let e=0;e=0;s--){let i=e<0?En(n.node(0),n.node(s),n.before(s+1),n.index(s),e,t):En(n.node(0),n.node(s),n.after(s+1),n.index(s)+1,e,t);if(i)return i}return null}static near(n,e=1){return this.findFrom(n,e)||this.findFrom(n,-e)||new kr(n.node(0))}static atStart(n){return En(n,n,0,0,1)||new kr(n)}static atEnd(n){return En(n,n,n.content.size,n.childCount,-1)||new kr(n)}static fromJSON(n,e){if(!e||!e.type)throw new RangeError("Invalid input for Selection.fromJSON");let t=Ml[e.type];if(!t)throw new RangeError(`No selection type ${e.type} defined`);return t.fromJSON(n,e)}static jsonID(n,e){if(n in Ml)throw new RangeError("Duplicate use of selection JSON ID "+n);return Ml[n]=e,e.prototype.jsonID=n,e}getBookmark(){return Nn.between(this.$anchor,this.$head).getBookmark()}};ie.prototype.visible=!0;var Bk=class{constructor(n,e){this.$from=n,this.$to=e}},kh=!1;function bh(n){!kh&&!n.parent.inlineContent&&(kh=!0,console.warn("TextSelection endpoint not pointing into a node with inline content ("+n.parent.type.name+")"))}var Nn=class mr extends ie{constructor(e,t=e){bh(e),bh(t),super(e,t)}get $cursor(){return this.$anchor.pos==this.$head.pos?this.$head:null}map(e,t){let r=e.resolve(t.map(this.head));if(!r.parent.inlineContent)return ie.near(r);let s=e.resolve(t.map(this.anchor));return new mr(s.parent.inlineContent?s:r,r)}replace(e,t=G.empty){if(super.replace(e,t),t==G.empty){let r=this.$from.marksAcross(this.$to);r&&e.ensureMarks(r)}}eq(e){return e instanceof mr&&e.anchor==this.anchor&&e.head==this.head}getBookmark(){return new Vh(this.anchor,this.head)}toJSON(){return{type:"text",anchor:this.anchor,head:this.head}}static fromJSON(e,t){if(typeof t.anchor!="number"||typeof t.head!="number")throw new RangeError("Invalid input for TextSelection.fromJSON");return new mr(e.resolve(t.anchor),e.resolve(t.head))}static create(e,t,r=t){let s=e.resolve(t);return new this(s,r==t?s:e.resolve(r))}static between(e,t,r){let s=e.pos-t.pos;if((!r||s)&&(r=s>=0?1:-1),!t.parent.inlineContent){let i=ie.findFrom(t,r,!0)||ie.findFrom(t,-r,!0);if(i)t=i.$head;else return ie.near(t,r)}return e.parent.inlineContent||(s==0?e=t:(e=(ie.findFrom(e,-r,!0)||ie.findFrom(e,r,!0)).$anchor,e.pos0?0:1);s>0?o=0;o+=s){let l=e.child(o);if(l.isAtom){if(!i&&Rn.isSelectable(l))return Rn.create(n,t-(s<0?l.nodeSize:0))}else{let a=En(n,l,t+s,s<0?l.childCount:0,s,i);if(a)return a}t+=l.nodeSize*s}return null}function xh(n,e,t){let r=n.steps.length-1;if(r{o==null&&(o=u)}),n.setSelection(ie.near(n.doc.resolve(o),t))}function wh(n,e){return!e||!n?n:n.bind(e)}var Es=class{constructor(n,e,t){this.name=n,this.init=wh(e.init,t),this.apply=wh(e.apply,t)}},NC=[new Es("doc",{init(n){return n.doc||n.schema.topNodeType.createAndFill()},apply(n){return n.doc}}),new Es("selection",{init(n,e){return n.selection||ie.atStart(e.doc)},apply(n){return n.selection}}),new Es("storedMarks",{init(n){return n.storedMarks||null},apply(n,e,t,r){return r.selection.$cursor?n.storedMarks:null}}),new Es("scrollToSelection",{init(){return 0},apply(n,e){return n.scrolledIntoView?e+1:e}})],Hk=(n,e)=>{var t;let{state:r,view:s}=n,{selection:i}=r;if(!i.empty)return!1;let{$from:o}=i;if(o.parentOffset!==0)return!1;let l=o.depth-1,a=o.node(l),c=o.index(l);if(c===0)return!1;if(a.type===e)return n.commands.lift(e.name);let u=a.child(c-1);if(u.type!==e||!((t=u.lastChild)!=null&&t.isTextblock))return!1;let d=o.before(),f=d-1-1,{tr:p}=r;return p.delete(d,o.after()).insert(f,o.parent.content),p.setSelection(Nn.create(p.doc,f)),s.dispatch(p.scrollIntoView()),!0},_k=/^\s*>\s$/,Jh=$.create({name:"blockquote",addOptions(){return{HTMLAttributes:{}}},content:"block+",group:"block",defining:!0,parseHTML(){return[{tag:"blockquote"}]},renderHTML({HTMLAttributes:n}){return Tn("blockquote",{...D(this.options.HTMLAttributes,n),children:Tn("slot",{})})},parseMarkdown:(n,e)=>{var t;let r=(t=e.parseBlockChildren)!=null?t:e.parseChildren;return e.createNode("blockquote",void 0,r(n.tokens||[]))},renderMarkdown:(n,e)=>{if(!n.content)return"";let t=">",r=[];return n.content.forEach((s,i)=>{var o,l;let u=((l=(o=e.renderChild)==null?void 0:o.call(e,s,i))!=null?l:e.renderChildren([s])).split(` +`).map(d=>d.trim()===""?t:`${t} ${d}`);r.push(u.join(` `))}),r.join(` -${n} -`)},addCommands(){return{setBlockquote:()=>({commands:t})=>t.wrapIn(this.name),toggleBlockquote:()=>({commands:t})=>t.toggleWrap(this.name),unsetBlockquote:()=>({commands:t})=>t.lift(this.name)}},addKeyboardShortcuts(){return{"Mod-Shift-b":()=>this.editor.commands.toggleBlockquote()}},addInputRules(){return[Fe({find:Ey,type:this.type})]}});var Ny=/(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))$/,Ry=/(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))/g,Oy=/(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))$/,Iy=/(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))/g,sd=xe.create({name:"bold",addOptions(){return{HTMLAttributes:{}}},parseHTML(){return[{tag:"strong"},{tag:"b",getAttrs:t=>t.style.fontWeight!=="normal"&&null},{style:"font-weight=400",clearMark:t=>t.type.name===this.name},{style:"font-weight",getAttrs:t=>/^(bold(er)?|[5-9]\d{2,})$/.test(t)&&null}]},renderHTML({HTMLAttributes:t}){return hn("strong",{...I(this.options.HTMLAttributes,t),children:hn("slot",{})})},markdownTokenName:"strong",parseMarkdown:(t,e)=>e.applyMark("bold",e.parseInline(t.tokens||[])),renderMarkdown:(t,e)=>`**${e.renderChildren(t)}**`,addCommands(){return{setBold:()=>({commands:t})=>t.setMark(this.name),toggleBold:()=>({commands:t})=>t.toggleMark(this.name),unsetBold:()=>({commands:t})=>t.unsetMark(this.name)}},addKeyboardShortcuts(){return{"Mod-b":()=>this.editor.commands.toggleBold(),"Mod-B":()=>this.editor.commands.toggleBold()}},addInputRules(){return[He({find:Ny,type:this.type}),He({find:Oy,type:this.type})]},addPasteRules(){return[Ne({find:Ry,type:this.type}),Ne({find:Iy,type:this.type})]}});var Dy=/(^|[^`])`([^`]+)`(?!`)$/,Py=/(^|[^`])`([^`]+)`(?!`)/g,id=xe.create({name:"code",addOptions(){return{HTMLAttributes:{}}},excludes:"_",code:!0,exitable:!0,parseHTML(){return[{tag:"code"}]},renderHTML({HTMLAttributes:t}){return["code",I(this.options.HTMLAttributes,t),0]},markdownTokenName:"codespan",parseMarkdown:(t,e)=>e.applyMark("code",[{type:"text",text:t.text||""}]),renderMarkdown:(t,e)=>t.content?`\`${e.renderChildren(t.content)}\``:"",addCommands(){return{setCode:()=>({commands:t})=>t.setMark(this.name),toggleCode:()=>({commands:t})=>t.toggleMark(this.name),unsetCode:()=>({commands:t})=>t.unsetMark(this.name)}},addKeyboardShortcuts(){return{"Mod-e":()=>this.editor.commands.toggleCode()}},addInputRules(){return[He({find:Dy,type:this.type})]},addPasteRules(){return[Ne({find:Py,type:this.type})]}});var Lo=4,Ly=/^```([a-z]+)?[\s\n]$/,zy=/^~~~([a-z]+)?[\s\n]$/,od=$.create({name:"codeBlock",addOptions(){return{languageClassPrefix:"language-",exitOnTripleEnter:!0,exitOnArrowDown:!0,defaultLanguage:null,enableTabIndentation:!1,tabSize:Lo,HTMLAttributes:{}}},content:"text*",marks:"",group:"block",code:!0,defining:!0,addAttributes(){return{language:{default:this.options.defaultLanguage,parseHTML:t=>{var e;let{languageClassPrefix:n}=this.options;if(!n)return null;let i=[...((e=t.firstElementChild)==null?void 0:e.classList)||[]].filter(o=>o.startsWith(n)).map(o=>o.replace(n,""))[0];return i||null},rendered:!1}}},parseHTML(){return[{tag:"pre",preserveWhitespace:"full"}]},renderHTML({node:t,HTMLAttributes:e}){return["pre",I(this.options.HTMLAttributes,e),["code",{class:t.attrs.language?this.options.languageClassPrefix+t.attrs.language:null},0]]},markdownTokenName:"code",parseMarkdown:(t,e)=>{var n;return((n=t.raw)==null?void 0:n.startsWith("```"))===!1&&t.codeBlockStyle!=="indented"?[]:e.createNode("codeBlock",{language:t.lang||null},t.text?[e.createTextNode(t.text)]:[])},renderMarkdown:(t,e)=>{var n;let r="",s=((n=t.attrs)==null?void 0:n.language)||"";return t.content?r=[`\`\`\`${s}`,e.renderChildren(t.content),"```"].join(` +${t} +`)},addCommands(){return{setBlockquote:()=>({commands:n})=>n.wrapIn(this.name),toggleBlockquote:()=>({commands:n})=>n.toggleWrap(this.name),unsetBlockquote:()=>({commands:n})=>n.lift(this.name)}},addKeyboardShortcuts(){return{"Mod-Shift-b":()=>this.editor.commands.toggleBlockquote(),Backspace:()=>Hk(this.editor,this.type)}},addInputRules(){return[Ue({find:_k,type:this.type})]}});var Vk=/(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))$/,Wk=/(?:^|\s)(\*\*(?!\s+\*\*)((?:[^*]+))\*\*(?!\s+\*\*))/g,jk=/(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))$/,Jk=/(?:^|\s)(__(?!\s+__)((?:[^_]+))__(?!\s+__))/g,Kh=Ee.create({name:"bold",addOptions(){return{HTMLAttributes:{}}},parseHTML(){return[{tag:"strong"},{tag:"b",getAttrs:n=>n.style.fontWeight!=="normal"&&null},{style:"font-weight=400",clearMark:n=>n.type.name===this.name},{style:"font-weight",getAttrs:n=>/^(bold(er)?|[5-9]\d{2,})$/.test(n)&&null}]},renderHTML({HTMLAttributes:n}){return Tn("strong",{...D(this.options.HTMLAttributes,n),children:Tn("slot",{})})},markdownTokenName:"strong",parseMarkdown:(n,e)=>e.applyMark("bold",e.parseInline(n.tokens||[])),markdownOptions:{htmlReopen:{open:"",close:""}},renderMarkdown:(n,e)=>`**${e.renderChildren(n)}**`,addCommands(){return{setBold:()=>({commands:n})=>n.setMark(this.name),toggleBold:()=>({commands:n})=>n.toggleMark(this.name),unsetBold:()=>({commands:n})=>n.unsetMark(this.name)}},addKeyboardShortcuts(){return{"Mod-b":()=>this.editor.commands.toggleBold(),"Mod-B":()=>this.editor.commands.toggleBold()}},addInputRules(){return[qe({find:Vk,type:this.type}),qe({find:jk,type:this.type})]},addPasteRules(){return[ze({find:Wk,type:this.type}),ze({find:Jk,type:this.type})]}});var Kk=n=>{let e=/`([^`]+)`(?!`)$/.exec(n);return!e||e.index>0&&n[e.index-1]==="`"?null:{index:e.index,text:e[0],replaceWith:e[1]}},qk=n=>{let e=/`([^`]+)`(?!`)/g,t=[],r;for(;(r=e.exec(n))!==null;)r.index>0&&n[r.index-1]==="`"||t.push({index:r.index,text:r[0],replaceWith:r[1]});return t},qh=Ee.create({name:"code",addOptions(){return{HTMLAttributes:{}}},excludes:"_",code:!0,exitable:!0,parseHTML(){return[{tag:"code"}]},renderHTML({HTMLAttributes:n}){return["code",D(this.options.HTMLAttributes,n),0]},markdownTokenName:"codespan",parseMarkdown:(n,e)=>e.applyMark("code",[{type:"text",text:n.text||""}]),renderMarkdown:(n,e)=>n.content?`\`${e.renderChildren(n.content)}\``:"",addCommands(){return{setCode:()=>({commands:n})=>n.setMark(this.name),toggleCode:()=>({commands:n})=>n.toggleMark(this.name),unsetCode:()=>({commands:n})=>n.unsetMark(this.name)}},addKeyboardShortcuts(){return{"Mod-e":()=>this.editor.commands.toggleCode()}},addInputRules(){return[qe({find:Kk,type:this.type})]},addPasteRules(){return[ze({find:qk,type:this.type})]}});var Bl=4,Uk=/^```([a-z]+)?[\s\n]$/,Gk=/^~~~([a-z]+)?[\s\n]$/,Uh=$.create({name:"codeBlock",addOptions(){return{languageClassPrefix:"language-",exitOnTripleEnter:!0,exitOnArrowDown:!0,defaultLanguage:null,enableTabIndentation:!1,tabSize:Bl,HTMLAttributes:{}}},content:"text*",marks:"",group:"block",code:!0,defining:!0,addAttributes(){return{language:{default:this.options.defaultLanguage,parseHTML:n=>{var e;let{languageClassPrefix:t}=this.options;if(!t)return null;let i=[...((e=n.firstElementChild)==null?void 0:e.classList)||[]].filter(o=>o.startsWith(t)).map(o=>o.replace(t,""))[0];return i||null},rendered:!1}}},parseHTML(){return[{tag:"pre",preserveWhitespace:"full"}]},renderHTML({node:n,HTMLAttributes:e}){return["pre",D(this.options.HTMLAttributes,e),["code",{class:n.attrs.language?this.options.languageClassPrefix+n.attrs.language:null},0]]},markdownTokenName:"code",parseMarkdown:(n,e)=>{var t,r;return((t=n.raw)==null?void 0:t.startsWith("```"))===!1&&((r=n.raw)==null?void 0:r.startsWith("~~~"))===!1&&n.codeBlockStyle!=="indented"?[]:e.createNode("codeBlock",{language:n.lang||null},n.text?[e.createTextNode(n.text)]:[])},renderMarkdown:(n,e)=>{var t;let r="",s=((t=n.attrs)==null?void 0:t.language)||"";return n.content?r=[`\`\`\`${s}`,e.renderChildren(n.content),"```"].join(` `):r=`\`\`\`${s} -\`\`\``,r},addCommands(){return{setCodeBlock:t=>({commands:e})=>e.setNode(this.name,t),toggleCodeBlock:t=>({commands:e})=>e.toggleNode(this.name,"paragraph",t)}},addKeyboardShortcuts(){return{"Mod-Alt-c":()=>this.editor.commands.toggleCodeBlock(),Backspace:()=>{let{empty:t,$anchor:e}=this.editor.state.selection,n=e.pos===1;return!t||e.parent.type.name!==this.name?!1:n||!e.parent.textContent.length?this.editor.commands.clearNodes():!1},Tab:({editor:t})=>{var e;if(!this.options.enableTabIndentation)return!1;let n=(e=this.options.tabSize)!=null?e:Lo,{state:r}=t,{selection:s}=r,{$from:i,empty:o}=s;if(i.parent.type!==this.type)return!1;let l=" ".repeat(n);return o?t.commands.insertContent(l):t.commands.command(({tr:a})=>{let{from:c,to:u}=s,f=r.doc.textBetween(c,u,` +\`\`\``,r},addCommands(){return{setCodeBlock:n=>({commands:e})=>e.setNode(this.name,n),toggleCodeBlock:n=>({commands:e})=>e.toggleNode(this.name,"paragraph",n)}},addKeyboardShortcuts(){return{"Mod-Alt-c":()=>this.editor.commands.toggleCodeBlock(),Backspace:()=>{let{empty:n,$anchor:e}=this.editor.state.selection,t=e.pos===1;return!n||e.parent.type.name!==this.name?!1:t||!e.parent.textContent.length?this.editor.commands.clearNodes():!1},Tab:({editor:n})=>{var e;if(!this.options.enableTabIndentation)return!1;let t=(e=this.options.tabSize)!=null?e:Bl,{state:r}=n,{selection:s}=r,{$from:i,empty:o}=s;if(i.parent.type!==this.type)return!1;let l=" ".repeat(t);return o?n.commands.insertContent(l):n.commands.command(({tr:a})=>{let{from:c,to:u}=s,f=r.doc.textBetween(c,u,` `,` `).split(` `).map(p=>l+p).join(` -`);return a.replaceWith(c,u,r.schema.text(f)),!0})},"Shift-Tab":({editor:t})=>{var e;if(!this.options.enableTabIndentation)return!1;let n=(e=this.options.tabSize)!=null?e:Lo,{state:r}=t,{selection:s}=r,{$from:i,empty:o}=s;return i.parent.type!==this.type?!1:o?t.commands.command(({tr:l})=>{var a;let{pos:c}=i,u=i.start(),d=i.end(),f=r.doc.textBetween(u,d,` +`);return a.replaceWith(c,u,r.schema.text(f)),!0})},"Shift-Tab":({editor:n})=>{var e;if(!this.options.enableTabIndentation)return!1;let t=(e=this.options.tabSize)!=null?e:Bl,{state:r}=n,{selection:s}=r,{$from:i,empty:o}=s;return i.parent.type!==this.type?!1:o?n.commands.command(({tr:l})=>{var a;let{pos:c}=i,u=i.start(),d=i.end(),f=r.doc.textBetween(u,d,` `,` `).split(` -`),p=0,m=0,g=c-u;for(let N=0;N=g){p=N;break}m+=f[N].length+1}let k=((a=f[p].match(/^ */))==null?void 0:a[0])||"",w=Math.min(k.length,n);if(w===0)return!0;let M=u;for(let N=0;N{let{from:a,to:c}=s,h=r.doc.textBetween(a,c,` +`),p=0,m=0,g=c-u;for(let M=0;M=g){p=M;break}m+=f[M].length+1}let k=((a=f[p].match(/^ */))==null?void 0:a[0])||"",w=Math.min(k.length,t);if(w===0)return!0;let C=u;for(let M=0;M{let{from:a,to:c}=s,h=r.doc.textBetween(a,c,` `,` `).split(` -`).map(f=>{var p;let m=((p=f.match(/^ */))==null?void 0:p[0])||"",g=Math.min(m.length,n);return f.slice(g)}).join(` -`);return l.replaceWith(a,c,r.schema.text(h)),!0})},Enter:({editor:t})=>{if(!this.options.exitOnTripleEnter)return!1;let{state:e}=t,{selection:n}=e,{$from:r,empty:s}=n;if(!s||r.parent.type!==this.type)return!1;let i=r.parentOffset===r.parent.nodeSize-2,o=r.parent.textContent.endsWith(` +`).map(f=>{var p;let m=((p=f.match(/^ */))==null?void 0:p[0])||"",g=Math.min(m.length,t);return f.slice(g)}).join(` +`);return l.replaceWith(a,c,r.schema.text(h)),!0})},Enter:({editor:n})=>{if(!this.options.exitOnTripleEnter)return!1;let{state:e}=n,{selection:t}=e,{$from:r,empty:s}=t;if(!s||r.parent.type!==this.type)return!1;let i=r.parentOffset===r.parent.nodeSize-2,o=r.parent.textContent.endsWith(` -`);return!i||!o?!1:t.chain().command(({tr:l})=>(l.delete(r.pos-2,r.pos),!0)).exitCode().run()},ArrowDown:({editor:t})=>{if(!this.options.exitOnArrowDown)return!1;let{state:e}=t,{selection:n,doc:r}=e,{$from:s,empty:i}=n;if(!i||s.parent.type!==this.type||!(s.parentOffset===s.parent.nodeSize-2))return!1;let l=s.after();return l===void 0?!1:r.nodeAt(l)?t.commands.command(({tr:c})=>(c.setSelection(R.near(r.resolve(l))),!0)):t.commands.exitCode()}}},addInputRules(){return[Kn({find:Ly,type:this.type,getAttributes:t=>({language:t[1]})}),Kn({find:zy,type:this.type,getAttributes:t=>({language:t[1]})})]},addProseMirrorPlugins(){return[new O({key:new P("codeBlockVSCodeHandler"),props:{handlePaste:(t,e)=>{if(!e.clipboardData||this.editor.isActive(this.type.name))return!1;let n=e.clipboardData.getData("text/plain"),r=e.clipboardData.getData("vscode-editor-data"),s=r?JSON.parse(r):void 0,i=s?.mode;if(!n||!i)return!1;let{tr:o,schema:l}=t.state,a=l.text(n.replace(/\r\n?/g,` -`));return o.replaceSelectionWith(this.type.create({language:i},a)),o.selection.$from.parent.type!==this.type&&o.setSelection(A.near(o.doc.resolve(Math.max(0,o.selection.from-2)))),o.setMeta("paste",!0),t.dispatch(o),!0}}})]}});var ld=$.create({name:"doc",topNode:!0,content:"block+",renderMarkdown:(t,e)=>t.content?e.renderChildren(t.content,` +`);return!i||!o?!1:n.chain().command(({tr:l})=>(l.delete(r.pos-2,r.pos),!0)).exitCode().run()},ArrowDown:({editor:n})=>{if(!this.options.exitOnArrowDown)return!1;let{state:e}=n,{selection:t,doc:r}=e,{$from:s,empty:i}=t;if(!i||s.parent.type!==this.type||!(s.parentOffset===s.parent.nodeSize-2))return!1;let l=s.after();return l===void 0?!1:r.nodeAt(l)?n.commands.command(({tr:c})=>(c.setSelection(O.near(r.resolve(l))),!0)):n.commands.exitCode()}}},addInputRules(){return[cr({find:Uk,type:this.type,getAttributes:n=>({language:n[1]})}),cr({find:Gk,type:this.type,getAttributes:n=>({language:n[1]})})]},addProseMirrorPlugins(){return[new I({key:new L("codeBlockVSCodeHandler"),props:{handlePaste:(n,e)=>{if(!e.clipboardData||this.editor.isActive(this.type.name))return!1;let t=e.clipboardData.getData("text/plain"),r=e.clipboardData.getData("vscode-editor-data"),s=r?JSON.parse(r):void 0,i=s?.mode;if(!t||!i)return!1;let{tr:o,schema:l}=n.state,a=l.text(t.replace(/\r\n?/g,` +`));return o.replaceSelectionWith(this.type.create({language:i},a)),o.selection.$from.parent.type!==this.type&&o.setSelection(A.near(o.doc.resolve(Math.max(0,o.selection.from-2)))),o.setMeta("paste",!0),n.dispatch(o),!0}}})]}});var Gh=$.create({name:"doc",topNode:!0,content:"block+",renderMarkdown:(n,e)=>n.content?e.renderChildren(n.content,` -`):""});var ad=$.create({name:"hardBreak",markdownTokenName:"br",addOptions(){return{keepMarks:!0,HTMLAttributes:{}}},inline:!0,group:"inline",selectable:!1,linebreakReplacement:!0,parseHTML(){return[{tag:"br"}]},renderHTML({HTMLAttributes:t}){return["br",I(this.options.HTMLAttributes,t)]},renderText(){return` +`):""});var Xh=$.create({name:"hardBreak",markdownTokenName:"br",addOptions(){return{keepMarks:!0,HTMLAttributes:{}}},inline:!0,group:"inline",selectable:!1,linebreakReplacement:!0,parseHTML(){return[{tag:"br"}]},renderHTML({HTMLAttributes:n}){return["br",D(this.options.HTMLAttributes,n)]},renderText(){return` `},renderMarkdown:()=>` -`,parseMarkdown:()=>({type:"hardBreak"}),addCommands(){return{setHardBreak:()=>({commands:t,chain:e,state:n,editor:r})=>t.first([()=>t.exitCode(),()=>t.command(()=>{let{selection:s,storedMarks:i}=n;if(s.$from.parent.type.spec.isolating)return!1;let{keepMarks:o}=this.options,{splittableMarks:l}=r.extensionManager,a=i||s.$to.parentOffset&&s.$from.marks();return e().insertContent({type:this.name}).command(({tr:c,dispatch:u})=>{if(u&&a&&o){let d=a.filter(h=>l.includes(h.type.name));c.ensureMarks(d)}return!0}).run()})])}},addKeyboardShortcuts(){return{"Mod-Enter":()=>this.editor.commands.setHardBreak(),"Shift-Enter":()=>this.editor.commands.setHardBreak()}}});var cd=$.create({name:"heading",addOptions(){return{levels:[1,2,3,4,5,6],HTMLAttributes:{}}},content:"inline*",group:"block",defining:!0,addAttributes(){return{level:{default:1,rendered:!1}}},parseHTML(){return this.options.levels.map(t=>({tag:`h${t}`,attrs:{level:t}}))},renderHTML({node:t,HTMLAttributes:e}){return[`h${this.options.levels.includes(t.attrs.level)?t.attrs.level:this.options.levels[0]}`,I(this.options.HTMLAttributes,e),0]},parseMarkdown:(t,e)=>e.createNode("heading",{level:t.depth||1},e.parseInline(t.tokens||[])),renderMarkdown:(t,e)=>{var n;let r=(n=t.attrs)!=null&&n.level?parseInt(t.attrs.level,10):1,s="#".repeat(r);return t.content?`${s} ${e.renderChildren(t.content)}`:""},addCommands(){return{setHeading:t=>({commands:e})=>this.options.levels.includes(t.level)?e.setNode(this.name,t):!1,toggleHeading:t=>({commands:e})=>this.options.levels.includes(t.level)?e.toggleNode(this.name,"paragraph",t):!1}},addKeyboardShortcuts(){return this.options.levels.reduce((t,e)=>({...t,[`Mod-Alt-${e}`]:()=>this.editor.commands.toggleHeading({level:e})}),{})},addInputRules(){return this.options.levels.map(t=>Kn({find:new RegExp(`^(#{${Math.min(...this.options.levels)},${t}})\\s$`),type:this.type,getAttributes:{level:t}}))}});var ud=$.create({name:"horizontalRule",addOptions(){return{HTMLAttributes:{},nextNodeType:"paragraph"}},group:"block",parseHTML(){return[{tag:"hr"}]},renderHTML({HTMLAttributes:t}){return["hr",I(this.options.HTMLAttributes,t)]},markdownTokenName:"hr",parseMarkdown:(t,e)=>e.createNode("horizontalRule"),renderMarkdown:()=>"---",addCommands(){return{setHorizontalRule:()=>({chain:t,state:e})=>{if(!td(e,e.schema.nodes[this.name]))return!1;let{selection:n}=e,{$to:r}=n,s=t();return Jr(n)?s.insertContentAt(r.pos,{type:this.name}):s.insertContent({type:this.name}),s.command(({state:i,tr:o,dispatch:l})=>{if(l){let{$to:a}=o.selection,c=a.end();if(a.nodeAfter)a.nodeAfter.isTextblock?o.setSelection(A.create(o.doc,a.pos+1)):a.nodeAfter.isBlock?o.setSelection(E.create(o.doc,a.pos)):o.setSelection(A.create(o.doc,a.pos));else{let u=i.schema.nodes[this.options.nextNodeType]||a.parent.type.contentMatch.defaultType,d=u?.create();d&&(o.insert(c,d),o.setSelection(A.create(o.doc,c+1)))}o.scrollIntoView()}return!0}).run()}}},addInputRules(){return[Qr({find:/^(?:---|—-|___\s|\*\*\*\s)$/,type:this.type})]}});var By=/(?:^|\s)(\*(?!\s+\*)((?:[^*]+))\*(?!\s+\*))$/,$y=/(?:^|\s)(\*(?!\s+\*)((?:[^*]+))\*(?!\s+\*))/g,Hy=/(?:^|\s)(_(?!\s+_)((?:[^_]+))_(?!\s+_))$/,Fy=/(?:^|\s)(_(?!\s+_)((?:[^_]+))_(?!\s+_))/g,dd=xe.create({name:"italic",addOptions(){return{HTMLAttributes:{}}},parseHTML(){return[{tag:"em"},{tag:"i",getAttrs:t=>t.style.fontStyle!=="normal"&&null},{style:"font-style=normal",clearMark:t=>t.type.name===this.name},{style:"font-style=italic"}]},renderHTML({HTMLAttributes:t}){return["em",I(this.options.HTMLAttributes,t),0]},addCommands(){return{setItalic:()=>({commands:t})=>t.setMark(this.name),toggleItalic:()=>({commands:t})=>t.toggleMark(this.name),unsetItalic:()=>({commands:t})=>t.unsetMark(this.name)}},markdownTokenName:"em",parseMarkdown:(t,e)=>e.applyMark("italic",e.parseInline(t.tokens||[])),renderMarkdown:(t,e)=>`*${e.renderChildren(t)}*`,addKeyboardShortcuts(){return{"Mod-i":()=>this.editor.commands.toggleItalic(),"Mod-I":()=>this.editor.commands.toggleItalic()}},addInputRules(){return[He({find:By,type:this.type}),He({find:Hy,type:this.type})]},addPasteRules(){return[Ne({find:$y,type:this.type}),Ne({find:Fy,type:this.type})]}});var _y="aaa1rp3bb0ott3vie4c1le2ogado5udhabi7c0ademy5centure6ountant0s9o1tor4d0s1ult4e0g1ro2tna4f0l1rica5g0akhan5ency5i0g1rbus3force5tel5kdn3l0ibaba4pay4lfinanz6state5y2sace3tom5m0azon4ericanexpress7family11x2fam3ica3sterdam8nalytics7droid5quan4z2o0l2partments8p0le4q0uarelle8r0ab1mco4chi3my2pa2t0e3s0da2ia2sociates9t0hleta5torney7u0ction5di0ble3o3spost5thor3o0s4w0s2x0a2z0ure5ba0by2idu3namex4d1k2r0celona5laycard4s5efoot5gains6seball5ketball8uhaus5yern5b0c1t1va3cg1n2d1e0ats2uty4er2rlin4st0buy5t2f1g1h0arti5i0ble3d1ke2ng0o3o1z2j1lack0friday9ockbuster8g1omberg7ue3m0s1w2n0pparibas9o0ats3ehringer8fa2m1nd2o0k0ing5sch2tik2on4t1utique6x2r0adesco6idgestone9oadway5ker3ther5ussels7s1t1uild0ers6siness6y1zz3v1w1y1z0h3ca0b1fe2l0l1vinklein9m0era3p2non3petown5ital0one8r0avan4ds2e0er0s4s2sa1e1h1ino4t0ering5holic7ba1n1re3c1d1enter4o1rn3f0a1d2g1h0anel2nel4rity4se2t2eap3intai5ristmas6ome4urch5i0priani6rcle4sco3tadel4i0c2y3k1l0aims4eaning6ick2nic1que6othing5ud3ub0med6m1n1o0ach3des3ffee4llege4ogne5m0mbank4unity6pany2re3uter5sec4ndos3struction8ulting7tact3ractors9oking4l1p2rsica5untry4pon0s4rses6pa2r0edit0card4union9icket5own3s1uise0s6u0isinella9v1w1x1y0mru3ou3z2dad1nce3ta1e1ing3sun4y2clk3ds2e0al0er2s3gree4livery5l1oitte5ta3mocrat6ntal2ist5si0gn4v2hl2iamonds6et2gital5rect0ory7scount3ver5h2y2j1k1m1np2o0cs1tor4g1mains5t1wnload7rive4tv2ubai3nlop4pont4rban5vag2r2z2earth3t2c0o2deka3u0cation8e1g1mail3erck5nergy4gineer0ing9terprises10pson4quipment8r0icsson6ni3s0q1tate5t1u0rovision8s2vents5xchange6pert3osed4ress5traspace10fage2il1rwinds6th3mily4n0s2rm0ers5shion4t3edex3edback6rrari3ero6i0delity5o2lm2nal1nce1ial7re0stone6mdale6sh0ing5t0ness6j1k1lickr3ghts4r2orist4wers5y2m1o0o0d1tball6rd1ex2sale4um3undation8x2r0ee1senius7l1ogans4ntier7tr2ujitsu5n0d2rniture7tbol5yi3ga0l0lery3o1up4me0s3p1rden4y2b0iz3d0n2e0a1nt0ing5orge5f1g0ee3h1i0ft0s3ves2ing5l0ass3e1obal2o4m0ail3bh2o1x2n1odaddy5ld0point6f2o0dyear5g0le4p1t1v2p1q1r0ainger5phics5tis4een3ipe3ocery4up4s1t1u0cci3ge2ide2tars5ru3w1y2hair2mburg5ngout5us3bo2dfc0bank7ealth0care8lp1sinki6re1mes5iphop4samitsu7tachi5v2k0t2m1n1ockey4ldings5iday5medepot5goods5s0ense7nda3rse3spital5t0ing5t0els3mail5use3w2r1sbc3t1u0ghes5yatt3undai7ibm2cbc2e1u2d1e0ee3fm2kano4l1m0amat4db2mo0bilien9n0c1dustries8finiti5o2g1k1stitute6urance4e4t0ernational10uit4vestments10o1piranga7q1r0ish4s0maili5t0anbul7t0au2v3jaguar4va3cb2e0ep2tzt3welry6io2ll2m0p2nj2o0bs1urg4t1y2p0morgan6rs3uegos4niper7kaufen5ddi3e0rryhotels6properties14fh2g1h1i0a1ds2m1ndle4tchen5wi3m1n1oeln3matsu5sher5p0mg2n2r0d1ed3uokgroup8w1y0oto4z2la0caixa5mborghini8er3nd0rover6xess5salle5t0ino3robe5w0yer5b1c1ds2ease3clerc5frak4gal2o2xus4gbt3i0dl2fe0insurance9style7ghting6ke2lly3mited4o2ncoln4k2ve1ing5k1lc1p2oan0s3cker3us3l1ndon4tte1o3ve3pl0financial11r1s1t0d0a3u0ndbeck6xe1ury5v1y2ma0drid4if1son4keup4n0agement7go3p1rket0ing3s4riott5shalls7ttel5ba2c0kinsey7d1e0d0ia3et2lbourne7me1orial6n0u2rckmsd7g1h1iami3crosoft7l1ni1t2t0subishi9k1l0b1s2m0a2n1o0bi0le4da2e1i1m1nash3ey2ster5rmon3tgage6scow4to0rcycles9v0ie4p1q1r1s0d2t0n1r2u0seum3ic4v1w1x1y1z2na0b1goya4me2vy3ba2c1e0c1t0bank4flix4work5ustar5w0s2xt0direct7us4f0l2g0o2hk2i0co2ke1on3nja3ssan1y5l1o0kia3rton4w0ruz3tv4p1r0a1w2tt2u1yc2z2obi1server7ffice5kinawa6layan0group9lo3m0ega4ne1g1l0ine5oo2pen3racle3nge4g0anic5igins6saka4tsuka4t2vh3pa0ge2nasonic7ris2s1tners4s1y3y2ccw3e0t2f0izer5g1h0armacy6d1ilips5one2to0graphy6s4ysio5ics1tet2ures6d1n0g1k2oneer5zza4k1l0ace2y0station9umbing5s3m1n0c2ohl2ker3litie5rn2st3r0axi3ess3ime3o0d0uctions8f1gressive8mo2perties3y5tection8u0dential9s1t1ub2w0c2y2qa1pon3uebec3st5racing4dio4e0ad1lestate6tor2y4cipes5d0stone5umbrella9hab3ise0n3t2liance6n0t0als5pair3ort3ublican8st0aurant8view0s5xroth6ich0ardli6oh3l1o1p2o0cks3deo3gers4om3s0vp3u0gby3hr2n2w0e2yukyu6sa0arland6fe0ty4kura4le1on3msclub4ung5ndvik0coromant12ofi4p1rl2s1ve2xo3b0i1s2c0b1haeffler7midt4olarships8ol3ule3warz5ience5ot3d1e0arch3t2cure1ity6ek2lect4ner3rvices6ven3w1x0y3fr2g1h0angrila6rp3ell3ia1ksha5oes2p0ping5uji3w3i0lk2na1gles5te3j1k0i0n2y0pe4l0ing4m0art3ile4n0cf3o0ccer3ial4ftbank4ware6hu2lar2utions7ng1y2y2pa0ce3ort2t3r0l2s1t0ada2ples4r1tebank4farm7c0group6ockholm6rage3e3ream4udio2y3yle4u0cks3pplies3y2ort5rf1gery5zuki5v1watch4iss4x1y0dney4stems6z2tab1ipei4lk2obao4rget4tamotors6r2too4x0i3c0i2d0k2eam2ch0nology8l1masek5nnis4va3f1g1h0d1eater2re6iaa2ckets5enda4ps2res2ol4j0maxx4x2k0maxx5l1m0all4n1o0day3kyo3ols3p1ray3shiba5tal3urs3wn2yota3s3r0ade1ing4ining5vel0ers0insurance16ust3v2t1ube2i1nes3shu4v0s2w1z2ua1bank3s2g1k1nicom3versity8o2ol2ps2s1y1z2va0cations7na1guard7c1e0gas3ntures6risign5m\xF6gensberater2ung14sicherung10t2g1i0ajes4deo3g1king4llas4n1p1rgin4sa1ion4va1o3laanderen9n1odka3lvo3te1ing3o2yage5u2wales2mart4ter4ng0gou5tch0es6eather0channel12bcam3er2site5d0ding5ibo2r3f1hoswho6ien2ki2lliamhill9n0dows4e1ners6me2olterskluwer11odside6rk0s2ld3w2s1tc1f3xbox3erox4ihuan4n2xx2yz3yachts4hoo3maxun5ndex5e1odobashi7ga2kohama6u0tube6t1un3za0ppos4ra3ero3ip2m1one3uerich6w2",Vy="\u03B5\u03BB1\u03C52\u0431\u04331\u0435\u043B3\u0434\u0435\u0442\u04384\u0435\u044E2\u043A\u0430\u0442\u043E\u043B\u0438\u043A6\u043E\u043C3\u043C\u043A\u04342\u043E\u043D1\u0441\u043A\u0432\u04306\u043E\u043D\u043B\u0430\u0439\u043D5\u0440\u04333\u0440\u0443\u04412\u04442\u0441\u0430\u0439\u04423\u0440\u04313\u0443\u043A\u04403\u049B\u0430\u04373\u0570\u0561\u05753\u05D9\u05E9\u05E8\u05D0\u05DC5\u05E7\u05D5\u05DD3\u0627\u0628\u0648\u0638\u0628\u064A5\u0631\u0627\u0645\u0643\u06485\u0644\u0627\u0631\u062F\u06464\u0628\u062D\u0631\u064A\u06465\u062C\u0632\u0627\u0626\u06315\u0633\u0639\u0648\u062F\u064A\u06296\u0639\u0644\u064A\u0627\u06465\u0645\u063A\u0631\u06285\u0645\u0627\u0631\u0627\u062A5\u06CC\u0631\u0627\u06465\u0628\u0627\u0631\u062A2\u0632\u0627\u06314\u064A\u062A\u06433\u06BE\u0627\u0631\u062A5\u062A\u0648\u0646\u06334\u0633\u0648\u062F\u0627\u06463\u0631\u064A\u06295\u0634\u0628\u0643\u06294\u0639\u0631\u0627\u06422\u06282\u0645\u0627\u06464\u0641\u0644\u0633\u0637\u064A\u06466\u0642\u0637\u06313\u0643\u0627\u062B\u0648\u0644\u064A\u06436\u0648\u06453\u0645\u0635\u06312\u0644\u064A\u0633\u064A\u06275\u0648\u0631\u064A\u062A\u0627\u0646\u064A\u06277\u0642\u06394\u0647\u0645\u0631\u0627\u06475\u067E\u0627\u06A9\u0633\u062A\u0627\u06467\u0680\u0627\u0631\u062A4\u0915\u0949\u092E3\u0928\u0947\u091F3\u092D\u093E\u0930\u09240\u092E\u094D3\u094B\u09245\u0938\u0902\u0917\u0920\u09285\u09AC\u09BE\u0982\u09B2\u09BE5\u09AD\u09BE\u09B0\u09A42\u09F0\u09A44\u0A2D\u0A3E\u0A30\u0A244\u0AAD\u0ABE\u0AB0\u0AA44\u0B2D\u0B3E\u0B30\u0B244\u0B87\u0BA8\u0BCD\u0BA4\u0BBF\u0BAF\u0BBE6\u0BB2\u0B99\u0BCD\u0B95\u0BC86\u0B9A\u0BBF\u0B99\u0BCD\u0B95\u0BAA\u0BCD\u0BAA\u0BC2\u0BB0\u0BCD11\u0C2D\u0C3E\u0C30\u0C24\u0C4D5\u0CAD\u0CBE\u0CB0\u0CA44\u0D2D\u0D3E\u0D30\u0D24\u0D025\u0DBD\u0D82\u0D9A\u0DCF4\u0E04\u0E2D\u0E213\u0E44\u0E17\u0E223\u0EA5\u0EB2\u0EA73\u10D2\u10D42\u307F\u3093\u306A3\u30A2\u30DE\u30BE\u30F34\u30AF\u30E9\u30A6\u30C94\u30B0\u30FC\u30B0\u30EB4\u30B3\u30E02\u30B9\u30C8\u30A23\u30BB\u30FC\u30EB3\u30D5\u30A1\u30C3\u30B7\u30E7\u30F36\u30DD\u30A4\u30F3\u30C84\u4E16\u754C2\u4E2D\u4FE11\u56FD1\u570B1\u6587\u7F513\u4E9A\u9A6C\u900A3\u4F01\u4E1A2\u4F5B\u5C712\u4FE1\u606F2\u5065\u5EB72\u516B\u53662\u516C\u53F81\u76CA2\u53F0\u6E7E1\u70632\u5546\u57CE1\u5E971\u68072\u5609\u91CC0\u5927\u9152\u5E975\u5728\u7EBF2\u5927\u62FF2\u5929\u4E3B\u65593\u5A31\u4E502\u5BB6\u96FB2\u5E7F\u4E1C2\u5FAE\u535A2\u6148\u55842\u6211\u7231\u4F603\u624B\u673A2\u62DB\u80582\u653F\u52A11\u5E9C2\u65B0\u52A0\u57612\u95FB2\u65F6\u5C1A2\u66F8\u7C4D2\u673A\u67842\u6DE1\u9A6C\u95213\u6E38\u620F2\u6FB3\u95802\u70B9\u770B2\u79FB\u52A82\u7EC4\u7EC7\u673A\u67844\u7F51\u57401\u5E971\u7AD91\u7EDC2\u8054\u901A2\u8C37\u6B4C2\u8D2D\u72692\u901A\u8CA92\u96C6\u56E22\u96FB\u8A0A\u76C8\u79D14\u98DE\u5229\u6D663\u98DF\u54C12\u9910\u53852\u9999\u683C\u91CC\u62C93\u6E2F2\uB2F7\uB1371\uCEF42\uC0BC\uC1312\uD55C\uAD6D2",Vo="numeric",Wo="ascii",jo="alpha",Gn="asciinumeric",Jn="alphanumeric",Ko="domain",kd="emoji",Wy="scheme",jy="slashscheme",zo="whitespace";function Ky(t,e){return t in e||(e[t]=[]),e[t]}function zt(t,e,n){e[Vo]&&(e[Gn]=!0,e[Jn]=!0),e[Wo]&&(e[Gn]=!0,e[jo]=!0),e[Gn]&&(e[Jn]=!0),e[jo]&&(e[Jn]=!0),e[Jn]&&(e[Ko]=!0),e[kd]&&(e[Ko]=!0);for(let r in e){let s=Ky(r,n);s.indexOf(t)<0&&s.push(t)}}function Uy(t,e){let n={};for(let r in e)e[r].indexOf(t)>=0&&(n[r]=!0);return n}function ye(t=null){this.j={},this.jr=[],this.jd=null,this.t=t}ye.groups={};ye.prototype={accepts(){return!!this.t},go(t){let e=this,n=e.j[t];if(n)return n;for(let r=0;rt.ta(e,n,r,s),G=(t,e,n,r,s)=>t.tr(e,n,r,s),hd=(t,e,n,r,s)=>t.ts(e,n,r,s),x=(t,e,n,r,s)=>t.tt(e,n,r,s),it="WORD",Uo="UWORD",bd="ASCIINUMERICAL",xd="ALPHANUMERICAL",tr="LOCALHOST",qo="TLD",Jo="UTLD",ns="SCHEME",fn="SLASH_SCHEME",Xo="NUM",Go="WS",Qo="NL",Xn="OPENBRACE",Qn="CLOSEBRACE",rs="OPENBRACKET",ss="CLOSEBRACKET",is="OPENPAREN",ls="CLOSEPAREN",as="OPENANGLEBRACKET",cs="CLOSEANGLEBRACKET",us="FULLWIDTHLEFTPAREN",ds="FULLWIDTHRIGHTPAREN",hs="LEFTCORNERBRACKET",fs="RIGHTCORNERBRACKET",ps="LEFTWHITECORNERBRACKET",ms="RIGHTWHITECORNERBRACKET",gs="FULLWIDTHLESSTHAN",ys="FULLWIDTHGREATERTHAN",ks="AMPERSAND",bs="APOSTROPHE",xs="ASTERISK",yt="AT",ws="BACKSLASH",Ss="BACKTICK",Cs="CARET",kt="COLON",Yo="COMMA",Ms="DOLLAR",_e="DOT",Ts="EQUALS",Zo="EXCLAMATION",Oe="HYPHEN",Yn="PERCENT",vs="PIPE",As="PLUS",Es="POUND",Zn="QUERY",el="QUOTE",wd="FULLWIDTHMIDDLEDOT",tl="SEMI",Ve="SLASH",er="TILDE",Ns="UNDERSCORE",Sd="EMOJI",Rs="SYM",Cd=Object.freeze({__proto__:null,ALPHANUMERICAL:xd,AMPERSAND:ks,APOSTROPHE:bs,ASCIINUMERICAL:bd,ASTERISK:xs,AT:yt,BACKSLASH:ws,BACKTICK:Ss,CARET:Cs,CLOSEANGLEBRACKET:cs,CLOSEBRACE:Qn,CLOSEBRACKET:ss,CLOSEPAREN:ls,COLON:kt,COMMA:Yo,DOLLAR:Ms,DOT:_e,EMOJI:Sd,EQUALS:Ts,EXCLAMATION:Zo,FULLWIDTHGREATERTHAN:ys,FULLWIDTHLEFTPAREN:us,FULLWIDTHLESSTHAN:gs,FULLWIDTHMIDDLEDOT:wd,FULLWIDTHRIGHTPAREN:ds,HYPHEN:Oe,LEFTCORNERBRACKET:hs,LEFTWHITECORNERBRACKET:ps,LOCALHOST:tr,NL:Qo,NUM:Xo,OPENANGLEBRACKET:as,OPENBRACE:Xn,OPENBRACKET:rs,OPENPAREN:is,PERCENT:Yn,PIPE:vs,PLUS:As,POUND:Es,QUERY:Zn,QUOTE:el,RIGHTCORNERBRACKET:fs,RIGHTWHITECORNERBRACKET:ms,SCHEME:ns,SEMI:tl,SLASH:Ve,SLASH_SCHEME:fn,SYM:Rs,TILDE:er,TLD:qo,UNDERSCORE:Ns,UTLD:Jo,UWORD:Uo,WORD:it,WS:Go}),rt=/[a-z]/,qn=/\p{L}/u,Bo=/\p{Emoji}/u;var st=/\d/,$o=/\s/;var fd="\r",Ho=` -`,qy="\uFE0F",Jy="\u200D",Fo="\uFFFC",Zr=null,es=null;function Gy(t=[]){let e={};ye.groups=e;let n=new ye;Zr==null&&(Zr=pd(_y)),es==null&&(es=pd(Vy)),x(n,"'",bs),x(n,"{",Xn),x(n,"}",Qn),x(n,"[",rs),x(n,"]",ss),x(n,"(",is),x(n,")",ls),x(n,"<",as),x(n,">",cs),x(n,"\uFF08",us),x(n,"\uFF09",ds),x(n,"\u300C",hs),x(n,"\u300D",fs),x(n,"\u300E",ps),x(n,"\u300F",ms),x(n,"\uFF1C",gs),x(n,"\uFF1E",ys),x(n,"&",ks),x(n,"*",xs),x(n,"@",yt),x(n,"`",Ss),x(n,"^",Cs),x(n,":",kt),x(n,",",Yo),x(n,"$",Ms),x(n,".",_e),x(n,"=",Ts),x(n,"!",Zo),x(n,"-",Oe),x(n,"%",Yn),x(n,"|",vs),x(n,"+",As),x(n,"#",Es),x(n,"?",Zn),x(n,'"',el),x(n,"/",Ve),x(n,";",tl),x(n,"~",er),x(n,"_",Ns),x(n,"\\",ws),x(n,"\u30FB",wd);let r=G(n,st,Xo,{[Vo]:!0});G(r,st,r);let s=G(r,rt,bd,{[Gn]:!0}),i=G(r,qn,xd,{[Jn]:!0}),o=G(n,rt,it,{[Wo]:!0});G(o,st,s),G(o,rt,o),G(s,st,s),G(s,rt,s);let l=G(n,qn,Uo,{[jo]:!0});G(l,rt),G(l,st,i),G(l,qn,l),G(i,st,i),G(i,rt),G(i,qn,i);let a=x(n,Ho,Qo,{[zo]:!0}),c=x(n,fd,Go,{[zo]:!0}),u=G(n,$o,Go,{[zo]:!0});x(n,Fo,u),x(c,Ho,a),x(c,Fo,u),G(c,$o,u),x(u,fd),x(u,Ho),G(u,$o,u),x(u,Fo,u);let d=G(n,Bo,Sd,{[kd]:!0});x(d,"#"),G(d,Bo,d),x(d,qy,d);let h=x(d,Jy);x(h,"#"),G(h,Bo,d);let f=[[rt,o],[st,s]],p=[[rt,null],[qn,l],[st,i]];for(let m=0;mm[0]>g[0]?1:-1);for(let m=0;m=0?k[Ko]=!0:rt.test(g)?st.test(g)?k[Gn]=!0:k[Wo]=!0:k[Vo]=!0,hd(n,g,g,k)}return hd(n,"localhost",tr,{ascii:!0}),n.jd=new ye(Rs),{start:n,tokens:Object.assign({groups:e},Cd)}}function Md(t,e){let n=Xy(e.replace(/[A-Z]/g,l=>l.toLowerCase())),r=n.length,s=[],i=0,o=0;for(;o=0&&(d+=n[o].length,h++),c+=n[o].length,i+=n[o].length,o++;i-=d,o-=h,c-=d,s.push({t:u.t,v:e.slice(i-c,i),s:i-c,e:i})}return s}function Xy(t){let e=[],n=t.length,r=0;for(;r56319||r+1===n||(i=t.charCodeAt(r+1))<56320||i>57343?t[r]:t.slice(r,r+2);e.push(o),r+=o.length}return e}function gt(t,e,n,r,s){let i,o=e.length;for(let l=0;l=0;)i++;if(i>0){e.push(n.join(""));for(let o=parseInt(t.substring(r,r+i),10);o>0;o--)n.pop();r+=i}else n.push(t[r]),r++}return e}var nr={defaultProtocol:"http",events:null,format:md,formatHref:md,nl2br:!1,tagName:"a",target:null,rel:null,validate:!0,truncate:1/0,className:null,attributes:null,ignoreTags:[],render:null};function nl(t,e=null){let n=Object.assign({},nr);t&&(n=Object.assign(n,t instanceof nl?t.o:t));let r=n.ignoreTags,s=[];for(let i=0;in?r.substring(0,n)+"\u2026":r},toFormattedHref(t){return t.get("formatHref",this.toHref(t.get("defaultProtocol")),this)},startIndex(){return this.tk[0].s},endIndex(){return this.tk[this.tk.length-1].e},toObject(t=nr.defaultProtocol){return{type:this.t,value:this.toString(),isLink:this.isLink,href:this.toHref(t),start:this.startIndex(),end:this.endIndex()}},toFormattedObject(t){return{type:this.t,value:this.toFormattedString(t),isLink:this.isLink,href:this.toFormattedHref(t),start:this.startIndex(),end:this.endIndex()}},validate(t){return t.get("validate",this.toString(),this)},render(t){let e=this,n=this.toHref(t.get("defaultProtocol")),r=t.get("formatHref",n,this),s=t.get("tagName",n,e),i=this.toFormattedString(t),o={},l=t.get("className",n,e),a=t.get("target",n,e),c=t.get("rel",n,e),u=t.getObj("attributes",n,e),d=t.getObj("events",n,e);return o.href=r,l&&(o.class=l),a&&(o.target=a),c&&(o.rel=c),u&&Object.assign(o,u),{tagName:s,attributes:o,content:i,eventListeners:d}}};function Os(t,e){class n extends Td{constructor(s,i){super(s,i),this.t=t}}for(let r in e)n.prototype[r]=e[r];return n.t=t,n}var gd=Os("email",{isLink:!0,toHref(){return"mailto:"+this.toString()}}),yd=Os("text"),Qy=Os("nl"),ts=Os("url",{isLink:!0,toHref(t=nr.defaultProtocol){return this.hasProtocol()?this.v:`${t}://${this.v}`},hasProtocol(){let t=this.tk;return t.length>=2&&t[0].t!==tr&&t[1].t===kt}});var Re=t=>new ye(t);function Yy({groups:t}){let e=t.domain.concat([ks,xs,yt,ws,Ss,Cs,Ms,Ts,Oe,Xo,Yn,vs,As,Es,Ve,Rs,er,Ns]),n=[bs,kt,Yo,_e,Zo,Yn,Zn,el,tl,as,cs,Xn,Qn,ss,rs,is,ls,us,ds,hs,fs,ps,ms,gs,ys],r=[ks,bs,xs,ws,Ss,Cs,Ms,Ts,Oe,Xn,Qn,Yn,vs,As,Es,Zn,Ve,Rs,er,Ns],s=Re(),i=x(s,er);L(i,r,i),L(i,t.domain,i);let o=Re(),l=Re(),a=Re();L(s,t.domain,o),L(s,t.scheme,l),L(s,t.slashscheme,a),L(o,r,i),L(o,t.domain,o);let c=x(o,yt);x(i,yt,c),x(l,yt,c),x(a,yt,c);let u=x(i,_e);L(u,r,i),L(u,t.domain,i);let d=Re();L(c,t.domain,d),L(d,t.domain,d);let h=x(d,_e);L(h,t.domain,d);let f=Re(gd);L(h,t.tld,f),L(h,t.utld,f),x(c,tr,f);let p=x(d,Oe);x(p,Oe,p),L(p,t.domain,d),L(f,t.domain,d),x(f,_e,h),x(f,Oe,p);let m=x(f,kt);L(m,t.numeric,gd);let g=x(o,Oe),y=x(o,_e);x(g,Oe,g),L(g,t.domain,o),L(y,r,i),L(y,t.domain,o);let k=Re(ts);L(y,t.tld,k),L(y,t.utld,k),L(k,t.domain,o),L(k,r,i),x(k,_e,y),x(k,Oe,g),x(k,yt,c);let w=x(k,kt),M=Re(ts);L(w,t.numeric,M);let S=Re(ts),N=Re();L(S,e,S),L(S,n,N),L(N,e,S),L(N,n,N),x(k,Ve,S),x(M,Ve,S);let v=x(l,kt),D=x(a,kt),z=x(D,Ve),ke=x(z,Ve);L(l,t.domain,o),x(l,_e,y),x(l,Oe,g),L(a,t.domain,o),x(a,_e,y),x(a,Oe,g),L(v,t.domain,S),x(v,Ve,S),x(v,Zn,S),L(ke,t.domain,S),L(ke,e,S),x(ke,Ve,S);let Kt=[[Xn,Qn],[rs,ss],[is,ls],[as,cs],[us,ds],[hs,fs],[ps,ms],[gs,ys]];for(let lt=0;lt=0&&h++,s++,u++;if(h<0)s-=u,s0&&(i.push(_o(yd,e,o)),o=[]),s-=h,u-=h;let f=d.t,p=n.slice(s-u,s);i.push(_o(f,e,p))}}return o.length>0&&i.push(_o(yd,e,o)),i}function _o(t,e,n){let r=n[0].s,s=n[n.length-1].e,i=e.slice(r,s);return new t(i,n)}var ek=typeof console<"u"&&console&&console.warn||(()=>{}),tk="until manual call of linkify.init(). Register all schemes and plugins before invoking linkify the first time.",U={scanner:null,parser:null,tokenQueue:[],pluginQueue:[],customSchemes:[],initialized:!1};function vd(){return ye.groups={},U.scanner=null,U.parser=null,U.tokenQueue=[],U.pluginQueue=[],U.customSchemes=[],U.initialized=!1,U}function rl(t,e=!1){if(U.initialized&&ek(`linkifyjs: already initialized - will not register custom scheme "${t}" ${tk}`),!/^[0-9a-z]+(-[0-9a-z]+)*$/.test(t))throw new Error(`linkifyjs: incorrect scheme format. +`,parseMarkdown:()=>({type:"hardBreak"}),addCommands(){return{setHardBreak:()=>({commands:n,chain:e,state:t,editor:r})=>n.first([()=>n.exitCode(),()=>n.command(()=>{let{selection:s,storedMarks:i}=t;if(s.$from.parent.type.spec.isolating)return!1;let{keepMarks:o}=this.options,{splittableMarks:l}=r.extensionManager,a=i||s.$to.parentOffset&&s.$from.marks();return e().insertContent({type:this.name}).command(({tr:c,dispatch:u})=>{if(u&&a&&o){let d=a.filter(h=>l.includes(h.type.name));c.ensureMarks(d)}return!0}).run()})])}},addKeyboardShortcuts(){return{"Mod-Enter":()=>this.editor.commands.setHardBreak(),"Shift-Enter":()=>this.editor.commands.setHardBreak()}}});var Qh=$.create({name:"heading",addOptions(){return{levels:[1,2,3,4,5,6],HTMLAttributes:{}}},content:"inline*",group:"block",defining:!0,addAttributes(){return{level:{default:1,rendered:!1}}},parseHTML(){return this.options.levels.map(n=>({tag:`h${n}`,attrs:{level:n}}))},renderHTML({node:n,HTMLAttributes:e}){return[`h${this.options.levels.includes(n.attrs.level)?n.attrs.level:this.options.levels[0]}`,D(this.options.HTMLAttributes,e),0]},parseMarkdown:(n,e)=>e.createNode("heading",{level:n.depth||1},e.parseInline(n.tokens||[])),renderMarkdown:(n,e)=>{var t;let r=(t=n.attrs)!=null&&t.level?parseInt(n.attrs.level,10):1,s="#".repeat(r);return n.content?`${s} ${e.renderChildren(n.content)}`:""},addCommands(){return{setHeading:n=>({commands:e})=>this.options.levels.includes(n.level)?e.setNode(this.name,n):!1,toggleHeading:n=>({commands:e})=>this.options.levels.includes(n.level)?e.toggleNode(this.name,"paragraph",n):!1}},addKeyboardShortcuts(){return this.options.levels.reduce((n,e)=>({...n,[`Mod-Alt-${e}`]:()=>this.editor.commands.toggleHeading({level:e})}),{})},addInputRules(){return this.options.levels.map(n=>cr({find:new RegExp(`^(#{${Math.min(...this.options.levels)},${n}})\\s$`),type:this.type,getAttributes:{level:n}}))}});var Yh=$.create({name:"horizontalRule",addOptions(){return{HTMLAttributes:{},nextNodeType:"paragraph"}},group:"block",parseHTML(){return[{tag:"hr"}]},renderHTML({HTMLAttributes:n}){return["hr",D(this.options.HTMLAttributes,n)]},markdownTokenName:"hr",parseMarkdown:(n,e)=>e.createNode("horizontalRule"),renderMarkdown:()=>"---",addCommands(){return{setHorizontalRule:()=>({chain:n,state:e})=>{if(!ah(e,e.schema.nodes[this.name]))return!1;let{selection:t}=e,{$to:r}=t,s=n();return Ss(t)?s.insertContentAt(r.pos,{type:this.name}):s.insertContent({type:this.name}),s.command(({state:i,tr:o,dispatch:l})=>{if(l){let{$to:a}=o.selection,c=a.end();if(a.nodeAfter)a.nodeAfter.isTextblock?o.setSelection(A.create(o.doc,a.pos+1)):a.nodeAfter.isBlock?o.setSelection(N.create(o.doc,a.pos)):o.setSelection(A.create(o.doc,a.pos));else{let u=i.schema.nodes[this.options.nextNodeType]||a.parent.type.contentMatch.defaultType,d=u?.create();d&&(o.insert(c,d),o.setSelection(A.create(o.doc,c+1)))}o.scrollIntoView()}return!0}).run()}}},addInputRules(){return[Ts({find:/^(?:---|—-|___\s|\*\*\*\s)$/,type:this.type})]}});var Xk=/(?:^|\s)(\*(?!\s+\*)((?:[^*]+))\*(?!\s+\*))$/,Qk=/(?:^|\s)(\*(?!\s+\*)((?:[^*]+))\*(?!\s+\*))/g,Yk=/(?:^|\s)(_(?!\s+_)((?:[^_]+))_(?!\s+_))$/,Zk=/(?:^|\s)(_(?!\s+_)((?:[^_]+))_(?!\s+_))/g,Zh=Ee.create({name:"italic",addOptions(){return{HTMLAttributes:{}}},parseHTML(){return[{tag:"em"},{tag:"i",getAttrs:n=>n.style.fontStyle!=="normal"&&null},{style:"font-style=normal",clearMark:n=>n.type.name===this.name},{style:"font-style=italic"}]},renderHTML({HTMLAttributes:n}){return["em",D(this.options.HTMLAttributes,n),0]},addCommands(){return{setItalic:()=>({commands:n})=>n.setMark(this.name),toggleItalic:()=>({commands:n})=>n.toggleMark(this.name),unsetItalic:()=>({commands:n})=>n.unsetMark(this.name)}},markdownTokenName:"em",parseMarkdown:(n,e)=>e.applyMark("italic",e.parseInline(n.tokens||[])),markdownOptions:{htmlReopen:{open:"",close:""}},renderMarkdown:(n,e)=>`*${e.renderChildren(n)}*`,addKeyboardShortcuts(){return{"Mod-i":()=>this.editor.commands.toggleItalic(),"Mod-I":()=>this.editor.commands.toggleItalic()}},addInputRules(){return[qe({find:Xk,type:this.type}),qe({find:Yk,type:this.type})]},addPasteRules(){return[ze({find:Qk,type:this.type}),ze({find:Zk,type:this.type})]}});var eb="aaa1rp3bb0ott3vie4c1le2ogado5udhabi7c0ademy5centure6ountant0s9o1tor4d0s1ult4e0g1ro2tna4f0l1rica5g0akhan5ency5i0g1rbus3force5tel5kdn3l0ibaba4pay4lfinanz6state5y2sace3tom5m0azon4ericanexpress7family11x2fam3ica3sterdam8nalytics7droid5quan4z2o0l2partments8p0le4q0uarelle8r0ab1mco4chi3my2pa2t0e3s0da2ia2sociates9t0hleta5torney7u0ction5di0ble3o3spost5thor3o0s4w0s2x0a2z0ure5ba0by2idu3namex4d1k2r0celona5laycard4s5efoot5gains6seball5ketball8uhaus5yern5b0c1t1va3cg1n2d1e0ats2uty4er2rlin4st0buy5t2f1g1h0arti5i0ble3d1ke2ng0o3o1z2j1lack0friday9ockbuster8g1omberg7ue3m0s1w2n0pparibas9o0ats3ehringer8fa2m1nd2o0k0ing5sch2tik2on4t1utique6x2r0adesco6idgestone9oadway5ker3ther5ussels7s1t1uild0ers6siness6y1zz3v1w1y1z0h3ca0b1fe2l0l1vinklein9m0era3p2non3petown5ital0one8r0avan4ds2e0er0s4s2sa1e1h1ino4t0ering5holic7ba1n1re3c1d1enter4o1rn3f0a1d2g1h0anel2nel4rity4se2t2eap3intai5ristmas6ome4urch5i0priani6rcle4sco3tadel4i0c2y3k1l0aims4eaning6ick2nic1que6othing5ud3ub0med6m1n1o0ach3des3ffee4llege4ogne5m0mbank4unity6pany2re3uter5sec4ndos3struction8ulting7tact3ractors9oking4l1p2rsica5untry4pon0s4rses6pa2r0edit0card4union9icket5own3s1uise0s6u0isinella9v1w1x1y0mru3ou3z2dad1nce3ta1e1ing3sun4y2clk3ds2e0al0er2s3gree4livery5l1oitte5ta3mocrat6ntal2ist5si0gn4v2hl2iamonds6et2gital5rect0ory7scount3ver5h2y2j1k1m1np2o0cs1tor4g1mains5t1wnload7rive4tv2ubai3pont4rban5vag2r2z2earth3t2c0o2deka3u0cation8e1g1mail3erck5nergy4gineer0ing9terprises10pson4quipment8r0icsson6ni3s0q1tate5t1u0rovision8s2vents5xchange6pert3osed4ress5traspace10fage2il1rwinds6th3mily4n0s2rm0ers5shion4t3edex3edback6rrari3ero6i0delity5o2lm2nal1nce1ial7re0stone6mdale6sh0ing5t0ness6j1k1lickr3ghts4r2orist4wers5y2m1o0o0d1tball6rd1ex2sale4um3undation8x2r0ee1senius7l1ogans4ntier7tr2ujitsu5n0d2rniture7tbol5yi3ga0l0lery3o1up4me0s3p1rden4y2b0iz3d0n2e0a1nt0ing5orge5f1g0ee3h1i0ft0s3ves2ing5l0ass3e1obal2o4m0ail3bh2o1x2n1odaddy5ld0point6f2odyear5g0le4p1t1v2p1q1r0ainger5phics5tis4een3ipe3ocery4up4s1t1u0cci3ge2ide2tars5ru3w1y2hair2mburg5ngout5us3bo2dfc0bank7ealth0care8lp1sinki6re1mes5iphop4samitsu7tachi5v2k0t2m1n1ockey4ldings5iday5medepot5goods5s0ense7nda3rse3spital5t0ing5t0els3mail5use3w2r1sbc3t1u0ghes5yatt3undai7ibm2cbc2e1u2d1e0ee3fm2kano4l1m0amat4db2mo0bilien9n0c1dustries8finiti5o2g1k1stitute6urance4e4t0ernational10uit4vestments10o1piranga7q1r0ish4s0maili5t0anbul7t0au2v3jaguar4va3cb2e0ep2tzt3welry6io2ll2m0p2nj2o0bs1urg4t1y2p0morgan6rs3uegos4niper7kaufen5ddi3e0rryhotels6properties14fh2g1h1i0a1ds2m1ndle4tchen5wi3m1n1oeln3matsu5sher5p0mg2n2r0d1ed3uokgroup8w1y0oto4z2la0caixa5mborghini8er3nd0rover6xess5salle5t0ino3robe5w0yer5b1c1ds2ease3clerc5frak4gal2o2xus4gbt3i0dl2fe0insurance9style7ghting6ke2lly3mited4o2ncoln4k2ve1ing5k1lc1p2oan0s3cker3us3l1ndon4tte1o3ve3pl0financial11r1s1t0d0a3u0ndbeck6xe1ury5v1y2ma0drid4if1son4keup4n0agement7go3p1rket0ing3s4riott5shalls7ttel5ba2c0kinsey7d1e0d0ia3et2lbourne7me1orial6n0u2rck0msd7g1h1iami3crosoft7l1ni1t2t0subishi9k1l0b1s2m0a2n1o0bi0le4da2e1i1m1nash3ey2ster5rmon3tgage6scow4to0rcycles9v0ie4p1q1r1s0d2t0n1r2u0seum3ic4v1w1x1y1z2na0b1goya4me2vy3ba2c1e0c1t0bank4flix4work5ustar5w0s2xt0direct7us4f0l2g0o2hk2i0co2ke1on3nja3ssan1y5l1o0kia3rton4w0ruz3tv4p1r0a1w2tt2u1yc2z2obi1server7ffice5kinawa6layan0group9lo3m0ega4ne1g1l0ine5oo2pen3racle3nge4g0anic5igins6saka4tsuka4t2vh3pa0ge2nasonic7ris2s1tners4s1y3y2ccw3e0t2f0izer5g1h0armacy6d1ilips5one2to0graphy6s4ysio5ics1tet2ures6d1n0g1k2oneer5zza4k1l0ace2y0station9umbing5s3m1n0c2ohl2ker3litie5rn2st3r0axi3ess3ime3o0d0uctions8f1gressive8mo2perties3y5tection8u0dential9s1t1ub2w0c2y2qa1pon3uebec3st5racing4dio4e0ad1lestate6tor2y4cipes5d0umbrella9hab3ise0n3t2liance6n0t0als5pair3ort3ublican8st0aurant8view0s5xroth6ich0ardli6oh3l1o1p2o0cks3deo3gers4om3s0vp3u0gby3hr2n2w0e2yukyu6sa0arland6fe0ty4kura4le1on3msclub4ung5ndvik0coromant12ofi4p1rl2s1ve2xo3b0i1s2c0b1haeffler7midt4olarships8ol3ule3warz5ience5ot3d1e0arch3t2cure1ity6ek2lect4ner3rvices6ven3w1x0y3fr2g1h0angrila6rp3ell3ia1ksha5oes2p0ping5uji3w3i0lk2na1gles5te3j1k0i0n2y0pe4l0ing4m0art3ile4n0cf3o0ccer3ial4ftbank4ware6hu2lar2utions7ng1y2y2pa0ce3ort2t3r0l2s1t0ada2ples4r1tebank4farm7c0group6ockholm6rage3e3ream4udio2y3yle4u0cks3pplies3y2ort5rf1gery5zuki5v1watch4iss4x1y0dney4stems6z2tab1ipei4lk2obao4rget4tamotors6r2too4x0i3c0i2d0k2eam2ch0nology8l1masek5nnis4va3f1g1h0d1eater2re6iaa2ckets5enda4ps2res2ol4j0maxx4x2k0maxx5l1m0all4n1o0day3kyo3ols3p1ray3shiba5tal3urs3wn2yota3s3r0ade1ing4ining5vel0ers0insurance16ust3v2t1ube2i1nes3shu4v0s2w1z2ua1bank3s2g1k1nicom3versity8o2ol2ps2s1y1z2va0cations7na1guard7c1e0gas3ntures6risign5m\xF6gensberater2ung14sicherung10t2g1i0ajes4deo3g1king4llas4n1p1rgin4sa1ion4va1o3laanderen9n1odka3lvo3te1ing3o2yage5u2wales2mart4ter4ng0gou5tch0es6eather0channel12bcam3er2site5d0ding5ibo2r3f1hoswho6ien2ki2lliamhill9n0dows4e1ners6me2oodside6rk0s2ld3w2s1tc1f3xbox3erox4ihuan4n2xx2yz3yachts4hoo3maxun5ndex5e1odobashi7ga2kohama6u0tube6t1un3za0ppos4ra3ero3ip2m1one3uerich6w2",tb="\u03B5\u03BB1\u03C52\u0431\u04331\u0435\u043B3\u0434\u0435\u0442\u04384\u0435\u044E2\u043A\u0430\u0442\u043E\u043B\u0438\u043A6\u043E\u043C3\u043C\u043A\u04342\u043E\u043D1\u0441\u043A\u0432\u04306\u043E\u043D\u043B\u0430\u0439\u043D5\u0440\u04333\u0440\u0443\u04412\u04442\u0441\u0430\u0439\u04423\u0440\u04313\u0443\u043A\u04403\u049B\u0430\u04373\u0570\u0561\u05753\u05D9\u05E9\u05E8\u05D0\u05DC5\u05E7\u05D5\u05DD3\u0627\u0628\u0648\u0638\u0628\u064A5\u0631\u0627\u0645\u0643\u06485\u0644\u0627\u0631\u062F\u06464\u0628\u062D\u0631\u064A\u06465\u062C\u0632\u0627\u0626\u06315\u0633\u0639\u0648\u062F\u064A\u06296\u0639\u0644\u064A\u0627\u06465\u0645\u063A\u0631\u06285\u0645\u0627\u0631\u0627\u062A5\u06CC\u0631\u0627\u06465\u0628\u0627\u0631\u062A2\u0632\u0627\u06314\u064A\u062A\u06433\u06BE\u0627\u0631\u062A5\u062A\u0648\u0646\u06334\u0633\u0648\u062F\u0627\u06463\u0631\u064A\u06295\u0634\u0628\u0643\u06294\u0639\u0631\u0627\u06422\u06282\u0645\u0627\u06464\u0641\u0644\u0633\u0637\u064A\u06466\u0642\u0637\u06313\u0643\u0627\u062B\u0648\u0644\u064A\u06436\u0648\u06453\u0645\u0635\u06312\u0644\u064A\u0633\u064A\u06275\u0648\u0631\u064A\u062A\u0627\u0646\u064A\u06277\u0642\u06394\u0647\u0645\u0631\u0627\u06475\u067E\u0627\u06A9\u0633\u062A\u0627\u06467\u0680\u0627\u0631\u062A4\u0915\u0949\u092E3\u0928\u0947\u091F3\u092D\u093E\u0930\u09240\u092E\u094D3\u094B\u09245\u0938\u0902\u0917\u0920\u09285\u09AC\u09BE\u0982\u09B2\u09BE5\u09AD\u09BE\u09B0\u09A42\u09F0\u09A44\u0A2D\u0A3E\u0A30\u0A244\u0AAD\u0ABE\u0AB0\u0AA44\u0B2D\u0B3E\u0B30\u0B244\u0B87\u0BA8\u0BCD\u0BA4\u0BBF\u0BAF\u0BBE6\u0BB2\u0B99\u0BCD\u0B95\u0BC86\u0B9A\u0BBF\u0B99\u0BCD\u0B95\u0BAA\u0BCD\u0BAA\u0BC2\u0BB0\u0BCD11\u0C2D\u0C3E\u0C30\u0C24\u0C4D5\u0CAD\u0CBE\u0CB0\u0CA44\u0D2D\u0D3E\u0D30\u0D24\u0D025\u0DBD\u0D82\u0D9A\u0DCF4\u0E04\u0E2D\u0E213\u0E44\u0E17\u0E223\u0EA5\u0EB2\u0EA73\u10D2\u10D42\u307F\u3093\u306A3\u30A2\u30DE\u30BE\u30F34\u30AF\u30E9\u30A6\u30C94\u30B0\u30FC\u30B0\u30EB4\u30B3\u30E02\u30B9\u30C8\u30A23\u30BB\u30FC\u30EB3\u30D5\u30A1\u30C3\u30B7\u30E7\u30F36\u30DD\u30A4\u30F3\u30C84\u4E16\u754C2\u4E2D\u4FE11\u56FD1\u570B1\u6587\u7F513\u4E9A\u9A6C\u900A3\u4F01\u4E1A2\u4F5B\u5C712\u4FE1\u606F2\u5065\u5EB72\u516B\u53662\u516C\u53F81\u76CA2\u53F0\u6E7E1\u70632\u5546\u57CE1\u5E971\u68072\u5609\u91CC0\u5927\u9152\u5E975\u5728\u7EBF2\u5927\u62FF2\u5929\u4E3B\u65593\u5A31\u4E502\u5BB6\u96FB2\u5E7F\u4E1C2\u5FAE\u535A2\u6148\u55842\u6211\u7231\u4F603\u624B\u673A2\u62DB\u80582\u653F\u52A11\u5E9C2\u65B0\u52A0\u57612\u95FB2\u65F6\u5C1A2\u66F8\u7C4D2\u673A\u67842\u6DE1\u9A6C\u95213\u6E38\u620F2\u6FB3\u95802\u70B9\u770B2\u79FB\u52A82\u7EC4\u7EC7\u673A\u67844\u7F51\u57401\u5E971\u7AD91\u7EDC2\u8054\u901A2\u8C37\u6B4C2\u8D2D\u72692\u901A\u8CA92\u96C6\u56E22\u96FB\u8A0A\u76C8\u79D14\u98DE\u5229\u6D663\u98DF\u54C12\u9910\u53852\u9999\u683C\u91CC\u62C93\u6E2F2\uB2F7\uB1371\uCEF42\uC0BC\uC1312\uD55C\uAD6D2",jl="numeric",Jl="ascii",Kl="alpha",Sr="asciinumeric",wr="alphanumeric",ql="domain",of="emoji",nb="scheme",rb="slashscheme",$l="whitespace";function sb(n,e){return n in e||(e[n]=[]),e[n]}function Xt(n,e,t){e[jl]&&(e[Sr]=!0,e[wr]=!0),e[Jl]&&(e[Sr]=!0,e[Kl]=!0),e[Sr]&&(e[wr]=!0),e[Kl]&&(e[wr]=!0),e[wr]&&(e[ql]=!0),e[of]&&(e[ql]=!0);for(let r in e){let s=sb(r,t);s.indexOf(n)<0&&s.push(n)}}function ib(n,e){let t={};for(let r in e)e[r].indexOf(n)>=0&&(t[r]=!0);return t}function Me(n=null){this.j={},this.jr=[],this.jd=null,this.t=n}Me.groups={};Me.prototype={accepts(){return!!this.t},go(n){let e=this,t=e.j[n];if(t)return t;for(let r=0;rn.ta(e,t,r,s),X=(n,e,t,r,s)=>n.tr(e,t,r,s),ef=(n,e,t,r,s)=>n.ts(e,t,r,s),S=(n,e,t,r,s)=>n.tt(e,t,r,s),mt="WORD",Ul="UWORD",lf="ASCIINUMERICAL",af="ALPHANUMERICAL",Er="LOCALHOST",Gl="TLD",Xl="UTLD",Fs="SCHEME",On="SLASH_SCHEME",Yl="NUM",Ql="WS",Zl="NL",Cr="OPENBRACE",vr="CLOSEBRACE",Hs="OPENBRACKET",_s="CLOSEBRACKET",Vs="OPENPAREN",Ws="CLOSEPAREN",js="OPENANGLEBRACKET",Js="CLOSEANGLEBRACKET",Ks="FULLWIDTHLEFTPAREN",qs="FULLWIDTHRIGHTPAREN",Us="LEFTCORNERBRACKET",Gs="RIGHTCORNERBRACKET",Xs="LEFTWHITECORNERBRACKET",Qs="RIGHTWHITECORNERBRACKET",Ys="FULLWIDTHLESSTHAN",Zs="FULLWIDTHGREATERTHAN",ei="AMPERSAND",ti="APOSTROPHE",ni="ASTERISK",Et="AT",ri="BACKSLASH",si="BACKTICK",ii="CARET",Qt="COLON",ea="COMMA",oi="DOLLAR",Ge="DOT",li="EQUALS",ta="EXCLAMATION",$e="HYPHEN",Tr="PERCENT",ai="PIPE",ci="PLUS",ui="POUND",Mr="QUERY",na="QUOTE",cf="FULLWIDTHMIDDLEDOT",ra="SEMI",Xe="SLASH",Ar="TILDE",di="UNDERSCORE",uf="EMOJI",hi="SYM",df=Object.freeze({__proto__:null,ALPHANUMERICAL:af,AMPERSAND:ei,APOSTROPHE:ti,ASCIINUMERICAL:lf,ASTERISK:ni,AT:Et,BACKSLASH:ri,BACKTICK:si,CARET:ii,CLOSEANGLEBRACKET:Js,CLOSEBRACE:vr,CLOSEBRACKET:_s,CLOSEPAREN:Ws,COLON:Qt,COMMA:ea,DOLLAR:oi,DOT:Ge,EMOJI:uf,EQUALS:li,EXCLAMATION:ta,FULLWIDTHGREATERTHAN:Zs,FULLWIDTHLEFTPAREN:Ks,FULLWIDTHLESSTHAN:Ys,FULLWIDTHMIDDLEDOT:cf,FULLWIDTHRIGHTPAREN:qs,HYPHEN:$e,LEFTCORNERBRACKET:Us,LEFTWHITECORNERBRACKET:Xs,LOCALHOST:Er,NL:Zl,NUM:Yl,OPENANGLEBRACKET:js,OPENBRACE:Cr,OPENBRACKET:Hs,OPENPAREN:Vs,PERCENT:Tr,PIPE:ai,PLUS:ci,POUND:ui,QUERY:Mr,QUOTE:na,RIGHTCORNERBRACKET:Gs,RIGHTWHITECORNERBRACKET:Qs,SCHEME:Fs,SEMI:ra,SLASH:Xe,SLASH_SCHEME:On,SYM:hi,TILDE:Ar,TLD:Gl,UNDERSCORE:di,UTLD:Xl,UWORD:Ul,WORD:mt,WS:Ql}),ft=/[a-z]/,xr=/\p{L}/u,Fl=/\p{Emoji}/u;var pt=/\d/,Hl=/\s/;var tf="\r",_l=` +`,ob="\uFE0F",lb="\u200D",Vl="\uFFFC",zs=null,Bs=null;function ab(n=[]){let e={};Me.groups=e;let t=new Me;zs==null&&(zs=nf(eb)),Bs==null&&(Bs=nf(tb)),S(t,"'",ti),S(t,"{",Cr),S(t,"}",vr),S(t,"[",Hs),S(t,"]",_s),S(t,"(",Vs),S(t,")",Ws),S(t,"<",js),S(t,">",Js),S(t,"\uFF08",Ks),S(t,"\uFF09",qs),S(t,"\u300C",Us),S(t,"\u300D",Gs),S(t,"\u300E",Xs),S(t,"\u300F",Qs),S(t,"\uFF1C",Ys),S(t,"\uFF1E",Zs),S(t,"&",ei),S(t,"*",ni),S(t,"@",Et),S(t,"`",si),S(t,"^",ii),S(t,":",Qt),S(t,",",ea),S(t,"$",oi),S(t,".",Ge),S(t,"=",li),S(t,"!",ta),S(t,"-",$e),S(t,"%",Tr),S(t,"|",ai),S(t,"+",ci),S(t,"#",ui),S(t,"?",Mr),S(t,'"',na),S(t,"/",Xe),S(t,";",ra),S(t,"~",Ar),S(t,"_",di),S(t,"\\",ri),S(t,"\u30FB",cf);let r=X(t,pt,Yl,{[jl]:!0});X(r,pt,r);let s=X(r,ft,lf,{[Sr]:!0}),i=X(r,xr,af,{[wr]:!0}),o=X(t,ft,mt,{[Jl]:!0});X(o,pt,s),X(o,ft,o),X(s,pt,s),X(s,ft,s);let l=X(t,xr,Ul,{[Kl]:!0});X(l,ft),X(l,pt,i),X(l,xr,l),X(i,pt,i),X(i,ft),X(i,xr,i);let a=S(t,_l,Zl,{[$l]:!0}),c=S(t,tf,Ql,{[$l]:!0}),u=X(t,Hl,Ql,{[$l]:!0});S(t,Vl,u),S(c,_l,a),S(c,Vl,u),X(c,Hl,u),S(u,tf),S(u,_l),X(u,Hl,u),S(u,Vl,u);let d=X(t,Fl,uf,{[of]:!0});S(d,"#"),X(d,Fl,d),S(d,ob,d);let h=S(d,lb);S(h,"#"),X(h,Fl,d);let f=[[ft,o],[pt,s]],p=[[ft,null],[xr,l],[pt,i]];for(let m=0;mm[0]>g[0]?1:-1);for(let m=0;m=0?k[ql]=!0:ft.test(g)?pt.test(g)?k[Sr]=!0:k[Jl]=!0:k[jl]=!0,ef(t,g,g,k)}return ef(t,"localhost",Er,{ascii:!0}),t.jd=new Me(hi),{start:t,tokens:Object.assign({groups:e},df)}}function hf(n,e){let t=cb(e.replace(/[A-Z]/g,l=>l.toLowerCase())),r=t.length,s=[],i=0,o=0;for(;o=0&&(d+=t[o].length,h++),c+=t[o].length,i+=t[o].length,o++;i-=d,o-=h,c-=d,s.push({t:u.t,v:e.slice(i-c,i),s:i-c,e:i})}return s}function cb(n){let e=[],t=n.length,r=0;for(;r56319||r+1===t||(i=n.charCodeAt(r+1))<56320||i>57343?n[r]:n.slice(r,r+2);e.push(o),r+=o.length}return e}function At(n,e,t,r,s){let i,o=e.length;for(let l=0;l=0;)i++;if(i>0){e.push(t.join(""));for(let o=parseInt(n.substring(r,r+i),10);o>0;o--)t.pop();r+=i}else t.push(n[r]),r++}return e}var Nr={defaultProtocol:"http",events:null,format:rf,formatHref:rf,nl2br:!1,tagName:"a",target:null,rel:null,validate:!0,truncate:1/0,className:null,attributes:null,ignoreTags:[],render:null};function sa(n,e=null){let t=Object.assign({},Nr);n&&(t=Object.assign(t,n instanceof sa?n.o:n));let r=t.ignoreTags,s=[];for(let i=0;it?r.substring(0,t)+"\u2026":r},toFormattedHref(n){return n.get("formatHref",this.toHref(n.get("defaultProtocol")),this)},startIndex(){return this.tk[0].s},endIndex(){return this.tk[this.tk.length-1].e},toObject(n=Nr.defaultProtocol){return{type:this.t,value:this.toString(),isLink:this.isLink,href:this.toHref(n),start:this.startIndex(),end:this.endIndex()}},toFormattedObject(n){return{type:this.t,value:this.toFormattedString(n),isLink:this.isLink,href:this.toFormattedHref(n),start:this.startIndex(),end:this.endIndex()}},validate(n){return n.get("validate",this.toString(),this)},render(n){let e=this,t=this.toHref(n.get("defaultProtocol")),r=n.get("formatHref",t,this),s=n.get("tagName",t,e),i=this.toFormattedString(n),o={},l=n.get("className",t,e),a=n.get("target",t,e),c=n.get("rel",t,e),u=n.getObj("attributes",t,e),d=n.getObj("events",t,e);return o.href=r,l&&(o.class=l),a&&(o.target=a),c&&(o.rel=c),u&&Object.assign(o,u),{tagName:s,attributes:o,content:i,eventListeners:d}}};function fi(n,e){class t extends ff{constructor(s,i){super(s,i),this.t=n}}for(let r in e)t.prototype[r]=e[r];return t.t=n,t}var ub=fi("email",{isLink:!0,toHref(){return"mailto:"+this.toString()}}),sf=fi("text"),db=fi("nl"),$s=fi("url",{isLink:!0,toHref(n=Nr.defaultProtocol){return this.hasProtocol()?this.v:`${n}://${this.v}`},hasProtocol(){let n=this.tk;return n.length>=2&&n[0].t!==Er&&n[1].t===Qt}});var Be=n=>new Me(n);function hb({groups:n}){let e=n.domain.concat([ei,ni,Et,ri,si,ii,oi,li,$e,Yl,Tr,ai,ci,ui,Xe,hi,Ar,di]),t=[ti,Qt,ea,Ge,ta,Tr,Mr,na,ra,js,Js,Cr,vr,_s,Hs,Vs,Ws,Ks,qs,Us,Gs,Xs,Qs,Ys,Zs],r=[ei,ti,ni,ri,si,ii,oi,li,$e,Cr,vr,Tr,ai,ci,ui,Mr,Xe,hi,Ar,di],s=Be(),i=S(s,Ar);z(i,r,i),z(i,n.domain,i);let o=Be(),l=Be(),a=Be();z(s,n.domain,o),z(s,n.scheme,l),z(s,n.slashscheme,a),z(o,r,i),z(o,n.domain,o);let c=S(o,Et);S(i,Et,c),S(l,Et,c),S(a,Et,c);let u=S(i,Ge);z(u,r,i),z(u,n.domain,i);let d=Be();z(c,n.domain,d),z(d,n.domain,d);let h=S(d,Ge);z(h,n.domain,d);let f=Be(ub);z(h,n.tld,f),z(h,n.utld,f),S(c,Er,f);let p=S(d,$e);S(p,$e,p),z(p,n.domain,d),z(f,n.domain,d),S(f,Ge,h),S(f,$e,p);let m=S(o,$e),g=S(o,Ge);S(m,$e,m),z(m,n.domain,o),z(g,r,i),z(g,n.domain,o);let y=Be($s);z(g,n.tld,y),z(g,n.utld,y),z(y,n.domain,o),z(y,r,i),S(y,Ge,g),S(y,$e,m),S(y,Et,c);let k=S(y,Qt),w=Be($s);z(k,n.numeric,w);let C=Be($s),x=Be();z(C,e,C),z(C,t,x),z(x,e,C),z(x,t,x),S(y,Xe,C),S(w,Xe,C);let M=S(l,Qt),T=S(a,Qt),R=S(T,Xe),P=S(R,Xe);z(l,n.domain,o),S(l,Ge,g),S(l,$e,m),z(a,n.domain,o),S(a,Ge,g),S(a,$e,m),z(M,n.domain,C),S(M,Xe,C),S(M,Mr,C),z(P,n.domain,C),z(P,e,C),S(P,Xe,C);let te=[[Cr,vr],[Hs,_s],[Vs,Ws],[js,Js],[Ks,qs],[Us,Gs],[Xs,Qs],[Ys,Zs]];for(let tt=0;tt=0&&h++,s++,u++;if(h<0)s-=u,s0&&(i.push(Wl(sf,e,o)),o=[]),s-=h,u-=h;let f=d.t,p=t.slice(s-u,s);i.push(Wl(f,e,p))}}return o.length>0&&i.push(Wl(sf,e,o)),i}function Wl(n,e,t){let r=t[0].s,s=t[t.length-1].e,i=e.slice(r,s);return new n(i,t)}var pb=typeof console<"u"&&console&&console.warn||(()=>{}),mb="until manual call of linkify.init(). Register all schemes and plugins before invoking linkify the first time.",K={scanner:null,parser:null,tokenQueue:[],pluginQueue:[],customSchemes:[],initialized:!1};function pf(){return Me.groups={},K.scanner=null,K.parser=null,K.tokenQueue=[],K.pluginQueue=[],K.customSchemes=[],K.initialized=!1,K}function ia(n,e=!1){if(K.initialized&&pb(`linkifyjs: already initialized - will not register custom scheme "${n}" ${mb}`),!/^[0-9a-z]+(-[0-9a-z]+)*$/.test(n))throw new Error(`linkifyjs: incorrect scheme format. 1. Must only contain digits, lowercase ASCII letters or "-" 2. Cannot start or end with "-" -3. "-" cannot repeat`);U.customSchemes.push([t,e])}function nk(){U.scanner=Gy(U.customSchemes);for(let t=0;t{let s=e.some(c=>c.docChanged)&&!n.doc.eq(r.doc),i=e.some(c=>c.getMeta("preventAutolink"));if(!s||i)return;let{tr:o}=r,l=vo(n.doc,[...e]);if(Oo(l).forEach(({newRange:c})=>{let u=Eu(r.doc,c,f=>f.isTextblock),d,h;if(u.length>1)d=u[0],h=r.doc.textBetween(d.pos,d.pos+d.node.nodeSize,void 0," ");else if(u.length){let f=r.doc.textBetween(c.from,c.to," "," ");if(!sk.test(f))return;d=u[0],h=r.doc.textBetween(d.pos,c.to,void 0," ")}if(d&&h){let f=h.split(rk).filter(Boolean);if(f.length<=0)return!1;let p=f[f.length-1],m=d.pos+h.lastIndexOf(p);if(!p)return!1;let g=Is(p).map(y=>y.toObject(t.defaultProtocol));if(!ok(g))return!1;g.filter(y=>y.isLink).map(y=>({...y,from:m+y.start+1,to:m+y.end+1})).filter(y=>r.schema.marks.code?!r.doc.rangeHasMark(y.from,y.to,r.schema.marks.code):!0).filter(y=>t.validate(y.value)).filter(y=>t.shouldAutoLink(y.value)).forEach(y=>{qr(y.from,y.to,r.doc).some(k=>k.mark.type===t.type)||o.addMark(y.from,y.to,t.type.create({href:y.href}))})}}),!!o.steps.length)return o}})}function ak(t){return new O({key:new P("handleClickLink"),props:{handleClick:(e,n,r)=>{var s,i;if(r.button!==0||!e.editable)return!1;let o=null;if(r.target instanceof HTMLAnchorElement)o=r.target;else{let a=r.target;if(!a)return!1;let c=t.editor.view.dom;o=a.closest("a"),o&&!c.contains(o)&&(o=null)}if(!o)return!1;let l=!1;if(t.enableClickSelection&&(l=t.editor.commands.extendMarkRange(t.type.name)),t.openOnClick){let a=Ro(e.state,t.type.name),c=(s=o.href)!=null?s:a.href,u=(i=o.target)!=null?i:a.target;c&&(window.open(c,u),l=!0)}return l}}})}function ck(t){return new O({key:new P("handlePasteLink"),props:{handlePaste:(e,n,r)=>{let{shouldAutoLink:s}=t,{state:i}=e,{selection:o}=i,{empty:l}=o;if(l)return!1;let a="";r.content.forEach(u=>{a+=u.textContent});let c=Ds(a,{defaultProtocol:t.defaultProtocol}).find(u=>u.isLink&&u.value===a);return!a||!c||s!==void 0&&!s(c.value)?!1:t.editor.commands.setMark(t.type,{href:c.href})}}})}function Bt(t,e){let n=["http","https","ftp","ftps","mailto","tel","callto","sms","cid","xmpp"];return e&&e.forEach(r=>{let s=typeof r=="string"?r:r.scheme;s&&n.push(s)}),!t||t.replace(ik,"").match(new RegExp(`^(?:(?:${n.join("|")}):|[^a-z]|[a-z0-9+.-]+(?:[^a-z+.-:]|$))`,"i"))}var il=xe.create({name:"link",priority:1e3,keepOnSplit:!1,exitable:!0,onCreate(){this.options.validate&&!this.options.shouldAutoLink&&(this.options.shouldAutoLink=this.options.validate,console.warn("The `validate` option is deprecated. Rename to the `shouldAutoLink` option instead.")),this.options.protocols.forEach(t=>{if(typeof t=="string"){rl(t);return}rl(t.scheme,t.optionalSlashes)})},onDestroy(){vd()},inclusive(){return this.options.autolink},addOptions(){return{openOnClick:!0,enableClickSelection:!1,linkOnPaste:!0,autolink:!0,protocols:[],defaultProtocol:"http",HTMLAttributes:{target:"_blank",rel:"noopener noreferrer nofollow",class:null},isAllowedUri:(t,e)=>!!Bt(t,e.protocols),validate:t=>!!t,shouldAutoLink:t=>{let e=/^[a-z][a-z0-9+.-]*:\/\//i.test(t),n=/^[a-z][a-z0-9+.-]*:/i.test(t);if(e||n&&!t.includes("@"))return!0;let s=(t.includes("@")?t.split("@").pop():t).split(/[/?#:]/)[0];return!(/^\d{1,3}(\.\d{1,3}){3}$/.test(s)||!/\./.test(s))}}},addAttributes(){return{href:{default:null,parseHTML(t){return t.getAttribute("href")}},target:{default:this.options.HTMLAttributes.target},rel:{default:this.options.HTMLAttributes.rel},class:{default:this.options.HTMLAttributes.class},title:{default:null}}},parseHTML(){return[{tag:"a[href]",getAttrs:t=>{let e=t.getAttribute("href");return!e||!this.options.isAllowedUri(e,{defaultValidate:n=>!!Bt(n,this.options.protocols),protocols:this.options.protocols,defaultProtocol:this.options.defaultProtocol})?!1:null}}]},renderHTML({HTMLAttributes:t}){return this.options.isAllowedUri(t.href,{defaultValidate:e=>!!Bt(e,this.options.protocols),protocols:this.options.protocols,defaultProtocol:this.options.defaultProtocol})?["a",I(this.options.HTMLAttributes,t),0]:["a",I(this.options.HTMLAttributes,{...t,href:""}),0]},markdownTokenName:"link",parseMarkdown:(t,e)=>e.applyMark("link",e.parseInline(t.tokens||[]),{href:t.href,title:t.title||null}),renderMarkdown:(t,e)=>{var n,r,s,i;let o=(r=(n=t.attrs)==null?void 0:n.href)!=null?r:"",l=(i=(s=t.attrs)==null?void 0:s.title)!=null?i:"",a=e.renderChildren(t);return l?`[${a}](${o} "${l}")`:`[${a}](${o})`},addCommands(){return{setLink:t=>({chain:e})=>{let{href:n}=t;return this.options.isAllowedUri(n,{defaultValidate:r=>!!Bt(r,this.options.protocols),protocols:this.options.protocols,defaultProtocol:this.options.defaultProtocol})?e().setMark(this.name,t).setMeta("preventAutolink",!0).run():!1},toggleLink:t=>({chain:e})=>{let{href:n}=t||{};return n&&!this.options.isAllowedUri(n,{defaultValidate:r=>!!Bt(r,this.options.protocols),protocols:this.options.protocols,defaultProtocol:this.options.defaultProtocol})?!1:e().toggleMark(this.name,t,{extendEmptyMarkRange:!0}).setMeta("preventAutolink",!0).run()},unsetLink:()=>({chain:t})=>t().unsetMark(this.name,{extendEmptyMarkRange:!0}).setMeta("preventAutolink",!0).run()}},addPasteRules(){return[Ne({find:t=>{let e=[];if(t){let{protocols:n,defaultProtocol:r}=this.options,s=Ds(t).filter(i=>i.isLink&&this.options.isAllowedUri(i.value,{defaultValidate:o=>!!Bt(o,n),protocols:n,defaultProtocol:r}));s.length&&s.forEach(i=>{this.options.shouldAutoLink(i.value)&&e.push({text:i.value,data:{href:i.href},index:i.start})})}return e},type:this.type,getAttributes:t=>{var e;return{href:(e=t.data)==null?void 0:e.href}}})]},addProseMirrorPlugins(){let t=[],{protocols:e,defaultProtocol:n}=this.options;return this.options.autolink&&t.push(lk({type:this.type,defaultProtocol:this.options.defaultProtocol,validate:r=>this.options.isAllowedUri(r,{defaultValidate:s=>!!Bt(s,e),protocols:e,defaultProtocol:n}),shouldAutoLink:this.options.shouldAutoLink})),t.push(ak({type:this.type,editor:this.editor,openOnClick:this.options.openOnClick==="whenNotEditable"?!0:this.options.openOnClick,enableClickSelection:this.options.enableClickSelection})),this.options.linkOnPaste&&t.push(ck({editor:this.editor,defaultProtocol:this.options.defaultProtocol,type:this.type,shouldAutoLink:this.options.shouldAutoLink})),t}}),uk=il;var dk=Object.defineProperty,hk=(t,e)=>{for(var n in e)dk(t,n,{get:e[n],enumerable:!0})},fk="listItem",Ad="textStyle",Ed=/^\s*([-+*])\s$/,al=$.create({name:"bulletList",addOptions(){return{itemTypeName:"listItem",HTMLAttributes:{},keepMarks:!1,keepAttributes:!1}},group:"block list",content(){return`${this.options.itemTypeName}+`},parseHTML(){return[{tag:"ul"}]},renderHTML({HTMLAttributes:t}){return["ul",I(this.options.HTMLAttributes,t),0]},markdownTokenName:"list",parseMarkdown:(t,e)=>t.type!=="list"||t.ordered?[]:{type:"bulletList",content:t.items?e.parseChildren(t.items):[]},renderMarkdown:(t,e)=>t.content?e.renderChildren(t.content,` -`):"",markdownOptions:{indentsContent:!0},addCommands(){return{toggleBulletList:()=>({commands:t,chain:e})=>this.options.keepAttributes?e().toggleList(this.name,this.options.itemTypeName,this.options.keepMarks).updateAttributes(fk,this.editor.getAttributes(Ad)).run():t.toggleList(this.name,this.options.itemTypeName,this.options.keepMarks)}},addKeyboardShortcuts(){return{"Mod-Shift-8":()=>this.editor.commands.toggleBulletList()}},addInputRules(){let t=Fe({find:Ed,type:this.type});return(this.options.keepMarks||this.options.keepAttributes)&&(t=Fe({find:Ed,type:this.type,keepMarks:this.options.keepMarks,keepAttributes:this.options.keepAttributes,getAttributes:()=>this.editor.getAttributes(Ad),editor:this.editor})),[t]}}),cl=$.create({name:"listItem",addOptions(){return{HTMLAttributes:{},bulletListTypeName:"bulletList",orderedListTypeName:"orderedList"}},content:"paragraph block*",defining:!0,parseHTML(){return[{tag:"li"}]},renderHTML({HTMLAttributes:t}){return["li",I(this.options.HTMLAttributes,t),0]},markdownTokenName:"list_item",parseMarkdown:(t,e)=>{if(t.type!=="list_item")return[];let n=[];if(t.tokens&&t.tokens.length>0)if(t.tokens.some(s=>s.type==="paragraph"))n=e.parseChildren(t.tokens);else{let s=t.tokens[0];if(s&&s.type==="text"&&s.tokens&&s.tokens.length>0){if(n=[{type:"paragraph",content:e.parseInline(s.tokens)}],t.tokens.length>1){let o=t.tokens.slice(1),l=e.parseChildren(o);n.push(...l)}}else n=e.parseChildren(t.tokens)}return n.length===0&&(n=[{type:"paragraph",content:[]}]),{type:"listItem",content:n}},renderMarkdown:(t,e,n)=>Un(t,e,r=>{var s,i;return r.parentType==="bulletList"?"- ":r.parentType==="orderedList"?`${(((i=(s=r.meta)==null?void 0:s.parentAttrs)==null?void 0:i.start)||1)+r.index}. `:"- "},n),addKeyboardShortcuts(){return{Enter:()=>this.editor.commands.splitListItem(this.name),Tab:()=>this.editor.commands.sinkListItem(this.name),"Shift-Tab":()=>this.editor.commands.liftListItem(this.name)}}}),pk={};hk(pk,{findListItemPos:()=>rr,getNextListDepth:()=>ul,handleBackspace:()=>ol,handleDelete:()=>ll,hasListBefore:()=>Id,hasListItemAfter:()=>mk,hasListItemBefore:()=>Dd,listItemHasSubList:()=>Pd,nextListIsDeeper:()=>Ld,nextListIsHigher:()=>zd});var rr=(t,e)=>{let{$from:n}=e.selection,r=J(t,e.schema),s=null,i=n.depth,o=n.pos,l=null;for(;i>0&&l===null;)s=n.node(i),s.type===r?l=i:(i-=1,o-=1);return l===null?null:{$pos:e.doc.resolve(o),depth:l}},ul=(t,e)=>{let n=rr(t,e);if(!n)return!1;let[,r]=Lu(e,t,n.$pos.pos+4);return r},Id=(t,e,n)=>{let{$anchor:r}=t.selection,s=Math.max(0,r.pos-2),i=t.doc.resolve(s).node();return!(!i||!n.includes(i.type.name))},Dd=(t,e)=>{var n;let{$anchor:r}=e.selection,s=e.doc.resolve(r.pos-2);return!(s.index()===0||((n=s.nodeBefore)==null?void 0:n.type.name)!==t)},Pd=(t,e,n)=>{if(!n)return!1;let r=J(t,e.schema),s=!1;return n.descendants(i=>{i.type===r&&(s=!0)}),s},ol=(t,e,n)=>{if(t.commands.undoInputRule())return!0;if(t.state.selection.from!==t.state.selection.to)return!1;if(!$e(t.state,e)&&Id(t.state,e,n)){let{$anchor:l}=t.state.selection,a=t.state.doc.resolve(l.before()-1),c=[];a.node().descendants((h,f)=>{h.type.name===e&&c.push({node:h,pos:f})});let u=c.at(-1);if(!u)return!1;let d=t.state.doc.resolve(a.start()+u.pos+1);return t.chain().cut({from:l.start()-1,to:l.end()+1},d.end()).joinForward().run()}if(!$e(t.state,e)||!Bu(t.state))return!1;let r=rr(e,t.state);if(!r)return!1;let i=t.state.doc.resolve(r.$pos.pos-2).node(r.depth),o=Pd(e,t.state,i);return Dd(e,t.state)&&!o?t.commands.joinItemBackward():t.chain().liftListItem(e).run()},Ld=(t,e)=>{let n=ul(t,e),r=rr(t,e);return!r||!n?!1:n>r.depth},zd=(t,e)=>{let n=ul(t,e),r=rr(t,e);return!r||!n?!1:n{if(!$e(t.state,e)||!zu(t.state,e))return!1;let{selection:n}=t.state,{$from:r,$to:s}=n;return!n.empty&&r.sameParent(s)?!1:Ld(e,t.state)?t.chain().focus(t.state.selection.from+4).lift(e).joinBackward().run():zd(e,t.state)?t.chain().joinForward().joinBackward().run():t.commands.joinItemForward()},mk=(t,e)=>{var n;let{$anchor:r}=e.selection,s=e.doc.resolve(r.pos-r.parentOffset-2);return!(s.index()===s.parent.childCount-1||((n=s.nodeAfter)==null?void 0:n.type.name)!==t)},dl=B.create({name:"listKeymap",addOptions(){return{listTypes:[{itemName:"listItem",wrapperNames:["bulletList","orderedList"]},{itemName:"taskItem",wrapperNames:["taskList"]}]}},addKeyboardShortcuts(){return{Delete:({editor:t})=>{let e=!1;return this.options.listTypes.forEach(({itemName:n})=>{t.state.schema.nodes[n]!==void 0&&ll(t,n)&&(e=!0)}),e},"Mod-Delete":({editor:t})=>{let e=!1;return this.options.listTypes.forEach(({itemName:n})=>{t.state.schema.nodes[n]!==void 0&&ll(t,n)&&(e=!0)}),e},Backspace:({editor:t})=>{let e=!1;return this.options.listTypes.forEach(({itemName:n,wrapperNames:r})=>{t.state.schema.nodes[n]!==void 0&&ol(t,n,r)&&(e=!0)}),e},"Mod-Backspace":({editor:t})=>{let e=!1;return this.options.listTypes.forEach(({itemName:n,wrapperNames:r})=>{t.state.schema.nodes[n]!==void 0&&ol(t,n,r)&&(e=!0)}),e}}}}),Nd=/^(\s*)(\d+)\.\s+(.*)$/,gk=/^\s/;function yk(t){let e=[],n=0,r=0;for(;ne;)h.push(t[d]),d+=1;if(h.length>0){let f=Math.min(...h.map(m=>m.indent)),p=Bd(h,f,n);c.push({type:"list",ordered:!0,start:h[0].number,items:p,raw:h.map(m=>m.raw).join(` -`)})}s.push({type:"list_item",raw:o.raw,tokens:c}),i=d}else i+=1}return s}function kk(t,e){return t.map(n=>{if(n.type!=="list_item")return e.parseChildren([n])[0];let r=[];return n.tokens&&n.tokens.length>0&&n.tokens.forEach(s=>{if(s.type==="paragraph"||s.type==="list"||s.type==="blockquote"||s.type==="code")r.push(...e.parseChildren([s]));else if(s.type==="text"&&s.tokens){let i=e.parseChildren([s]);r.push({type:"paragraph",content:i})}else{let i=e.parseChildren([s]);i.length>0&&r.push(...i)}}),{type:"listItem",content:r}})}var bk="listItem",Rd="textStyle",Od=/^(\d+)\.\s$/,hl=$.create({name:"orderedList",addOptions(){return{itemTypeName:"listItem",HTMLAttributes:{},keepMarks:!1,keepAttributes:!1}},group:"block list",content(){return`${this.options.itemTypeName}+`},addAttributes(){return{start:{default:1,parseHTML:t=>t.hasAttribute("start")?parseInt(t.getAttribute("start")||"",10):1},type:{default:null,parseHTML:t=>t.getAttribute("type")}}},parseHTML(){return[{tag:"ol"}]},renderHTML({HTMLAttributes:t}){let{start:e,...n}=t;return e===1?["ol",I(this.options.HTMLAttributes,n),0]:["ol",I(this.options.HTMLAttributes,t),0]},markdownTokenName:"list",parseMarkdown:(t,e)=>{if(t.type!=="list"||!t.ordered)return[];let n=t.start||1,r=t.items?kk(t.items,e):[];return n!==1?{type:"orderedList",attrs:{start:n},content:r}:{type:"orderedList",content:r}},renderMarkdown:(t,e)=>t.content?e.renderChildren(t.content,` -`):"",markdownTokenizer:{name:"orderedList",level:"block",start:t=>{let e=t.match(/^(\s*)(\d+)\.\s+/),n=e?.index;return n!==void 0?n:-1},tokenize:(t,e,n)=>{var r;let s=t.split(` -`),[i,o]=yk(s);if(i.length===0)return;let l=Bd(i,0,n);return l.length===0?void 0:{type:"list",ordered:!0,start:((r=i[0])==null?void 0:r.number)||1,items:l,raw:s.slice(0,o).join(` -`)}}},markdownOptions:{indentsContent:!0},addCommands(){return{toggleOrderedList:()=>({commands:t,chain:e})=>this.options.keepAttributes?e().toggleList(this.name,this.options.itemTypeName,this.options.keepMarks).updateAttributes(bk,this.editor.getAttributes(Rd)).run():t.toggleList(this.name,this.options.itemTypeName,this.options.keepMarks)}},addKeyboardShortcuts(){return{"Mod-Shift-7":()=>this.editor.commands.toggleOrderedList()}},addInputRules(){let t=Fe({find:Od,type:this.type,getAttributes:e=>({start:+e[1]}),joinPredicate:(e,n)=>n.childCount+n.attrs.start===+e[1]});return(this.options.keepMarks||this.options.keepAttributes)&&(t=Fe({find:Od,type:this.type,keepMarks:this.options.keepMarks,keepAttributes:this.options.keepAttributes,getAttributes:e=>({start:+e[1],...this.editor.getAttributes(Rd)}),joinPredicate:(e,n)=>n.childCount+n.attrs.start===+e[1],editor:this.editor})),[t]}}),xk=/^\s*(\[([( |x])?\])\s$/,Ps=$.create({name:"taskItem",addOptions(){return{nested:!1,HTMLAttributes:{},taskListTypeName:"taskList",a11y:void 0}},content(){return this.options.nested?"paragraph block*":"paragraph+"},defining:!0,addAttributes(){return{checked:{default:!1,keepOnSplit:!1,parseHTML:t=>{let e=t.getAttribute("data-checked");return e===""||e==="true"},renderHTML:t=>({"data-checked":t.checked})}}},parseHTML(){return[{tag:`li[data-type="${this.name}"]`,priority:51}]},renderHTML({node:t,HTMLAttributes:e}){return["li",I(this.options.HTMLAttributes,e,{"data-type":this.name}),["label",["input",{type:"checkbox",checked:t.attrs.checked?"checked":null}],["span"]],["div",0]]},parseMarkdown:(t,e)=>{let n=[];if(t.tokens&&t.tokens.length>0?n.push(e.createNode("paragraph",{},e.parseInline(t.tokens))):t.text?n.push(e.createNode("paragraph",{},[e.createNode("text",{text:t.text})])):n.push(e.createNode("paragraph",{},[])),t.nestedTokens&&t.nestedTokens.length>0){let r=e.parseChildren(t.nestedTokens);n.push(...r)}return e.createNode("taskItem",{checked:t.checked||!1},n)},renderMarkdown:(t,e)=>{var n;let s=`- [${(n=t.attrs)!=null&&n.checked?"x":" "}] `;return Un(t,e,s)},addKeyboardShortcuts(){let t={Enter:()=>this.editor.commands.splitListItem(this.name),"Shift-Tab":()=>this.editor.commands.liftListItem(this.name)};return this.options.nested?{...t,Tab:()=>this.editor.commands.sinkListItem(this.name)}:t},addNodeView(){return({node:t,HTMLAttributes:e,getPos:n,editor:r})=>{let s=document.createElement("li"),i=document.createElement("label"),o=document.createElement("span"),l=document.createElement("input"),a=document.createElement("div"),c=d=>{var h,f;l.ariaLabel=((f=(h=this.options.a11y)==null?void 0:h.checkboxLabel)==null?void 0:f.call(h,d,l.checked))||`Task item checkbox for ${d.textContent||"empty task item"}`};c(t),i.contentEditable="false",l.type="checkbox",l.addEventListener("mousedown",d=>d.preventDefault()),l.addEventListener("change",d=>{if(!r.isEditable&&!this.options.onReadOnlyChecked){l.checked=!l.checked;return}let{checked:h}=d.target;r.isEditable&&typeof n=="function"&&r.chain().focus(void 0,{scrollIntoView:!1}).command(({tr:f})=>{let p=n();if(typeof p!="number")return!1;let m=f.doc.nodeAt(p);return f.setNodeMarkup(p,void 0,{...m?.attrs,checked:h}),!0}).run(),!r.isEditable&&this.options.onReadOnlyChecked&&(this.options.onReadOnlyChecked(t,h)||(l.checked=!l.checked))}),Object.entries(this.options.HTMLAttributes).forEach(([d,h])=>{s.setAttribute(d,h)}),s.dataset.checked=t.attrs.checked,l.checked=t.attrs.checked,i.append(l,o),s.append(i,a),Object.entries(e).forEach(([d,h])=>{s.setAttribute(d,h)});let u=new Set(Object.keys(e));return{dom:s,contentDOM:a,update:d=>{if(d.type!==this.type)return!1;s.dataset.checked=d.attrs.checked,l.checked=d.attrs.checked,c(d);let h=r.extensionManager.attributes,f=dn(d,h),p=new Set(Object.keys(f)),m=this.options.HTMLAttributes;return u.forEach(g=>{p.has(g)||(g in m?s.setAttribute(g,m[g]):s.removeAttribute(g))}),Object.entries(f).forEach(([g,y])=>{y==null?g in m?s.setAttribute(g,m[g]):s.removeAttribute(g):s.setAttribute(g,y)}),u=p,!0}}}},addInputRules(){return[Fe({find:xk,type:this.type,getAttributes:t=>({checked:t[t.length-1]==="x"})})]}}),Ls=$.create({name:"taskList",addOptions(){return{itemTypeName:"taskItem",HTMLAttributes:{}}},group:"block list",content(){return`${this.options.itemTypeName}+`},parseHTML(){return[{tag:`ul[data-type="${this.name}"]`,priority:51}]},renderHTML({HTMLAttributes:t}){return["ul",I(this.options.HTMLAttributes,t,{"data-type":this.name}),0]},parseMarkdown:(t,e)=>e.createNode("taskList",{},e.parseChildren(t.items||[])),renderMarkdown:(t,e)=>t.content?e.renderChildren(t.content,` -`):"",markdownTokenizer:{name:"taskList",level:"block",start(t){var e;let n=(e=t.match(/^\s*[-+*]\s+\[([ xX])\]\s+/))==null?void 0:e.index;return n!==void 0?n:-1},tokenize(t,e,n){let r=i=>{let o=Yr(i,{itemPattern:/^(\s*)([-+*])\s+\[([ xX])\]\s+(.*)$/,extractItemData:l=>({indentLevel:l[1].length,mainContent:l[4],checked:l[3].toLowerCase()==="x"}),createToken:(l,a)=>({type:"taskItem",raw:"",mainContent:l.mainContent,indentLevel:l.indentLevel,checked:l.checked,text:l.mainContent,tokens:n.inlineTokens(l.mainContent),nestedTokens:a}),customNestedParser:r},n);return o?[{type:"taskList",raw:o.raw,items:o.items}]:n.blockTokens(i)},s=Yr(t,{itemPattern:/^(\s*)([-+*])\s+\[([ xX])\]\s+(.*)$/,extractItemData:i=>({indentLevel:i[1].length,mainContent:i[4],checked:i[3].toLowerCase()==="x"}),createToken:(i,o)=>({type:"taskItem",raw:"",mainContent:i.mainContent,indentLevel:i.indentLevel,checked:i.checked,text:i.mainContent,tokens:n.inlineTokens(i.mainContent),nestedTokens:o}),customNestedParser:r},n);if(s)return{type:"taskList",raw:s.raw,items:s.items}}},markdownOptions:{indentsContent:!0},addCommands(){return{toggleTaskList:()=>({commands:t})=>t.toggleList(this.name,this.options.itemTypeName)}},addKeyboardShortcuts(){return{"Mod-Shift-9":()=>this.editor.commands.toggleTaskList()}}}),F1=B.create({name:"listKit",addExtensions(){let t=[];return this.options.bulletList!==!1&&t.push(al.configure(this.options.bulletList)),this.options.listItem!==!1&&t.push(cl.configure(this.options.listItem)),this.options.listKeymap!==!1&&t.push(dl.configure(this.options.listKeymap)),this.options.orderedList!==!1&&t.push(hl.configure(this.options.orderedList)),this.options.taskItem!==!1&&t.push(Ps.configure(this.options.taskItem)),this.options.taskList!==!1&&t.push(Ls.configure(this.options.taskList)),t}});var $d=" ",wk="\xA0",Hd=$.create({name:"paragraph",priority:1e3,addOptions(){return{HTMLAttributes:{}}},group:"block",content:"inline*",parseHTML(){return[{tag:"p"}]},renderHTML({HTMLAttributes:t}){return["p",I(this.options.HTMLAttributes,t),0]},parseMarkdown:(t,e)=>{let n=t.tokens||[];if(n.length===1&&n[0].type==="image")return e.parseChildren([n[0]]);let r=e.parseInline(n);return r.length===1&&r[0].type==="text"&&(r[0].text===$d||r[0].text===wk)?e.createNode("paragraph",void 0,[]):e.createNode("paragraph",void 0,r)},renderMarkdown:(t,e)=>{if(!t)return"";let n=Array.isArray(t.content)?t.content:[];return n.length===0?$d:e.renderChildren(n)},addCommands(){return{setParagraph:()=>({commands:t})=>t.setNode(this.name)}},addKeyboardShortcuts(){return{"Mod-Alt-0":()=>this.editor.commands.setParagraph()}}});var Sk=/(?:^|\s)(~~(?!\s+~~)((?:[^~]+))~~(?!\s+~~))$/,Ck=/(?:^|\s)(~~(?!\s+~~)((?:[^~]+))~~(?!\s+~~))/g,Fd=xe.create({name:"strike",addOptions(){return{HTMLAttributes:{}}},parseHTML(){return[{tag:"s"},{tag:"del"},{tag:"strike"},{style:"text-decoration",consuming:!1,getAttrs:t=>t.includes("line-through")?{}:!1}]},renderHTML({HTMLAttributes:t}){return["s",I(this.options.HTMLAttributes,t),0]},markdownTokenName:"del",parseMarkdown:(t,e)=>e.applyMark("strike",e.parseInline(t.tokens||[])),renderMarkdown:(t,e)=>`~~${e.renderChildren(t)}~~`,addCommands(){return{setStrike:()=>({commands:t})=>t.setMark(this.name),toggleStrike:()=>({commands:t})=>t.toggleMark(this.name),unsetStrike:()=>({commands:t})=>t.unsetMark(this.name)}},addKeyboardShortcuts(){return{"Mod-Shift-s":()=>this.editor.commands.toggleStrike()}},addInputRules(){return[He({find:Sk,type:this.type})]},addPasteRules(){return[Ne({find:Ck,type:this.type})]}});var _d=$.create({name:"text",group:"inline",parseMarkdown:t=>({type:"text",text:t.text||""}),renderMarkdown:t=>t.text||""});var Vd=xe.create({name:"underline",addOptions(){return{HTMLAttributes:{}}},parseHTML(){return[{tag:"u"},{style:"text-decoration",consuming:!1,getAttrs:t=>t.includes("underline")?{}:!1}]},renderHTML({HTMLAttributes:t}){return["u",I(this.options.HTMLAttributes,t),0]},parseMarkdown(t,e){return e.applyMark(this.name||"underline",e.parseInline(t.tokens||[]))},renderMarkdown(t,e){return`++${e.renderChildren(t)}++`},markdownTokenizer:{name:"underline",level:"inline",start(t){return t.indexOf("++")},tokenize(t,e,n){let s=/^(\+\+)([\s\S]+?)(\+\+)/.exec(t);if(!s)return;let i=s[2].trim();return{type:"underline",raw:s[0],text:i,tokens:n.inlineTokens(i)}}},addCommands(){return{setUnderline:()=>({commands:t})=>t.setMark(this.name),toggleUnderline:()=>({commands:t})=>t.toggleMark(this.name),unsetUnderline:()=>({commands:t})=>t.unsetMark(this.name)}},addKeyboardShortcuts(){return{"Mod-u":()=>this.editor.commands.toggleUnderline(),"Mod-U":()=>this.editor.commands.toggleUnderline()}}});function Wd(t={}){return new O({view(e){return new fl(e,t)}})}var fl=class{constructor(e,n){var r;this.editorView=e,this.cursorPos=null,this.element=null,this.timeout=-1,this.width=(r=n.width)!==null&&r!==void 0?r:1,this.color=n.color===!1?void 0:n.color||"black",this.class=n.class,this.handlers=["dragover","dragend","drop","dragleave"].map(s=>{let i=o=>{this[s](o)};return e.dom.addEventListener(s,i),{name:s,handler:i}})}destroy(){this.handlers.forEach(({name:e,handler:n})=>this.editorView.dom.removeEventListener(e,n))}update(e,n){this.cursorPos!=null&&n.doc!=e.state.doc&&(this.cursorPos>e.state.doc.content.size?this.setCursor(null):this.updateOverlay())}setCursor(e){e!=this.cursorPos&&(this.cursorPos=e,e==null?(this.element.parentNode.removeChild(this.element),this.element=null):this.updateOverlay())}updateOverlay(){let e=this.editorView.state.doc.resolve(this.cursorPos),n=!e.parent.inlineContent,r,s=this.editorView.dom,i=s.getBoundingClientRect(),o=i.width/s.offsetWidth,l=i.height/s.offsetHeight;if(n){let d=e.nodeBefore,h=e.nodeAfter;if(d||h){let f=this.editorView.nodeDOM(this.cursorPos-(d?d.nodeSize:0));if(f){let p=f.getBoundingClientRect(),m=d?p.bottom:p.top;d&&h&&(m=(m+this.editorView.nodeDOM(this.cursorPos).getBoundingClientRect().top)/2);let g=this.width/2*l;r={left:p.left,right:p.right,top:m-g,bottom:m+g}}}}if(!r){let d=this.editorView.coordsAtPos(this.cursorPos),h=this.width/2*o;r={left:d.left-h,right:d.left+h,top:d.top,bottom:d.bottom}}let a=this.editorView.dom.offsetParent;this.element||(this.element=a.appendChild(document.createElement("div")),this.class&&(this.element.className=this.class),this.element.style.cssText="position: absolute; z-index: 50; pointer-events: none;",this.color&&(this.element.style.backgroundColor=this.color)),this.element.classList.toggle("prosemirror-dropcursor-block",n),this.element.classList.toggle("prosemirror-dropcursor-inline",!n);let c,u;if(!a||a==document.body&&getComputedStyle(a).position=="static")c=-pageXOffset,u=-pageYOffset;else{let d=a.getBoundingClientRect(),h=d.width/a.offsetWidth,f=d.height/a.offsetHeight;c=d.left-a.scrollLeft*h,u=d.top-a.scrollTop*f}this.element.style.left=(r.left-c)/o+"px",this.element.style.top=(r.top-u)/l+"px",this.element.style.width=(r.right-r.left)/o+"px",this.element.style.height=(r.bottom-r.top)/l+"px"}scheduleRemoval(e){clearTimeout(this.timeout),this.timeout=setTimeout(()=>this.setCursor(null),e)}dragover(e){if(!this.editorView.editable)return;let n=this.editorView.posAtCoords({left:e.clientX,top:e.clientY}),r=n&&n.inside>=0&&this.editorView.state.doc.nodeAt(n.inside),s=r&&r.type.spec.disableDropCursor,i=typeof s=="function"?s(this.editorView,n,e):s;if(n&&!i){let o=n.pos;if(this.editorView.dragging&&this.editorView.dragging.slice){let l=kr(this.editorView.state.doc,o,this.editorView.dragging.slice);l!=null&&(o=l)}this.setCursor(o),this.scheduleRemoval(5e3)}}dragend(){this.scheduleRemoval(20)}drop(){this.scheduleRemoval(20)}dragleave(e){this.editorView.dom.contains(e.relatedTarget)||this.setCursor(null)}};var ae=class t extends R{constructor(e){super(e,e)}map(e,n){let r=e.resolve(n.map(this.head));return t.valid(r)?new t(r):R.near(r)}content(){return C.empty}eq(e){return e instanceof t&&e.head==this.head}toJSON(){return{type:"gapcursor",pos:this.head}}static fromJSON(e,n){if(typeof n.pos!="number")throw new RangeError("Invalid input for GapCursor.fromJSON");return new t(e.resolve(n.pos))}getBookmark(){return new pl(this.anchor)}static valid(e){let n=e.parent;if(n.isTextblock||!Mk(e)||!Tk(e))return!1;let r=n.type.spec.allowGapCursor;if(r!=null)return r;let s=n.contentMatchAt(e.index()).defaultType;return s&&s.isTextblock}static findGapCursorFrom(e,n,r=!1){e:for(;;){if(!r&&t.valid(e))return e;let s=e.pos,i=null;for(let o=e.depth;;o--){let l=e.node(o);if(n>0?e.indexAfter(o)0){i=l.child(n>0?e.indexAfter(o):e.index(o)-1);break}else if(o==0)return null;s+=n;let a=e.doc.resolve(s);if(t.valid(a))return a}for(;;){let o=n>0?i.firstChild:i.lastChild;if(!o){if(i.isAtom&&!i.isText&&!E.isSelectable(i)){e=e.doc.resolve(s+i.nodeSize*n),r=!1;continue e}break}i=o,s+=n;let l=e.doc.resolve(s);if(t.valid(l))return l}return null}}};ae.prototype.visible=!1;ae.findFrom=ae.findGapCursorFrom;R.jsonID("gapcursor",ae);var pl=class t{constructor(e){this.pos=e}map(e){return new t(e.map(this.pos))}resolve(e){let n=e.resolve(this.pos);return ae.valid(n)?new ae(n):R.near(n)}};function jd(t){return t.isAtom||t.spec.isolating||t.spec.createGapCursor}function Mk(t){for(let e=t.depth;e>=0;e--){let n=t.index(e),r=t.node(e);if(n==0){if(r.type.spec.isolating)return!0;continue}for(let s=r.child(n-1);;s=s.lastChild){if(s.childCount==0&&!s.inlineContent||jd(s.type))return!0;if(s.inlineContent)return!1}}return!0}function Tk(t){for(let e=t.depth;e>=0;e--){let n=t.indexAfter(e),r=t.node(e);if(n==r.childCount){if(r.type.spec.isolating)return!0;continue}for(let s=r.child(n);;s=s.firstChild){if(s.childCount==0&&!s.inlineContent||jd(s.type))return!0;if(s.inlineContent)return!1}}return!0}function Kd(){return new O({props:{decorations:Nk,createSelectionBetween(t,e,n){return e.pos==n.pos&&ae.valid(n)?new ae(n):null},handleClick:Ak,handleKeyDown:vk,handleDOMEvents:{beforeinput:Ek}}})}var vk=Bn({ArrowLeft:zs("horiz",-1),ArrowRight:zs("horiz",1),ArrowUp:zs("vert",-1),ArrowDown:zs("vert",1)});function zs(t,e){let n=t=="vert"?e>0?"down":"up":e>0?"right":"left";return function(r,s,i){let o=r.selection,l=e>0?o.$to:o.$from,a=o.empty;if(o instanceof A){if(!i.endOfTextblock(n)||l.depth==0)return!1;a=!1,l=r.doc.resolve(e>0?l.after():l.before())}let c=ae.findGapCursorFrom(l,e,a);return c?(s&&s(r.tr.setSelection(new ae(c))),!0):!1}}function Ak(t,e,n){if(!t||!t.editable)return!1;let r=t.state.doc.resolve(e);if(!ae.valid(r))return!1;let s=t.posAtCoords({left:n.clientX,top:n.clientY});return s&&s.inside>-1&&E.isSelectable(t.state.doc.nodeAt(s.inside))?!1:(t.dispatch(t.state.tr.setSelection(new ae(r))),!0)}function Ek(t,e){if(e.inputType!="insertCompositionText"||!(t.state.selection instanceof ae))return!1;let{$from:n}=t.state.selection,r=n.parent.contentMatchAt(n.index()).findWrapping(t.state.schema.nodes.text);if(!r)return!1;let s=b.empty;for(let o=r.length-1;o>=0;o--)s=b.from(r[o].createAndFill(null,s));let i=t.state.tr.replace(n.pos,n.pos,new C(s,0,0));return i.setSelection(A.near(i.doc.resolve(n.pos+1))),t.dispatch(i),!1}function Nk(t){if(!(t.selection instanceof ae))return null;let e=document.createElement("div");return e.className="ProseMirror-gapcursor",K.create(t.doc,[Q.widget(t.selection.head,e,{key:"gapcursor"})])}var Bs=200,re=function(){};re.prototype.append=function(e){return e.length?(e=re.from(e),!this.length&&e||e.length=n?re.empty:this.sliceInner(Math.max(0,e),Math.min(this.length,n))};re.prototype.get=function(e){if(!(e<0||e>=this.length))return this.getInner(e)};re.prototype.forEach=function(e,n,r){n===void 0&&(n=0),r===void 0&&(r=this.length),n<=r?this.forEachInner(e,n,r,0):this.forEachInvertedInner(e,n,r,0)};re.prototype.map=function(e,n,r){n===void 0&&(n=0),r===void 0&&(r=this.length);var s=[];return this.forEach(function(i,o){return s.push(e(i,o))},n,r),s};re.from=function(e){return e instanceof re?e:e&&e.length?new Ud(e):re.empty};var Ud=(function(t){function e(r){t.call(this),this.values=r}t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e;var n={length:{configurable:!0},depth:{configurable:!0}};return e.prototype.flatten=function(){return this.values},e.prototype.sliceInner=function(s,i){return s==0&&i==this.length?this:new e(this.values.slice(s,i))},e.prototype.getInner=function(s){return this.values[s]},e.prototype.forEachInner=function(s,i,o,l){for(var a=i;a=o;a--)if(s(this.values[a],l+a)===!1)return!1},e.prototype.leafAppend=function(s){if(this.length+s.length<=Bs)return new e(this.values.concat(s.flatten()))},e.prototype.leafPrepend=function(s){if(this.length+s.length<=Bs)return new e(s.flatten().concat(this.values))},n.length.get=function(){return this.values.length},n.depth.get=function(){return 0},Object.defineProperties(e.prototype,n),e})(re);re.empty=new Ud([]);var Rk=(function(t){function e(n,r){t.call(this),this.left=n,this.right=r,this.length=n.length+r.length,this.depth=Math.max(n.depth,r.depth)+1}return t&&(e.__proto__=t),e.prototype=Object.create(t&&t.prototype),e.prototype.constructor=e,e.prototype.flatten=function(){return this.left.flatten().concat(this.right.flatten())},e.prototype.getInner=function(r){return rl&&this.right.forEachInner(r,Math.max(s-l,0),Math.min(this.length,i)-l,o+l)===!1)return!1},e.prototype.forEachInvertedInner=function(r,s,i,o){var l=this.left.length;if(s>l&&this.right.forEachInvertedInner(r,s-l,Math.max(i,l)-l,o+l)===!1||i=i?this.right.slice(r-i,s-i):this.left.slice(r,i).append(this.right.slice(0,s-i))},e.prototype.leafAppend=function(r){var s=this.right.leafAppend(r);if(s)return new e(this.left,s)},e.prototype.leafPrepend=function(r){var s=this.left.leafPrepend(r);if(s)return new e(s,this.right)},e.prototype.appendInner=function(r){return this.left.depth>=Math.max(this.right.depth,r.depth)+1?new e(this.left,new e(this.right,r)):new e(this,r)},e})(re),ml=re;var Ok=500,Ht=class t{constructor(e,n){this.items=e,this.eventCount=n}popEvent(e,n){if(this.eventCount==0)return null;let r=this.items.length;for(;;r--)if(this.items.get(r-1).selection){--r;break}let s,i;n&&(s=this.remapping(r,this.items.length),i=s.maps.length);let o=e.tr,l,a,c=[],u=[];return this.items.forEach((d,h)=>{if(!d.step){s||(s=this.remapping(r,h+1),i=s.maps.length),i--,u.push(d);return}if(s){u.push(new We(d.map));let f=d.step.map(s.slice(i)),p;f&&o.maybeStep(f).doc&&(p=o.mapping.maps[o.mapping.maps.length-1],c.push(new We(p,void 0,void 0,c.length+u.length))),i--,p&&s.appendMap(p,i)}else o.maybeStep(d.step);if(d.selection)return l=s?d.selection.map(s.slice(i)):d.selection,a=new t(this.items.slice(0,r).append(u.reverse().concat(c)),this.eventCount-1),!1},this.items.length,0),{remaining:a,transform:o,selection:l}}addTransform(e,n,r,s){let i=[],o=this.eventCount,l=this.items,a=!s&&l.length?l.get(l.length-1):null;for(let u=0;uDk&&(l=Ik(l,c),o-=c),new t(l.append(i),o)}remapping(e,n){let r=new Cn;return this.items.forEach((s,i)=>{let o=s.mirrorOffset!=null&&i-s.mirrorOffset>=e?r.maps.length-s.mirrorOffset:void 0;r.appendMap(s.map,o)},e,n),r}addMaps(e){return this.eventCount==0?this:new t(this.items.append(e.map(n=>new We(n))),this.eventCount)}rebased(e,n){if(!this.eventCount)return this;let r=[],s=Math.max(0,this.items.length-n),i=e.mapping,o=e.steps.length,l=this.eventCount;this.items.forEach(h=>{h.selection&&l--},s);let a=n;this.items.forEach(h=>{let f=i.getMirror(--a);if(f==null)return;o=Math.min(o,f);let p=i.maps[f];if(h.step){let m=e.steps[f].invert(e.docs[f]),g=h.selection&&h.selection.map(i.slice(a+1,f));g&&l++,r.push(new We(p,m,g))}else r.push(new We(p))},s);let c=[];for(let h=n;hOk&&(d=d.compress(this.items.length-r.length)),d}emptyItemCount(){let e=0;return this.items.forEach(n=>{n.step||e++}),e}compress(e=this.items.length){let n=this.remapping(0,e),r=n.maps.length,s=[],i=0;return this.items.forEach((o,l)=>{if(l>=e)s.push(o),o.selection&&i++;else if(o.step){let a=o.step.map(n.slice(r)),c=a&&a.getMap();if(r--,c&&n.appendMap(c,r),a){let u=o.selection&&o.selection.map(n.slice(r));u&&i++;let d=new We(c.invert(),a,u),h,f=s.length-1;(h=s.length&&s[f].merge(d))?s[f]=h:s.push(d)}}else o.map&&r--},this.items.length,0),new t(ml.from(s.reverse()),i)}};Ht.empty=new Ht(ml.empty,0);function Ik(t,e){let n;return t.forEach((r,s)=>{if(r.selection&&e--==0)return n=s,!1}),t.slice(n)}var We=class t{constructor(e,n,r,s){this.map=e,this.step=n,this.selection=r,this.mirrorOffset=s}merge(e){if(this.step&&e.step&&!e.selection){let n=e.step.merge(this.step);if(n)return new t(n.getMap().invert(),n,this.selection)}}},je=class{constructor(e,n,r,s,i){this.done=e,this.undone=n,this.prevRanges=r,this.prevTime=s,this.prevComposition=i}},Dk=20;function Pk(t,e,n,r){let s=n.getMeta($t),i;if(s)return s.historyState;n.getMeta(Bk)&&(t=new je(t.done,t.undone,null,0,-1));let o=n.getMeta("appendedTransaction");if(n.steps.length==0)return t;if(o&&o.getMeta($t))return o.getMeta($t).redo?new je(t.done.addTransform(n,void 0,r,$s(e)),t.undone,qd(n.mapping.maps),t.prevTime,t.prevComposition):new je(t.done,t.undone.addTransform(n,void 0,r,$s(e)),null,t.prevTime,t.prevComposition);if(n.getMeta("addToHistory")!==!1&&!(o&&o.getMeta("addToHistory")===!1)){let l=n.getMeta("composition"),a=t.prevTime==0||!o&&t.prevComposition!=l&&(t.prevTime<(n.time||0)-r.newGroupDelay||!Lk(n,t.prevRanges)),c=o?gl(t.prevRanges,n.mapping):qd(n.mapping.maps);return new je(t.done.addTransform(n,a?e.selection.getBookmark():void 0,r,$s(e)),Ht.empty,c,n.time,l??t.prevComposition)}else return(i=n.getMeta("rebased"))?new je(t.done.rebased(n,i),t.undone.rebased(n,i),gl(t.prevRanges,n.mapping),t.prevTime,t.prevComposition):new je(t.done.addMaps(n.mapping.maps),t.undone.addMaps(n.mapping.maps),gl(t.prevRanges,n.mapping),t.prevTime,t.prevComposition)}function Lk(t,e){if(!e)return!1;if(!t.docChanged)return!0;let n=!1;return t.mapping.maps[0].forEach((r,s)=>{for(let i=0;i=e[i]&&(n=!0)}),n}function qd(t){let e=[];for(let n=t.length-1;n>=0&&e.length==0;n--)t[n].forEach((r,s,i,o)=>e.push(i,o));return e}function gl(t,e){if(!t)return null;let n=[];for(let r=0;r{let s=$t.getState(n);if(!s||(t?s.undone:s.done).eventCount==0)return!1;if(r){let i=zk(s,n,t);i&&r(e?i.scrollIntoView():i)}return!0}}var kl=Hs(!1,!0),bl=Hs(!0,!0),hS=Hs(!1,!1),fS=Hs(!0,!1);var bS=B.create({name:"characterCount",addOptions(){return{limit:null,mode:"textSize",textCounter:t=>t.length,wordCounter:t=>t.split(" ").filter(e=>e!=="").length}},addStorage(){return{characters:()=>0,words:()=>0}},onBeforeCreate(){this.storage.characters=t=>{let e=t?.node||this.editor.state.doc;if((t?.mode||this.options.mode)==="textSize"){let r=e.textBetween(0,e.content.size,void 0," ");return this.options.textCounter(r)}return e.nodeSize},this.storage.words=t=>{let e=t?.node||this.editor.state.doc,n=e.textBetween(0,e.content.size," "," ");return this.options.wordCounter(n)}},addProseMirrorPlugins(){let t=!1;return[new O({key:new P("characterCount"),appendTransaction:(e,n,r)=>{if(t)return;let s=this.options.limit;if(s==null||s===0){t=!0;return}let i=this.storage.characters({node:r.doc});if(i>s){let o=i-s,l=0,a=o;console.warn(`[CharacterCount] Initial content exceeded limit of ${s} characters. Content was automatically trimmed.`);let c=r.tr.deleteRange(l,a);return t=!0,c}t=!0},filterTransaction:(e,n)=>{let r=this.options.limit;if(!e.docChanged||r===0||r===null||r===void 0)return!0;let s=this.storage.characters({node:n.doc}),i=this.storage.characters({node:e.doc});if(i<=r||s>r&&i>r&&i<=s)return!0;if(s>r&&i>r&&i>s||!e.getMeta("paste"))return!1;let l=e.selection.$head.pos,a=i-r,c=l-a,u=l;return e.deleteRange(c,u),!(this.storage.characters({node:e.doc})>r)}})]}}),Yd=B.create({name:"dropCursor",addOptions(){return{color:"currentColor",width:1,class:void 0}},addProseMirrorPlugins(){return[Wd(this.options)]}}),TS=B.create({name:"focus",addOptions(){return{className:"has-focus",mode:"all"}},addProseMirrorPlugins(){return[new O({key:new P("focus"),props:{decorations:({doc:t,selection:e})=>{let{isEditable:n,isFocused:r}=this.editor,{anchor:s}=e,i=[];if(!n||!r)return K.create(t,[]);let o=0;this.options.mode==="deepest"&&t.descendants((a,c)=>{if(a.isText)return;if(!(s>=c&&s<=c+a.nodeSize-1))return!1;o+=1});let l=0;return t.descendants((a,c)=>{if(a.isText||!(s>=c&&s<=c+a.nodeSize-1))return!1;if(l+=1,this.options.mode==="deepest"&&o-l>0||this.options.mode==="shallowest"&&l>1)return this.options.mode==="deepest";i.push(Q.node(c,c+a.nodeSize,{class:this.options.className}))}),K.create(t,i)}}})]}}),Zd=B.create({name:"gapCursor",addProseMirrorPlugins(){return[Kd()]},extendNodeSchema(t){var e;let n={name:t.name,options:t.options,storage:t.storage};return{allowGapCursor:(e=_(T(t,"allowGapCursor",n)))!=null?e:null}}}),Xd="placeholder";function $k(t){return t.replace(/\s+/g,"-").replace(/[^a-zA-Z0-9-]/g,"").replace(/^[0-9-]+/,"").replace(/^-+/,"").toLowerCase()}var xl=B.create({name:"placeholder",addOptions(){return{emptyEditorClass:"is-editor-empty",emptyNodeClass:"is-empty",dataAttribute:Xd,placeholder:"Write something \u2026",showOnlyWhenEditable:!0,showOnlyCurrent:!0,includeChildren:!1}},addProseMirrorPlugins(){let t=this.options.dataAttribute?`data-${$k(this.options.dataAttribute)}`:`data-${Xd}`;return[new O({key:new P("placeholder"),props:{decorations:({doc:e,selection:n})=>{let r=this.editor.isEditable||!this.options.showOnlyWhenEditable,{anchor:s}=n,i=[];if(!r)return null;let o=this.editor.isEmpty;return e.descendants((l,a)=>{let c=s>=a&&s<=a+l.nodeSize,u=!l.isLeaf&&jn(l);if((c||!this.options.showOnlyCurrent)&&u){let d=[this.options.emptyNodeClass];o&&d.push(this.options.emptyEditorClass);let h=Q.node(a,a+l.nodeSize,{class:d.join(" "),[t]:typeof this.options.placeholder=="function"?this.options.placeholder({editor:this.editor,node:l,pos:a,hasAnchor:c}):this.options.placeholder});i.push(h)}return this.options.includeChildren}),K.create(e,i)}}})]}}),PS=B.create({name:"selection",addOptions(){return{className:"selection"}},addProseMirrorPlugins(){let{editor:t,options:e}=this;return[new O({key:new P("selection"),props:{decorations(n){return n.selection.empty||t.isFocused||!t.isEditable||Jr(n.selection)||t.view.dragging?null:K.create(n.doc,[Q.inline(n.selection.from,n.selection.to,{class:e.className})])}}})]}});function Qd({types:t,node:e}){return e&&Array.isArray(t)&&t.includes(e.type)||e?.type===t}var eh=B.create({name:"trailingNode",addOptions(){return{node:void 0,notAfter:[]}},addProseMirrorPlugins(){var t;let e=new P(this.name),n=this.options.node||((t=this.editor.schema.topNodeType.contentMatch.defaultType)==null?void 0:t.name)||"paragraph",r=Object.entries(this.editor.schema.nodes).map(([,s])=>s).filter(s=>(this.options.notAfter||[]).concat(n).includes(s.name));return[new O({key:e,appendTransaction:(s,i,o)=>{let{doc:l,tr:a,schema:c}=o,u=e.getState(o),d=l.content.size,h=c.nodes[n];if(u)return a.insert(d,h.create())},state:{init:(s,i)=>{let o=i.tr.doc.lastChild;return!Qd({node:o,types:r})},apply:(s,i)=>{if(!s.docChanged||s.getMeta("__uniqueIDTransaction"))return i;let o=s.doc.lastChild;return!Qd({node:o,types:r})}}})]}}),th=B.create({name:"undoRedo",addOptions(){return{depth:100,newGroupDelay:500}},addCommands(){return{undo:()=>({state:t,dispatch:e})=>kl(t,e),redo:()=>({state:t,dispatch:e})=>bl(t,e)}},addProseMirrorPlugins(){return[Gd(this.options)]},addKeyboardShortcuts(){return{"Mod-z":()=>this.editor.commands.undo(),"Shift-Mod-z":()=>this.editor.commands.redo(),"Mod-y":()=>this.editor.commands.redo(),"Mod-\u044F":()=>this.editor.commands.undo(),"Shift-Mod-\u044F":()=>this.editor.commands.redo()}}});var Hk=B.create({name:"starterKit",addExtensions(){var t,e,n,r;let s=[];return this.options.bold!==!1&&s.push(sd.configure(this.options.bold)),this.options.blockquote!==!1&&s.push(rd.configure(this.options.blockquote)),this.options.bulletList!==!1&&s.push(al.configure(this.options.bulletList)),this.options.code!==!1&&s.push(id.configure(this.options.code)),this.options.codeBlock!==!1&&s.push(od.configure(this.options.codeBlock)),this.options.document!==!1&&s.push(ld.configure(this.options.document)),this.options.dropcursor!==!1&&s.push(Yd.configure(this.options.dropcursor)),this.options.gapcursor!==!1&&s.push(Zd.configure(this.options.gapcursor)),this.options.hardBreak!==!1&&s.push(ad.configure(this.options.hardBreak)),this.options.heading!==!1&&s.push(cd.configure(this.options.heading)),this.options.undoRedo!==!1&&s.push(th.configure(this.options.undoRedo)),this.options.horizontalRule!==!1&&s.push(ud.configure(this.options.horizontalRule)),this.options.italic!==!1&&s.push(dd.configure(this.options.italic)),this.options.listItem!==!1&&s.push(cl.configure(this.options.listItem)),this.options.listKeymap!==!1&&s.push(dl.configure((t=this.options)==null?void 0:t.listKeymap)),this.options.link!==!1&&s.push(il.configure((e=this.options)==null?void 0:e.link)),this.options.orderedList!==!1&&s.push(hl.configure(this.options.orderedList)),this.options.paragraph!==!1&&s.push(Hd.configure(this.options.paragraph)),this.options.strike!==!1&&s.push(Fd.configure(this.options.strike)),this.options.text!==!1&&s.push(_d.configure(this.options.text)),this.options.underline!==!1&&s.push(Vd.configure((n=this.options)==null?void 0:n.underline)),this.options.trailingNode!==!1&&s.push(eh.configure((r=this.options)==null?void 0:r.trailingNode)),s}}),Fk=Hk;var _k=xl;var Vk=/(?:^|\s)(!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\))$/,Wk=$.create({name:"image",addOptions(){return{inline:!1,allowBase64:!1,HTMLAttributes:{},resize:!1}},inline(){return this.options.inline},group(){return this.options.inline?"inline":"block"},draggable:!0,addAttributes(){return{src:{default:null},alt:{default:null},title:{default:null},width:{default:null},height:{default:null}}},parseHTML(){return[{tag:this.options.allowBase64?"img[src]":'img[src]:not([src^="data:"])'}]},renderHTML({HTMLAttributes:t}){return["img",I(this.options.HTMLAttributes,t)]},parseMarkdown:(t,e)=>e.createNode("image",{src:t.href,title:t.title,alt:t.text}),renderMarkdown:t=>{var e,n,r,s,i,o;let l=(n=(e=t.attrs)==null?void 0:e.src)!=null?n:"",a=(s=(r=t.attrs)==null?void 0:r.alt)!=null?s:"",c=(o=(i=t.attrs)==null?void 0:i.title)!=null?o:"";return c?`![${a}](${l} "${c}")`:`![${a}](${l})`},addNodeView(){if(!this.options.resize||!this.options.resize.enabled||typeof document>"u")return null;let{directions:t,minWidth:e,minHeight:n,alwaysPreserveAspectRatio:r}=this.options.resize;return({node:s,getPos:i,HTMLAttributes:o,editor:l})=>{let a=document.createElement("img");Object.entries(o).forEach(([d,h])=>{if(h!=null)switch(d){case"width":case"height":break;default:a.setAttribute(d,h);break}}),a.src=o.src;let c=new ed({element:a,editor:l,node:s,getPos:i,onResize:(d,h)=>{a.style.width=`${d}px`,a.style.height=`${h}px`},onCommit:(d,h)=>{let f=i();f!==void 0&&this.editor.chain().setNodeSelection(f).updateAttributes(this.name,{width:d,height:h}).run()},onUpdate:(d,h,f)=>d.type===s.type,options:{directions:t,min:{width:e,height:n},preserveAspectRatio:r===!0}}),u=c.dom;return u.style.visibility="hidden",u.style.pointerEvents="none",a.onload=()=>{u.style.visibility="",u.style.pointerEvents=""},c}},addCommands(){return{setImage:t=>({commands:e})=>e.insertContent({type:this.name,attrs:t})}},addInputRules(){return[Qr({find:Vk,type:this.type,getAttributes:t=>{let[,,e,n,r]=t;return{src:n,alt:e,title:r}}})]}}),jk=Wk;var Sl,Cl;if(typeof WeakMap<"u"){let t=new WeakMap;Sl=e=>t.get(e),Cl=(e,n)=>(t.set(e,n),n)}else{let t=[],n=0;Sl=r=>{for(let s=0;s(n==10&&(n=0),t[n++]=r,t[n++]=s)}var q=class{constructor(t,e,n,r){this.width=t,this.height=e,this.map=n,this.problems=r}findCell(t){for(let e=0;e=n){(i||(i=[])).push({type:"overlong_rowspan",pos:u,n:y-w});break}let M=s+w*e;for(let S=0;Sr&&(i+=c.attrs.colspan)}}for(let o=0;o1&&(n=!0)}e==-1?e=i:e!=i&&(e=Math.max(e,i))}return e}function qk(t,e,n){t.problems||(t.problems=[]);let r={};for(let s=0;s0;e--)if(t.node(e).type.spec.tableRole=="row")return t.node(0).resolve(t.before(e+1));return null}function Gk(t){for(let e=t.depth;e>0;e--){let n=t.node(e).type.spec.tableRole;if(n==="cell"||n==="header_cell")return t.node(e)}return null}function De(t){let e=t.selection.$head;for(let n=e.depth;n>0;n--)if(e.node(n).type.spec.tableRole=="row")return!0;return!1}function Ks(t){let e=t.selection;if("$anchorCell"in e&&e.$anchorCell)return e.$anchorCell.pos>e.$headCell.pos?e.$anchorCell:e.$headCell;if("node"in e&&e.node&&e.node.type.spec.tableRole=="cell")return e.$anchor;let n=Ft(e.$head)||Xk(e.$head);if(n)return n;throw new RangeError(`No cell found around position ${e.head}`)}function Xk(t){for(let e=t.nodeAfter,n=t.pos;e;e=e.firstChild,n++){let r=e.type.spec.tableRole;if(r=="cell"||r=="header_cell")return t.doc.resolve(n)}for(let e=t.nodeBefore,n=t.pos;e;e=e.lastChild,n--){let r=e.type.spec.tableRole;if(r=="cell"||r=="header_cell")return t.doc.resolve(n-e.nodeSize)}}function Ml(t){return t.parent.type.spec.tableRole=="row"&&!!t.nodeAfter}function Qk(t){return t.node(0).resolve(t.pos+t.nodeAfter.nodeSize)}function Al(t,e){return t.depth==e.depth&&t.pos>=e.start(-1)&&t.pos<=e.end(-1)}function dh(t,e,n){let r=t.node(-1),s=q.get(r),i=t.start(-1),o=s.nextCell(t.pos-i,e,n);return o==null?null:t.node(0).resolve(i+o)}function _t(t,e,n=1){let r={...t,colspan:t.colspan-n};return r.colwidth&&(r.colwidth=r.colwidth.slice(),r.colwidth.splice(e,n),r.colwidth.some(s=>s>0)||(r.colwidth=null)),r}function hh(t,e,n=1){let r={...t,colspan:t.colspan+n};if(r.colwidth){r.colwidth=r.colwidth.slice();for(let s=0;su!=n.pos-i);a.unshift(n.pos-i);let c=a.map(u=>{let d=r.nodeAt(u);if(!d)throw new RangeError(`No cell with offset ${u} found`);let h=i+u+1;return new en(l.resolve(h),l.resolve(h+d.content.size))});super(c[0].$from,c[0].$to,c),this.$anchorCell=e,this.$headCell=n}map(e,n){let r=e.resolve(n.map(this.$anchorCell.pos)),s=e.resolve(n.map(this.$headCell.pos));if(Ml(r)&&Ml(s)&&Al(r,s)){let i=this.$anchorCell.node(-1)!=r.node(-1);return i&&this.isRowSelection()?ot.rowSelection(r,s):i&&this.isColSelection()?ot.colSelection(r,s):new ot(r,s)}return A.between(r,s)}content(){let e=this.$anchorCell.node(-1),n=q.get(e),r=this.$anchorCell.start(-1),s=n.rectBetween(this.$anchorCell.pos-r,this.$headCell.pos-r),i={},o=[];for(let a=s.top;a0||g>0){let y=p.attrs;if(m>0&&(y=_t(y,0,m)),g>0&&(y=_t(y,y.colspan-g,g)),f.lefts.bottom){let y={...p.attrs,rowspan:Math.min(f.bottom,s.bottom)-Math.max(f.top,s.top)};f.top0)return!1;let r=e+this.$anchorCell.nodeAfter.attrs.rowspan,s=n+this.$headCell.nodeAfter.attrs.rowspan;return Math.max(r,s)==this.$headCell.node(-1).childCount}static colSelection(e,n=e){let r=e.node(-1),s=q.get(r),i=e.start(-1),o=s.findCell(e.pos-i),l=s.findCell(n.pos-i),a=e.node(0);return o.top<=l.top?(o.top>0&&(e=a.resolve(i+s.map[o.left])),l.bottom0&&(n=a.resolve(i+s.map[l.left])),o.bottom0)return!1;let o=s+this.$anchorCell.nodeAfter.attrs.colspan,l=i+this.$headCell.nodeAfter.attrs.colspan;return Math.max(o,l)==n.width}eq(e){return e instanceof ot&&e.$anchorCell.pos==this.$anchorCell.pos&&e.$headCell.pos==this.$headCell.pos}static rowSelection(e,n=e){let r=e.node(-1),s=q.get(r),i=e.start(-1),o=s.findCell(e.pos-i),l=s.findCell(n.pos-i),a=e.node(0);return o.left<=l.left?(o.left>0&&(e=a.resolve(i+s.map[o.top*s.width])),l.right0&&(n=a.resolve(i+s.map[l.top*s.width])),o.right{e.push(Q.node(r,r+n.nodeSize,{class:"selectedCell"}))}),K.create(t.doc,e)}function t0({$from:t,$to:e}){if(t.pos==e.pos||t.pos=0&&!(t.after(s+1)=0&&!(e.before(i+1)>e.start(i));i--,r--);return n==r&&/row|table/.test(t.node(s).type.spec.tableRole)}function n0({$from:t,$to:e}){let n,r;for(let s=t.depth;s>0;s--){let i=t.node(s);if(i.type.spec.tableRole==="cell"||i.type.spec.tableRole==="header_cell"){n=i;break}}for(let s=e.depth;s>0;s--){let i=e.node(s);if(i.type.spec.tableRole==="cell"||i.type.spec.tableRole==="header_cell"){r=i;break}}return n!==r&&e.parentOffset===0}function r0(t,e,n){let r=(e||t).selection,s=(e||t).doc,i,o;if(r instanceof E&&(o=r.node.type.spec.tableRole)){if(o=="cell"||o=="header_cell")i=W.create(s,r.from);else if(o=="row"){let l=s.resolve(r.from+1);i=W.rowSelection(l,l)}else if(!n){let l=q.get(r.node),a=r.from+1,c=a+l.map[l.width*l.height-1];i=W.create(s,a+1,c)}}else r instanceof A&&t0(r)?i=A.create(s,r.from):r instanceof A&&n0(r)&&(i=A.create(s,r.$from.start(),r.$from.end()));return i&&(e||(e=t.tr)).setSelection(i),e}var s0=new P("fix-tables");function ph(t,e,n,r){let s=t.childCount,i=e.childCount;e:for(let o=0,l=0;o{s.type.spec.tableRole=="table"&&(n=i0(t,s,i,n))};return e?e.doc!=t.doc&&ph(e.doc,t.doc,0,r):t.doc.descendants(r),n}function i0(t,e,n,r){let s=q.get(e);if(!s.problems)return r;r||(r=t.tr);let i=[];for(let a=0;a0){let f="cell";u.firstChild&&(f=u.firstChild.type.spec.tableRole);let p=[];for(let g=0;g0?-1:0;Yk(e,r,s+i)&&(i=s==0||s==e.width?null:0);for(let o=0;o0&&s0&&e.map[l-1]==a||s0?-1:0;l0(e,r,s+l)&&(l=s==0||s==e.height?null:0);for(let c=0,u=e.width*s;c0&&s0&&d==e.map[u-e.width]){let h=n.nodeAt(d).attrs;t.setNodeMarkup(t.mapping.slice(l).map(d+r),null,{...h,rowspan:h.rowspan-1}),c+=h.colspan-1}else if(s0&&n[i]==n[i-1]||r.right0&&n[s]==n[s-t]||r.bottom0){let u=a+1+c.content.size,d=nh(c)?a+1:u;i.replaceWith(d+r.tableStart,u+r.tableStart,l)}i.setSelection(new W(i.doc.resolve(a+r.tableStart))),e(i)}return!0}function Rl(t,e){let n=ce(t.schema);return u0(({node:r})=>n[r.type.spec.tableRole])(t,e)}function u0(t){return(e,n)=>{let r=e.selection,s,i;if(r instanceof W){if(r.$anchorCell.pos!=r.$headCell.pos)return!1;s=r.$anchorCell.nodeAfter,i=r.$anchorCell.pos}else{var o;if(s=Gk(r.$from),!s)return!1;i=(o=Ft(r.$from))===null||o===void 0?void 0:o.pos}if(s==null||i==null||s.attrs.colspan==1&&s.attrs.rowspan==1)return!1;if(n){let l=s.attrs,a=[],c=l.colwidth;l.rowspan>1&&(l={...l,rowspan:1}),l.colspan>1&&(l={...l,colspan:1});let u=Ke(e),d=e.tr;for(let f=0;f{o.attrs[t]!==e&&i.setNodeMarkup(l,null,{...o.attrs,[t]:e})}):i.setNodeMarkup(s.pos,null,{...s.nodeAfter.attrs,[t]:e}),r(i)}return!0}}function d0(t){return function(e,n){if(!De(e))return!1;if(n){let r=ce(e.schema),s=Ke(e),i=e.tr,o=s.map.cellsInRect(t=="column"?{left:s.left,top:0,right:s.right,bottom:s.map.height}:t=="row"?{left:0,top:s.top,right:s.map.width,bottom:s.bottom}:s),l=o.map(a=>s.table.nodeAt(a));for(let a=0;a{let f=h+i.tableStart,p=o.doc.nodeAt(f);p&&o.setNodeMarkup(f,d,p.attrs)}),r(o)}return!0}}var pC=pn("row",{useDeprecatedLogic:!0}),mC=pn("column",{useDeprecatedLogic:!0}),Mh=pn("cell",{useDeprecatedLogic:!0});function h0(t,e){if(e<0){let n=t.nodeBefore;if(n)return t.pos-n.nodeSize;for(let r=t.index(-1)-1,s=t.before();r>=0;r--){let i=t.node(-1).child(r),o=i.lastChild;if(o)return s-1-o.nodeSize;s-=i.nodeSize}}else{if(t.index()0;r--)if(n.node(r).type.spec.tableRole=="table")return e&&e(t.tr.delete(n.before(r),n.after(r)).scrollIntoView()),!0;return!1}function Fs(t,e){let n=t.selection;if(!(n instanceof W))return!1;if(e){let r=t.tr,s=ce(t.schema).cell.createAndFill().content;n.forEachCell((i,o)=>{i.content.eq(s)||r.replace(r.mapping.map(o+1),r.mapping.map(o+i.nodeSize-1),new C(s,0,0))}),r.docChanged&&e(r)}return!0}function f0(t){if(t.size===0)return null;let{content:e,openStart:n,openEnd:r}=t;for(;e.childCount==1&&(n>0&&r>0||e.child(0).type.spec.tableRole=="table");)n--,r--,e=e.child(0).content;let s=e.child(0),i=s.type.spec.tableRole,o=s.type.schema,l=[];if(i=="row")for(let a=0;a=0;o--){let{rowspan:l,colspan:a}=i.child(o).attrs;for(let c=s;c=e.length&&e.push(b.empty),n[s]r&&(h=h.type.createChecked(_t(h.attrs,h.attrs.colspan,u+h.attrs.colspan-r),h.content)),c.push(h),u+=h.attrs.colspan;for(let f=1;fs&&(d=d.type.create({...d.attrs,rowspan:Math.max(1,s-d.attrs.rowspan)},d.content)),a.push(d)}i.push(b.from(a))}n=i,e=s}return{width:t,height:e,rows:n}}function g0(t,e,n,r,s,i,o){let l=t.doc.type.schema,a=ce(l),c,u;if(s>e.width)for(let d=0,h=0;de.height){let d=[];for(let p=0,m=(e.height-1)*e.width;p=e.width?!1:n.nodeAt(e.map[m+p]).type==a.header_cell;d.push(g?u||(u=a.header_cell.createAndFill()):c||(c=a.cell.createAndFill()))}let h=a.row.create(null,b.from(d)),f=[];for(let p=e.height;p{if(!s)return!1;let i=n.selection;if(i instanceof W)return Ws(n,r,R.near(i.$headCell,e));if(t!="horiz"&&!i.empty)return!1;let o=vh(s,t,e);if(o==null)return!1;if(t=="horiz")return Ws(n,r,R.near(n.doc.resolve(i.head+e),e));{let l=n.doc.resolve(o),a=dh(l,t,e),c;return a?c=R.near(a,1):e<0?c=R.near(n.doc.resolve(l.before(-1)),-1):c=R.near(n.doc.resolve(l.after(-1)),1),Ws(n,r,c)}}}function Vs(t,e){return(n,r,s)=>{if(!s)return!1;let i=n.selection,o;if(i instanceof W)o=i;else{let a=vh(s,t,e);if(a==null)return!1;o=new W(n.doc.resolve(a))}let l=dh(o.$headCell,t,e);return l?Ws(n,r,new W(o.$anchorCell,l)):!1}}function k0(t,e){let n=t.state.doc,r=Ft(n.resolve(e));return r?(t.dispatch(t.state.tr.setSelection(new W(r))),!0):!1}function b0(t,e,n){if(!De(t.state))return!1;let r=f0(n),s=t.state.selection;if(s instanceof W){r||(r={width:1,height:1,rows:[b.from(Tl(ce(t.state.schema).cell,n))]});let i=s.$anchorCell.node(-1),o=s.$anchorCell.start(-1),l=q.get(i).rectBetween(s.$anchorCell.pos-o,s.$headCell.pos-o);return r=m0(r,l.right-l.left,l.bottom-l.top),oh(t.state,t.dispatch,o,l,r),!0}else if(r){let i=Ks(t.state),o=i.start(-1);return oh(t.state,t.dispatch,o,q.get(i.node(-1)).findCell(i.pos-o),r),!0}else return!1}function x0(t,e){var n;if(e.button!=0||e.ctrlKey||e.metaKey)return;let r=lh(t,e.target),s;if(e.shiftKey&&t.state.selection instanceof W)i(t.state.selection.$anchorCell,e),e.preventDefault();else if(e.shiftKey&&r&&(s=Ft(t.state.selection.$anchor))!=null&&((n=wl(t,e))===null||n===void 0?void 0:n.pos)!=s.pos)i(s,e),e.preventDefault();else if(!r)return;function i(a,c){let u=wl(t,c),d=bt.getState(t.state)==null;if(!u||!Al(a,u))if(d)u=a;else return;let h=new W(a,u);if(d||!t.state.selection.eq(h)){let f=t.state.tr.setSelection(h);d&&f.setMeta(bt,a.pos),t.dispatch(f)}}function o(){t.root.removeEventListener("mouseup",o),t.root.removeEventListener("dragstart",o),t.root.removeEventListener("mousemove",l),bt.getState(t.state)!=null&&t.dispatch(t.state.tr.setMeta(bt,-1))}function l(a){let c=a,u=bt.getState(t.state),d;if(u!=null)d=t.state.doc.resolve(u);else if(lh(t,c.target)!=r&&(d=wl(t,e),!d))return o();d&&i(d,c)}t.root.addEventListener("mouseup",o),t.root.addEventListener("dragstart",o),t.root.addEventListener("mousemove",l)}function vh(t,e,n){if(!(t.state.selection instanceof A))return null;let{$head:r}=t.state.selection;for(let s=r.depth-1;s>=0;s--){let i=r.node(s);if((n<0?r.index(s):r.indexAfter(s))!=(n<0?0:i.childCount))return null;if(i.type.spec.tableRole=="cell"||i.type.spec.tableRole=="header_cell"){let o=r.before(s),l=e=="vert"?n>0?"down":"up":n>0?"right":"left";return t.endOfTextblock(l)?o:null}}return null}function lh(t,e){for(;e&&e!=t.dom;e=e.parentNode)if(e.nodeName=="TD"||e.nodeName=="TH")return e;return null}function wl(t,e){let n=t.posAtCoords({left:e.clientX,top:e.clientY});if(!n)return null;let{inside:r,pos:s}=n;return r>=0&&Ft(t.state.doc.resolve(r))||Ft(t.state.doc.resolve(s))}var w0=class{constructor(t,e){this.node=t,this.defaultCellMinWidth=e,this.dom=document.createElement("div"),this.dom.className="tableWrapper",this.table=this.dom.appendChild(document.createElement("table")),this.table.style.setProperty("--default-cell-min-width",`${e}px`),this.colgroup=this.table.appendChild(document.createElement("colgroup")),vl(t,this.colgroup,this.table,e),this.contentDOM=this.table.appendChild(document.createElement("tbody"))}update(t){return t.type!=this.node.type?!1:(this.node=t,vl(t,this.colgroup,this.table,this.defaultCellMinWidth),!0)}ignoreMutation(t){return t.type=="attributes"&&(t.target==this.table||this.colgroup.contains(t.target))}};function vl(t,e,n,r,s,i){let o=0,l=!0,a=e.firstChild,c=t.firstChild;if(c){for(let d=0,h=0;dnew r(d,n,h)),new S0(-1,!1)},apply(o,l){return l.apply(o)}},props:{attributes:o=>{let l=we.getState(o);return l&&l.activeHandle>-1?{class:"resize-cursor"}:{}},handleDOMEvents:{mousemove:(o,l)=>{C0(o,l,t,s)},mouseleave:o=>{M0(o)},mousedown:(o,l)=>{T0(o,l,e,n)}},decorations:o=>{let l=we.getState(o);if(l&&l.activeHandle>-1)return R0(o,l.activeHandle)},nodeViews:{}}});return i}var S0=class js{constructor(e,n){this.activeHandle=e,this.dragging=n}apply(e){let n=this,r=e.getMeta(we);if(r&&r.setHandle!=null)return new js(r.setHandle,!1);if(r&&r.setDragging!==void 0)return new js(n.activeHandle,r.setDragging);if(n.activeHandle>-1&&e.docChanged){let s=e.mapping.map(n.activeHandle,-1);return Ml(e.doc.resolve(s))||(s=-1),new js(s,n.dragging)}return n}};function C0(t,e,n,r){if(!t.editable)return;let s=we.getState(t.state);if(s&&!s.dragging){let i=A0(e.target),o=-1;if(i){let{left:l,right:a}=i.getBoundingClientRect();e.clientX-l<=n?o=ah(t,e,"left",n):a-e.clientX<=n&&(o=ah(t,e,"right",n))}if(o!=s.activeHandle){if(!r&&o!==-1){let l=t.state.doc.resolve(o),a=l.node(-1),c=q.get(a),u=l.start(-1);if(c.colCount(l.pos-u)+l.nodeAfter.attrs.colspan-1==c.width-1)return}Eh(t,o)}}}function M0(t){if(!t.editable)return;let e=we.getState(t.state);e&&e.activeHandle>-1&&!e.dragging&&Eh(t,-1)}function T0(t,e,n,r){var s;if(!t.editable)return!1;let i=(s=t.dom.ownerDocument.defaultView)!==null&&s!==void 0?s:window,o=we.getState(t.state);if(!o||o.activeHandle==-1||o.dragging)return!1;let l=t.state.doc.nodeAt(o.activeHandle),a=v0(t,o.activeHandle,l.attrs);t.dispatch(t.state.tr.setMeta(we,{setDragging:{startX:e.clientX,startWidth:a}}));function c(d){i.removeEventListener("mouseup",c),i.removeEventListener("mousemove",u);let h=we.getState(t.state);h?.dragging&&(E0(t,h.activeHandle,ch(h.dragging,d,n)),t.dispatch(t.state.tr.setMeta(we,{setDragging:null})))}function u(d){if(!d.which)return c(d);let h=we.getState(t.state);if(h&&h.dragging){let f=ch(h.dragging,d,n);uh(t,h.activeHandle,f,r)}}return uh(t,o.activeHandle,a,r),i.addEventListener("mouseup",c),i.addEventListener("mousemove",u),e.preventDefault(),!0}function v0(t,e,{colspan:n,colwidth:r}){let s=r&&r[r.length-1];if(s)return s;let i=t.domAtPos(e),o=i.node.childNodes[i.offset].offsetWidth,l=n;if(r)for(let a=0;a{var e,n;let r=t.getAttribute("colwidth"),s=r?r.split(",").map(i=>parseInt(i,10)):null;if(!s){let i=(e=t.closest("table"))==null?void 0:e.querySelectorAll("colgroup > col"),o=Array.from(((n=t.parentElement)==null?void 0:n.children)||[]).indexOf(t);if(o&&o>-1&&i&&i[o]){let l=i[o].getAttribute("width");return l?[parseInt(l,10)]:null}}return s}}}},tableRole:"cell",isolating:!0,parseHTML(){return[{tag:"td"}]},renderHTML({HTMLAttributes:t}){return["td",I(this.options.HTMLAttributes,t),0]}}),Js=$.create({name:"tableHeader",addOptions(){return{HTMLAttributes:{}}},content:"block+",addAttributes(){return{colspan:{default:1},rowspan:{default:1},colwidth:{default:null,parseHTML:t=>{let e=t.getAttribute("colwidth");return e?e.split(",").map(r=>parseInt(r,10)):null}}}},tableRole:"header_cell",isolating:!0,parseHTML(){return[{tag:"th"}]},renderHTML({HTMLAttributes:t}){return["th",I(this.options.HTMLAttributes,t),0]}}),Gs=$.create({name:"tableRow",addOptions(){return{HTMLAttributes:{}}},content:"(tableCell | tableHeader)*",tableRole:"row",parseHTML(){return[{tag:"tr"}]},renderHTML({HTMLAttributes:t}){return["tr",I(this.options.HTMLAttributes,t),0]}});function Il(t,e){return e?["width",`${Math.max(e,t)}px`]:["min-width",`${t}px`]}function Rh(t,e,n,r,s,i){var o;let l=0,a=!0,c=e.firstChild,u=t.firstChild;if(u!==null)for(let h=0,f=0;h{let r=t.nodes[n];r.spec.tableRole&&(e[r.spec.tableRole]=r)}),t.cached.tableNodeTypes=e,e}function P0(t,e,n,r,s){let i=D0(t),o=[],l=[];for(let c=0;c{let{selection:e}=t.state;if(!L0(e))return!1;let n=0,r=Ao(e.ranges[0].$from,i=>i.type.name==="table");return r?.node.descendants(i=>{if(i.type.name==="table")return!1;["tableCell","tableHeader"].includes(i.type.name)&&(n+=1)}),n===e.ranges.length?(t.commands.deleteTable(),!0):!1},z0="";function B0(t){return(t||"").replace(/\s+/g," ").trim()}function $0(t,e,n={}){var r;let s=(r=n.cellLineSeparator)!=null?r:z0;if(!t||!t.content||t.content.length===0)return"";let i=[];t.content.forEach(p=>{let m=[];p.content&&p.content.forEach(g=>{let y="";g.content&&Array.isArray(g.content)&&g.content.length>1?y=g.content.map(S=>e.renderChildren(S)).join(s):y=g.content?e.renderChildren(g.content):"";let k=B0(y),w=g.type==="tableHeader";m.push({text:k,isHeader:w})}),i.push(m)});let o=i.reduce((p,m)=>Math.max(p,m.length),0);if(o===0)return"";let l=new Array(o).fill(0);i.forEach(p=>{var m;for(let g=0;gl[g]&&(l[g]=k),l[g]<3&&(l[g]=3)}});let a=(p,m)=>p+" ".repeat(Math.max(0,m-p.length)),c=i[0],u=c.some(p=>p.isHeader),d=` -`,h=new Array(o).fill(0).map((p,m)=>u&&c[m]&&c[m].text||"");return d+=`| ${h.map((p,m)=>a(p,l[m])).join(" | ")} | -`,d+=`| ${l.map(p=>"-".repeat(Math.max(3,p))).join(" | ")} | -`,(u?i.slice(1):i).forEach(p=>{d+=`| ${new Array(o).fill(0).map((m,g)=>a(p[g]&&p[g].text||"",l[g])).join(" | ")} | -`}),d}var H0=$0,Ih=$.create({name:"table",addOptions(){return{HTMLAttributes:{},resizable:!1,renderWrapper:!1,handleWidth:5,cellMinWidth:25,View:O0,lastColumnResizable:!0,allowTableNodeSelection:!1}},content:"tableRow+",tableRole:"table",isolating:!0,group:"block",parseHTML(){return[{tag:"table"}]},renderHTML({node:t,HTMLAttributes:e}){let{colgroup:n,tableWidth:r,tableMinWidth:s}=I0(t,this.options.cellMinWidth),i=e.style;function o(){return i||(r?`width: ${r}`:`min-width: ${s}`)}let l=["table",I(this.options.HTMLAttributes,e,{style:o()}),n,["tbody",0]];return this.options.renderWrapper?["div",{class:"tableWrapper"},l]:l},parseMarkdown:(t,e)=>{let n=[];if(t.header){let r=[];t.header.forEach(s=>{r.push(e.createNode("tableHeader",{},[{type:"paragraph",content:e.parseInline(s.tokens)}]))}),n.push(e.createNode("tableRow",{},r))}return t.rows&&t.rows.forEach(r=>{let s=[];r.forEach(i=>{s.push(e.createNode("tableCell",{},[{type:"paragraph",content:e.parseInline(i.tokens)}]))}),n.push(e.createNode("tableRow",{},s))}),e.createNode("table",void 0,n)},renderMarkdown:(t,e)=>H0(t,e),addCommands(){return{insertTable:({rows:t=3,cols:e=3,withHeaderRow:n=!0}={})=>({tr:r,dispatch:s,editor:i})=>{let o=P0(i.schema,t,e,n);if(s){let l=r.selection.from+1;r.replaceSelectionWith(o).scrollIntoView().setSelection(A.near(r.doc.resolve(l)))}return!0},addColumnBefore:()=>({state:t,dispatch:e})=>gh(t,e),addColumnAfter:()=>({state:t,dispatch:e})=>yh(t,e),deleteColumn:()=>({state:t,dispatch:e})=>kh(t,e),addRowBefore:()=>({state:t,dispatch:e})=>xh(t,e),addRowAfter:()=>({state:t,dispatch:e})=>wh(t,e),deleteRow:()=>({state:t,dispatch:e})=>Sh(t,e),deleteTable:()=>({state:t,dispatch:e})=>Th(t,e),mergeCells:()=>({state:t,dispatch:e})=>Nl(t,e),splitCell:()=>({state:t,dispatch:e})=>Rl(t,e),toggleHeaderColumn:()=>({state:t,dispatch:e})=>pn("column")(t,e),toggleHeaderRow:()=>({state:t,dispatch:e})=>pn("row")(t,e),toggleHeaderCell:()=>({state:t,dispatch:e})=>Mh(t,e),mergeOrSplit:()=>({state:t,dispatch:e})=>Nl(t,e)?!0:Rl(t,e),setCellAttribute:(t,e)=>({state:n,dispatch:r})=>Ch(t,e)(n,r),goToNextCell:()=>({state:t,dispatch:e})=>Ol(1)(t,e),goToPreviousCell:()=>({state:t,dispatch:e})=>Ol(-1)(t,e),fixTables:()=>({state:t,dispatch:e})=>(e&&El(t),!0),setCellSelection:t=>({tr:e,dispatch:n})=>{if(n){let r=W.create(e.doc,t.anchorCell,t.headCell);e.setSelection(r)}return!0}}},addKeyboardShortcuts(){return{Tab:()=>this.editor.commands.goToNextCell()?!0:this.editor.can().addRowAfter()?this.editor.chain().addRowAfter().goToNextCell().run():!1,"Shift-Tab":()=>this.editor.commands.goToPreviousCell(),Backspace:Us,"Mod-Backspace":Us,Delete:Us,"Mod-Delete":Us}},addProseMirrorPlugins(){return[...this.options.resizable&&this.editor.isEditable?[Ah({handleWidth:this.options.handleWidth,cellMinWidth:this.options.cellMinWidth,defaultCellMinWidth:this.options.cellMinWidth,View:this.options.View,lastColumnResizable:this.options.lastColumnResizable})]:[],Nh({allowTableNodeSelection:this.options.allowTableNodeSelection})]},extendNodeSchema(t){let e={name:t.name,options:t.options,storage:t.storage};return{tableRole:_(T(t,"tableRole",e))}}}),RC=B.create({name:"tableKit",addExtensions(){let t=[];return this.options.table!==!1&&t.push(Ih.configure(this.options.table)),this.options.tableCell!==!1&&t.push(qs.configure(this.options.tableCell)),this.options.tableHeader!==!1&&t.push(Js.configure(this.options.tableHeader)),this.options.tableRow!==!1&&t.push(Gs.configure(this.options.tableRow)),t}});var F0=Gs;var _0=qs;var V0=Js;var W0=Ls;var j0=Ps;function zl(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}var jt=zl();function Hh(t){jt=t}var Vt={exec:()=>null};function H(t,e=""){let n=typeof t=="string"?t:t.source,r={replace:(s,i)=>{let o=typeof i=="string"?i:i.source;return o=o.replace(fe.caret,"$1"),n=n.replace(s,o),r},getRegex:()=>new RegExp(n,e)};return r}var K0=(()=>{try{return!!new RegExp("(?<=1)(?/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] +\S/,listReplaceTask:/^\[[ xX]\] +/,listTaskCheckbox:/\[[ xX]\]/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,unescapeTest:/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/ig,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:t=>new RegExp(`^( {0,3}${t})((?:[ ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:t=>new RegExp(`^ {0,${Math.min(3,t-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`),hrRegex:t=>new RegExp(`^ {0,${Math.min(3,t-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:t=>new RegExp(`^ {0,${Math.min(3,t-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:t=>new RegExp(`^ {0,${Math.min(3,t-1)}}#`),htmlBeginRegex:t=>new RegExp(`^ {0,${Math.min(3,t-1)}}<(?:[a-z].*>|!--)`,"i"),blockquoteBeginRegex:t=>new RegExp(`^ {0,${Math.min(3,t-1)}}>`)},U0=/^(?:[ \t]*(?:\n|$))+/,q0=/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,J0=/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,lr=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,G0=/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,Bl=/ {0,3}(?:[*+-]|\d{1,9}[.)])/,Fh=/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,_h=H(Fh).replace(/bull/g,Bl).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/\|table/g,"").getRegex(),X0=H(Fh).replace(/bull/g,Bl).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/table/g,/ {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(),$l=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,Q0=/^[^\n]+/,Hl=/(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/,Y0=H(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",Hl).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),Z0=H(/^(bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,Bl).getRegex(),ti="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",Fl=/|$))/,eb=H("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))","i").replace("comment",Fl).replace("tag",ti).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),Vh=H($l).replace("hr",lr).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",ti).getRegex(),tb=H(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",Vh).getRegex(),_l={blockquote:tb,code:q0,def:Y0,fences:J0,heading:G0,hr:lr,html:eb,lheading:_h,list:Z0,newline:U0,paragraph:Vh,table:Vt,text:Q0},Dh=H("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",lr).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code","(?: {4}| {0,3} )[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",ti).getRegex(),nb={..._l,lheading:X0,table:Dh,paragraph:H($l).replace("hr",lr).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",Dh).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",ti).getRegex()},rb={..._l,html:H(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment",Fl).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:Vt,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:H($l).replace("hr",lr).replace("heading",` *#{1,6} *[^ -]`).replace("lheading",_h).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},sb=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,ib=/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,Wh=/^( {2,}|\\)\n(?!\s*$)/,ob=/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\`+)[^`]+\k(?!`))*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)/).replace("precode-",K0?"(?`+)[^`]+\k(?!`)/).replace("html",/<(?! )[^<>]*?>/).getRegex(),qh=/^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/,fb=H(qh,"u").replace(/punct/g,ni).getRegex(),pb=H(qh,"u").replace(/punct/g,Kh).getRegex(),Jh="^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)",mb=H(Jh,"gu").replace(/notPunctSpace/g,jh).replace(/punctSpace/g,Vl).replace(/punct/g,ni).getRegex(),gb=H(Jh,"gu").replace(/notPunctSpace/g,cb).replace(/punctSpace/g,ab).replace(/punct/g,Kh).getRegex(),yb=H("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,jh).replace(/punctSpace/g,Vl).replace(/punct/g,ni).getRegex(),kb=H(/^~~?(?:((?!~)punct)|[^\s~])/,"u").replace(/punct/g,Uh).getRegex(),bb="^[^~]+(?=[^~])|(?!~)punct(~~?)(?=[\\s]|$)|notPunctSpace(~~?)(?!~)(?=punctSpace|$)|(?!~)punctSpace(~~?)(?=notPunctSpace)|[\\s](~~?)(?!~)(?=punct)|(?!~)punct(~~?)(?!~)(?=punct)|notPunctSpace(~~?)(?=notPunctSpace)",xb=H(bb,"gu").replace(/notPunctSpace/g,db).replace(/punctSpace/g,ub).replace(/punct/g,Uh).getRegex(),wb=H(/\\(punct)/,"gu").replace(/punct/g,ni).getRegex(),Sb=H(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),Cb=H(Fl).replace("(?:-->|$)","-->").getRegex(),Mb=H("^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment",Cb).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),Ys=/(?:\[(?:\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`+[^`]*?`+(?!`)|[^\[\]\\`])*?/,Tb=H(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]*(?:\n[ \t]*)?)(title))?\s*\)/).replace("label",Ys).replace("href",/<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),Gh=H(/^!?\[(label)\]\[(ref)\]/).replace("label",Ys).replace("ref",Hl).getRegex(),Xh=H(/^!?\[(ref)\](?:\[\])?/).replace("ref",Hl).getRegex(),vb=H("reflink|nolink(?!\\()","g").replace("reflink",Gh).replace("nolink",Xh).getRegex(),Ph=/[hH][tT][tT][pP][sS]?|[fF][tT][pP]/,Wl={_backpedal:Vt,anyPunctuation:wb,autolink:Sb,blockSkip:hb,br:Wh,code:ib,del:Vt,delLDelim:Vt,delRDelim:Vt,emStrongLDelim:fb,emStrongRDelimAst:mb,emStrongRDelimUnd:yb,escape:sb,link:Tb,nolink:Xh,punctuation:lb,reflink:Gh,reflinkSearch:vb,tag:Mb,text:ob,url:Vt},Ab={...Wl,link:H(/^!?\[(label)\]\((.*?)\)/).replace("label",Ys).getRegex(),reflink:H(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",Ys).getRegex()},Dl={...Wl,emStrongRDelimAst:gb,emStrongLDelim:pb,delLDelim:kb,delRDelim:xb,url:H(/^((?:protocol):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/).replace("protocol",Ph).replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/,text:H(/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\":">",'"':""","'":"'"},Lh=t=>Nb[t];function Ue(t,e){if(e){if(fe.escapeTest.test(t))return t.replace(fe.escapeReplace,Lh)}else if(fe.escapeTestNoEncode.test(t))return t.replace(fe.escapeReplaceNoEncode,Lh);return t}function zh(t){try{t=encodeURI(t).replace(fe.percentDecode,"%")}catch{return null}return t}function Bh(t,e){let n=t.replace(fe.findPipe,(i,o,l)=>{let a=!1,c=o;for(;--c>=0&&l[c]==="\\";)a=!a;return a?"|":" |"}),r=n.split(fe.splitPipe),s=0;if(r[0].trim()||r.shift(),r.length>0&&!r.at(-1)?.trim()&&r.pop(),e)if(r.length>e)r.splice(e);else for(;r.length0?-2:-1}function Ob(t,e=0){let n=e,r="";for(let s of t)if(s===" "){let i=4-n%4;r+=" ".repeat(i),n+=i}else r+=s,n++;return r}function $h(t,e,n,r,s){let i=e.href,o=e.title||null,l=t[1].replace(s.other.outputLinkReplace,"$1");r.state.inLink=!0;let a={type:t[0].charAt(0)==="!"?"image":"link",raw:n,href:i,title:o,text:l,tokens:r.inlineTokens(l)};return r.state.inLink=!1,a}function Ib(t,e,n){let r=t.match(n.other.indentCodeCompensation);if(r===null)return e;let s=r[1];return e.split(` -`).map(i=>{let o=i.match(n.other.beginningSpace);if(o===null)return i;let[l]=o;return l.length>=s.length?i.slice(s.length):i}).join(` -`)}var Zs=class{constructor(t){j(this,"options");j(this,"rules");j(this,"lexer");this.options=t||jt}space(t){let e=this.rules.block.newline.exec(t);if(e&&e[0].length>0)return{type:"space",raw:e[0]}}code(t){let e=this.rules.block.code.exec(t);if(e){let n=e[0].replace(this.rules.other.codeRemoveIndent,"");return{type:"code",raw:e[0],codeBlockStyle:"indented",text:this.options.pedantic?n:ir(n,` -`)}}}fences(t){let e=this.rules.block.fences.exec(t);if(e){let n=e[0],r=Ib(n,e[3]||"",this.rules);return{type:"code",raw:n,lang:e[2]?e[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):e[2],text:r}}}heading(t){let e=this.rules.block.heading.exec(t);if(e){let n=e[2].trim();if(this.rules.other.endingHash.test(n)){let r=ir(n,"#");(this.options.pedantic||!r||this.rules.other.endingSpaceChar.test(r))&&(n=r.trim())}return{type:"heading",raw:e[0],depth:e[1].length,text:n,tokens:this.lexer.inline(n)}}}hr(t){let e=this.rules.block.hr.exec(t);if(e)return{type:"hr",raw:ir(e[0],` -`)}}blockquote(t){let e=this.rules.block.blockquote.exec(t);if(e){let n=ir(e[0],` +3. "-" cannot repeat`);K.customSchemes.push([n,e])}function gb(){K.scanner=ab(K.customSchemes);for(let n=0;n{let s=e.some(c=>c.docChanged)&&!t.doc.eq(r.doc),i=e.some(c=>c.getMeta("preventAutolink"));if(!s||i)return;let{tr:o}=r,l=hl(t.doc,[...e]);if(kl(l).forEach(({newRange:c})=>{let u=Ld(r.doc,c,f=>f.isTextblock),d,h;if(u.length>1)d=u[0],h=r.doc.textBetween(d.pos,d.pos+d.node.nodeSize,void 0," ");else if(u.length){let f=r.doc.textBetween(c.from,c.to," "," ");if(!kb.test(f))return;d=u[0],h=r.doc.textBetween(d.pos,c.to,void 0," ")}if(d&&h){let f=h.split(yb).filter(Boolean);if(f.length<=0)return!1;let p=f[f.length-1],m=d.pos+h.lastIndexOf(p);if(!p)return!1;let g=pi(p).map(y=>y.toObject(n.defaultProtocol));if(!xb(g))return!1;g.filter(y=>y.isLink).map(y=>({...y,from:m+y.start+1,to:m+y.end+1})).filter(y=>r.schema.marks.code?!r.doc.rangeHasMark(y.from,y.to,r.schema.marks.code):!0).filter(y=>n.validate(y.value)).filter(y=>n.shouldAutoLink(y.value)).forEach(y=>{ws(y.from,y.to,r.doc).some(k=>k.mark.type===n.type)||o.addMark(y.from,y.to,n.type.create({href:y.href}))})}}),!!o.steps.length)return o}})}function Sb(n){return new I({key:new L("handleClickLink"),props:{handleClick:(e,t,r)=>{var s,i;if(r.button!==0||!e.editable)return!1;let o=null;if(r.target instanceof HTMLAnchorElement)o=r.target;else{let a=r.target;if(!a)return!1;let c=n.editor.view.dom;o=a.closest("a"),o&&!c.contains(o)&&(o=null)}if(!o)return!1;let l=!1;if(n.enableClickSelection&&(l=n.editor.commands.extendMarkRange(n.type.name)),n.openOnClick){let a=yl(e.state,n.type.name),c=(s=o.href)!=null?s:a.href,u=(i=o.target)!=null?i:a.target;c&&(window.open(c,u),l=!0)}return l}}})}function Cb(n){return new I({key:new L("handlePasteLink"),props:{handlePaste:(e,t,r)=>{let{shouldAutoLink:s}=n,{state:i}=e,{selection:o}=i,{empty:l}=o;if(l)return!1;let a="";r.content.forEach(u=>{a+=u.textContent});let c=mi(a,{defaultProtocol:n.defaultProtocol}).find(u=>u.isLink&&u.value===a);return!a||!c||s!==void 0&&!s(c.value)?!1:n.editor.commands.setMark(n.type,{href:c.href})}}})}function Yt(n,e){let t=["http","https","ftp","ftps","mailto","tel","callto","sms","cid","xmpp"];return e&&e.forEach(r=>{let s=typeof r=="string"?r:r.scheme;s&&t.push(s)}),!n||n.replace(bb,"").match(new RegExp(`^(?:(?:${t.join("|")}):|[^a-z]|[a-z0-9+.-]+(?:[^a-z+.-:]|$))`,"i"))}var la=Ee.create({name:"link",priority:1e3,keepOnSplit:!1,exitable:!0,onCreate(){this.options.validate&&!this.options.shouldAutoLink&&(this.options.shouldAutoLink=this.options.validate,console.warn("The `validate` option is deprecated. Rename to the `shouldAutoLink` option instead.")),this.options.protocols.forEach(n=>{if(typeof n=="string"){ia(n);return}ia(n.scheme,n.optionalSlashes)})},onDestroy(){pf()},inclusive(){return this.options.autolink},addOptions(){return{openOnClick:!0,enableClickSelection:!1,linkOnPaste:!0,autolink:!0,protocols:[],defaultProtocol:"http",HTMLAttributes:{target:"_blank",rel:"noopener noreferrer nofollow",class:null},isAllowedUri:(n,e)=>!!Yt(n,e.protocols),validate:n=>!!n,shouldAutoLink:n=>{let e=/^[a-z][a-z0-9+.-]*:\/\//i.test(n),t=/^[a-z][a-z0-9+.-]*:/i.test(n);if(e||t&&!n.includes("@"))return!0;let s=(n.includes("@")?n.split("@").pop():n).split(/[/?#:]/)[0];return!(/^\d{1,3}(\.\d{1,3}){3}$/.test(s)||!/\./.test(s))}}},addAttributes(){return{href:{default:null,parseHTML(n){return n.getAttribute("href")}},target:{default:this.options.HTMLAttributes.target},rel:{default:this.options.HTMLAttributes.rel},class:{default:this.options.HTMLAttributes.class},title:{default:null}}},parseHTML(){return[{tag:"a[href]",getAttrs:n=>{let e=n.getAttribute("href");return!e||!this.options.isAllowedUri(e,{defaultValidate:t=>!!Yt(t,this.options.protocols),protocols:this.options.protocols,defaultProtocol:this.options.defaultProtocol})?!1:null}}]},renderHTML({HTMLAttributes:n}){return this.options.isAllowedUri(n.href,{defaultValidate:e=>!!Yt(e,this.options.protocols),protocols:this.options.protocols,defaultProtocol:this.options.defaultProtocol})?["a",D(this.options.HTMLAttributes,n),0]:["a",D(this.options.HTMLAttributes,{...n,href:""}),0]},markdownTokenName:"link",parseMarkdown:(n,e)=>e.applyMark("link",e.parseInline(n.tokens||[]),{href:n.href,title:n.title||null}),renderMarkdown:(n,e)=>{var t,r,s,i;let o=(r=(t=n.attrs)==null?void 0:t.href)!=null?r:"",l=(i=(s=n.attrs)==null?void 0:s.title)!=null?i:"",a=e.renderChildren(n);return l?`[${a}](${o} "${l}")`:`[${a}](${o})`},addCommands(){return{setLink:n=>({chain:e})=>{let{href:t}=n;return this.options.isAllowedUri(t,{defaultValidate:r=>!!Yt(r,this.options.protocols),protocols:this.options.protocols,defaultProtocol:this.options.defaultProtocol})?e().setMark(this.name,n).setMeta("preventAutolink",!0).run():!1},toggleLink:n=>({chain:e})=>{let{href:t}=n||{};return t&&!this.options.isAllowedUri(t,{defaultValidate:r=>!!Yt(r,this.options.protocols),protocols:this.options.protocols,defaultProtocol:this.options.defaultProtocol})?!1:e().toggleMark(this.name,n,{extendEmptyMarkRange:!0}).setMeta("preventAutolink",!0).run()},unsetLink:()=>({chain:n})=>n().unsetMark(this.name,{extendEmptyMarkRange:!0}).setMeta("preventAutolink",!0).run()}},addPasteRules(){return[ze({find:n=>{let e=[];if(n){let{protocols:t,defaultProtocol:r}=this.options,s=mi(n).filter(i=>i.isLink&&this.options.isAllowedUri(i.value,{defaultValidate:o=>!!Yt(o,t),protocols:t,defaultProtocol:r}));s.length&&s.forEach(i=>{this.options.shouldAutoLink(i.value)&&e.push({text:i.value,data:{href:i.href},index:i.start})})}return e},type:this.type,getAttributes:n=>{var e;return{href:(e=n.data)==null?void 0:e.href}}})]},addProseMirrorPlugins(){let n=[],{protocols:e,defaultProtocol:t}=this.options;return this.options.autolink&&n.push(wb({type:this.type,defaultProtocol:this.options.defaultProtocol,validate:r=>this.options.isAllowedUri(r,{defaultValidate:s=>!!Yt(s,e),protocols:e,defaultProtocol:t}),shouldAutoLink:this.options.shouldAutoLink})),n.push(Sb({type:this.type,editor:this.editor,openOnClick:this.options.openOnClick==="whenNotEditable"?!0:this.options.openOnClick,enableClickSelection:this.options.enableClickSelection})),this.options.linkOnPaste&&n.push(Cb({editor:this.editor,defaultProtocol:this.options.defaultProtocol,type:this.type,shouldAutoLink:this.options.shouldAutoLink})),n}}),vb=la;var Tb=Object.defineProperty,Mb=(n,e)=>{for(var t in e)Tb(n,t,{get:e[t],enumerable:!0})},Ab="listItem",mf="textStyle",gf=/^\s*([-+*])\s$/,ua=$.create({name:"bulletList",addOptions(){return{itemTypeName:"listItem",HTMLAttributes:{},keepMarks:!1,keepAttributes:!1}},group:"block list",content(){return`${this.options.itemTypeName}+`},parseHTML(){return[{tag:"ul"}]},renderHTML({HTMLAttributes:n}){return["ul",D(this.options.HTMLAttributes,n),0]},markdownTokenName:"list",parseMarkdown:(n,e)=>n.type!=="list"||n.ordered?[]:{type:"bulletList",content:n.items?e.parseChildren(n.items):[]},renderMarkdown:(n,e)=>n.content?e.renderChildren(n.content,` +`):"",markdownOptions:{indentsContent:!0},addCommands(){return{toggleBulletList:()=>({commands:n,chain:e})=>this.options.keepAttributes?e().toggleList(this.name,this.options.itemTypeName,this.options.keepMarks).updateAttributes(Ab,this.editor.getAttributes(mf)).run():n.toggleList(this.name,this.options.itemTypeName,this.options.keepMarks)}},addKeyboardShortcuts(){return{"Mod-Shift-8":()=>this.editor.commands.toggleBulletList()}},addInputRules(){let n=Ue({find:gf,type:this.type});return(this.options.keepMarks||this.options.keepAttributes)&&(n=Ue({find:gf,type:this.type,keepMarks:this.options.keepMarks,keepAttributes:this.options.keepAttributes,getAttributes:()=>this.editor.getAttributes(mf),editor:this.editor})),[n]}}),Eb=(n,e,t)=>{let{selection:r}=n;if(!r.empty)return null;let{$from:s}=r;if(!s.parent.isTextblock||s.parentOffset!==s.parent.content.size)return null;let i=-1;for(let f=s.depth;f>0;f-=1)if(s.node(f).type.name===e){i=f;break}if(i<0)return null;let o=s.node(i),l=s.index(i);if(l+1>=o.childCount)return null;let a=o.child(l+1);if(!t.includes(a.type.name))return null;let c=n.schema.nodes[e],u=!1;if(a.forEach(f=>{f.type===c&&f.childCount>1&&(u=!0)}),!u)return null;let d=n.doc.resolve(s.after()).nodeAfter;if(!d||!t.includes(d.type.name))return null;let h=[];return d.forEach(f=>{h.push(f)}),h.length===0?null:{listItemDepth:i,nestedList:d,nestedListPos:s.after(),insertPos:s.after(i),items:h}},Nb=(n,e,t,r)=>{let s=Eb(n,t,r);if(!s)return!1;let{selection:i}=n,{nestedList:o,nestedListPos:l,insertPos:a,items:c}=s,u=n.tr;u.delete(l,l+o.nodeSize);let d=u.mapping.map(a);return u.insert(d,b.from(c)),u.setSelection(i.map(u.doc,u.mapping)),e&&e(u),!0},Rb=(n,e,t)=>Nb(n.state,n.view.dispatch,e,t),xf=(n,e)=>B.create({name:`${n}BranchingDeleteKeymap`,priority:101,addKeyboardShortcuts(){let t=()=>Rb(this.editor,n,e);return{Delete:t,"Mod-Delete":t}}});function Ob(n){var e,t;let r=(e=n.tokens)==null?void 0:e[0];return!!(n.text&&((t=n.tokens)==null?void 0:t.length)===1&&r?.type==="list"&&r.ordered&&r.raw===n.text)}function Ib(n,e){return e.tokenizeInline?e.parseInline(e.tokenizeInline(n)):e.parseInline([{type:"text",raw:n,text:n}])}var da=$.create({name:"listItem",addOptions(){return{HTMLAttributes:{},bulletListTypeName:"bulletList",orderedListTypeName:"orderedList"}},content:"paragraph block*",defining:!0,parseHTML(){return[{tag:"li"}]},renderHTML({HTMLAttributes:n}){return["li",D(this.options.HTMLAttributes,n),0]},markdownTokenName:"list_item",parseMarkdown:(n,e)=>{var t;if(n.type!=="list_item")return[];let r=(t=e.parseBlockChildren)!=null?t:e.parseChildren,s=[];if(n.tokens&&n.tokens.length>0){if(Ob(n))return{type:"listItem",content:[{type:"paragraph",content:Ib(n.text||"",e)}]};if(n.tokens.some(o=>o.type==="paragraph"))s=r(n.tokens);else{let o=n.tokens[0];if(o&&o.type==="text"&&o.tokens&&o.tokens.length>0){if(s=[{type:"paragraph",content:e.parseInline(o.tokens)}],n.tokens.length>1){let a=n.tokens.slice(1),c=r(a);s.push(...c)}}else s=r(n.tokens)}}return s.length===0&&(s=[{type:"paragraph",content:[]}]),{type:"listItem",content:s}},renderMarkdown:(n,e,t)=>ur(n,e,r=>{var s,i;return r.parentType==="bulletList"?"- ":r.parentType==="orderedList"?`${(((i=(s=r.meta)==null?void 0:s.parentAttrs)==null?void 0:i.start)||1)+r.index}. `:"- "},t),addExtensions(){return[xf(this.name,[this.options.bulletListTypeName,this.options.orderedListTypeName])]},addKeyboardShortcuts(){return{Enter:()=>this.editor.commands.splitListItem(this.name),Tab:()=>this.editor.commands.sinkListItem(this.name),"Shift-Tab":()=>this.editor.commands.liftListItem(this.name)}}}),Db={};Mb(Db,{findListItemPos:()=>gi,getNextListDepth:()=>ha,handleBackspace:()=>aa,handleDelete:()=>ca,hasListBefore:()=>wf,hasListItemAfter:()=>Pb,hasListItemBefore:()=>Lb,listItemHasSubList:()=>zb,nextListIsDeeper:()=>Sf,nextListIsHigher:()=>Cf});var gi=(n,e)=>{let{$from:t}=e.selection,r=U(n,e.schema),s=null,i=t.depth,o=t.pos,l=null;for(;i>0&&l===null;)s=t.node(i),s.type===r?l=i:(i-=1,o-=1);return l===null?null:{$pos:e.doc.resolve(o),depth:l}},ha=(n,e)=>{let t=gi(n,e);if(!t)return!1;let[,r]=Vd(e,n,t.$pos.pos+4);return r},wf=(n,e,t)=>{let{$anchor:r}=n.selection,s=Math.max(0,r.pos-2),i=n.doc.resolve(s).node();return!(!i||!t.includes(i.type.name))},aa=(n,e,t)=>{if(n.commands.undoInputRule())return!0;if(n.state.selection.from!==n.state.selection.to)return!1;if(!Ke(n.state,e)&&wf(n.state,e,t)){let{$anchor:r}=n.state.selection,s=n.state.doc.resolve(r.before()-1),i=[];s.node().descendants((a,c)=>{a.type.name===e&&i.push({node:a,pos:c})});let o=i.at(-1);if(!o)return!1;let l=n.state.doc.resolve(s.start()+o.pos+1);return n.chain().cut({from:r.start()-1,to:r.end()+1},l.end()).joinForward().run()}return!Ke(n.state,e)||!jd(n.state)?!1:n.chain().liftListItem(e).run()},Sf=(n,e)=>{let t=ha(n,e),r=gi(n,e);return!r||!t?!1:t>r.depth},Cf=(n,e)=>{let t=ha(n,e),r=gi(n,e);return!r||!t?!1:t{if(!Ke(n.state,e)||!Wd(n.state,e))return!1;let{selection:t}=n.state,{$from:r,$to:s}=t;return!t.empty&&r.sameParent(s)?!1:Sf(e,n.state)?n.chain().focus(n.state.selection.from+4).lift(e).joinBackward().run():Cf(e,n.state)?n.chain().joinForward().joinBackward().run():n.commands.joinItemForward()},Pb=(n,e)=>{var t;let{$anchor:r}=e.selection,s=e.doc.resolve(r.pos-r.parentOffset-2);return!(s.index()===s.parent.childCount-1||((t=s.nodeAfter)==null?void 0:t.type.name)!==n)},Lb=(n,e)=>{var t;let{$anchor:r}=e.selection,s=e.doc.resolve(r.pos-2);return!(s.index()===0||((t=s.nodeBefore)==null?void 0:t.type.name)!==n)},zb=(n,e,t)=>{if(!t)return!1;let r=U(n,e.schema),s=!1;return t.descendants(i=>{i.type===r&&(s=!0)}),s},fa=B.create({name:"listKeymap",addOptions(){return{listTypes:[{itemName:"listItem",wrapperNames:["bulletList","orderedList"]},{itemName:"taskItem",wrapperNames:["taskList"]}]}},addKeyboardShortcuts(){return{Delete:({editor:n})=>{let e=!1;return this.options.listTypes.forEach(({itemName:t})=>{n.state.schema.nodes[t]!==void 0&&ca(n,t)&&(e=!0)}),e},"Mod-Delete":({editor:n})=>{let e=!1;return this.options.listTypes.forEach(({itemName:t})=>{n.state.schema.nodes[t]!==void 0&&ca(n,t)&&(e=!0)}),e},Backspace:({editor:n})=>{let e=!1;return this.options.listTypes.forEach(({itemName:t,wrapperNames:r})=>{n.state.schema.nodes[t]!==void 0&&aa(n,t,r)&&(e=!0)}),e},"Mod-Backspace":({editor:n})=>{let e=!1;return this.options.listTypes.forEach(({itemName:t,wrapperNames:r})=>{n.state.schema.nodes[t]!==void 0&&aa(n,t,r)&&(e=!0)}),e}}}}),yf=/^(\s*)(\d+)\.\s+(.*)$/,Bb=/^\s/;function $b(n){let e=n.trimStart();return/^[-+*]\s+/.test(e)||/^\d+\.\s+/.test(e)||/^>\s?/.test(e)||/^```/.test(e)||/^~~~/.test(e)}function Fb(n){let e=[],t=[],r=!1;return n.forEach(s=>{if(r){t.push(s);return}if(s.trim()===""){r=!0,t.push(s);return}if(e.length>0&&$b(s)){r=!0,t.push(s);return}e.push(s)}),{paragraphLines:e,blockLines:t}}function Hb(n){let e=[],t=0,r=0;for(;te;)h.push(n[d]),d+=1;if(h.length>0){let f=Math.min(...h.map(m=>m.indent)),p=vf(h,f,t);c.push({type:"list",ordered:!0,start:h[0].number,items:p,raw:h.map(m=>m.raw).join(` +`)})}r.push({type:"list_item",raw:i.raw,tokens:c}),s=d}else s+=1}return r}function _b(n,e){return n.map(t=>{if(t.type!=="list_item")return e.parseChildren([t])[0];let r=[];return t.tokens&&t.tokens.length>0&&t.tokens.forEach(s=>{if(s.type==="paragraph"||s.type==="list"||s.type==="blockquote"||s.type==="code")r.push(...e.parseChildren([s]));else if(s.type==="text"&&s.tokens){let i=e.parseChildren([s]);r.push({type:"paragraph",content:i})}else{let i=e.parseChildren([s]);i.length>0&&r.push(...i)}}),{type:"listItem",content:r}})}var Vb="listItem",kf="textStyle",bf=/^(\d+)\.\s$/,pa=$.create({name:"orderedList",addOptions(){return{itemTypeName:"listItem",HTMLAttributes:{},keepMarks:!1,keepAttributes:!1}},group:"block list",content(){return`${this.options.itemTypeName}+`},addAttributes(){return{start:{default:1,parseHTML:n=>n.hasAttribute("start")?parseInt(n.getAttribute("start")||"",10):1},type:{default:null,parseHTML:n=>n.getAttribute("type")}}},parseHTML(){return[{tag:"ol"}]},renderHTML({HTMLAttributes:n}){let{start:e,...t}=n;return e===1?["ol",D(this.options.HTMLAttributes,t),0]:["ol",D(this.options.HTMLAttributes,n),0]},markdownTokenName:"list",parseMarkdown:(n,e)=>{if(n.type!=="list"||!n.ordered)return[];let t=n.start||1,r=n.items?_b(n.items,e):[];return t!==1?{type:"orderedList",attrs:{start:t},content:r}:{type:"orderedList",content:r}},renderMarkdown:(n,e)=>n.content?e.renderChildren(n.content,` +`):"",markdownTokenizer:{name:"orderedList",level:"block",start:n=>{let e=n.match(/^(\s*)(\d+)\.\s+/),t=e?.index;return t!==void 0?t:-1},tokenize:(n,e,t)=>{var r;let s=n.split(` +`),[i,o]=Hb(s);if(i.length===0)return;let l=vf(i,0,t);return l.length===0?void 0:{type:"list",ordered:!0,start:((r=i[0])==null?void 0:r.number)||1,items:l,raw:s.slice(0,o).join(` +`)}}},markdownOptions:{indentsContent:!0},addCommands(){return{toggleOrderedList:()=>({commands:n,chain:e})=>this.options.keepAttributes?e().toggleList(this.name,this.options.itemTypeName,this.options.keepMarks).updateAttributes(Vb,this.editor.getAttributes(kf)).run():n.toggleList(this.name,this.options.itemTypeName,this.options.keepMarks)}},addKeyboardShortcuts(){return{"Mod-Shift-7":()=>this.editor.commands.toggleOrderedList()}},addInputRules(){let n=Ue({find:bf,type:this.type,getAttributes:e=>({start:+e[1]}),joinPredicate:(e,t)=>t.childCount+t.attrs.start===+e[1]});return(this.options.keepMarks||this.options.keepAttributes)&&(n=Ue({find:bf,type:this.type,keepMarks:this.options.keepMarks,keepAttributes:this.options.keepAttributes,getAttributes:e=>({start:+e[1],...this.editor.getAttributes(kf)}),joinPredicate:(e,t)=>t.childCount+t.attrs.start===+e[1],editor:this.editor})),[n]}}),Wb=/^\s*(\[([( |x])?\])\s$/,yi=$.create({name:"taskItem",addOptions(){return{nested:!1,HTMLAttributes:{},taskListTypeName:"taskList",a11y:void 0}},content(){return this.options.nested?"paragraph block*":"paragraph+"},defining:!0,addAttributes(){return{checked:{default:!1,keepOnSplit:!1,parseHTML:n=>{let e=n.getAttribute("data-checked");return e===""||e==="true"},renderHTML:n=>({"data-checked":n.checked})}}},parseHTML(){return[{tag:`li[data-type="${this.name}"]`,priority:51}]},renderHTML({node:n,HTMLAttributes:e}){return["li",D(this.options.HTMLAttributes,e,{"data-type":this.name}),["label",["input",{type:"checkbox",checked:n.attrs.checked?"checked":null}],["span"]],["div",0]]},parseMarkdown:(n,e)=>{let t=[];if(n.tokens&&n.tokens.length>0?t.push(e.createNode("paragraph",{},e.parseInline(n.tokens))):n.text?t.push(e.createNode("paragraph",{},[e.createNode("text",{text:n.text})])):t.push(e.createNode("paragraph",{},[])),n.nestedTokens&&n.nestedTokens.length>0){let r=e.parseChildren(n.nestedTokens);t.push(...r)}return e.createNode("taskItem",{checked:n.checked||!1},t)},renderMarkdown:(n,e)=>{var t;let s=`- [${(t=n.attrs)!=null&&t.checked?"x":" "}] `;return ur(n,e,s)},addExtensions(){return this.options.nested?[xf(this.name,[this.options.taskListTypeName])]:[]},addKeyboardShortcuts(){let n={Enter:()=>this.editor.commands.splitListItem(this.name),"Shift-Tab":()=>this.editor.commands.liftListItem(this.name)};return this.options.nested?{...n,Tab:()=>this.editor.commands.sinkListItem(this.name)}:n},addNodeView(){return({node:n,HTMLAttributes:e,getPos:t,editor:r})=>{let s=document.createElement("li"),i=document.createElement("label"),o=document.createElement("span"),l=document.createElement("input"),a=document.createElement("div"),c=d=>{var h,f;l.ariaLabel=((f=(h=this.options.a11y)==null?void 0:h.checkboxLabel)==null?void 0:f.call(h,d,l.checked))||`Task item checkbox for ${d.textContent||"empty task item"}`};c(n),i.contentEditable="false",l.type="checkbox",l.addEventListener("mousedown",d=>d.preventDefault()),l.addEventListener("change",d=>{if(!r.isEditable&&!this.options.onReadOnlyChecked){l.checked=!l.checked;return}let{checked:h}=d.target;r.isEditable&&typeof t=="function"&&r.chain().focus(void 0,{scrollIntoView:!1}).command(({tr:f})=>{let p=t();if(typeof p!="number")return!1;let m=f.doc.nodeAt(p);return f.setNodeMarkup(p,void 0,{...m?.attrs,checked:h}),!0}).run(),!r.isEditable&&this.options.onReadOnlyChecked&&(this.options.onReadOnlyChecked(n,h)||(l.checked=!l.checked))}),Object.entries(this.options.HTMLAttributes).forEach(([d,h])=>{s.setAttribute(d,h)}),s.dataset.checked=n.attrs.checked,l.checked=n.attrs.checked,i.append(l,o),s.append(i,a),Object.entries(e).forEach(([d,h])=>{s.setAttribute(d,h)});let u=new Set(Object.keys(e));return{dom:s,contentDOM:a,update:d=>{if(d.type!==this.type)return!1;s.dataset.checked=d.attrs.checked,l.checked=d.attrs.checked,c(d);let h=r.extensionManager.attributes,f=Cn(d,h),p=new Set(Object.keys(f)),m=this.options.HTMLAttributes;return u.forEach(g=>{p.has(g)||(g in m?s.setAttribute(g,m[g]):s.removeAttribute(g))}),Object.entries(f).forEach(([g,y])=>{y==null?g in m?s.setAttribute(g,m[g]):s.removeAttribute(g):s.setAttribute(g,y)}),u=p,!0}}}},addInputRules(){return[Ue({find:Wb,type:this.type,getAttributes:n=>({checked:n[n.length-1]==="x"})})]}}),ki=$.create({name:"taskList",addOptions(){return{itemTypeName:"taskItem",HTMLAttributes:{}}},group:"block list",content(){return`${this.options.itemTypeName}+`},parseHTML(){return[{tag:`ul[data-type="${this.name}"]`,priority:51}]},renderHTML({HTMLAttributes:n}){return["ul",D(this.options.HTMLAttributes,n,{"data-type":this.name}),0]},parseMarkdown:(n,e)=>e.createNode("taskList",{},e.parseChildren(n.items||[])),renderMarkdown:(n,e)=>n.content?e.renderChildren(n.content,` +`):"",markdownTokenizer:{name:"taskList",level:"block",start(n){var e;let t=(e=n.match(/^\s*[-+*]\s+\[([ xX])\]\s+/))==null?void 0:e.index;return t!==void 0?t:-1},tokenize(n,e,t){let r=i=>{let o=Ms(i,{itemPattern:/^(\s*)([-+*])\s+\[([ xX])\]\s+(.*)$/,extractItemData:l=>({indentLevel:l[1].length,mainContent:l[4],checked:l[3].toLowerCase()==="x"}),createToken:(l,a)=>({type:"taskItem",raw:"",mainContent:l.mainContent,indentLevel:l.indentLevel,checked:l.checked,text:l.mainContent,tokens:t.inlineTokens(l.mainContent),nestedTokens:a}),customNestedParser:r},t);return o?[{type:"taskList",raw:o.raw,items:o.items}]:t.blockTokens(i)},s=Ms(n,{itemPattern:/^(\s*)([-+*])\s+\[([ xX])\]\s+(.*)$/,extractItemData:i=>({indentLevel:i[1].length,mainContent:i[4],checked:i[3].toLowerCase()==="x"}),createToken:(i,o)=>({type:"taskItem",raw:"",mainContent:i.mainContent,indentLevel:i.indentLevel,checked:i.checked,text:i.mainContent,tokens:t.inlineTokens(i.mainContent),nestedTokens:o}),customNestedParser:r},t);if(s)return{type:"taskList",raw:s.raw,items:s.items}}},markdownOptions:{indentsContent:!0},addCommands(){return{toggleTaskList:()=>({commands:n})=>n.toggleList(this.name,this.options.itemTypeName)}},addKeyboardShortcuts(){return{"Mod-Shift-9":()=>this.editor.commands.toggleTaskList()}}}),Iv=B.create({name:"listKit",addExtensions(){let n=[];return this.options.bulletList!==!1&&n.push(ua.configure(this.options.bulletList)),this.options.listItem!==!1&&n.push(da.configure(this.options.listItem)),this.options.listKeymap!==!1&&n.push(fa.configure(this.options.listKeymap)),this.options.orderedList!==!1&&n.push(pa.configure(this.options.orderedList)),this.options.taskItem!==!1&&n.push(yi.configure(this.options.taskItem)),this.options.taskList!==!1&&n.push(ki.configure(this.options.taskList)),n}});var bi=" ",ma="\xA0",Tf=$.create({name:"paragraph",priority:1e3,addOptions(){return{HTMLAttributes:{}}},group:"block",content:"inline*",parseHTML(){return[{tag:"p"}]},renderHTML({HTMLAttributes:n}){return["p",D(this.options.HTMLAttributes,n),0]},parseMarkdown:(n,e)=>{let t=n.tokens||[];if(t.length===1&&t[0].type==="image")return e.parseChildren([t[0]]);let r=e.parseInline(t);return t.length===1&&t[0].type==="text"&&(t[0].raw===bi||t[0].text===bi||t[0].raw===ma||t[0].text===ma)&&r.length===1&&r[0].type==="text"&&(r[0].text===bi||r[0].text===ma)?e.createNode("paragraph",void 0,[]):e.createNode("paragraph",void 0,r)},renderMarkdown:(n,e,t)=>{var r,s;if(!n)return"";let i=Array.isArray(n.content)?n.content:[];if(i.length===0){let o=Array.isArray((r=t?.previousNode)==null?void 0:r.content)?t.previousNode.content:[];return((s=t?.previousNode)==null?void 0:s.type)==="paragraph"&&o.length===0?bi:""}return e.renderChildren(i)},addCommands(){return{setParagraph:()=>({commands:n})=>n.setNode(this.name)}},addKeyboardShortcuts(){return{"Mod-Alt-0":()=>this.editor.commands.setParagraph()}}});var jb=/(?:^|\s)(~~(?!\s+~~)((?:[^~]+))~~(?!\s+~~))$/,Jb=/(?:^|\s)(~~(?!\s+~~)((?:[^~]+))~~(?!\s+~~))/g,Mf=Ee.create({name:"strike",addOptions(){return{HTMLAttributes:{}}},parseHTML(){return[{tag:"s"},{tag:"del"},{tag:"strike"},{style:"text-decoration",consuming:!1,getAttrs:n=>n.includes("line-through")?{}:!1}]},renderHTML({HTMLAttributes:n}){return["s",D(this.options.HTMLAttributes,n),0]},markdownTokenName:"del",parseMarkdown:(n,e)=>e.applyMark("strike",e.parseInline(n.tokens||[])),renderMarkdown:(n,e)=>`~~${e.renderChildren(n)}~~`,addCommands(){return{setStrike:()=>({commands:n})=>n.setMark(this.name),toggleStrike:()=>({commands:n})=>n.toggleMark(this.name),unsetStrike:()=>({commands:n})=>n.unsetMark(this.name)}},addKeyboardShortcuts(){return{"Mod-Shift-s":()=>this.editor.commands.toggleStrike()}},addInputRules(){return[qe({find:jb,type:this.type})]},addPasteRules(){return[ze({find:Jb,type:this.type})]}});var Af=$.create({name:"text",group:"inline",parseMarkdown:n=>({type:"text",text:n.text||""}),renderMarkdown:n=>n.text||""});var Ef=Ee.create({name:"underline",addOptions(){return{HTMLAttributes:{}}},parseHTML(){return[{tag:"u"},{style:"text-decoration",consuming:!1,getAttrs:n=>n.includes("underline")?{}:!1}]},renderHTML({HTMLAttributes:n}){return["u",D(this.options.HTMLAttributes,n),0]},parseMarkdown(n,e){return e.applyMark(this.name||"underline",e.parseInline(n.tokens||[]))},renderMarkdown(n,e){return`++${e.renderChildren(n)}++`},markdownTokenizer:{name:"underline",level:"inline",start(n){return n.indexOf("++")},tokenize(n,e,t){let s=/^(\+\+)([\s\S]+?)(\+\+)/.exec(n);if(!s)return;let i=s[2].trim();return{type:"underline",raw:s[0],text:i,tokens:t.inlineTokens(i)}}},addCommands(){return{setUnderline:()=>({commands:n})=>n.setMark(this.name),toggleUnderline:()=>({commands:n})=>n.toggleMark(this.name),unsetUnderline:()=>({commands:n})=>n.unsetMark(this.name)}},addKeyboardShortcuts(){return{"Mod-u":()=>this.editor.commands.toggleUnderline(),"Mod-U":()=>this.editor.commands.toggleUnderline()}}});function Nf(n={}){return new I({view(e){return new ga(e,n)}})}var ga=class{constructor(e,t){var r;this.editorView=e,this.cursorPos=null,this.element=null,this.timeout=-1,this.width=(r=t.width)!==null&&r!==void 0?r:1,this.color=t.color===!1?void 0:t.color||"black",this.class=t.class,this.handlers=["dragover","dragend","drop","dragleave"].map(s=>{let i=o=>{this[s](o)};return e.dom.addEventListener(s,i),{name:s,handler:i}})}destroy(){this.handlers.forEach(({name:e,handler:t})=>this.editorView.dom.removeEventListener(e,t))}update(e,t){this.cursorPos!=null&&t.doc!=e.state.doc&&(this.cursorPos>e.state.doc.content.size?this.setCursor(null):this.updateOverlay())}setCursor(e){e!=this.cursorPos&&(this.cursorPos=e,e==null?(this.element.parentNode.removeChild(this.element),this.element=null):this.updateOverlay())}updateOverlay(){let e=this.editorView.state.doc.resolve(this.cursorPos),t=!e.parent.inlineContent,r,s=this.editorView.dom,i=s.getBoundingClientRect(),o=i.width/s.offsetWidth,l=i.height/s.offsetHeight;if(t){let d=e.nodeBefore,h=e.nodeAfter;if(d||h){let f=this.editorView.nodeDOM(this.cursorPos-(d?d.nodeSize:0));if(f){let p=f.getBoundingClientRect(),m=d?p.bottom:p.top;d&&h&&(m=(m+this.editorView.nodeDOM(this.cursorPos).getBoundingClientRect().top)/2);let g=this.width/2*l;r={left:p.left,right:p.right,top:m-g,bottom:m+g}}}}if(!r){let d=this.editorView.coordsAtPos(this.cursorPos),h=this.width/2*o;r={left:d.left-h,right:d.left+h,top:d.top,bottom:d.bottom}}let a=this.editorView.dom.offsetParent;this.element||(this.element=a.appendChild(document.createElement("div")),this.class&&(this.element.className=this.class),this.element.style.cssText="position: absolute; z-index: 50; pointer-events: none;",this.color&&(this.element.style.backgroundColor=this.color)),this.element.classList.toggle("prosemirror-dropcursor-block",t),this.element.classList.toggle("prosemirror-dropcursor-inline",!t);let c,u;if(!a||a==document.body&&getComputedStyle(a).position=="static")c=-pageXOffset,u=-pageYOffset;else{let d=a.getBoundingClientRect(),h=d.width/a.offsetWidth,f=d.height/a.offsetHeight;c=d.left-a.scrollLeft*h,u=d.top-a.scrollTop*f}this.element.style.left=(r.left-c)/o+"px",this.element.style.top=(r.top-u)/l+"px",this.element.style.width=(r.right-r.left)/o+"px",this.element.style.height=(r.bottom-r.top)/l+"px"}scheduleRemoval(e){clearTimeout(this.timeout),this.timeout=setTimeout(()=>this.setCursor(null),e)}dragover(e){if(!this.editorView.editable)return;let t=this.editorView.posAtCoords({left:e.clientX,top:e.clientY}),r=t&&t.inside>=0&&this.editorView.state.doc.nodeAt(t.inside),s=r&&r.type.spec.disableDropCursor,i=typeof s=="function"?s(this.editorView,t,e):s;if(t&&!i){let o=t.pos;if(this.editorView.dragging&&this.editorView.dragging.slice){let l=Kr(this.editorView.state.doc,o,this.editorView.dragging.slice);l!=null&&(o=l)}this.setCursor(o),this.scheduleRemoval(5e3)}}dragend(){this.scheduleRemoval(20)}drop(){this.scheduleRemoval(20)}dragleave(e){this.editorView.dom.contains(e.relatedTarget)||this.setCursor(null)}};var ge=class n extends O{constructor(e){super(e,e)}map(e,t){let r=e.resolve(t.map(this.head));return n.valid(r)?new n(r):O.near(r)}content(){return v.empty}eq(e){return e instanceof n&&e.head==this.head}toJSON(){return{type:"gapcursor",pos:this.head}}static fromJSON(e,t){if(typeof t.pos!="number")throw new RangeError("Invalid input for GapCursor.fromJSON");return new n(e.resolve(t.pos))}getBookmark(){return new ya(this.anchor)}static valid(e){let t=e.parent;if(t.inlineContent||!Kb(e)||!qb(e))return!1;let r=t.type.spec.allowGapCursor;if(r!=null)return r;let s=t.contentMatchAt(e.index()).defaultType;return s&&s.isTextblock}static findGapCursorFrom(e,t,r=!1){e:for(;;){if(!r&&n.valid(e))return e;let s=e.pos,i=null;for(let o=e.depth;;o--){let l=e.node(o);if(t>0?e.indexAfter(o)0){i=l.child(t>0?e.indexAfter(o):e.index(o)-1);break}else if(o==0)return null;s+=t;let a=e.doc.resolve(s);if(n.valid(a))return a}for(;;){let o=t>0?i.firstChild:i.lastChild;if(!o){if(i.isAtom&&!i.isText&&!N.isSelectable(i)){e=e.doc.resolve(s+i.nodeSize*t),r=!1;continue e}break}i=o,s+=t;let l=e.doc.resolve(s);if(n.valid(l))return l}return null}}};ge.prototype.visible=!1;ge.findFrom=ge.findGapCursorFrom;O.jsonID("gapcursor",ge);var ya=class n{constructor(e){this.pos=e}map(e){return new n(e.map(this.pos))}resolve(e){let t=e.resolve(this.pos);return ge.valid(t)?new ge(t):O.near(t)}};function Rf(n){return n.isAtom||n.spec.isolating||n.spec.createGapCursor}function Kb(n){for(let e=n.depth;e>=0;e--){let t=n.index(e),r=n.node(e);if(t==0){if(r.type.spec.isolating)return!0;continue}for(let s=r.child(t-1);;s=s.lastChild){if(s.childCount==0&&!s.inlineContent||Rf(s.type))return!0;if(s.inlineContent)return!1}}return!0}function qb(n){for(let e=n.depth;e>=0;e--){let t=n.indexAfter(e),r=n.node(e);if(t==r.childCount){if(r.type.spec.isolating)return!0;continue}for(let s=r.child(t);;s=s.firstChild){if(s.childCount==0&&!s.inlineContent||Rf(s.type))return!0;if(s.inlineContent)return!1}}return!0}function Of(){return new I({props:{decorations:Qb,createSelectionBetween(n,e,t){return e.pos==t.pos&&ge.valid(t)?new ge(t):null},handleClick:Gb,handleKeyDown:Ub,handleDOMEvents:{beforeinput:Xb}}})}var Ub=rr({ArrowLeft:xi("horiz",-1),ArrowRight:xi("horiz",1),ArrowUp:xi("vert",-1),ArrowDown:xi("vert",1)});function xi(n,e){let t=n=="vert"?e>0?"down":"up":e>0?"right":"left";return function(r,s,i){let o=r.selection,l=e>0?o.$to:o.$from,a=o.empty;if(o instanceof A){if(!i.endOfTextblock(t)||l.depth==0)return!1;a=!1,l=r.doc.resolve(e>0?l.after():l.before())}let c=ge.findGapCursorFrom(l,e,a);return c?(s&&s(r.tr.setSelection(new ge(c))),!0):!1}}function Gb(n,e,t){if(!n||!n.editable)return!1;let r=n.state.doc.resolve(e);if(!ge.valid(r))return!1;let s=n.posAtCoords({left:t.clientX,top:t.clientY});return s&&s.inside>-1&&N.isSelectable(n.state.doc.nodeAt(s.inside))?!1:(n.dispatch(n.state.tr.setSelection(new ge(r))),!0)}function Xb(n,e){if(e.inputType!="insertCompositionText"||!(n.state.selection instanceof ge))return!1;let{$from:t}=n.state.selection,r=t.parent.contentMatchAt(t.index()).findWrapping(n.state.schema.nodes.text);if(!r)return!1;let s=b.empty;for(let o=r.length-1;o>=0;o--)s=b.from(r[o].createAndFill(null,s));let i=n.state.tr.replace(t.pos,t.pos,new v(s,0,0));return i.setSelection(A.near(i.doc.resolve(t.pos+1))),n.dispatch(i),!1}function Qb(n){if(!(n.selection instanceof ge))return null;let e=document.createElement("div");return e.className="ProseMirror-gapcursor",J.create(n.doc,[Z.widget(n.selection.head,e,{key:"gapcursor"})])}var wi=200,ce=function(){};ce.prototype.append=function(e){return e.length?(e=ce.from(e),!this.length&&e||e.length=t?ce.empty:this.sliceInner(Math.max(0,e),Math.min(this.length,t))};ce.prototype.get=function(e){if(!(e<0||e>=this.length))return this.getInner(e)};ce.prototype.forEach=function(e,t,r){t===void 0&&(t=0),r===void 0&&(r=this.length),t<=r?this.forEachInner(e,t,r,0):this.forEachInvertedInner(e,t,r,0)};ce.prototype.map=function(e,t,r){t===void 0&&(t=0),r===void 0&&(r=this.length);var s=[];return this.forEach(function(i,o){return s.push(e(i,o))},t,r),s};ce.from=function(e){return e instanceof ce?e:e&&e.length?new If(e):ce.empty};var If=(function(n){function e(r){n.call(this),this.values=r}n&&(e.__proto__=n),e.prototype=Object.create(n&&n.prototype),e.prototype.constructor=e;var t={length:{configurable:!0},depth:{configurable:!0}};return e.prototype.flatten=function(){return this.values},e.prototype.sliceInner=function(s,i){return s==0&&i==this.length?this:new e(this.values.slice(s,i))},e.prototype.getInner=function(s){return this.values[s]},e.prototype.forEachInner=function(s,i,o,l){for(var a=i;a=o;a--)if(s(this.values[a],l+a)===!1)return!1},e.prototype.leafAppend=function(s){if(this.length+s.length<=wi)return new e(this.values.concat(s.flatten()))},e.prototype.leafPrepend=function(s){if(this.length+s.length<=wi)return new e(s.flatten().concat(this.values))},t.length.get=function(){return this.values.length},t.depth.get=function(){return 0},Object.defineProperties(e.prototype,t),e})(ce);ce.empty=new If([]);var Yb=(function(n){function e(t,r){n.call(this),this.left=t,this.right=r,this.length=t.length+r.length,this.depth=Math.max(t.depth,r.depth)+1}return n&&(e.__proto__=n),e.prototype=Object.create(n&&n.prototype),e.prototype.constructor=e,e.prototype.flatten=function(){return this.left.flatten().concat(this.right.flatten())},e.prototype.getInner=function(r){return rl&&this.right.forEachInner(r,Math.max(s-l,0),Math.min(this.length,i)-l,o+l)===!1)return!1},e.prototype.forEachInvertedInner=function(r,s,i,o){var l=this.left.length;if(s>l&&this.right.forEachInvertedInner(r,s-l,Math.max(i,l)-l,o+l)===!1||i=i?this.right.slice(r-i,s-i):this.left.slice(r,i).append(this.right.slice(0,s-i))},e.prototype.leafAppend=function(r){var s=this.right.leafAppend(r);if(s)return new e(this.left,s)},e.prototype.leafPrepend=function(r){var s=this.left.leafPrepend(r);if(s)return new e(s,this.right)},e.prototype.appendInner=function(r){return this.left.depth>=Math.max(this.right.depth,r.depth)+1?new e(this.left,new e(this.right,r)):new e(this,r)},e})(ce),ka=ce;var Zb=500,en=class n{constructor(e,t){this.items=e,this.eventCount=t}popEvent(e,t){if(this.eventCount==0)return null;let r=this.items.length;for(;;r--)if(this.items.get(r-1).selection){--r;break}let s,i;t&&(s=this.remapping(r,this.items.length),i=s.maps.length);let o=e.tr,l,a,c=[],u=[];return this.items.forEach((d,h)=>{if(!d.step){s||(s=this.remapping(r,h+1),i=s.maps.length),i--,u.push(d);return}if(s){u.push(new Qe(d.map));let f=d.step.map(s.slice(i)),p;f&&o.maybeStep(f).doc&&(p=o.mapping.maps[o.mapping.maps.length-1],c.push(new Qe(p,void 0,void 0,c.length+u.length))),i--,p&&s.appendMap(p,i)}else o.maybeStep(d.step);if(d.selection)return l=s?d.selection.map(s.slice(i)):d.selection,a=new n(this.items.slice(0,r).append(u.reverse().concat(c)),this.eventCount-1),!1},this.items.length,0),{remaining:a,transform:o,selection:l}}addTransform(e,t,r,s){let i=[],o=this.eventCount,l=this.items,a=!s&&l.length?l.get(l.length-1):null;for(let u=0;utx&&(l=ex(l,c),o-=c),new n(l.append(i),o)}remapping(e,t){let r=new _n;return this.items.forEach((s,i)=>{let o=s.mirrorOffset!=null&&i-s.mirrorOffset>=e?r.maps.length-s.mirrorOffset:void 0;r.appendMap(s.map,o)},e,t),r}addMaps(e){return this.eventCount==0?this:new n(this.items.append(e.map(t=>new Qe(t))),this.eventCount)}rebased(e,t){if(!this.eventCount)return this;let r=[],s=Math.max(0,this.items.length-t),i=e.mapping,o=e.steps.length,l=this.eventCount;this.items.forEach(h=>{h.selection&&l--},s);let a=t;this.items.forEach(h=>{let f=i.getMirror(--a);if(f==null)return;o=Math.min(o,f);let p=i.maps[f];if(h.step){let m=e.steps[f].invert(e.docs[f]),g=h.selection&&h.selection.map(i.slice(a+1,f));g&&l++,r.push(new Qe(p,m,g))}else r.push(new Qe(p))},s);let c=[];for(let h=t;hZb&&(d=d.compress(this.items.length-r.length)),d}emptyItemCount(){let e=0;return this.items.forEach(t=>{t.step||e++}),e}compress(e=this.items.length){let t=this.remapping(0,e),r=t.maps.length,s=[],i=0;return this.items.forEach((o,l)=>{if(l>=e)s.push(o),o.selection&&i++;else if(o.step){let a=o.step.map(t.slice(r)),c=a&&a.getMap();if(r--,c&&t.appendMap(c,r),a){let u=o.selection&&o.selection.map(t.slice(r));u&&i++;let d=new Qe(c.invert(),a,u),h,f=s.length-1;(h=s.length&&s[f].merge(d))?s[f]=h:s.push(d)}}else o.map&&r--},this.items.length,0),new n(ka.from(s.reverse()),i)}};en.empty=new en(ka.empty,0);function ex(n,e){let t;return n.forEach((r,s)=>{if(r.selection&&e--==0)return t=s,!1}),n.slice(t)}var Qe=class n{constructor(e,t,r,s){this.map=e,this.step=t,this.selection=r,this.mirrorOffset=s}merge(e){if(this.step&&e.step&&!e.selection){let t=e.step.merge(this.step);if(t)return new n(t.getMap().invert(),t,this.selection)}}},Ye=class{constructor(e,t,r,s,i){this.done=e,this.undone=t,this.prevRanges=r,this.prevTime=s,this.prevComposition=i}},tx=20;function nx(n,e,t,r){let s=t.getMeta(Zt),i;if(s)return s.historyState;t.getMeta(ix)&&(n=new Ye(n.done,n.undone,null,0,-1));let o=t.getMeta("appendedTransaction");if(t.steps.length==0)return n;if(o&&o.getMeta(Zt))return o.getMeta(Zt).redo?new Ye(n.done.addTransform(t,void 0,r,Si(e)),n.undone,Df(t.mapping.maps),n.prevTime,n.prevComposition):new Ye(n.done,n.undone.addTransform(t,void 0,r,Si(e)),null,n.prevTime,n.prevComposition);if(t.getMeta("addToHistory")!==!1&&!(o&&o.getMeta("addToHistory")===!1)){let l=t.getMeta("composition"),a=n.prevTime==0||!o&&n.prevComposition!=l&&(n.prevTime<(t.time||0)-r.newGroupDelay||!rx(t,n.prevRanges)),c=o?ba(n.prevRanges,t.mapping):Df(t.mapping.maps);return new Ye(n.done.addTransform(t,a?e.selection.getBookmark():void 0,r,Si(e)),en.empty,c,t.time,l??n.prevComposition)}else return(i=t.getMeta("rebased"))?new Ye(n.done.rebased(t,i),n.undone.rebased(t,i),ba(n.prevRanges,t.mapping),n.prevTime,n.prevComposition):new Ye(n.done.addMaps(t.mapping.maps),n.undone.addMaps(t.mapping.maps),ba(n.prevRanges,t.mapping),n.prevTime,n.prevComposition)}function rx(n,e){if(!e)return!1;if(!n.docChanged)return!0;let t=!1;return n.mapping.maps[0].forEach((r,s)=>{for(let i=0;i=e[i]&&(t=!0)}),t}function Df(n){let e=[];for(let t=n.length-1;t>=0&&e.length==0;t--)n[t].forEach((r,s,i,o)=>e.push(i,o));return e}function ba(n,e){if(!n)return null;let t=[];for(let r=0;r{let s=Zt.getState(t);if(!s||(n?s.undone:s.done).eventCount==0)return!1;if(r){let i=sx(s,t,n);i&&r(e?i.scrollIntoView():i)}return!0}}var wa=Ci(!1,!0),Sa=Ci(!0,!0),sT=Ci(!1,!1),iT=Ci(!0,!1);var dT=B.create({name:"characterCount",addOptions(){return{limit:null,autoTrim:!0,mode:"textSize",textCounter:n=>n.length,wordCounter:n=>n.split(" ").filter(e=>e!=="").length}},addStorage(){return{characters:()=>0,words:()=>0}},onBeforeCreate(){this.storage.characters=n=>{let e=n?.node||this.editor.state.doc;if((n?.mode||this.options.mode)==="textSize"){let r=e.textBetween(0,e.content.size,void 0," ");return this.options.textCounter(r)}return e.nodeSize},this.storage.words=n=>{let e=n?.node||this.editor.state.doc,t=e.textBetween(0,e.content.size," "," ");return this.options.wordCounter(t)}},addProseMirrorPlugins(){let n=!1;return[new I({key:new L("characterCount"),appendTransaction:(e,t,r)=>{if(n)return;let s=this.options.limit,i=this.options.autoTrim;if(s==null||s===0||i===!1){n=!0;return}let o=this.storage.characters({node:r.doc});if(o>s){let l=o-s,a=0,c=l;console.warn(`[CharacterCount] Initial content exceeded limit of ${s} characters. Content was automatically trimmed.`);let u=r.tr.deleteRange(a,c);return n=!0,u}n=!0},filterTransaction:(e,t)=>{let r=this.options.limit;if(!e.docChanged||r===0||r===null||r===void 0)return!0;let s=this.storage.characters({node:t.doc}),i=this.storage.characters({node:e.doc});if(i<=r||s>r&&i>r&&i<=s)return!0;if(s>r&&i>r&&i>s||!e.getMeta("paste"))return!1;let l=e.selection.$head.pos,a=i-r,c=l-a,u=l;return e.deleteRange(c,u),!(this.storage.characters({node:e.doc})>r)}})]}}),Hf=B.create({name:"dropCursor",addOptions(){return{color:"currentColor",width:1,class:void 0}},addProseMirrorPlugins(){return[Nf(this.options)]}}),yT=B.create({name:"focus",addOptions(){return{className:"has-focus",mode:"all"}},addProseMirrorPlugins(){return[new I({key:new L("focus"),props:{decorations:({doc:n,selection:e})=>{let{isEditable:t,isFocused:r}=this.editor,{anchor:s}=e,i=[];if(!t||!r)return J.create(n,[]);let o=0;this.options.mode==="deepest"&&n.descendants((a,c)=>{if(a.isText)return;if(!(s>=c&&s<=c+a.nodeSize-1))return!1;o+=1});let l=0;return n.descendants((a,c)=>{if(a.isText||!(s>=c&&s<=c+a.nodeSize-1))return!1;if(l+=1,this.options.mode==="deepest"&&o-l>0||this.options.mode==="shallowest"&&l>1)return this.options.mode==="deepest";i.push(Z.node(c,c+a.nodeSize,{class:this.options.className}))}),J.create(n,i)}}})]}}),_f=B.create({name:"gapCursor",addProseMirrorPlugins(){return[Of()]},extendNodeSchema(n){var e;let t={name:n.name,options:n.options,storage:n.storage};return{allowGapCursor:(e=F(E(n,"allowGapCursor",t)))!=null?e:null}}}),Vf="placeholder",Rr=new L("tiptap__placeholder"),zf=200;function Bf(n){let{editor:e,placeholder:t,dataAttribute:r,pos:s,node:i,isEmptyDoc:o,hasAnchor:l,classes:{emptyNode:a,emptyEditor:c}}=n,u=[a];return o&&u.push(c),Z.node(s,s+i.nodeSize,{class:u.join(" "),[r]:typeof t=="function"?t({editor:e,node:i,pos:s,hasAnchor:l}):t})}function $f(n,e){return typeof n=="function"?n(e):n}function ox({editor:n,options:e,dataAttribute:t,doc:r,selection:s}){var i,o;if(!(n.isEditable||!e.showOnlyWhenEditable))return null;let{anchor:a}=s,c=[],u=n.isEmpty;if(e.showOnlyCurrent&&!e.includeChildren){let h=r.resolve(a),f=h.depth>0?h.node(1):h.nodeAfter,p=h.depth>0?h.before(1):a;if(f&&f.type.isTextblock&&vn(f)){let m=a>=p&&a<=p+f.nodeSize;c.push(Bf({editor:n,isEmptyDoc:u,dataAttribute:t,hasAnchor:m,placeholder:e.placeholder,classes:{emptyEditor:e.emptyEditorClass,emptyNode:$f(e.emptyNodeClass,{editor:n,node:f,pos:p,hasAnchor:m})},node:f,pos:p}))}}else{let h=Rr.getState(n.state),f=(i=h?.topPos)!=null?i:0,p=(o=h?.bottomPos)!=null?o:r.content.size;r.nodesBetween(f,p,(m,g)=>{let y=a>=g&&a<=g+m.nodeSize,k=!m.isLeaf&&vn(m);return m.type.isTextblock&&(y||!e.showOnlyCurrent)&&k&&c.push(Bf({editor:n,isEmptyDoc:u,dataAttribute:t,hasAnchor:y,placeholder:e.placeholder,classes:{emptyEditor:e.emptyEditorClass,emptyNode:$f(e.emptyNodeClass,{editor:n,node:m,pos:g,hasAnchor:y})},node:m,pos:g})),e.includeChildren})}return J.create(r,c)}function lx(n){return n.replace(/\s+/g,"-").replace(/[^a-zA-Z0-9-]/g,"").replace(/^[0-9-]+/,"").replace(/^-+/,"").toLowerCase()}function ax(n){let e=getComputedStyle(n),t=`${e.overflow} ${e.overflowY} ${e.overflowX}`;return/auto|scroll|overlay/.test(t)}function cx(n){let e=n;for(;e;){if(ax(e))return e;let t=e.parentElement;if(!t){let r=e.getRootNode();if(r instanceof ShadowRoot){e=r.host;continue}return window}e=t}return window}function ux(n){return n===window?{top:0,bottom:window.innerHeight}:n.getBoundingClientRect()}function dx({doc:n,view:e,scrollContainer:t}){let r=e.dom.getBoundingClientRect(),s=t?ux(t):{top:0,bottom:window.innerHeight},i=Math.max(r.top,s.top)-zf,o=Math.min(r.bottom,s.bottom)+zf;if(i>=o)return{top:0,bottom:n.content.size};let a=getComputedStyle(e.dom).direction==="rtl"?Math.max(r.right-2,r.left+2):r.left+2,c=e.posAtCoords({left:a,top:i+2}),u=e.posAtCoords({left:a,top:o-2});return{top:c?c.pos:0,bottom:u?u.pos:n.content.size}}var hx={init(){return{topPos:null,bottomPos:null}},apply(n,e){let t=n.getMeta(Rr);return t?.positions?{topPos:t.positions.top,bottomPos:t.positions.bottom}:n.docChanged?{topPos:e.topPos!==null?n.mapping.map(e.topPos):null,bottomPos:e.bottomPos!==null?n.mapping.map(e.bottomPos):null}:e}};function fx(n){let e=cx(n.dom),t=()=>{let l=dx({view:n,doc:n.state.doc,scrollContainer:e}),a=Rr.getState(n.state);if(a?.topPos===l.top&&a?.bottomPos===l.bottom)return;let c=n.state.tr.setMeta(Rr,{positions:l});n.dispatch(c)},r=null,s=0,i=150,o=()=>{r===null&&(r=requestAnimationFrame(()=>{r=null;let l=performance.now();l-s>=i?(s=l,t()):o()}))};return e.addEventListener("scroll",o,{passive:!0}),t(),{update(l,a){n.state.doc.content.size!==a.doc.content.size&&o()},destroy:()=>{r!==null&&cancelAnimationFrame(r),e.removeEventListener("scroll",o)}}}function px({editor:n,options:e}){let t=e.dataAttribute?`data-${lx(e.dataAttribute)}`:`data-${Vf}`;return new I({key:Rr,state:hx,view:fx,props:{decorations:({doc:r,selection:s})=>ox({editor:n,options:e,dataAttribute:t,doc:r,selection:s})}})}var Ca=B.create({name:"placeholder",addOptions(){return{emptyEditorClass:"is-editor-empty",emptyNodeClass:"is-empty",dataAttribute:Vf,placeholder:"Write something \u2026",showOnlyWhenEditable:!0,showOnlyCurrent:!0,includeChildren:!1}},addProseMirrorPlugins(){return[px({editor:this.editor,options:this.options})]}}),mx=`.ProseMirror:not(.ProseMirror-focused) *::selection { + background: transparent; +} + +.ProseMirror:not(.ProseMirror-focused) *::-moz-selection { + background: transparent; +}`,NT=B.create({name:"selection",addOptions(){return{className:"selection"}},addProseMirrorPlugins(){let{editor:n,options:e}=this;return n.options.injectCSS&&typeof document<"u"&&xl(mx,n.options.injectNonce,"selection"),[new I({key:new L("selection"),props:{decorations(t){return t.selection.empty||n.isFocused||!n.isEditable||Ss(t.selection)||n.view.dragging?null:J.create(t.doc,[Z.inline(t.selection.from,t.selection.to,{class:e.className})])}}})]}}),gx="skipTrailingNode";function Ff({types:n,node:e}){return e&&Array.isArray(n)&&n.includes(e.type)||e?.type===n}var Wf=B.create({name:"trailingNode",addOptions(){return{node:void 0,notAfter:[]}},addProseMirrorPlugins(){var n;let e=new L(this.name),t=this.options.node||((n=this.editor.schema.topNodeType.contentMatch.defaultType)==null?void 0:n.name)||"paragraph",r=Object.entries(this.editor.schema.nodes).map(([,s])=>s).filter(s=>(this.options.notAfter||[]).concat(t).includes(s.name));return[new I({key:e,appendTransaction:(s,i,o)=>{let{doc:l,tr:a,schema:c}=o,u=e.getState(o),d=l.content.size,h=c.nodes[t];if(!s.some(f=>f.getMeta(gx))&&u)return a.insert(d,h.create())},state:{init:(s,i)=>{let o=i.tr.doc.lastChild;return!Ff({node:o,types:r})},apply:(s,i)=>{if(!s.docChanged||s.getMeta("__uniqueIDTransaction"))return i;let o=s.doc.lastChild;return!Ff({node:o,types:r})}}})]}}),jf=B.create({name:"undoRedo",addOptions(){return{depth:100,newGroupDelay:500}},addCommands(){return{undo:()=>({state:n,dispatch:e})=>wa(n,e),redo:()=>({state:n,dispatch:e})=>Sa(n,e)}},addProseMirrorPlugins(){return[Lf(this.options)]},addKeyboardShortcuts(){return{"Mod-z":()=>this.editor.commands.undo(),"Shift-Mod-z":()=>this.editor.commands.redo(),"Mod-y":()=>this.editor.commands.redo(),"Mod-\u044F":()=>this.editor.commands.undo(),"Shift-Mod-\u044F":()=>this.editor.commands.redo()}}});var yx=B.create({name:"starterKit",addExtensions(){var n,e,t,r;let s=[];return this.options.bold!==!1&&s.push(Kh.configure(this.options.bold)),this.options.blockquote!==!1&&s.push(Jh.configure(this.options.blockquote)),this.options.bulletList!==!1&&s.push(ua.configure(this.options.bulletList)),this.options.code!==!1&&s.push(qh.configure(this.options.code)),this.options.codeBlock!==!1&&s.push(Uh.configure(this.options.codeBlock)),this.options.document!==!1&&s.push(Gh.configure(this.options.document)),this.options.dropcursor!==!1&&s.push(Hf.configure(this.options.dropcursor)),this.options.gapcursor!==!1&&s.push(_f.configure(this.options.gapcursor)),this.options.hardBreak!==!1&&s.push(Xh.configure(this.options.hardBreak)),this.options.heading!==!1&&s.push(Qh.configure(this.options.heading)),this.options.undoRedo!==!1&&s.push(jf.configure(this.options.undoRedo)),this.options.horizontalRule!==!1&&s.push(Yh.configure(this.options.horizontalRule)),this.options.italic!==!1&&s.push(Zh.configure(this.options.italic)),this.options.listItem!==!1&&s.push(da.configure(this.options.listItem)),this.options.listKeymap!==!1&&s.push(fa.configure((n=this.options)==null?void 0:n.listKeymap)),this.options.link!==!1&&s.push(la.configure((e=this.options)==null?void 0:e.link)),this.options.orderedList!==!1&&s.push(pa.configure(this.options.orderedList)),this.options.paragraph!==!1&&s.push(Tf.configure(this.options.paragraph)),this.options.strike!==!1&&s.push(Mf.configure(this.options.strike)),this.options.text!==!1&&s.push(Af.configure(this.options.text)),this.options.underline!==!1&&s.push(Ef.configure((t=this.options)==null?void 0:t.underline)),this.options.trailingNode!==!1&&s.push(Wf.configure((r=this.options)==null?void 0:r.trailingNode)),s}}),kx=yx;var bx=Ca;var xx=/(?:^|\s)(!\[(.+|:?)]\((\S+)(?:(?:\s+)["'](\S+)["'])?\))$/,wx=$.create({name:"image",addOptions(){return{inline:!1,allowBase64:!1,HTMLAttributes:{},resize:!1}},inline(){return this.options.inline},group(){return this.options.inline?"inline":"block"},draggable:!0,addAttributes(){return{src:{default:null},alt:{default:null},title:{default:null},width:{default:null},height:{default:null}}},parseHTML(){return[{tag:this.options.allowBase64?"img[src]":'img[src]:not([src^="data:"])'}]},renderHTML({HTMLAttributes:n}){return["img",D(this.options.HTMLAttributes,n)]},parseMarkdown:(n,e)=>e.createNode("image",{src:n.href,title:n.title,alt:n.text}),renderMarkdown:n=>{var e,t,r,s,i,o;let l=(t=(e=n.attrs)==null?void 0:e.src)!=null?t:"",a=(s=(r=n.attrs)==null?void 0:r.alt)!=null?s:"",c=(o=(i=n.attrs)==null?void 0:i.title)!=null?o:"";return c?`![${a}](${l} "${c}")`:`![${a}](${l})`},addNodeView(){if(!this.options.resize||!this.options.resize.enabled||typeof document>"u")return null;let{directions:n,minWidth:e,minHeight:t,alwaysPreserveAspectRatio:r}=this.options.resize;return({node:s,getPos:i,HTMLAttributes:o,editor:l})=>{let a=document.createElement("img");a.draggable=!1;let c=D(this.options.HTMLAttributes,o);Object.entries(c).forEach(([h,f])=>{if(f!=null)switch(h){case"width":case"height":break;default:a.setAttribute(h,f);break}}),c.src!==null&&(a.src=c.src);let u=new lh({element:a,editor:l,node:s,getPos:i,onResize:(h,f)=>{a.style.width=`${h}px`,a.style.height=`${f}px`},onCommit:(h,f)=>{let p=i();p!==void 0&&this.editor.chain().setNodeSelection(p).updateAttributes(this.name,{width:h,height:f}).run()},onUpdate:(h,f,p)=>h.type===s.type,options:{directions:n,min:{width:e,height:t},preserveAspectRatio:r===!0}}),d=u.dom;return d.style.visibility="hidden",d.style.pointerEvents="none",a.onload=()=>{d.style.visibility="",d.style.pointerEvents=""},u}},addCommands(){return{setImage:n=>({commands:e})=>e.insertContent({type:this.name,attrs:n})}},addInputRules(){return[Ts({find:xx,type:this.type,getAttributes:n=>{let[,,e,t,r]=n;return{src:t,alt:e,title:r}}})]}}),Sx=wx;var Ta,Ma;if(typeof WeakMap<"u"){let n=new WeakMap;Ta=e=>n.get(e),Ma=(e,t)=>(n.set(e,t),t)}else{let n=[],t=0;Ta=r=>{for(let s=0;s(t==10&&(t=0),n[t++]=r,n[t++]=s)}var q=class{constructor(n,e,t,r){this.width=n,this.height=e,this.map=t,this.problems=r}findCell(n){for(let e=0;e=t){(i||(i=[])).push({type:"overlong_rowspan",pos:u,n:y-w});break}let C=s+w*e;for(let x=0;xr&&(i+=c.attrs.colspan)}}for(let o=0;o1&&(t=!0)}e==-1?e=i:e!=i&&(e=Math.max(e,i))}return e}function Tx(n,e,t){n.problems||(n.problems=[]);let r={};for(let s=0;s0;e--)if(n.node(e).type.spec.tableRole=="row")return n.node(0).resolve(n.before(e+1));return null}function Ax(n){for(let e=n.depth;e>0;e--){let t=n.node(e).type.spec.tableRole;if(t==="cell"||t==="header_cell")return n.node(e)}return null}function He(n){let e=n.selection.$head;for(let t=e.depth;t>0;t--)if(e.node(t).type.spec.tableRole=="row")return!0;return!1}function Ni(n){let e=n.selection;if("$anchorCell"in e&&e.$anchorCell)return e.$anchorCell.pos>e.$headCell.pos?e.$anchorCell:e.$headCell;if("node"in e&&e.node&&e.node.type.spec.tableRole=="cell")return e.$anchor;let t=tn(e.$head)||Ex(e.$head);if(t)return t;throw new RangeError(`No cell found around position ${e.head}`)}function Ex(n){for(let e=n.nodeAfter,t=n.pos;e;e=e.firstChild,t++){let r=e.type.spec.tableRole;if(r=="cell"||r=="header_cell")return n.doc.resolve(t)}for(let e=n.nodeBefore,t=n.pos;e;e=e.lastChild,t--){let r=e.type.spec.tableRole;if(r=="cell"||r=="header_cell")return n.doc.resolve(t-e.nodeSize)}}function Aa(n){return n.parent.type.spec.tableRole=="row"&&!!n.nodeAfter}function Nx(n){return n.node(0).resolve(n.pos+n.nodeAfter.nodeSize)}function Ra(n,e){return n.depth==e.depth&&n.pos>=e.start(-1)&&n.pos<=e.end(-1)}function ep(n,e,t){let r=n.node(-1),s=q.get(r),i=n.start(-1),o=s.nextCell(n.pos-i,e,t);return o==null?null:n.node(0).resolve(i+o)}function nn(n,e,t=1){let r={...n,colspan:n.colspan-t};return r.colwidth&&(r.colwidth=r.colwidth.slice(),r.colwidth.splice(e,t),r.colwidth.some(s=>s>0)||(r.colwidth=null)),r}function tp(n,e,t=1){let r={...n,colspan:n.colspan+t};if(r.colwidth){r.colwidth=r.colwidth.slice();for(let s=0;su!=t.pos-i);a.unshift(t.pos-i);let c=a.map(u=>{let d=r.nodeAt(u);if(!d)throw new RangeError(`No cell with offset ${u} found`);let h=i+u+1;return new pn(l.resolve(h),l.resolve(h+d.content.size))});super(c[0].$from,c[0].$to,c),this.$anchorCell=e,this.$headCell=t}map(e,t){let r=e.resolve(t.map(this.$anchorCell.pos)),s=e.resolve(t.map(this.$headCell.pos));if(Aa(r)&&Aa(s)&&Ra(r,s)){let i=this.$anchorCell.node(-1)!=r.node(-1);return i&&this.isRowSelection()?gt.rowSelection(r,s):i&&this.isColSelection()?gt.colSelection(r,s):new gt(r,s)}return A.between(r,s)}content(){let e=this.$anchorCell.node(-1),t=q.get(e),r=this.$anchorCell.start(-1),s=t.rectBetween(this.$anchorCell.pos-r,this.$headCell.pos-r),i={},o=[];for(let a=s.top;a0||g>0){let y=p.attrs;if(m>0&&(y=nn(y,0,m)),g>0&&(y=nn(y,y.colspan-g,g)),f.lefts.bottom){let y={...p.attrs,rowspan:Math.min(f.bottom,s.bottom)-Math.max(f.top,s.top)};f.top0)return!1;let r=e+this.$anchorCell.nodeAfter.attrs.rowspan,s=t+this.$headCell.nodeAfter.attrs.rowspan;return Math.max(r,s)==this.$headCell.node(-1).childCount}static colSelection(e,t=e){let r=e.node(-1),s=q.get(r),i=e.start(-1),o=s.findCell(e.pos-i),l=s.findCell(t.pos-i),a=e.node(0);return o.top<=l.top?(o.top>0&&(e=a.resolve(i+s.map[o.left])),l.bottom0&&(t=a.resolve(i+s.map[l.left])),o.bottom0)return!1;let o=s+this.$anchorCell.nodeAfter.attrs.colspan,l=i+this.$headCell.nodeAfter.attrs.colspan;return Math.max(o,l)==t.width}eq(e){return e instanceof gt&&e.$anchorCell.pos==this.$anchorCell.pos&&e.$headCell.pos==this.$headCell.pos}static rowSelection(e,t=e){let r=e.node(-1),s=q.get(r),i=e.start(-1),o=s.findCell(e.pos-i),l=s.findCell(t.pos-i),a=e.node(0);return o.left<=l.left?(o.left>0&&(e=a.resolve(i+s.map[o.top*s.width])),l.right0&&(t=a.resolve(i+s.map[l.top*s.width])),o.right{e.push(Z.node(r,r+t.nodeSize,{class:"selectedCell"}))}),J.create(n.doc,e)}function Dx({$from:n,$to:e}){if(n.pos==e.pos||n.pos=0&&!(n.after(s+1)=0&&!(e.before(i+1)>e.start(i));i--,r--);return t==r&&/row|table/.test(n.node(s).type.spec.tableRole)}function Px({$from:n,$to:e}){let t,r;for(let s=n.depth;s>0;s--){let i=n.node(s);if(i.type.spec.tableRole==="cell"||i.type.spec.tableRole==="header_cell"){t=i;break}}for(let s=e.depth;s>0;s--){let i=e.node(s);if(i.type.spec.tableRole==="cell"||i.type.spec.tableRole==="header_cell"){r=i;break}}return t!==r&&e.parentOffset===0}function Lx(n,e,t){let r=(e||n).selection,s=(e||n).doc,i,o;if(r instanceof N&&(o=r.node.type.spec.tableRole)){if(o=="cell"||o=="header_cell")i=W.create(s,r.from);else if(o=="row"){let l=s.resolve(r.from+1);i=W.rowSelection(l,l)}else if(!t){let l=q.get(r.node),a=r.from+1,c=a+l.map[l.width*l.height-1];i=W.create(s,a+1,c)}}else r instanceof A&&Dx(r)?i=A.create(s,r.from):r instanceof A&&Px(r)&&(i=A.create(s,r.$from.start(),r.$from.end()));return i&&(e||(e=n.tr)).setSelection(i),e}var zx=new L("fix-tables");function rp(n,e,t,r){let s=n.childCount,i=e.childCount;e:for(let o=0,l=0;o{s.type.spec.tableRole=="table"&&(t=Bx(n,s,i,t))};return e?e.doc!=n.doc&&rp(e.doc,n.doc,0,r):n.doc.descendants(r),t}function Bx(n,e,t,r){let s=q.get(e);if(!s.problems)return r;r||(r=n.tr);let i=[];for(let a=0;a0){let f="cell";u.firstChild&&(f=u.firstChild.type.spec.tableRole);let p=[];for(let g=0;g0?-1:0;Rx(e,r,s+i)&&(i=s==0||s==e.width?null:0);for(let o=0;o0&&s0&&e.map[l-1]==a||s0?-1:0;Fx(e,r,s+l)&&(l=s==0||s==e.height?null:0);for(let c=0,u=e.width*s;c0&&s0&&d==e.map[u-e.width]){let h=t.nodeAt(d).attrs;n.setNodeMarkup(n.mapping.slice(l).map(d+r),null,{...h,rowspan:h.rowspan-1}),c+=h.colspan-1}else if(s0&&t[i]==t[i-1]||r.right0&&t[s]==t[s-n]||r.bottom0){let u=a+1+c.content.size,d=Jf(c)?a+1:u;i.replaceWith(d+r.tableStart,u+r.tableStart,l)}i.setSelection(new W(i.doc.resolve(a+r.tableStart))),e(i)}return!0}function Da(n,e){let t=ye(n.schema);return Vx(({node:r})=>t[r.type.spec.tableRole])(n,e)}function Vx(n){return(e,t)=>{let r=e.selection,s,i;if(r instanceof W){if(r.$anchorCell.pos!=r.$headCell.pos)return!1;s=r.$anchorCell.nodeAfter,i=r.$anchorCell.pos}else{var o;if(s=Ax(r.$from),!s)return!1;i=(o=tn(r.$from))===null||o===void 0?void 0:o.pos}if(s==null||i==null||s.attrs.colspan==1&&s.attrs.rowspan==1)return!1;if(t){let l=s.attrs,a=[],c=l.colwidth;l.rowspan>1&&(l={...l,rowspan:1}),l.colspan>1&&(l={...l,colspan:1});let u=Ze(e),d=e.tr;for(let f=0;f{o.attrs[n]!==e&&i.setNodeMarkup(l,null,{...o.attrs,[n]:e})}):i.setNodeMarkup(s.pos,null,{...s.nodeAfter.attrs,[n]:e}),r(i)}return!0}}function Wx(n){return function(e,t){if(!He(e))return!1;if(t){let r=ye(e.schema),s=Ze(e),i=e.tr,o=s.map.cellsInRect(n=="column"?{left:s.left,top:0,right:s.right,bottom:s.map.height}:n=="row"?{left:0,top:s.top,right:s.map.width,bottom:s.bottom}:s),l=o.map(a=>s.table.nodeAt(a));for(let a=0;a{let f=h+i.tableStart,p=o.doc.nodeAt(f);p&&o.setNodeMarkup(f,d,p.attrs)}),r(o)}return!0}}var cM=In("row",{useDeprecatedLogic:!0}),uM=In("column",{useDeprecatedLogic:!0}),fp=In("cell",{useDeprecatedLogic:!0});function jx(n,e){if(e<0){let t=n.nodeBefore;if(t)return n.pos-t.nodeSize;for(let r=n.index(-1)-1,s=n.before();r>=0;r--){let i=n.node(-1).child(r),o=i.lastChild;if(o)return s-1-o.nodeSize;s-=i.nodeSize}}else{if(n.index()0;r--)if(t.node(r).type.spec.tableRole=="table")return e&&e(n.tr.delete(t.before(r),t.after(r)).scrollIntoView()),!0;return!1}function vi(n,e){let t=n.selection;if(!(t instanceof W))return!1;if(e){let r=n.tr,s=ye(n.schema).cell.createAndFill().content;t.forEachCell((i,o)=>{i.content.eq(s)||r.replace(r.mapping.map(o+1),r.mapping.map(o+i.nodeSize-1),new v(s,0,0))}),r.docChanged&&e(r)}return!0}function Jx(n){if(n.size===0)return null;let{content:e,openStart:t,openEnd:r}=n;for(;e.childCount==1&&(t>0&&r>0||e.child(0).type.spec.tableRole=="table");)t--,r--,e=e.child(0).content;let s=e.child(0),i=s.type.spec.tableRole,o=s.type.schema,l=[];if(i=="row")for(let a=0;a=0;o--){let{rowspan:l,colspan:a}=i.child(o).attrs;for(let c=s;c=e.length&&e.push(b.empty),t[s]r&&(h=h.type.createChecked(nn(h.attrs,h.attrs.colspan,u+h.attrs.colspan-r),h.content)),c.push(h),u+=h.attrs.colspan;for(let f=1;fs&&(d=d.type.create({...d.attrs,rowspan:Math.max(1,s-d.attrs.rowspan)},d.content)),a.push(d)}i.push(b.from(a))}t=i,e=s}return{width:n,height:e,rows:t}}function Ux(n,e,t,r,s,i,o){let l=n.doc.type.schema,a=ye(l),c,u;if(s>e.width)for(let d=0,h=0;de.height){let d=[];for(let p=0,m=(e.height-1)*e.width;p=e.width?!1:t.nodeAt(e.map[m+p]).type==a.header_cell;d.push(g?u||(u=a.header_cell.createAndFill()):c||(c=a.cell.createAndFill()))}let h=a.row.create(null,b.from(d)),f=[];for(let p=e.height;p{if(!s)return!1;let i=t.selection;if(i instanceof W)return Ai(t,r,O.near(i.$headCell,e));if(n!="horiz"&&!i.empty)return!1;let o=mp(s,n,e);if(o==null)return!1;if(n=="horiz")return Ai(t,r,O.near(t.doc.resolve(i.head+e),e));{let l=t.doc.resolve(o),a=ep(l,n,e),c;return a?c=O.near(a,1):e<0?c=O.near(t.doc.resolve(l.before(-1)),-1):c=O.near(t.doc.resolve(l.after(-1)),1),Ai(t,r,c)}}}function Mi(n,e){return(t,r,s)=>{if(!s)return!1;let i=t.selection,o;if(i instanceof W)o=i;else{let a=mp(s,n,e);if(a==null)return!1;o=new W(t.doc.resolve(a))}let l=ep(o.$headCell,n,e);return l?Ai(t,r,new W(o.$anchorCell,l)):!1}}function Xx(n,e){let t=n.state.doc,r=tn(t.resolve(e));return r?(n.dispatch(n.state.tr.setSelection(new W(r))),!0):!1}function Qx(n,e,t){if(!He(n.state))return!1;let r=Jx(t),s=n.state.selection;if(s instanceof W){r||(r={width:1,height:1,rows:[b.from(Ea(ye(n.state.schema).cell,t))]});let i=s.$anchorCell.node(-1),o=s.$anchorCell.start(-1),l=q.get(i).rectBetween(s.$anchorCell.pos-o,s.$headCell.pos-o);return r=qx(r,l.right-l.left,l.bottom-l.top),Gf(n.state,n.dispatch,o,l,r),!0}else if(r){let i=Ni(n.state),o=i.start(-1);return Gf(n.state,n.dispatch,o,q.get(i.node(-1)).findCell(i.pos-o),r),!0}else return!1}function Yx(n,e){var t;if(e.button!=0||e.ctrlKey||e.metaKey)return;let r=Xf(n,e.target),s;if(e.shiftKey&&n.state.selection instanceof W)i(n.state.selection.$anchorCell,e),e.preventDefault();else if(e.shiftKey&&r&&(s=tn(n.state.selection.$anchor))!=null&&((t=va(n,e))===null||t===void 0?void 0:t.pos)!=s.pos)i(s,e),e.preventDefault();else if(!r)return;function i(a,c){let u=va(n,c),d=Nt.getState(n.state)==null;if(!u||!Ra(a,u))if(d)u=a;else return;let h=new W(a,u);if(d||!n.state.selection.eq(h)){let f=n.state.tr.setSelection(h);d&&f.setMeta(Nt,a.pos),n.dispatch(f)}}function o(){n.root.removeEventListener("mouseup",o),n.root.removeEventListener("dragstart",o),n.root.removeEventListener("mousemove",l),Nt.getState(n.state)!=null&&n.dispatch(n.state.tr.setMeta(Nt,-1))}function l(a){let c=a,u=Nt.getState(n.state),d;if(u!=null)d=n.state.doc.resolve(u);else if(Xf(n,c.target)!=r&&(d=va(n,e),!d))return o();d&&i(d,c)}n.root.addEventListener("mouseup",o),n.root.addEventListener("dragstart",o),n.root.addEventListener("mousemove",l)}function mp(n,e,t){if(!(n.state.selection instanceof A))return null;let{$head:r}=n.state.selection;for(let s=r.depth-1;s>=0;s--){let i=r.node(s);if((t<0?r.index(s):r.indexAfter(s))!=(t<0?0:i.childCount))return null;if(i.type.spec.tableRole=="cell"||i.type.spec.tableRole=="header_cell"){let o=r.before(s),l=e=="vert"?t>0?"down":"up":t>0?"right":"left";return n.endOfTextblock(l)?o:null}}return null}function Xf(n,e){for(;e&&e!=n.dom;e=e.parentNode)if(e.nodeName=="TD"||e.nodeName=="TH")return e;return null}function va(n,e){let t=n.posAtCoords({left:e.clientX,top:e.clientY});if(!t)return null;let{inside:r,pos:s}=t;return r>=0&&tn(n.state.doc.resolve(r))||tn(n.state.doc.resolve(s))}var Zx=class{constructor(n,e){this.node=n,this.defaultCellMinWidth=e,this.dom=document.createElement("div"),this.dom.className="tableWrapper",this.table=this.dom.appendChild(document.createElement("table")),this.table.style.setProperty("--default-cell-min-width",`${e}px`),this.colgroup=this.table.appendChild(document.createElement("colgroup")),Na(n,this.colgroup,this.table,e),this.contentDOM=this.table.appendChild(document.createElement("tbody"))}update(n){return n.type!=this.node.type?!1:(this.node=n,Na(n,this.colgroup,this.table,this.defaultCellMinWidth),!0)}ignoreMutation(n){return n.type=="attributes"&&(n.target==this.table||this.colgroup.contains(n.target))}};function Na(n,e,t,r,s,i){let o=0,l=!0,a=e.firstChild,c=n.firstChild;if(c){for(let d=0,h=0;dnew r(d,t,h)),new ew(-1,!1)},apply(o,l){return l.apply(o)}},props:{attributes:o=>{let l=Ne.getState(o);return l&&l.activeHandle>-1?{class:"resize-cursor"}:{}},handleDOMEvents:{mousemove:(o,l)=>{tw(o,l,n,s)},mouseleave:o=>{nw(o)},mousedown:(o,l)=>{rw(o,l,e,t)}},decorations:o=>{let l=Ne.getState(o);if(l&&l.activeHandle>-1)return aw(o,l.activeHandle)},nodeViews:{}}});return i}var ew=class Ei{constructor(e,t){this.activeHandle=e,this.dragging=t}apply(e){let t=this,r=e.getMeta(Ne);if(r&&r.setHandle!=null)return new Ei(r.setHandle,!1);if(r&&r.setDragging!==void 0)return new Ei(t.activeHandle,r.setDragging);if(t.activeHandle>-1&&e.docChanged){let s=e.mapping.map(t.activeHandle,-1);return Aa(e.doc.resolve(s))||(s=-1),new Ei(s,t.dragging)}return t}};function tw(n,e,t,r){if(!n.editable)return;let s=Ne.getState(n.state);if(s&&!s.dragging){let i=iw(e.target),o=-1;if(i){let{left:l,right:a}=i.getBoundingClientRect();e.clientX-l<=t?o=Qf(n,e,"left",t):a-e.clientX<=t&&(o=Qf(n,e,"right",t))}if(o!=s.activeHandle){if(!r&&o!==-1){let l=n.state.doc.resolve(o),a=l.node(-1),c=q.get(a),u=l.start(-1);if(c.colCount(l.pos-u)+l.nodeAfter.attrs.colspan-1==c.width-1)return}yp(n,o)}}}function nw(n){if(!n.editable)return;let e=Ne.getState(n.state);e&&e.activeHandle>-1&&!e.dragging&&yp(n,-1)}function rw(n,e,t,r){var s;if(!n.editable)return!1;let i=(s=n.dom.ownerDocument.defaultView)!==null&&s!==void 0?s:window,o=Ne.getState(n.state);if(!o||o.activeHandle==-1||o.dragging)return!1;let l=n.state.doc.nodeAt(o.activeHandle),a=sw(n,o.activeHandle,l.attrs);n.dispatch(n.state.tr.setMeta(Ne,{setDragging:{startX:e.clientX,startWidth:a}}));function c(d){i.removeEventListener("mouseup",c),i.removeEventListener("mousemove",u);let h=Ne.getState(n.state);h?.dragging&&(ow(n,h.activeHandle,Yf(h.dragging,d,t)),n.dispatch(n.state.tr.setMeta(Ne,{setDragging:null})))}function u(d){if(!d.which)return c(d);let h=Ne.getState(n.state);if(h&&h.dragging){let f=Yf(h.dragging,d,t);Zf(n,h.activeHandle,f,r)}}return Zf(n,o.activeHandle,a,r),i.addEventListener("mouseup",c),i.addEventListener("mousemove",u),e.preventDefault(),!0}function sw(n,e,{colspan:t,colwidth:r}){let s=r&&r[r.length-1];if(s)return s;let i=n.domAtPos(e),o=i.node.childNodes[i.offset].offsetWidth,l=t;if(r)for(let a=0;acw(n),renderHTML:n=>n.align?{style:`text-align: ${n.align}`}:{}}}var Ii=$.create({name:"tableCell",addOptions(){return{HTMLAttributes:{}}},content:"block+",addAttributes(){return{colspan:{default:1},rowspan:{default:1},colwidth:{default:null,parseHTML:n=>{var e,t;let r=n.getAttribute("colwidth"),s=r?r.split(",").map(i=>parseInt(i,10)):null;if(!s){let i=(e=n.closest("table"))==null?void 0:e.querySelectorAll("colgroup > col"),o=Array.from(((t=n.parentElement)==null?void 0:t.children)||[]).indexOf(n);if(o&&o>-1&&i&&i[o]){let l=i[o].getAttribute("width");return l?[parseInt(l,10)]:null}}return s}},align:wp()}},tableRole:"cell",isolating:!0,parseHTML(){return[{tag:"td"}]},renderHTML({HTMLAttributes:n}){return["td",D(this.options.HTMLAttributes,n),0]}}),Di=$.create({name:"tableHeader",addOptions(){return{HTMLAttributes:{}}},content:"block+",addAttributes(){return{colspan:{default:1},rowspan:{default:1},colwidth:{default:null,parseHTML:n=>{let e=n.getAttribute("colwidth");return e?e.split(",").map(r=>parseInt(r,10)):null}},align:wp()}},tableRole:"header_cell",isolating:!0,parseHTML(){return[{tag:"th"}]},renderHTML({HTMLAttributes:n}){return["th",D(this.options.HTMLAttributes,n),0]}}),Pi=$.create({name:"tableRow",addOptions(){return{HTMLAttributes:{}}},content:"(tableCell | tableHeader)*",tableRole:"row",parseHTML(){return[{tag:"tr"}]},renderHTML({HTMLAttributes:n}){return["tr",D(this.options.HTMLAttributes,n),0]}});function La(n,e){return e?["width",`${Math.max(e,n)}px`]:["min-width",`${n}px`]}function bp(n,e,t,r,s,i){var o;let l=0,a=!0,c=e.firstChild,u=n.firstChild;if(u!==null)for(let h=0,f=0;h{let r=n.nodes[t];r.spec.tableRole&&(e[r.spec.tableRole]=r)}),n.cached.tableNodeTypes=e,e}function pw(n,e,t,r,s){let i=fw(n),o=[],l=[];for(let c=0;c{let{selection:e}=n.state;if(!mw(e))return!1;let t=0,r=fl(e.ranges[0].$from,i=>i.type.name==="table");return r?.node.descendants(i=>{if(i.type.name==="table")return!1;["tableCell","tableHeader"].includes(i.type.name)&&(t+=1)}),t===e.ranges.length?(n.commands.deleteTable(),!0):!1},gw="";function yw(n){return(n||"").replace(/\s+/g," ").trim()}function kw(n,e,t={}){var r;let s=(r=t.cellLineSeparator)!=null?r:gw;if(!n||!n.content||n.content.length===0)return"";let i=[];n.content.forEach(m=>{let g=[];m.content&&m.content.forEach(y=>{let k="";y.content&&Array.isArray(y.content)&&y.content.length>1?k=y.content.map(T=>e.renderChildren(T)).join(s):k=y.content?e.renderChildren(y.content):"";let w=yw(k),C=y.type==="tableHeader",x=uw(y.attrs);g.push({text:w,isHeader:C,align:x})}),i.push(g)});let o=i.reduce((m,g)=>Math.max(m,g.length),0);if(o===0)return"";let l=Array.from({length:o}).fill(0);i.forEach(m=>{var g;for(let y=0;yl[y]&&(l[y]=w),l[y]<3&&(l[y]=3)}});let a=(m,g)=>m+" ".repeat(Math.max(0,g-m.length)),c=i[0],u=c.some(m=>m.isHeader),d=Array.from({length:o}).fill(null);i.forEach(m=>{var g;for(let y=0;yu&&c[g]&&c[g].text||"");return h+=`| ${f.map((m,g)=>a(m,l[g])).join(" | ")} | +`,h+=`| ${l.map((m,g)=>{let y=Math.max(3,m),k=d[g];return k==="left"?`:${"-".repeat(y)}`:k==="right"?`${"-".repeat(y)}:`:k==="center"?`:${"-".repeat(y)}:`:"-".repeat(y)}).join(" | ")} | +`,(u?i.slice(1):i).forEach(m=>{h+=`| ${Array.from({length:o}).fill(0).map((g,y)=>a(m[y]&&m[y].text||"",l[y])).join(" | ")} | +`}),h}var bw=kw,Sp=$.create({name:"table",addOptions(){return{HTMLAttributes:{},resizable:!1,renderWrapper:!1,handleWidth:5,cellMinWidth:25,View:dw,lastColumnResizable:!0,allowTableNodeSelection:!1}},content:"tableRow+",tableRole:"table",isolating:!0,group:"block",parseHTML(){return[{tag:"table"}]},renderHTML({node:n,HTMLAttributes:e}){let{colgroup:t,tableWidth:r,tableMinWidth:s}=hw(n,this.options.cellMinWidth),i=e.style;function o(){return i||(r?`width: ${r}`:`min-width: ${s}`)}let l=["table",D(this.options.HTMLAttributes,e,{style:o()}),t,["tbody",0]];return this.options.renderWrapper?["div",{class:"tableWrapper"},l]:l},parseMarkdown:(n,e)=>{let t=[],r=Array.isArray(n.align)?n.align:[];if(n.header){let s=[];n.header.forEach((i,o)=>{var l;let a=Oi((l=r[o])!=null?l:i.align),c=a?{align:a}:{};s.push(e.createNode("tableHeader",c,[{type:"paragraph",content:e.parseInline(i.tokens)}]))}),t.push(e.createNode("tableRow",{},s))}return n.rows&&n.rows.forEach(s=>{let i=[];s.forEach((o,l)=>{var a;let c=Oi((a=r[l])!=null?a:o.align),u=c?{align:c}:{};i.push(e.createNode("tableCell",u,[{type:"paragraph",content:e.parseInline(o.tokens)}]))}),t.push(e.createNode("tableRow",{},i))}),e.createNode("table",void 0,t)},renderMarkdown:(n,e)=>bw(n,e),addCommands(){return{insertTable:({rows:n=3,cols:e=3,withHeaderRow:t=!0}={})=>({tr:r,dispatch:s,editor:i})=>{let o=pw(i.schema,n,e,t);if(s){let l=r.selection.from+1;r.replaceSelectionWith(o).scrollIntoView().setSelection(A.near(r.doc.resolve(l)))}return!0},addColumnBefore:()=>({state:n,dispatch:e})=>ip(n,e),addColumnAfter:()=>({state:n,dispatch:e})=>op(n,e),deleteColumn:()=>({state:n,dispatch:e})=>lp(n,e),addRowBefore:()=>({state:n,dispatch:e})=>cp(n,e),addRowAfter:()=>({state:n,dispatch:e})=>up(n,e),deleteRow:()=>({state:n,dispatch:e})=>dp(n,e),deleteTable:()=>({state:n,dispatch:e})=>pp(n,e),mergeCells:()=>({state:n,dispatch:e})=>Ia(n,e),splitCell:()=>({state:n,dispatch:e})=>Da(n,e),toggleHeaderColumn:()=>({state:n,dispatch:e})=>In("column")(n,e),toggleHeaderRow:()=>({state:n,dispatch:e})=>In("row")(n,e),toggleHeaderCell:()=>({state:n,dispatch:e})=>fp(n,e),mergeOrSplit:()=>({state:n,dispatch:e})=>Ia(n,e)?!0:Da(n,e),setCellAttribute:(n,e)=>({state:t,dispatch:r})=>hp(n,e)(t,r),goToNextCell:()=>({state:n,dispatch:e})=>Pa(1)(n,e),goToPreviousCell:()=>({state:n,dispatch:e})=>Pa(-1)(n,e),fixTables:()=>({state:n,dispatch:e})=>(e&&Oa(n),!0),setCellSelection:n=>({tr:e,dispatch:t})=>{if(t){let r=W.create(e.doc,n.anchorCell,n.headCell);e.setSelection(r)}return!0}}},addKeyboardShortcuts(){return{Tab:()=>this.editor.commands.goToNextCell()?!0:this.editor.can().addRowAfter()?this.editor.chain().addRowAfter().goToNextCell().run():!1,"Shift-Tab":()=>this.editor.commands.goToPreviousCell(),Backspace:Ri,"Mod-Backspace":Ri,Delete:Ri,"Mod-Delete":Ri}},addProseMirrorPlugins(){return[...this.options.resizable&&this.editor.isEditable?[gp({handleWidth:this.options.handleWidth,cellMinWidth:this.options.cellMinWidth,defaultCellMinWidth:this.options.cellMinWidth,View:this.options.View,lastColumnResizable:this.options.lastColumnResizable})]:[],kp({allowTableNodeSelection:this.options.allowTableNodeSelection})]},addNodeView(){let n=this.options.resizable&&this.editor.isEditable,e=this.options.View;return n||!e?null:({node:t,view:r,HTMLAttributes:s})=>{let i=D(this.options.HTMLAttributes,s);return new e(t,this.options.cellMinWidth,r,i)}},extendNodeSchema(n){let e={name:n.name,options:n.options,storage:n.storage};return{tableRole:F(E(n,"tableRole",e))}}}),TM=B.create({name:"tableKit",addExtensions(){let n=[];return this.options.table!==!1&&n.push(Sp.configure(this.options.table)),this.options.tableCell!==!1&&n.push(Ii.configure(this.options.tableCell)),this.options.tableHeader!==!1&&n.push(Di.configure(this.options.tableHeader)),this.options.tableRow!==!1&&n.push(Pi.configure(this.options.tableRow)),n}});var xw=Pi;var ww=Ii;var Sw=Di;var Cw=ki;var vw=yi;function Fa(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}var on=Fa();function Np(n){on=n}var rn={exec:()=>null};function H(n,e=""){let t=typeof n=="string"?n:n.source,r={replace:(s,i)=>{let o=typeof i=="string"?i:i.source;return o=o.replace(Ce.caret,"$1"),t=t.replace(s,o),r},getRegex:()=>new RegExp(t,e)};return r}var Tw=(()=>{try{return!!new RegExp("(?<=1)(?/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] +\S/,listReplaceTask:/^\[[ xX]\] +/,listTaskCheckbox:/\[[ xX]\]/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:n=>new RegExp(`^( {0,3}${n})((?:[ ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:n=>new RegExp(`^ {0,${Math.min(3,n-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`),hrRegex:n=>new RegExp(`^ {0,${Math.min(3,n-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:n=>new RegExp(`^ {0,${Math.min(3,n-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:n=>new RegExp(`^ {0,${Math.min(3,n-1)}}#`),htmlBeginRegex:n=>new RegExp(`^ {0,${Math.min(3,n-1)}}<(?:[a-z].*>|!--)`,"i"),blockquoteBeginRegex:n=>new RegExp(`^ {0,${Math.min(3,n-1)}}>`)},Mw=/^(?:[ \t]*(?:\n|$))+/,Aw=/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,Ew=/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,Pr=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,Nw=/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,Ha=/ {0,3}(?:[*+-]|\d{1,9}[.)])/,Rp=/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,Op=H(Rp).replace(/bull/g,Ha).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/\|table/g,"").getRegex(),Rw=H(Rp).replace(/bull/g,Ha).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/table/g,/ {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(),_a=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,Ow=/^[^\n]+/,Va=/(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/,Iw=H(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",Va).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),Dw=H(/^(bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,Ha).getRegex(),Hi="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",Wa=/|$))/,Pw=H("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))","i").replace("comment",Wa).replace("tag",Hi).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),Ip=H(_a).replace("hr",Pr).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",Hi).getRegex(),Lw=H(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",Ip).getRegex(),ja={blockquote:Lw,code:Aw,def:Iw,fences:Ew,heading:Nw,hr:Pr,html:Pw,lheading:Op,list:Dw,newline:Mw,paragraph:Ip,table:rn,text:Ow},Cp=H("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",Pr).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code","(?: {4}| {0,3} )[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",Hi).getRegex(),zw={...ja,lheading:Rw,table:Cp,paragraph:H(_a).replace("hr",Pr).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",Cp).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",Hi).getRegex()},Bw={...ja,html:H(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment",Wa).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:rn,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:H(_a).replace("hr",Pr).replace("heading",` *#{1,6} *[^ +]`).replace("lheading",Op).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},$w=/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,Fw=/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,Dp=/^( {2,}|\\)\n(?!\s*$)/,Hw=/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\`+)[^`]+\k(?!`))*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)/).replace("precode-",Tw?"(?`+)[^`]+\k(?!`)/).replace("html",/<(?! )[^<>]*?>/).getRegex(),Lp=/^(?:\*+(?:((?!\*)punct)|([^\s*]))?)|^_+(?:((?!_)punct)|([^\s_]))?/,Jw=H(Lp,"u").replace(/punct/g,Dn).getRegex(),Kw=H(Lp,"u").replace(/punct/g,Pp).getRegex(),zp="^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)",qw=H(zp,"gu").replace(/notPunctSpace/g,Ja).replace(/punctSpace/g,_i).replace(/punct/g,Dn).getRegex(),Uw=H(zp,"gu").replace(/notPunctSpace/g,Ww).replace(/punctSpace/g,Vw).replace(/punct/g,Pp).getRegex(),Gw=H("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,Ja).replace(/punctSpace/g,_i).replace(/punct/g,Dn).getRegex(),Xw=H(/^~~?(?:((?!~)punct)|[^\s~])/,"u").replace(/punct/g,Dn).getRegex(),Qw="^[^~]+(?=[^~])|(?!~)punct(~~?)(?=[\\s]|$)|notPunctSpace(~~?)(?!~)(?=punctSpace|$)|(?!~)punctSpace(~~?)(?=notPunctSpace)|[\\s](~~?)(?!~)(?=punct)|(?!~)punct(~~?)(?!~)(?=punct)|notPunctSpace(~~?)(?=notPunctSpace)",Yw=H(Qw,"gu").replace(/notPunctSpace/g,Ja).replace(/punctSpace/g,_i).replace(/punct/g,Dn).getRegex(),Zw=H(/\\(punct)/,"gu").replace(/punct/g,Dn).getRegex(),e1=H(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),t1=H(Wa).replace("(?:-->|$)","-->").getRegex(),n1=H("^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment",t1).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),Bi=/(?:\[(?:\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`+(?!`)[^`]*?`+(?!`)|``+(?=\])|[^\[\]\\`])*?/,r1=H(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]+(?:\n[ \t]*)?|\n[ \t]*)(title))?\s*\)/).replace("label",Bi).replace("href",/<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),Bp=H(/^!?\[(label)\]\[(ref)\]/).replace("label",Bi).replace("ref",Va).getRegex(),$p=H(/^!?\[(ref)\](?:\[\])?/).replace("ref",Va).getRegex(),s1=H("reflink|nolink(?!\\()","g").replace("reflink",Bp).replace("nolink",$p).getRegex(),vp=/[hH][tT][tT][pP][sS]?|[fF][tT][pP]/,Ka={_backpedal:rn,anyPunctuation:Zw,autolink:e1,blockSkip:jw,br:Dp,code:Fw,del:rn,delLDelim:rn,delRDelim:rn,emStrongLDelim:Jw,emStrongRDelimAst:qw,emStrongRDelimUnd:Gw,escape:$w,link:r1,nolink:$p,punctuation:_w,reflink:Bp,reflinkSearch:s1,tag:n1,text:Hw,url:rn},i1={...Ka,link:H(/^!?\[(label)\]\((.*?)\)/).replace("label",Bi).getRegex(),reflink:H(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",Bi).getRegex()},za={...Ka,emStrongRDelimAst:Uw,emStrongLDelim:Kw,delLDelim:Xw,delRDelim:Yw,url:H(/^((?:protocol):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/).replace("protocol",vp).replace("email",/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(),_backpedal:/(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/,text:H(/^([`~]+|[^`~])(?:(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\":">",'"':""","'":"'"},Tp=n=>l1[n];function et(n,e){if(e){if(Ce.escapeTest.test(n))return n.replace(Ce.escapeReplace,Tp)}else if(Ce.escapeTestNoEncode.test(n))return n.replace(Ce.escapeReplaceNoEncode,Tp);return n}function Mp(n){try{n=encodeURI(n).replace(Ce.percentDecode,"%")}catch{return null}return n}function Ap(n,e){let t=n.replace(Ce.findPipe,(i,o,l)=>{let a=!1,c=o;for(;--c>=0&&l[c]==="\\";)a=!a;return a?"|":" |"}),r=t.split(Ce.splitPipe),s=0;if(r[0].trim()||r.shift(),r.length>0&&!r.at(-1)?.trim()&&r.pop(),e)if(r.length>e)r.splice(e);else for(;r.length0?-2:-1}function c1(n,e=0){let t=e,r="";for(let s of n)if(s===" "){let i=4-t%4;r+=" ".repeat(i),t+=i}else r+=s,t++;return r}function Ep(n,e,t,r,s){let i=e.href,o=e.title||null,l=n[1].replace(s.other.outputLinkReplace,"$1");r.state.inLink=!0;let a={type:n[0].charAt(0)==="!"?"image":"link",raw:t,href:i,title:o,text:l,tokens:r.inlineTokens(l)};return r.state.inLink=!1,a}function u1(n,e,t){let r=n.match(t.other.indentCodeCompensation);if(r===null)return e;let s=r[1];return e.split(` +`).map(i=>{let o=i.match(t.other.beginningSpace);if(o===null)return i;let[l]=o;return l.length>=s.length?i.slice(s.length):i}).join(` +`)}var $i=class{constructor(n){j(this,"options");j(this,"rules");j(this,"lexer");this.options=n||on}space(n){let e=this.rules.block.newline.exec(n);if(e&&e[0].length>0)return{type:"space",raw:e[0]}}code(n){let e=this.rules.block.code.exec(n);if(e){let t=e[0].replace(this.rules.other.codeRemoveIndent,"");return{type:"code",raw:e[0],codeBlockStyle:"indented",text:this.options.pedantic?t:Ir(t,` +`)}}}fences(n){let e=this.rules.block.fences.exec(n);if(e){let t=e[0],r=u1(t,e[3]||"",this.rules);return{type:"code",raw:t,lang:e[2]?e[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):e[2],text:r}}}heading(n){let e=this.rules.block.heading.exec(n);if(e){let t=e[2].trim();if(this.rules.other.endingHash.test(t)){let r=Ir(t,"#");(this.options.pedantic||!r||this.rules.other.endingSpaceChar.test(r))&&(t=r.trim())}return{type:"heading",raw:e[0],depth:e[1].length,text:t,tokens:this.lexer.inline(t)}}}hr(n){let e=this.rules.block.hr.exec(n);if(e)return{type:"hr",raw:Ir(e[0],` +`)}}blockquote(n){let e=this.rules.block.blockquote.exec(n);if(e){let t=Ir(e[0],` `).split(` -`),r="",s="",i=[];for(;n.length>0;){let o=!1,l=[],a;for(a=0;a0;){let o=!1,l=[],a;for(a=0;a1,s={type:"list",raw:"",ordered:r,start:r?+n.slice(0,-1):"",loose:!1,items:[]};n=r?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=r?n:"[*+-]");let i=this.rules.other.listItemRegex(n),o=!1;for(;t;){let a=!1,c="",u="";if(!(e=i.exec(t))||this.rules.block.hr.test(t))break;c=e[0],t=t.substring(c.length);let d=Ob(e[2].split(` -`,1)[0],e[1].length),h=t.split(` +`+t.join(` +`),m=this.list(p);i[i.length-1]=m,r=r.substring(0,r.length-h.raw.length)+m.raw,s=s.substring(0,s.length-f.raw.length)+m.raw,t=p.substring(i.at(-1).raw.length).split(` +`);continue}}return{type:"blockquote",raw:r,tokens:i,text:s}}}list(n){let e=this.rules.block.list.exec(n);if(e){let t=e[1].trim(),r=t.length>1,s={type:"list",raw:"",ordered:r,start:r?+t.slice(0,-1):"",loose:!1,items:[]};t=r?`\\d{1,9}\\${t.slice(-1)}`:`\\${t}`,this.options.pedantic&&(t=r?t:"[*+-]");let i=this.rules.other.listItemRegex(t),o=!1;for(;n;){let a=!1,c="",u="";if(!(e=i.exec(n))||this.rules.block.hr.test(n))break;c=e[0],n=n.substring(c.length);let d=c1(e[2].split(` +`,1)[0],e[1].length),h=n.split(` `,1)[0],f=!d.trim(),p=0;if(this.options.pedantic?(p=2,u=d.trimStart()):f?p=e[1].length+1:(p=d.search(this.rules.other.nonSpaceChar),p=p>4?1:p,u=d.slice(p),p+=e[1].length),f&&this.rules.other.blankLine.test(h)&&(c+=h+` -`,t=t.substring(h.length+1),a=!0),!a){let m=this.rules.other.nextBulletRegex(p),g=this.rules.other.hrRegex(p),y=this.rules.other.fencesBeginRegex(p),k=this.rules.other.headingBeginRegex(p),w=this.rules.other.htmlBeginRegex(p),M=this.rules.other.blockquoteBeginRegex(p);for(;t;){let S=t.split(` -`,1)[0],N;if(h=S,this.options.pedantic?(h=h.replace(this.rules.other.listReplaceNesting," "),N=h):N=h.replace(this.rules.other.tabCharGlobal," "),y.test(h)||k.test(h)||w.test(h)||M.test(h)||m.test(h)||g.test(h))break;if(N.search(this.rules.other.nonSpaceChar)>=p||!h.trim())u+=` -`+N.slice(p);else{if(f||d.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)>=4||y.test(d)||k.test(d)||g.test(d))break;u+=` -`+h}f=!h.trim(),c+=S+` -`,t=t.substring(S.length+1),d=N.slice(p)}}s.loose||(o?s.loose=!0:this.rules.other.doubleBlankLine.test(c)&&(o=!0)),s.items.push({type:"list_item",raw:c,task:!!this.options.gfm&&this.rules.other.listIsTask.test(u),loose:!1,text:u,tokens:[]}),s.raw+=c}let l=s.items.at(-1);if(l)l.raw=l.raw.trimEnd(),l.text=l.text.trimEnd();else return;s.raw=s.raw.trimEnd();for(let a of s.items){if(this.lexer.state.top=!1,a.tokens=this.lexer.blockTokens(a.text,[]),a.task){if(a.text=a.text.replace(this.rules.other.listReplaceTask,""),a.tokens[0]?.type==="text"||a.tokens[0]?.type==="paragraph"){a.tokens[0].raw=a.tokens[0].raw.replace(this.rules.other.listReplaceTask,""),a.tokens[0].text=a.tokens[0].text.replace(this.rules.other.listReplaceTask,"");for(let u=this.lexer.inlineQueue.length-1;u>=0;u--)if(this.rules.other.listIsTask.test(this.lexer.inlineQueue[u].src)){this.lexer.inlineQueue[u].src=this.lexer.inlineQueue[u].src.replace(this.rules.other.listReplaceTask,"");break}}let c=this.rules.other.listTaskCheckbox.exec(a.raw);if(c){let u={type:"checkbox",raw:c[0]+" ",checked:c[0]!=="[ ]"};a.checked=u.checked,s.loose?a.tokens[0]&&["paragraph","text"].includes(a.tokens[0].type)&&"tokens"in a.tokens[0]&&a.tokens[0].tokens?(a.tokens[0].raw=u.raw+a.tokens[0].raw,a.tokens[0].text=u.raw+a.tokens[0].text,a.tokens[0].tokens.unshift(u)):a.tokens.unshift({type:"paragraph",raw:u.raw,text:u.raw,tokens:[u]}):a.tokens.unshift(u)}}if(!s.loose){let c=a.tokens.filter(d=>d.type==="space"),u=c.length>0&&c.some(d=>this.rules.other.anyLine.test(d.raw));s.loose=u}}if(s.loose)for(let a of s.items){a.loose=!0;for(let c of a.tokens)c.type==="text"&&(c.type="paragraph")}return s}}html(t){let e=this.rules.block.html.exec(t);if(e)return{type:"html",block:!0,raw:e[0],pre:e[1]==="pre"||e[1]==="script"||e[1]==="style",text:e[0]}}def(t){let e=this.rules.block.def.exec(t);if(e){let n=e[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal," "),r=e[2]?e[2].replace(this.rules.other.hrefBrackets,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",s=e[3]?e[3].substring(1,e[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"):e[3];return{type:"def",tag:n,raw:e[0],href:r,title:s}}}table(t){let e=this.rules.block.table.exec(t);if(!e||!this.rules.other.tableDelimiter.test(e[2]))return;let n=Bh(e[1]),r=e[2].replace(this.rules.other.tableAlignChars,"").split("|"),s=e[3]?.trim()?e[3].replace(this.rules.other.tableRowBlankLine,"").split(` -`):[],i={type:"table",raw:e[0],header:[],align:[],rows:[]};if(n.length===r.length){for(let o of r)this.rules.other.tableAlignRight.test(o)?i.align.push("right"):this.rules.other.tableAlignCenter.test(o)?i.align.push("center"):this.rules.other.tableAlignLeft.test(o)?i.align.push("left"):i.align.push(null);for(let o=0;o({text:l,tokens:this.lexer.inline(l),header:!1,align:i.align[a]})));return i}}lheading(t){let e=this.rules.block.lheading.exec(t);if(e)return{type:"heading",raw:e[0],depth:e[2].charAt(0)==="="?1:2,text:e[1],tokens:this.lexer.inline(e[1])}}paragraph(t){let e=this.rules.block.paragraph.exec(t);if(e){let n=e[1].charAt(e[1].length-1)===` -`?e[1].slice(0,-1):e[1];return{type:"paragraph",raw:e[0],text:n,tokens:this.lexer.inline(n)}}}text(t){let e=this.rules.block.text.exec(t);if(e)return{type:"text",raw:e[0],text:e[0],tokens:this.lexer.inline(e[0])}}escape(t){let e=this.rules.inline.escape.exec(t);if(e)return{type:"escape",raw:e[0],text:e[1]}}tag(t){let e=this.rules.inline.tag.exec(t);if(e)return!this.lexer.state.inLink&&this.rules.other.startATag.test(e[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(e[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(e[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(e[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:e[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:e[0]}}link(t){let e=this.rules.inline.link.exec(t);if(e){let n=e[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(n)){if(!this.rules.other.endAngleBracket.test(n))return;let i=ir(n.slice(0,-1),"\\");if((n.length-i.length)%2===0)return}else{let i=Rb(e[2],"()");if(i===-2)return;if(i>-1){let o=(e[0].indexOf("!")===0?5:4)+e[1].length+i;e[2]=e[2].substring(0,i),e[0]=e[0].substring(0,o).trim(),e[3]=""}}let r=e[2],s="";if(this.options.pedantic){let i=this.rules.other.pedanticHrefTitle.exec(r);i&&(r=i[1],s=i[3])}else s=e[3]?e[3].slice(1,-1):"";return r=r.trim(),this.rules.other.startAngleBracket.test(r)&&(this.options.pedantic&&!this.rules.other.endAngleBracket.test(n)?r=r.slice(1):r=r.slice(1,-1)),$h(e,{href:r&&r.replace(this.rules.inline.anyPunctuation,"$1"),title:s&&s.replace(this.rules.inline.anyPunctuation,"$1")},e[0],this.lexer,this.rules)}}reflink(t,e){let n;if((n=this.rules.inline.reflink.exec(t))||(n=this.rules.inline.nolink.exec(t))){let r=(n[2]||n[1]).replace(this.rules.other.multipleSpaceGlobal," "),s=e[r.toLowerCase()];if(!s){let i=n[0].charAt(0);return{type:"text",raw:i,text:i}}return $h(n,s,n[0],this.lexer,this.rules)}}emStrong(t,e,n=""){let r=this.rules.inline.emStrongLDelim.exec(t);if(!(!r||r[3]&&n.match(this.rules.other.unicodeAlphaNumeric))&&(!(r[1]||r[2])||!n||this.rules.inline.punctuation.exec(n))){let s=[...r[0]].length-1,i,o,l=s,a=0,c=r[0][0]==="*"?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(c.lastIndex=0,e=e.slice(-1*t.length+s);(r=c.exec(e))!=null;){if(i=r[1]||r[2]||r[3]||r[4]||r[5]||r[6],!i)continue;if(o=[...i].length,r[3]||r[4]){l+=o;continue}else if((r[5]||r[6])&&s%3&&!((s+o)%3)){a+=o;continue}if(l-=o,l>0)continue;o=Math.min(o,o+l+a);let u=[...r[0]][0].length,d=t.slice(0,s+r.index+u+o);if(Math.min(s,o)%2){let f=d.slice(1,-1);return{type:"em",raw:d,text:f,tokens:this.lexer.inlineTokens(f)}}let h=d.slice(2,-2);return{type:"strong",raw:d,text:h,tokens:this.lexer.inlineTokens(h)}}}}codespan(t){let e=this.rules.inline.code.exec(t);if(e){let n=e[2].replace(this.rules.other.newLineCharGlobal," "),r=this.rules.other.nonSpaceChar.test(n),s=this.rules.other.startingSpaceChar.test(n)&&this.rules.other.endingSpaceChar.test(n);return r&&s&&(n=n.substring(1,n.length-1)),{type:"codespan",raw:e[0],text:n}}}br(t){let e=this.rules.inline.br.exec(t);if(e)return{type:"br",raw:e[0]}}del(t,e,n=""){let r=this.rules.inline.delLDelim.exec(t);if(r&&(!r[1]||!n||this.rules.inline.punctuation.exec(n))){let s=[...r[0]].length-1,i,o,l=s,a=this.rules.inline.delRDelim;for(a.lastIndex=0,e=e.slice(-1*t.length+s);(r=a.exec(e))!=null;){if(i=r[1]||r[2]||r[3]||r[4]||r[5]||r[6],!i||(o=[...i].length,o!==s))continue;if(r[3]||r[4]){l+=o;continue}if(l-=o,l>0)continue;o=Math.min(o,o+l);let c=[...r[0]][0].length,u=t.slice(0,s+r.index+c+o),d=u.slice(s,-s);return{type:"del",raw:u,text:d,tokens:this.lexer.inlineTokens(d)}}}}autolink(t){let e=this.rules.inline.autolink.exec(t);if(e){let n,r;return e[2]==="@"?(n=e[1],r="mailto:"+n):(n=e[1],r=n),{type:"link",raw:e[0],text:n,href:r,tokens:[{type:"text",raw:n,text:n}]}}}url(t){let e;if(e=this.rules.inline.url.exec(t)){let n,r;if(e[2]==="@")n=e[0],r="mailto:"+n;else{let s;do s=e[0],e[0]=this.rules.inline._backpedal.exec(e[0])?.[0]??"";while(s!==e[0]);n=e[0],e[1]==="www."?r="http://"+e[0]:r=e[0]}return{type:"link",raw:e[0],text:n,href:r,tokens:[{type:"text",raw:n,text:n}]}}}inlineText(t){let e=this.rules.inline.text.exec(t);if(e){let n=this.lexer.state.inRawBlock;return{type:"text",raw:e[0],text:e[0],escaped:n}}}},Pe=class Pl{constructor(e){j(this,"tokens");j(this,"options");j(this,"state");j(this,"inlineQueue");j(this,"tokenizer");this.tokens=[],this.tokens.links=Object.create(null),this.options=e||jt,this.options.tokenizer=this.options.tokenizer||new Zs,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};let n={other:fe,block:Xs.normal,inline:sr.normal};this.options.pedantic?(n.block=Xs.pedantic,n.inline=sr.pedantic):this.options.gfm&&(n.block=Xs.gfm,this.options.breaks?n.inline=sr.breaks:n.inline=sr.gfm),this.tokenizer.rules=n}static get rules(){return{block:Xs,inline:sr}}static lex(e,n){return new Pl(n).lex(e)}static lexInline(e,n){return new Pl(n).inlineTokens(e)}lex(e){e=e.replace(fe.carriageReturn,` -`),this.blockTokens(e,this.tokens);for(let n=0;n(s=o.call({lexer:this},e,n))?(e=e.substring(s.raw.length),n.push(s),!0):!1))continue;if(s=this.tokenizer.space(e)){e=e.substring(s.raw.length);let o=n.at(-1);s.raw.length===1&&o!==void 0?o.raw+=` -`:n.push(s);continue}if(s=this.tokenizer.code(e)){e=e.substring(s.raw.length);let o=n.at(-1);o?.type==="paragraph"||o?.type==="text"?(o.raw+=(o.raw.endsWith(` +`,n=n.substring(h.length+1),a=!0),!a){let m=this.rules.other.nextBulletRegex(p),g=this.rules.other.hrRegex(p),y=this.rules.other.fencesBeginRegex(p),k=this.rules.other.headingBeginRegex(p),w=this.rules.other.htmlBeginRegex(p),C=this.rules.other.blockquoteBeginRegex(p);for(;n;){let x=n.split(` +`,1)[0],M;if(h=x,this.options.pedantic?(h=h.replace(this.rules.other.listReplaceNesting," "),M=h):M=h.replace(this.rules.other.tabCharGlobal," "),y.test(h)||k.test(h)||w.test(h)||C.test(h)||m.test(h)||g.test(h))break;if(M.search(this.rules.other.nonSpaceChar)>=p||!h.trim())u+=` +`+M.slice(p);else{if(f||d.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)>=4||y.test(d)||k.test(d)||g.test(d))break;u+=` +`+h}f=!h.trim(),c+=x+` +`,n=n.substring(x.length+1),d=M.slice(p)}}s.loose||(o?s.loose=!0:this.rules.other.doubleBlankLine.test(c)&&(o=!0)),s.items.push({type:"list_item",raw:c,task:!!this.options.gfm&&this.rules.other.listIsTask.test(u),loose:!1,text:u,tokens:[]}),s.raw+=c}let l=s.items.at(-1);if(l)l.raw=l.raw.trimEnd(),l.text=l.text.trimEnd();else return;s.raw=s.raw.trimEnd();for(let a of s.items){if(this.lexer.state.top=!1,a.tokens=this.lexer.blockTokens(a.text,[]),a.task){if(a.text=a.text.replace(this.rules.other.listReplaceTask,""),a.tokens[0]?.type==="text"||a.tokens[0]?.type==="paragraph"){a.tokens[0].raw=a.tokens[0].raw.replace(this.rules.other.listReplaceTask,""),a.tokens[0].text=a.tokens[0].text.replace(this.rules.other.listReplaceTask,"");for(let u=this.lexer.inlineQueue.length-1;u>=0;u--)if(this.rules.other.listIsTask.test(this.lexer.inlineQueue[u].src)){this.lexer.inlineQueue[u].src=this.lexer.inlineQueue[u].src.replace(this.rules.other.listReplaceTask,"");break}}let c=this.rules.other.listTaskCheckbox.exec(a.raw);if(c){let u={type:"checkbox",raw:c[0]+" ",checked:c[0]!=="[ ]"};a.checked=u.checked,s.loose?a.tokens[0]&&["paragraph","text"].includes(a.tokens[0].type)&&"tokens"in a.tokens[0]&&a.tokens[0].tokens?(a.tokens[0].raw=u.raw+a.tokens[0].raw,a.tokens[0].text=u.raw+a.tokens[0].text,a.tokens[0].tokens.unshift(u)):a.tokens.unshift({type:"paragraph",raw:u.raw,text:u.raw,tokens:[u]}):a.tokens.unshift(u)}}if(!s.loose){let c=a.tokens.filter(d=>d.type==="space"),u=c.length>0&&c.some(d=>this.rules.other.anyLine.test(d.raw));s.loose=u}}if(s.loose)for(let a of s.items){a.loose=!0;for(let c of a.tokens)c.type==="text"&&(c.type="paragraph")}return s}}html(n){let e=this.rules.block.html.exec(n);if(e)return{type:"html",block:!0,raw:e[0],pre:e[1]==="pre"||e[1]==="script"||e[1]==="style",text:e[0]}}def(n){let e=this.rules.block.def.exec(n);if(e){let t=e[1].toLowerCase().replace(this.rules.other.multipleSpaceGlobal," "),r=e[2]?e[2].replace(this.rules.other.hrefBrackets,"$1").replace(this.rules.inline.anyPunctuation,"$1"):"",s=e[3]?e[3].substring(1,e[3].length-1).replace(this.rules.inline.anyPunctuation,"$1"):e[3];return{type:"def",tag:t,raw:e[0],href:r,title:s}}}table(n){let e=this.rules.block.table.exec(n);if(!e||!this.rules.other.tableDelimiter.test(e[2]))return;let t=Ap(e[1]),r=e[2].replace(this.rules.other.tableAlignChars,"").split("|"),s=e[3]?.trim()?e[3].replace(this.rules.other.tableRowBlankLine,"").split(` +`):[],i={type:"table",raw:e[0],header:[],align:[],rows:[]};if(t.length===r.length){for(let o of r)this.rules.other.tableAlignRight.test(o)?i.align.push("right"):this.rules.other.tableAlignCenter.test(o)?i.align.push("center"):this.rules.other.tableAlignLeft.test(o)?i.align.push("left"):i.align.push(null);for(let o=0;o({text:l,tokens:this.lexer.inline(l),header:!1,align:i.align[a]})));return i}}lheading(n){let e=this.rules.block.lheading.exec(n);if(e){let t=e[1].trim();return{type:"heading",raw:e[0],depth:e[2].charAt(0)==="="?1:2,text:t,tokens:this.lexer.inline(t)}}}paragraph(n){let e=this.rules.block.paragraph.exec(n);if(e){let t=e[1].charAt(e[1].length-1)===` +`?e[1].slice(0,-1):e[1];return{type:"paragraph",raw:e[0],text:t,tokens:this.lexer.inline(t)}}}text(n){let e=this.rules.block.text.exec(n);if(e)return{type:"text",raw:e[0],text:e[0],tokens:this.lexer.inline(e[0])}}escape(n){let e=this.rules.inline.escape.exec(n);if(e)return{type:"escape",raw:e[0],text:e[1]}}tag(n){let e=this.rules.inline.tag.exec(n);if(e)return!this.lexer.state.inLink&&this.rules.other.startATag.test(e[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(e[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(e[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(e[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:e[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:e[0]}}link(n){let e=this.rules.inline.link.exec(n);if(e){let t=e[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(t)){if(!this.rules.other.endAngleBracket.test(t))return;let i=Ir(t.slice(0,-1),"\\");if((t.length-i.length)%2===0)return}else{let i=a1(e[2],"()");if(i===-2)return;if(i>-1){let o=(e[0].indexOf("!")===0?5:4)+e[1].length+i;e[2]=e[2].substring(0,i),e[0]=e[0].substring(0,o).trim(),e[3]=""}}let r=e[2],s="";if(this.options.pedantic){let i=this.rules.other.pedanticHrefTitle.exec(r);i&&(r=i[1],s=i[3])}else s=e[3]?e[3].slice(1,-1):"";return r=r.trim(),this.rules.other.startAngleBracket.test(r)&&(this.options.pedantic&&!this.rules.other.endAngleBracket.test(t)?r=r.slice(1):r=r.slice(1,-1)),Ep(e,{href:r&&r.replace(this.rules.inline.anyPunctuation,"$1"),title:s&&s.replace(this.rules.inline.anyPunctuation,"$1")},e[0],this.lexer,this.rules)}}reflink(n,e){let t;if((t=this.rules.inline.reflink.exec(n))||(t=this.rules.inline.nolink.exec(n))){let r=(t[2]||t[1]).replace(this.rules.other.multipleSpaceGlobal," "),s=e[r.toLowerCase()];if(!s){let i=t[0].charAt(0);return{type:"text",raw:i,text:i}}return Ep(t,s,t[0],this.lexer,this.rules)}}emStrong(n,e,t=""){let r=this.rules.inline.emStrongLDelim.exec(n);if(!(!r||!r[1]&&!r[2]&&!r[3]&&!r[4]||r[4]&&t.match(this.rules.other.unicodeAlphaNumeric))&&(!(r[1]||r[3])||!t||this.rules.inline.punctuation.exec(t))){let s=[...r[0]].length-1,i,o,l=s,a=0,c=r[0][0]==="*"?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(c.lastIndex=0,e=e.slice(-1*n.length+s);(r=c.exec(e))!==null;){if(i=r[1]||r[2]||r[3]||r[4]||r[5]||r[6],!i)continue;if(o=[...i].length,r[3]||r[4]){l+=o;continue}else if((r[5]||r[6])&&s%3&&!((s+o)%3)){a+=o;continue}if(l-=o,l>0)continue;o=Math.min(o,o+l+a);let u=[...r[0]][0].length,d=n.slice(0,s+r.index+u+o);if(Math.min(s,o)%2){let f=d.slice(1,-1);return{type:"em",raw:d,text:f,tokens:this.lexer.inlineTokens(f)}}let h=d.slice(2,-2);return{type:"strong",raw:d,text:h,tokens:this.lexer.inlineTokens(h)}}}}codespan(n){let e=this.rules.inline.code.exec(n);if(e){let t=e[2].replace(this.rules.other.newLineCharGlobal," "),r=this.rules.other.nonSpaceChar.test(t),s=this.rules.other.startingSpaceChar.test(t)&&this.rules.other.endingSpaceChar.test(t);return r&&s&&(t=t.substring(1,t.length-1)),{type:"codespan",raw:e[0],text:t}}}br(n){let e=this.rules.inline.br.exec(n);if(e)return{type:"br",raw:e[0]}}del(n,e,t=""){let r=this.rules.inline.delLDelim.exec(n);if(r&&(!r[1]||!t||this.rules.inline.punctuation.exec(t))){let s=[...r[0]].length-1,i,o,l=s,a=this.rules.inline.delRDelim;for(a.lastIndex=0,e=e.slice(-1*n.length+s);(r=a.exec(e))!==null;){if(i=r[1]||r[2]||r[3]||r[4]||r[5]||r[6],!i||(o=[...i].length,o!==s))continue;if(r[3]||r[4]){l+=o;continue}if(l-=o,l>0)continue;o=Math.min(o,o+l);let c=[...r[0]][0].length,u=n.slice(0,s+r.index+c+o),d=u.slice(s,-s);return{type:"del",raw:u,text:d,tokens:this.lexer.inlineTokens(d)}}}}autolink(n){let e=this.rules.inline.autolink.exec(n);if(e){let t,r;return e[2]==="@"?(t=e[1],r="mailto:"+t):(t=e[1],r=t),{type:"link",raw:e[0],text:t,href:r,tokens:[{type:"text",raw:t,text:t}]}}}url(n){let e;if(e=this.rules.inline.url.exec(n)){let t,r;if(e[2]==="@")t=e[0],r="mailto:"+t;else{let s;do s=e[0],e[0]=this.rules.inline._backpedal.exec(e[0])?.[0]??"";while(s!==e[0]);t=e[0],e[1]==="www."?r="http://"+e[0]:r=e[0]}return{type:"link",raw:e[0],text:t,href:r,tokens:[{type:"text",raw:t,text:t}]}}}inlineText(n){let e=this.rules.inline.text.exec(n);if(e){let t=this.lexer.state.inRawBlock;return{type:"text",raw:e[0],text:e[0],escaped:t}}}},_e=class Ba{constructor(e){j(this,"tokens");j(this,"options");j(this,"state");j(this,"inlineQueue");j(this,"tokenizer");this.tokens=[],this.tokens.links=Object.create(null),this.options=e||on,this.options.tokenizer=this.options.tokenizer||new $i,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};let t={other:Ce,block:Li.normal,inline:Or.normal};this.options.pedantic?(t.block=Li.pedantic,t.inline=Or.pedantic):this.options.gfm&&(t.block=Li.gfm,this.options.breaks?t.inline=Or.breaks:t.inline=Or.gfm),this.tokenizer.rules=t}static get rules(){return{block:Li,inline:Or}}static lex(e,t){return new Ba(t).lex(e)}static lexInline(e,t){return new Ba(t).inlineTokens(e)}lex(e){e=e.replace(Ce.carriageReturn,` +`),this.blockTokens(e,this.tokens);for(let t=0;t(s=o.call({lexer:this},e,t))?(e=e.substring(s.raw.length),t.push(s),!0):!1))continue;if(s=this.tokenizer.space(e)){e=e.substring(s.raw.length);let o=t.at(-1);s.raw.length===1&&o!==void 0?o.raw+=` +`:t.push(s);continue}if(s=this.tokenizer.code(e)){e=e.substring(s.raw.length);let o=t.at(-1);o?.type==="paragraph"||o?.type==="text"?(o.raw+=(o.raw.endsWith(` `)?"":` `)+s.raw,o.text+=` -`+s.text,this.inlineQueue.at(-1).src=o.text):n.push(s);continue}if(s=this.tokenizer.fences(e)){e=e.substring(s.raw.length),n.push(s);continue}if(s=this.tokenizer.heading(e)){e=e.substring(s.raw.length),n.push(s);continue}if(s=this.tokenizer.hr(e)){e=e.substring(s.raw.length),n.push(s);continue}if(s=this.tokenizer.blockquote(e)){e=e.substring(s.raw.length),n.push(s);continue}if(s=this.tokenizer.list(e)){e=e.substring(s.raw.length),n.push(s);continue}if(s=this.tokenizer.html(e)){e=e.substring(s.raw.length),n.push(s);continue}if(s=this.tokenizer.def(e)){e=e.substring(s.raw.length);let o=n.at(-1);o?.type==="paragraph"||o?.type==="text"?(o.raw+=(o.raw.endsWith(` +`+s.text,this.inlineQueue.at(-1).src=o.text):t.push(s);continue}if(s=this.tokenizer.fences(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.heading(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.hr(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.blockquote(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.list(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.html(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.def(e)){e=e.substring(s.raw.length);let o=t.at(-1);o?.type==="paragraph"||o?.type==="text"?(o.raw+=(o.raw.endsWith(` `)?"":` `)+s.raw,o.text+=` -`+s.raw,this.inlineQueue.at(-1).src=o.text):this.tokens.links[s.tag]||(this.tokens.links[s.tag]={href:s.href,title:s.title},n.push(s));continue}if(s=this.tokenizer.table(e)){e=e.substring(s.raw.length),n.push(s);continue}if(s=this.tokenizer.lheading(e)){e=e.substring(s.raw.length),n.push(s);continue}let i=e;if(this.options.extensions?.startBlock){let o=1/0,l=e.slice(1),a;this.options.extensions.startBlock.forEach(c=>{a=c.call({lexer:this},l),typeof a=="number"&&a>=0&&(o=Math.min(o,a))}),o<1/0&&o>=0&&(i=e.substring(0,o+1))}if(this.state.top&&(s=this.tokenizer.paragraph(i))){let o=n.at(-1);r&&o?.type==="paragraph"?(o.raw+=(o.raw.endsWith(` +`+s.raw,this.inlineQueue.at(-1).src=o.text):this.tokens.links[s.tag]||(this.tokens.links[s.tag]={href:s.href,title:s.title},t.push(s));continue}if(s=this.tokenizer.table(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.lheading(e)){e=e.substring(s.raw.length),t.push(s);continue}let i=e;if(this.options.extensions?.startBlock){let o=1/0,l=e.slice(1),a;this.options.extensions.startBlock.forEach(c=>{a=c.call({lexer:this},l),typeof a=="number"&&a>=0&&(o=Math.min(o,a))}),o<1/0&&o>=0&&(i=e.substring(0,o+1))}if(this.state.top&&(s=this.tokenizer.paragraph(i))){let o=t.at(-1);r&&o?.type==="paragraph"?(o.raw+=(o.raw.endsWith(` `)?"":` `)+s.raw,o.text+=` -`+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=o.text):n.push(s),r=i.length!==e.length,e=e.substring(s.raw.length);continue}if(s=this.tokenizer.text(e)){e=e.substring(s.raw.length);let o=n.at(-1);o?.type==="text"?(o.raw+=(o.raw.endsWith(` +`+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=o.text):t.push(s),r=i.length!==e.length,e=e.substring(s.raw.length);continue}if(s=this.tokenizer.text(e)){e=e.substring(s.raw.length);let o=t.at(-1);o?.type==="text"?(o.raw+=(o.raw.endsWith(` `)?"":` `)+s.raw,o.text+=` -`+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=o.text):n.push(s);continue}if(e){let o="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(o);break}else throw new Error(o)}}return this.state.top=!0,n}inline(e,n=[]){return this.inlineQueue.push({src:e,tokens:n}),n}inlineTokens(e,n=[]){let r=e,s=null;if(this.tokens.links){let a=Object.keys(this.tokens.links);if(a.length>0)for(;(s=this.tokenizer.rules.inline.reflinkSearch.exec(r))!=null;)a.includes(s[0].slice(s[0].lastIndexOf("[")+1,-1))&&(r=r.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+r.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;(s=this.tokenizer.rules.inline.anyPunctuation.exec(r))!=null;)r=r.slice(0,s.index)+"++"+r.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);let i;for(;(s=this.tokenizer.rules.inline.blockSkip.exec(r))!=null;)i=s[2]?s[2].length:0,r=r.slice(0,s.index+i)+"["+"a".repeat(s[0].length-i-2)+"]"+r.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);r=this.options.hooks?.emStrongMask?.call({lexer:this},r)??r;let o=!1,l="";for(;e;){o||(l=""),o=!1;let a;if(this.options.extensions?.inline?.some(u=>(a=u.call({lexer:this},e,n))?(e=e.substring(a.raw.length),n.push(a),!0):!1))continue;if(a=this.tokenizer.escape(e)){e=e.substring(a.raw.length),n.push(a);continue}if(a=this.tokenizer.tag(e)){e=e.substring(a.raw.length),n.push(a);continue}if(a=this.tokenizer.link(e)){e=e.substring(a.raw.length),n.push(a);continue}if(a=this.tokenizer.reflink(e,this.tokens.links)){e=e.substring(a.raw.length);let u=n.at(-1);a.type==="text"&&u?.type==="text"?(u.raw+=a.raw,u.text+=a.text):n.push(a);continue}if(a=this.tokenizer.emStrong(e,r,l)){e=e.substring(a.raw.length),n.push(a);continue}if(a=this.tokenizer.codespan(e)){e=e.substring(a.raw.length),n.push(a);continue}if(a=this.tokenizer.br(e)){e=e.substring(a.raw.length),n.push(a);continue}if(a=this.tokenizer.del(e,r,l)){e=e.substring(a.raw.length),n.push(a);continue}if(a=this.tokenizer.autolink(e)){e=e.substring(a.raw.length),n.push(a);continue}if(!this.state.inLink&&(a=this.tokenizer.url(e))){e=e.substring(a.raw.length),n.push(a);continue}let c=e;if(this.options.extensions?.startInline){let u=1/0,d=e.slice(1),h;this.options.extensions.startInline.forEach(f=>{h=f.call({lexer:this},d),typeof h=="number"&&h>=0&&(u=Math.min(u,h))}),u<1/0&&u>=0&&(c=e.substring(0,u+1))}if(a=this.tokenizer.inlineText(c)){e=e.substring(a.raw.length),a.raw.slice(-1)!=="_"&&(l=a.raw.slice(-1)),o=!0;let u=n.at(-1);u?.type==="text"?(u.raw+=a.raw,u.text+=a.text):n.push(a);continue}if(e){let u="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(u);break}else throw new Error(u)}}return n}},ei=class{constructor(t){j(this,"options");j(this,"parser");this.options=t||jt}space(t){return""}code({text:t,lang:e,escaped:n}){let r=(e||"").match(fe.notSpaceStart)?.[0],s=t.replace(fe.endingNewline,"")+` -`;return r?'
'+(n?s:Ue(s,!0))+`
-`:"
"+(n?s:Ue(s,!0))+`
-`}blockquote({tokens:t}){return`
-${this.parser.parse(t)}
-`}html({text:t}){return t}def(t){return""}heading({tokens:t,depth:e}){return`${this.parser.parseInline(t)} -`}hr(t){return`
-`}list(t){let e=t.ordered,n=t.start,r="";for(let o=0;o +`+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=o.text):t.push(s);continue}if(e){let o="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(o);break}else throw new Error(o)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){this.tokenizer.lexer=this;let r=e,s=null;if(this.tokens.links){let a=Object.keys(this.tokens.links);if(a.length>0)for(;(s=this.tokenizer.rules.inline.reflinkSearch.exec(r))!==null;)a.includes(s[0].slice(s[0].lastIndexOf("[")+1,-1))&&(r=r.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+r.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;(s=this.tokenizer.rules.inline.anyPunctuation.exec(r))!==null;)r=r.slice(0,s.index)+"++"+r.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);let i;for(;(s=this.tokenizer.rules.inline.blockSkip.exec(r))!==null;)i=s[2]?s[2].length:0,r=r.slice(0,s.index+i)+"["+"a".repeat(s[0].length-i-2)+"]"+r.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);r=this.options.hooks?.emStrongMask?.call({lexer:this},r)??r;let o=!1,l="";for(;e;){o||(l=""),o=!1;let a;if(this.options.extensions?.inline?.some(u=>(a=u.call({lexer:this},e,t))?(e=e.substring(a.raw.length),t.push(a),!0):!1))continue;if(a=this.tokenizer.escape(e)){e=e.substring(a.raw.length),t.push(a);continue}if(a=this.tokenizer.tag(e)){e=e.substring(a.raw.length),t.push(a);continue}if(a=this.tokenizer.link(e)){e=e.substring(a.raw.length),t.push(a);continue}if(a=this.tokenizer.reflink(e,this.tokens.links)){e=e.substring(a.raw.length);let u=t.at(-1);a.type==="text"&&u?.type==="text"?(u.raw+=a.raw,u.text+=a.text):t.push(a);continue}if(a=this.tokenizer.emStrong(e,r,l)){e=e.substring(a.raw.length),t.push(a);continue}if(a=this.tokenizer.codespan(e)){e=e.substring(a.raw.length),t.push(a);continue}if(a=this.tokenizer.br(e)){e=e.substring(a.raw.length),t.push(a);continue}if(a=this.tokenizer.del(e,r,l)){e=e.substring(a.raw.length),t.push(a);continue}if(a=this.tokenizer.autolink(e)){e=e.substring(a.raw.length),t.push(a);continue}if(!this.state.inLink&&(a=this.tokenizer.url(e))){e=e.substring(a.raw.length),t.push(a);continue}let c=e;if(this.options.extensions?.startInline){let u=1/0,d=e.slice(1),h;this.options.extensions.startInline.forEach(f=>{h=f.call({lexer:this},d),typeof h=="number"&&h>=0&&(u=Math.min(u,h))}),u<1/0&&u>=0&&(c=e.substring(0,u+1))}if(a=this.tokenizer.inlineText(c)){e=e.substring(a.raw.length),a.raw.slice(-1)!=="_"&&(l=a.raw.slice(-1)),o=!0;let u=t.at(-1);u?.type==="text"?(u.raw+=a.raw,u.text+=a.text):t.push(a);continue}if(e){let u="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(u);break}else throw new Error(u)}}return t}},Fi=class{constructor(n){j(this,"options");j(this,"parser");this.options=n||on}space(n){return""}code({text:n,lang:e,escaped:t}){let r=(e||"").match(Ce.notSpaceStart)?.[0],s=n.replace(Ce.endingNewline,"")+` +`;return r?'
'+(t?s:et(s,!0))+`
+`:"
"+(t?s:et(s,!0))+`
+`}blockquote({tokens:n}){return`
+${this.parser.parse(n)}
+`}html({text:n}){return n}def(n){return""}heading({tokens:n,depth:e}){return`${this.parser.parseInline(n)} +`}hr(n){return`
+`}list(n){let e=n.ordered,t=n.start,r="";for(let o=0;o `+r+" -`}listitem(t){return`
  • ${this.parser.parse(t.tokens)}
  • -`}checkbox({checked:t}){return" '}paragraph({tokens:t}){return`

    ${this.parser.parseInline(t)}

    -`}table(t){let e="",n="";for(let s=0;s${r}`),` +`}listitem(n){return`
  • ${this.parser.parse(n.tokens)}
  • +`}checkbox({checked:n}){return" '}paragraph({tokens:n}){return`

    ${this.parser.parseInline(n)}

    +`}table(n){let e="",t="";for(let s=0;s${r}`),`
    `+e+` `+r+`
    -`}tablerow({text:t}){return` -${t} -`}tablecell(t){let e=this.parser.parseInline(t.tokens),n=t.header?"th":"td";return(t.align?`<${n} align="${t.align}">`:`<${n}>`)+e+` -`}strong({tokens:t}){return`${this.parser.parseInline(t)}`}em({tokens:t}){return`${this.parser.parseInline(t)}`}codespan({text:t}){return`${Ue(t,!0)}`}br(t){return"
    "}del({tokens:t}){return`${this.parser.parseInline(t)}`}link({href:t,title:e,tokens:n}){let r=this.parser.parseInline(n),s=zh(t);if(s===null)return r;t=s;let i='
    ",i}image({href:t,title:e,text:n,tokens:r}){r&&(n=this.parser.parseInline(r,this.parser.textRenderer));let s=zh(t);if(s===null)return Ue(n);t=s;let i=`${Ue(n)}{let o=s[i].flat(1/0);n=n.concat(this.walkTokens(o,e))}):s.tokens&&(n=n.concat(this.walkTokens(s.tokens,e)))}}return n}use(...t){let e=this.defaults.extensions||{renderers:{},childTokens:{}};return t.forEach(n=>{let r={...n};if(r.async=this.defaults.async||r.async||!1,n.extensions&&(n.extensions.forEach(s=>{if(!s.name)throw new Error("extension name required");if("renderer"in s){let i=e.renderers[s.name];i?e.renderers[s.name]=function(...o){let l=s.renderer.apply(this,o);return l===!1&&(l=i.apply(this,o)),l}:e.renderers[s.name]=s.renderer}if("tokenizer"in s){if(!s.level||s.level!=="block"&&s.level!=="inline")throw new Error("extension level must be 'block' or 'inline'");let i=e[s.level];i?i.unshift(s.tokenizer):e[s.level]=[s.tokenizer],s.start&&(s.level==="block"?e.startBlock?e.startBlock.push(s.start):e.startBlock=[s.start]:s.level==="inline"&&(e.startInline?e.startInline.push(s.start):e.startInline=[s.start]))}"childTokens"in s&&s.childTokens&&(e.childTokens[s.name]=s.childTokens)}),r.extensions=e),n.renderer){let s=this.defaults.renderer||new ei(this.defaults);for(let i in n.renderer){if(!(i in s))throw new Error(`renderer '${i}' does not exist`);if(["options","parser"].includes(i))continue;let o=i,l=n.renderer[o],a=s[o];s[o]=(...c)=>{let u=l.apply(s,c);return u===!1&&(u=a.apply(s,c)),u||""}}r.renderer=s}if(n.tokenizer){let s=this.defaults.tokenizer||new Zs(this.defaults);for(let i in n.tokenizer){if(!(i in s))throw new Error(`tokenizer '${i}' does not exist`);if(["options","rules","lexer"].includes(i))continue;let o=i,l=n.tokenizer[o],a=s[o];s[o]=(...c)=>{let u=l.apply(s,c);return u===!1&&(u=a.apply(s,c)),u}}r.tokenizer=s}if(n.hooks){let s=this.defaults.hooks||new or;for(let i in n.hooks){if(!(i in s))throw new Error(`hook '${i}' does not exist`);if(["options","block"].includes(i))continue;let o=i,l=n.hooks[o],a=s[o];or.passThroughHooks.has(i)?s[o]=c=>{if(this.defaults.async&&or.passThroughHooksRespectAsync.has(i))return(async()=>{let d=await l.call(s,c);return a.call(s,d)})();let u=l.call(s,c);return a.call(s,u)}:s[o]=(...c)=>{if(this.defaults.async)return(async()=>{let d=await l.apply(s,c);return d===!1&&(d=await a.apply(s,c)),d})();let u=l.apply(s,c);return u===!1&&(u=a.apply(s,c)),u}}r.hooks=s}if(n.walkTokens){let s=this.defaults.walkTokens,i=n.walkTokens;r.walkTokens=function(o){let l=[];return l.push(i.call(this,o)),s&&(l=l.concat(s.call(this,o))),l}}this.defaults={...this.defaults,...r}}),this}setOptions(t){return this.defaults={...this.defaults,...t},this}lexer(t,e){return Pe.lex(t,e??this.defaults)}parser(t,e){return Le.parse(t,e??this.defaults)}parseMarkdown(t){return(e,n)=>{let r={...n},s={...this.defaults,...r},i=this.onError(!!s.silent,!!s.async);if(this.defaults.async===!0&&r.async===!1)return i(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(typeof e>"u"||e===null)return i(new Error("marked(): input parameter is undefined or null"));if(typeof e!="string")return i(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(e)+", string expected"));if(s.hooks&&(s.hooks.options=s,s.hooks.block=t),s.async)return(async()=>{let o=s.hooks?await s.hooks.preprocess(e):e,l=await(s.hooks?await s.hooks.provideLexer():t?Pe.lex:Pe.lexInline)(o,s),a=s.hooks?await s.hooks.processAllTokens(l):l;s.walkTokens&&await Promise.all(this.walkTokens(a,s.walkTokens));let c=await(s.hooks?await s.hooks.provideParser():t?Le.parse:Le.parseInline)(a,s);return s.hooks?await s.hooks.postprocess(c):c})().catch(i);try{s.hooks&&(e=s.hooks.preprocess(e));let o=(s.hooks?s.hooks.provideLexer():t?Pe.lex:Pe.lexInline)(e,s);s.hooks&&(o=s.hooks.processAllTokens(o)),s.walkTokens&&this.walkTokens(o,s.walkTokens);let l=(s.hooks?s.hooks.provideParser():t?Le.parse:Le.parseInline)(o,s);return s.hooks&&(l=s.hooks.postprocess(l)),l}catch(o){return i(o)}}}onError(t,e){return n=>{if(n.message+=` -Please report this to https://github.com/markedjs/marked.`,t){let r="

    An error occurred:

    "+Ue(n.message+"",!0)+"
    ";return e?Promise.resolve(r):r}if(e)return Promise.reject(n);throw n}}},Wt=new Db;function V(t,e){return Wt.parse(t,e)}V.options=V.setOptions=function(t){return Wt.setOptions(t),V.defaults=Wt.defaults,Hh(V.defaults),V};V.getDefaults=zl;V.defaults=jt;V.use=function(...t){return Wt.use(...t),V.defaults=Wt.defaults,Hh(V.defaults),V};V.walkTokens=function(t,e){return Wt.walkTokens(t,e)};V.parseInline=Wt.parseInline;V.Parser=Le;V.parser=Le.parse;V.Renderer=ei;V.TextRenderer=jl;V.Lexer=Pe;V.lexer=Pe.lex;V.Tokenizer=Zs;V.Hooks=or;V.parse=V;var JC=V.options,GC=V.setOptions,XC=V.use,QC=V.walkTokens,YC=V.parseInline;var ZC=Le.parse,eM=Pe.lex;function Pb(t,e){let r=e.split(` -`).flatMap(s=>[s,""]).map(s=>`${t}${s}`).join(` -`);return r.slice(0,r.length-1)}function Lb(t,e){let n=[];return Array.from(t.keys()).forEach(r=>{(!e||!e.marks||!e.marks.map(s=>s.type).includes(r))&&n.push(r)}),n}function zb(t,e){let n=[];return Array.from(e.entries()).forEach(([r,s])=>{t.has(r)||n.push({type:r,mark:s})}),n}function Bb(t,e,n,r){let s=!n,i=n&&n.type==="text"&&(!n.marks||n.marks.length===0),o=n&&n.type==="text"&&n.marks&&!r(e,new Map(n.marks.map(a=>[a.type,a]))),l=[];if(s||i||o)if(n&&n.type==="text"&&n.marks){let a=new Map(n.marks.map(c=>[c.type,c]));Array.from(t.keys()).forEach(c=>{a.has(c)||l.push(c)})}else(s||i)&&l.push(...Array.from(t.keys()));return l}function $b(t,e){let n="";return Array.from(t.keys()).reverse().forEach(r=>{let s=t.get(r),i=e(r,s);i&&(n=i+n)}),t.clear(),n}function Hb(t,e,n){let r="";return Array.from(t.entries()).forEach(([s,i])=>{let o=n(s,i);o&&(r+=o),e.set(s,i)}),r}function Kl(t){let n=(t.raw||t.text||"").match(/^(\s*)[-+*]\s+\[([ xX])\]\s+/);return n?{isTask:!0,checked:n[2].toLowerCase()==="x",indentLevel:n[1].length}:{isTask:!1,indentLevel:0}}function ri(t,e){return typeof t!="string"?"json":e}var Fb=class{constructor(t){this.baseExtensions=[],this.extensions=[],this.lastParseResult=null;var e,n,r,s,i;this.markedInstance=(e=t?.marked)!=null?e:V,this.lexer=new this.markedInstance.Lexer,this.indentStyle=(r=(n=t?.indentation)==null?void 0:n.style)!=null?r:"space",this.indentSize=(i=(s=t?.indentation)==null?void 0:s.size)!=null?i:2,this.baseExtensions=t?.extensions||[],t?.markedOptions&&typeof this.markedInstance.setOptions=="function"&&this.markedInstance.setOptions(t.markedOptions),this.registry=new Map,this.nodeTypeRegistry=new Map,t?.extensions&&(this.baseExtensions=t.extensions,Wn(t.extensions).forEach(l=>this.registerExtension(l,!1))),this.lexer=new this.markedInstance.Lexer}get instance(){return this.markedInstance}get indentCharacter(){return this.indentStyle==="space"?" ":" "}get indentString(){return this.indentCharacter.repeat(this.indentSize)}hasMarked(){return!!this.markedInstance}registerExtension(t,e=!0){var n,r;this.extensions.push(t);let s=t.name,i=T(t,"markdownTokenName")||s,o=T(t,"parseMarkdown"),l=T(t,"renderMarkdown"),a=T(t,"markdownTokenizer"),c=(n=T(t,"markdownOptions"))!=null?n:null,u=(r=c?.indentsContent)!=null?r:!1,d={tokenName:i,nodeName:s,parseMarkdown:o,renderMarkdown:l,isIndenting:u,tokenizer:a};if(i&&o){let h=this.registry.get(i)||[];h.push(d),this.registry.set(i,h)}if(l){let h=this.nodeTypeRegistry.get(s)||[];h.push(d),this.nodeTypeRegistry.set(s,h)}a&&this.hasMarked()&&(this.registerTokenizer(a),e&&(this.lexer=new this.markedInstance.Lexer))}registerTokenizer(t){if(!this.hasMarked())return;let{name:e,start:n,level:r="inline",tokenize:s}=t,l={inlineTokens:u=>this.lexer.inlineTokens(u),blockTokens:u=>this.lexer.blockTokens(u)},a;n?a=typeof n=="function"?n:u=>u.indexOf(n):a=u=>{let d=s(u,[],l);return d&&d.raw?u.indexOf(d.raw):-1};let c={name:e,level:r,start:a,tokenizer:(u,d)=>{let h=s(u,d,l);if(h&&h.type)return{...h,type:h.type||e,raw:h.raw||"",tokens:h.tokens||[]}},childTokens:[]};this.markedInstance.use({extensions:[c]})}getHandlersForToken(t){try{return this.registry.get(t)||[]}catch{return[]}}getHandlerForToken(t){let e=this.getHandlersForToken(t);if(e.length>0)return e[0];let n=this.getHandlersForNodeType(t);return n.length>0?n[0]:void 0}getHandlersForNodeType(t){try{return this.nodeTypeRegistry.get(t)||[]}catch{return[]}}serialize(t){if(!t)return"";let e=this.renderNodes(t,t);return this.isEmptyOutput(e)?"":e}isEmptyOutput(t){return!t||t.trim()===""?!0:t.replace(/ /g,"").replace(/\u00A0/g,"").trim()===""}parse(t){if(!this.hasMarked())throw new Error("No marked instance available for parsing");let e=this.markedInstance.lexer(t);return{type:"doc",content:this.parseTokens(e)}}parseTokens(t){return t.map(e=>this.parseToken(e)).filter(e=>e!==null).flatMap(e=>Array.isArray(e)?e:[e])}parseToken(t){if(!t.type)return null;if(t.type==="list")return this.parseListToken(t);let e=this.getHandlersForToken(t.type),n=this.createParseHelpers();if(e.find(s=>{if(!s.parseMarkdown)return!1;let i=s.parseMarkdown(t,n),o=this.normalizeParseResult(i);return o&&(!Array.isArray(o)||o.length>0)?(this.lastParseResult=o,!0):!1})&&this.lastParseResult){let s=this.lastParseResult;return this.lastParseResult=null,s}return this.parseFallbackToken(t)}parseListToken(t){if(!t.items||t.items.length===0)return this.parseTokenWithHandlers(t);let e=t.items.some(l=>Kl(l).isTask),n=t.items.some(l=>!Kl(l).isTask);if(!e||!n||this.getHandlersForToken("taskList").length===0)return this.parseTokenWithHandlers(t);let r=[],s=[],i=null;for(let l=0;l +${n} +`}tablecell(n){let e=this.parser.parseInline(n.tokens),t=n.header?"th":"td";return(n.align?`<${t} align="${n.align}">`:`<${t}>`)+e+` +`}strong({tokens:n}){return`${this.parser.parseInline(n)}`}em({tokens:n}){return`${this.parser.parseInline(n)}`}codespan({text:n}){return`${et(n,!0)}`}br(n){return"
    "}del({tokens:n}){return`${this.parser.parseInline(n)}`}link({href:n,title:e,tokens:t}){let r=this.parser.parseInline(t),s=Mp(n);if(s===null)return r;n=s;let i='
    ",i}image({href:n,title:e,text:t,tokens:r}){r&&(t=this.parser.parseInline(r,this.parser.textRenderer));let s=Mp(n);if(s===null)return et(t);n=s;let i=`${et(t)}{let o=s[i].flat(1/0);t=t.concat(this.walkTokens(o,e))}):s.tokens&&(t=t.concat(this.walkTokens(s.tokens,e)))}}return t}use(...n){let e=this.defaults.extensions||{renderers:{},childTokens:{}};return n.forEach(t=>{let r={...t};if(r.async=this.defaults.async||r.async||!1,t.extensions&&(t.extensions.forEach(s=>{if(!s.name)throw new Error("extension name required");if("renderer"in s){let i=e.renderers[s.name];i?e.renderers[s.name]=function(...o){let l=s.renderer.apply(this,o);return l===!1&&(l=i.apply(this,o)),l}:e.renderers[s.name]=s.renderer}if("tokenizer"in s){if(!s.level||s.level!=="block"&&s.level!=="inline")throw new Error("extension level must be 'block' or 'inline'");let i=e[s.level];i?i.unshift(s.tokenizer):e[s.level]=[s.tokenizer],s.start&&(s.level==="block"?e.startBlock?e.startBlock.push(s.start):e.startBlock=[s.start]:s.level==="inline"&&(e.startInline?e.startInline.push(s.start):e.startInline=[s.start]))}"childTokens"in s&&s.childTokens&&(e.childTokens[s.name]=s.childTokens)}),r.extensions=e),t.renderer){let s=this.defaults.renderer||new Fi(this.defaults);for(let i in t.renderer){if(!(i in s))throw new Error(`renderer '${i}' does not exist`);if(["options","parser"].includes(i))continue;let o=i,l=t.renderer[o],a=s[o];s[o]=(...c)=>{let u=l.apply(s,c);return u===!1&&(u=a.apply(s,c)),u||""}}r.renderer=s}if(t.tokenizer){let s=this.defaults.tokenizer||new $i(this.defaults);for(let i in t.tokenizer){if(!(i in s))throw new Error(`tokenizer '${i}' does not exist`);if(["options","rules","lexer"].includes(i))continue;let o=i,l=t.tokenizer[o],a=s[o];s[o]=(...c)=>{let u=l.apply(s,c);return u===!1&&(u=a.apply(s,c)),u}}r.tokenizer=s}if(t.hooks){let s=this.defaults.hooks||new Dr;for(let i in t.hooks){if(!(i in s))throw new Error(`hook '${i}' does not exist`);if(["options","block"].includes(i))continue;let o=i,l=t.hooks[o],a=s[o];Dr.passThroughHooks.has(i)?s[o]=c=>{if(this.defaults.async&&Dr.passThroughHooksRespectAsync.has(i))return(async()=>{let d=await l.call(s,c);return a.call(s,d)})();let u=l.call(s,c);return a.call(s,u)}:s[o]=(...c)=>{if(this.defaults.async)return(async()=>{let d=await l.apply(s,c);return d===!1&&(d=await a.apply(s,c)),d})();let u=l.apply(s,c);return u===!1&&(u=a.apply(s,c)),u}}r.hooks=s}if(t.walkTokens){let s=this.defaults.walkTokens,i=t.walkTokens;r.walkTokens=function(o){let l=[];return l.push(i.call(this,o)),s&&(l=l.concat(s.call(this,o))),l}}this.defaults={...this.defaults,...r}}),this}setOptions(n){return this.defaults={...this.defaults,...n},this}lexer(n,e){return _e.lex(n,e??this.defaults)}parser(n,e){return Ve.parse(n,e??this.defaults)}parseMarkdown(n){return(e,t)=>{let r={...t},s={...this.defaults,...r},i=this.onError(!!s.silent,!!s.async);if(this.defaults.async===!0&&r.async===!1)return i(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(typeof e>"u"||e===null)return i(new Error("marked(): input parameter is undefined or null"));if(typeof e!="string")return i(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(e)+", string expected"));if(s.hooks&&(s.hooks.options=s,s.hooks.block=n),s.async)return(async()=>{let o=s.hooks?await s.hooks.preprocess(e):e,l=await(s.hooks?await s.hooks.provideLexer(n):n?_e.lex:_e.lexInline)(o,s),a=s.hooks?await s.hooks.processAllTokens(l):l;s.walkTokens&&await Promise.all(this.walkTokens(a,s.walkTokens));let c=await(s.hooks?await s.hooks.provideParser(n):n?Ve.parse:Ve.parseInline)(a,s);return s.hooks?await s.hooks.postprocess(c):c})().catch(i);try{s.hooks&&(e=s.hooks.preprocess(e));let o=(s.hooks?s.hooks.provideLexer(n):n?_e.lex:_e.lexInline)(e,s);s.hooks&&(o=s.hooks.processAllTokens(o)),s.walkTokens&&this.walkTokens(o,s.walkTokens);let l=(s.hooks?s.hooks.provideParser(n):n?Ve.parse:Ve.parseInline)(o,s);return s.hooks&&(l=s.hooks.postprocess(l)),l}catch(o){return i(o)}}}onError(n,e){return t=>{if(t.message+=` +Please report this to https://github.com/markedjs/marked.`,n){let r="

    An error occurred:

    "+et(t.message+"",!0)+"
    ";return e?Promise.resolve(r):r}if(e)return Promise.reject(t);throw t}}},sn=new d1;function V(n,e){return sn.parse(n,e)}V.options=V.setOptions=function(n){return sn.setOptions(n),V.defaults=sn.defaults,Np(V.defaults),V};V.getDefaults=Fa;V.defaults=on;V.use=function(...n){return sn.use(...n),V.defaults=sn.defaults,Np(V.defaults),V};V.walkTokens=function(n,e){return sn.walkTokens(n,e)};V.parseInline=sn.parseInline;V.Parser=Ve;V.parser=Ve.parse;V.Renderer=Fi;V.TextRenderer=qa;V.Lexer=_e;V.lexer=_e.lex;V.Tokenizer=$i;V.Hooks=Dr;V.parse=V;var WM=V.options,jM=V.setOptions,JM=V.use,KM=V.walkTokens,qM=V.parseInline;var UM=Ve.parse,GM=_e.lex;function h1(n,e){let r=e.split(` +`).flatMap(s=>[s,""]).map(s=>`${n}${s}`).join(` +`);return r.slice(0,r.length-1)}function f1(n,e){let t=[];return Array.from(n.entries()).forEach(([r,s])=>{if(!e){t.push(r);return}(e.marks||[]).find(o=>o.type===r&&Tt(o.attrs,s.attrs))||t.push(r)}),t}function p1(n,e){let t=[];return Array.from(e.entries()).forEach(([r,s])=>{let i=n.get(r);(!i||!Tt(i.attrs,s.attrs))&&t.push({type:r,mark:s})}),t}function m1(n,e,t,r){let s=!t,i=t&&(!t.marks||t.marks.length===0),o=t&&t.marks&&!r(e,new Map(t.marks.map(a=>[a.type,a]))),l=[];return(s||i||o)&&(t&&t.marks?Array.from(n.entries()).reverse().forEach(([a,c])=>{t.marks.find(d=>d.type===a&&Tt(d.attrs,c.attrs))||l.push(a)}):(s||i)&&l.push(...Array.from(n.keys()).reverse())),l}function g1(n,e){let t="";return Array.from(n.keys()).reverse().forEach(r=>{let s=n.get(r),i=e(r,s);i&&(t=i+t)}),n.clear(),t}function y1(n,e,t){let r="";return Array.from(n.entries()).forEach(([s,i])=>{let o=t(s,i);o&&(r+=o),e.set(s,i)}),r}function Ua(n){let t=(n.raw||n.text||"").match(/^(\s*)[-+*]\s+\[([ xX])\]\s+/);return t?{isTask:!0,checked:t[2].toLowerCase()==="x",indentLevel:t[1].length}:{isTask:!1,indentLevel:0}}function Vi(n,e){return typeof n!="string"?"json":e}var k1=n=>{let e=window.HTMLUnknownElement;return typeof e=="function"&&n instanceof e},b1=class{constructor(n){this.activeParseLexer=null,this.extensionRanks=new Map,this.baseExtensions=[],this.extensions=[],this.codeTypes=new Set,this.schemaParseDomTagsCache=null,this.lastParseResult=null;var e,t,r,s,i;this.markedInstance=(e=n?.marked)!=null?e:V,this.indentStyle=(r=(t=n?.indentation)==null?void 0:t.style)!=null?r:"space",this.indentSize=(i=(s=n?.indentation)==null?void 0:s.size)!=null?i:2,this.baseExtensions=n?.extensions||[],n?.markedOptions&&typeof this.markedInstance.setOptions=="function"&&this.markedInstance.setOptions(n.markedOptions),this.registry=new Map,this.nodeTypeRegistry=new Map,n?.extensions&&(this.baseExtensions=n.extensions,Jt(ar(n.extensions)).forEach(l=>this.registerExtension(l)))}get instance(){return this.markedInstance}get indentCharacter(){return this.indentStyle==="space"?" ":" "}get indentString(){return this.indentCharacter.repeat(this.indentSize)}hasMarked(){return!!this.markedInstance}registerExtension(n){var e,t;this.extensions.push(n);let r=F(E(n,"code")),s=n.name;r&&this.codeTypes.add(s),this.extensionRanks.has(s)||this.extensionRanks.set(s,this.extensionRanks.size);let i=E(n,"markdownTokenName")||s,o=E(n,"parseMarkdown"),l=E(n,"renderMarkdown"),a=E(n,"markdownTokenizer"),c=(e=E(n,"markdownOptions"))!=null?e:null,u=(t=c?.indentsContent)!=null?t:!1,d=c?.htmlReopen,h={tokenName:i,nodeName:s,parseMarkdown:o,renderMarkdown:l,isIndenting:u,htmlReopen:d,tokenizer:a};if(i&&o){let f=this.registry.get(i)||[];f.push(h),this.registry.set(i,f)}if(l){let f=this.nodeTypeRegistry.get(s)||[];f.push(h),this.nodeTypeRegistry.set(s,f)}a&&this.hasMarked()&&this.registerTokenizer(a)}createLexer(){return new this.markedInstance.Lexer}createTokenizerHelpers(n){return{inlineTokens:e=>n.inlineTokens(e),blockTokens:e=>n.blockTokens(e)}}tokenizeInline(n){var e;return((e=this.activeParseLexer)!=null?e:this.createLexer()).inlineTokens(n)}registerTokenizer(n){if(!this.hasMarked())return;let{name:e,start:t,level:r="inline",tokenize:s}=n,i=this.createTokenizerHelpers.bind(this),o=this.createLexer.bind(this),l;t?l=typeof t=="function"?t:c=>c.indexOf(t):l=c=>{let u=s(c,[],this.createTokenizerHelpers(this.createLexer()));return u&&u.raw?c.indexOf(u.raw):-1};let a={name:e,level:r,start:l,tokenizer(c,u){let d=this.lexer?i(this.lexer):i(o()),h=s(c,u,d);if(h&&h.type)return{...h,type:h.type||e,raw:h.raw||"",tokens:h.tokens||[]}},childTokens:[]};this.markedInstance.use({extensions:[a]})}getHandlersForToken(n){try{return this.registry.get(n)||[]}catch{return[]}}getHandlerForToken(n){let e=this.getHandlersForToken(n);if(e.length>0)return e[0];let t=this.getHandlersForNodeType(n);return t.length>0?t[0]:void 0}getHandlersForNodeType(n){try{return this.nodeTypeRegistry.get(n)||[]}catch{return[]}}serialize(n){if(!n)return"";let e=this.renderNodes(n,n);return this.isEmptyOutput(e)?"":e}isEmptyOutput(n){return!n||n.trim()===""?!0:n.replace(/ /g,"").replace(/\u00A0/g,"").trim()===""}parse(n){if(!this.hasMarked())throw new Error("No marked instance available for parsing");let e=this.activeParseLexer,t=this.createLexer();this.activeParseLexer=t;try{let r=t.lex(n);return{type:"doc",content:this.parseTokens(r,!0)}}finally{this.activeParseLexer=e}}parseTokens(n,e=!1){let t=n.reduce((i,o,l)=>(o.type!=="space"&&i.push(l),i),[]),r=-1,s=0;return n.flatMap((i,o)=>{for(var l;s({type:"paragraph",content:[]}))}countParagraphSeparators(n){return(n.replace(/\r\n/g,` +`).match(/\n\n/g)||[]).length}parseToken(n,e=!1){if(!n.type)return null;if(n.type==="list")return this.parseListToken(n);let t=this.getHandlersForToken(n.type),r=this.createParseHelpers();if(t.find(i=>{if(!i.parseMarkdown)return!1;let o=i.parseMarkdown(n,r),l=this.normalizeParseResult(o);return l&&(!Array.isArray(l)||l.length>0)?(this.lastParseResult=l,!0):!1})&&this.lastParseResult){let i=this.lastParseResult;return this.lastParseResult=null,i}return this.parseFallbackToken(n,e)}parseListToken(n){if(!n.items||n.items.length===0)return this.parseTokenWithHandlers(n);let e=n.items.some(l=>Ua(l).isTask),t=n.items.some(l=>!Ua(l).isTask);if(!e||!t||this.getHandlersForToken("taskList").length===0)return this.parseTokenWithHandlers(n);let r=[],s=[],i=null;for(let l=0;l1&&m.slice(1).join(` -`).trim()){let M=m.slice(1),S=M.filter(N=>N.trim());if(S.length>0){let N=Math.min(...S.map(z=>z.length-z.trimStart().length)),D=M.map(z=>z.trim()?z.slice(N):"").join(` -`).trim();D&&(k=this.markedInstance.lexer(`${D} -`))}}h={type:"taskItem",raw:"",mainContent:y,indentLevel:d,checked:u??!1,text:y,tokens:this.lexer.inlineTokens(y),nestedTokens:k}}let f=c?"taskList":"list";i!==f?(s.length>0&&r.push({type:i,items:s}),s=[h],i=f):s.push(h)}s.length>0&&r.push({type:i,items:s});let o=[];for(let l=0;l0?o:null}parseTokenWithHandlers(t){if(!t.type)return null;let e=this.getHandlersForToken(t.type),n=this.createParseHelpers();if(e.find(s=>{if(!s.parseMarkdown)return!1;let i=s.parseMarkdown(t,n),o=this.normalizeParseResult(i);return o&&(!Array.isArray(o)||o.length>0)?(this.lastParseResult=o,!0):!1})&&this.lastParseResult){let s=this.lastParseResult;return this.lastParseResult=null,s}return this.parseFallbackToken(t)}createParseHelpers(){return{parseInline:t=>this.parseInlineTokens(t),parseChildren:t=>this.parseTokens(t),createTextNode:(t,e)=>({type:"text",text:t,marks:e||void 0}),createNode:(t,e,n)=>{let r={type:t,attrs:e||void 0,content:n||void 0};return(!e||Object.keys(e).length===0)&&delete r.attrs,r},applyMark:(t,e,n)=>({mark:t,content:e,attrs:n&&Object.keys(n).length>0?n:void 0})}}escapeRegex(t){return t.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}parseInlineTokens(t){var e,n,r,s;let i=[];for(let o=0;o|\/|$)/i);if(!c&&u&&!/\/>$/.test(a)){let h=u[1],f=this.escapeRegex(h),p=new RegExp(`^<\\/\\s*${f}\\b`,"i"),m=-1,g=[a];for(let y=o+1;y{if(r.type==="text"){let s=r.marks||[],i=n?{type:t,attrs:n}:{type:t};return{...r,marks:[...s,i]}}return{...r,content:r.content?this.applyMarkToContent(t,r.content,n):void 0}})}isMarkResult(t){return t&&typeof t=="object"&&"mark"in t}normalizeParseResult(t){return t?this.isMarkResult(t)?t.content:t:null}parseFallbackToken(t){switch(t.type){case"paragraph":return{type:"paragraph",content:t.tokens?this.parseInlineTokens(t.tokens):[]};case"heading":return{type:"heading",attrs:{level:t.depth||1},content:t.tokens?this.parseInlineTokens(t.tokens):[]};case"text":return{type:"text",text:t.text||""};case"html":return this.parseHTMLToken(t);case"space":return null;default:return t.tokens?this.parseTokens(t.tokens):null}}parseHTMLToken(t){let e=t.text||t.raw||"";if(!e.trim())return null;if(typeof window>"u")return t.block?{type:"paragraph",content:[{type:"text",text:e}]}:{type:"text",text:e};try{let n=Iu(e,this.baseExtensions);return n.type==="doc"&&n.content?t.block?n.content:n.content.length===1&&n.content[0].type==="paragraph"&&n.content[0].content?n.content[0].content:n.content:n}catch(n){throw new Error(`Failed to parse HTML in markdown: ${n}`)}}renderNodeToMarkdown(t,e,n=0,r=0){var s;if(t.type==="text")return t.text||"";if(!t.type)return"";let i=this.getHandlerForToken(t.type);if(!i)return"";let o={renderChildren:(c,u)=>{let d=i.isIndenting?r+1:r;return!Array.isArray(c)&&c.content?this.renderNodes(c.content,t,u||"",n,d):this.renderNodes(c,t,u||"",n,d)},indent:c=>this.indentString+c,wrapInBlock:Pb},l={index:n,level:r,parentType:e?.type,meta:{parentAttrs:e?.attrs}};return((s=i.renderMarkdown)==null?void 0:s.call(i,t,o,l))||""}renderNodes(t,e,n="",r=0,s=0){return Array.isArray(t)?this.renderNodesWithMarkBoundaries(t,e,n,s):t.type?this.renderNodeToMarkdown(t,e,r,s):""}renderNodesWithMarkBoundaries(t,e,n="",r=0){let s=[],i=new Map;return t.forEach((o,l)=>{let a=l[y.type,y])),d=zb(i,u),h=Lb(u,a),f="";if(h.length>0){let y=c.match(/(\s+)$/);y&&(f=y[1],c=c.slice(0,-f.length))}h.forEach(y=>{let k=u.get(y),w=this.getMarkClosing(y,k);w&&(c+=w),i.has(y)&&i.delete(y)});let p="";if(d.length>0){let y=c.match(/^(\s+)/);y&&(p=y[1],c=c.slice(p.length))}d.forEach(({type:y,mark:k})=>{let w=this.getMarkOpening(y,k);w&&(c=w+c),h.includes(y)||i.set(y,k)}),c=p+c;let m=Bb(i,u,a,this.markSetsEqual.bind(this)),g="";if(m.length>0){let y=c.match(/(\s+)$/);y&&(g=y[1],c=c.slice(0,-g.length))}m.forEach(y=>{let k=i.get(y),w=this.getMarkClosing(y,k);w&&(c+=w),i.delete(y)}),c+=g,c+=f,s.push(c)}else{let c=new Map(i),u=$b(i,this.getMarkClosing.bind(this)),d=this.renderNodeToMarkdown(o,e,l,r),h=o.type==="hardBreak"?"":Hb(c,i,this.getMarkOpening.bind(this));s.push(u+d+h)}}),s.join(n)}getMarkOpening(t,e){let n=this.getHandlersForNodeType(t),r=n.length>0?n[0]:void 0;if(!r||!r.renderMarkdown)return"";let s="\uE000__TIPTAP_MARKDOWN_PLACEHOLDER__\uE001",i={type:t,attrs:e.attrs||{},content:[{type:"text",text:s}]};try{let o=r.renderMarkdown(i,{renderChildren:()=>s,indent:a=>a,wrapInBlock:(a,c)=>a+c},{index:0,level:0,parentType:"text",meta:{}}),l=o.indexOf(s);return l>=0?o.substring(0,l):""}catch(o){throw new Error(`Failed to get mark opening for ${t}: ${o}`)}}getMarkClosing(t,e){let n=this.getHandlersForNodeType(t),r=n.length>0?n[0]:void 0;if(!r||!r.renderMarkdown)return"";let s="\uE000__TIPTAP_MARKDOWN_PLACEHOLDER__\uE001",i={type:t,attrs:e.attrs||{},content:[{type:"text",text:s}]};try{let o=r.renderMarkdown(i,{renderChildren:()=>s,indent:c=>c,wrapInBlock:(c,u)=>c+u},{index:0,level:0,parentType:"text",meta:{}}),l=o.indexOf(s),a=l+s.length;return l>=0?o.substring(a):""}catch(o){throw new Error(`Failed to get mark closing for ${t}: ${o}`)}}markSetsEqual(t,e){return t.size!==e.size?!1:Array.from(t.keys()).every(n=>e.has(n))}},Qh=Fb,_b=B.create({name:"markdown",addOptions(){return{indentation:{style:"space",size:2},marked:void 0,markedOptions:{}}},addCommands(){return{setContent:(t,e)=>{if(!e?.contentType)return Ee.setContent(t,e);if(ri(t,e?.contentType)!=="markdown"||!this.editor.markdown)return Ee.setContent(t,e);let r=this.editor.markdown.parse(t);return Ee.setContent(r,e)},insertContent:(t,e)=>{if(!e?.contentType)return Ee.insertContent(t,e);if(ri(t,e?.contentType)!=="markdown"||!this.editor.markdown)return Ee.insertContent(t,e);let r=this.editor.markdown.parse(t);return Ee.insertContent(r,e)},insertContentAt:(t,e,n)=>{if(!n?.contentType)return Ee.insertContentAt(t,e,n);if(ri(e,n?.contentType)!=="markdown"||!this.editor.markdown)return Ee.insertContentAt(t,e,n);let s=this.editor.markdown.parse(e);return Ee.insertContentAt(t,s,n)}}},addStorage(){return{manager:new Qh({indentation:this.options.indentation,marked:this.options.marked,markedOptions:this.options.markedOptions,extensions:[]})}},onBeforeCreate(){if(this.editor.markdown){console.error("[tiptap][markdown]: There is already a `markdown` property on the editor instance. This might lead to unexpected behavior.");return}if(this.storage.manager=new Qh({indentation:this.options.indentation,marked:this.options.marked,markedOptions:this.options.markedOptions,extensions:this.editor.extensionManager.baseExtensions}),this.editor.markdown=this.storage.manager,this.editor.getMarkdown=()=>this.storage.manager.serialize(this.editor.getJSON()),!this.editor.options.contentType||ri(this.editor.options.content,this.editor.options.contentType)!=="markdown")return;if(!this.editor.markdown)throw new Error('[tiptap][markdown]: The `contentType` option is set to "markdown", but the Markdown extension is not added to the editor. Please add the Markdown extension to use this feature.');if(this.editor.options.content===void 0||typeof this.editor.options.content!="string")throw new Error('[tiptap][markdown]: The `contentType` option is set to "markdown", but the initial content is not a string. Please provide the initial content as a markdown string.');let e=this.editor.markdown.parse(this.editor.options.content);this.editor.options.content=e}});export{W as CellSelection,by as Editor,jk as Image,uk as Link,_b as Markdown,_k as Placeholder,Fk as StarterKit,Ih as Table,_0 as TableCell,V0 as TableHeader,q as TableMap,F0 as TableRow,j0 as TaskItem,W0 as TaskList}; +`).trim()){let C=m.slice(1),x=C.filter(M=>M.trim());if(x.length>0){let M=Math.min(...x.map(P=>P.length-P.trimStart().length)),R=C.map(P=>P.trim()?P.slice(M):"").join(` +`).trim();R&&(k=this.markedInstance.lexer(`${R} +`))}}h={type:"taskItem",raw:"",mainContent:y,indentLevel:d,checked:u??!1,text:y,tokens:this.tokenizeInline(y),nestedTokens:k}}let f=c?"taskList":"list";i!==f?(s.length>0&&r.push({type:i,items:s}),s=[h],i=f):s.push(h)}s.length>0&&r.push({type:i,items:s});let o=[];for(let l=0;l0?o:null}parseTokenWithHandlers(n){if(!n.type)return null;let e=this.getHandlersForToken(n.type),t=this.createParseHelpers();if(e.find(s=>{if(!s.parseMarkdown)return!1;let i=s.parseMarkdown(n,t),o=this.normalizeParseResult(i);return o&&(!Array.isArray(o)||o.length>0)?(this.lastParseResult=o,!0):!1})&&this.lastParseResult){let s=this.lastParseResult;return this.lastParseResult=null,s}return this.parseFallbackToken(n)}createParseHelpers(){return{parseInline:n=>this.parseInlineTokens(n),tokenizeInline:n=>this.tokenizeInline(n),parseChildren:n=>this.parseTokens(n),parseBlockChildren:n=>this.parseTokens(n,!0),createTextNode:(n,e)=>({type:"text",text:n,marks:e||void 0}),createNode:(n,e,t)=>{let r={type:n,attrs:e||void 0,content:t||void 0};return(!e||Object.keys(e).length===0)&&delete r.attrs,r},applyMark:(n,e,t)=>({mark:n,content:e,attrs:t&&Object.keys(t).length>0?t:void 0})}}escapeRegex(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}parseInlineTokens(n){var e,t,r,s;let i=[];for(let o=0;o|\/|$)/i);if(!c&&u&&!/\/>$/.test(a)){let h=u[1],f=this.escapeRegex(h),p=new RegExp(`^<\\/\\s*${f}\\b`,"i"),m=-1,g=[a];for(let y=o+1;y0;o-=1){let l=i[o],a=i[o-1];if(l.type==="text"&&a.type==="text"){let c=l.marks||[],u=a.marks||[];uh(c,u)&&(a.text=(a.text||"")+(l.text||""),i.splice(o,1))}}return i}applyMarkToContent(n,e,t){return e.map(r=>{if(r.type==="text"){let s=r.marks||[],i=t?{type:n,attrs:t}:{type:n};return{...r,marks:[...s,i]}}return{...r,content:r.content?this.applyMarkToContent(n,r.content,t):void 0}})}isMarkResult(n){return n&&typeof n=="object"&&"mark"in n}normalizeParseResult(n){return n?this.isMarkResult(n)?n.content:n:null}parseFallbackToken(n,e=!1){switch(n.type){case"paragraph":return{type:"paragraph",content:n.tokens?this.parseInlineTokens(n.tokens):[]};case"heading":return{type:"heading",attrs:{level:n.depth||1},content:n.tokens?this.parseInlineTokens(n.tokens):[]};case"text":return{type:"text",text:wl(n.text||"")};case"html":return this.parseHTMLToken(n);case"escape":return{type:"text",text:n.text||""};case"space":return null;default:return n.tokens?this.parseTokens(n.tokens,e):null}}parseHTMLToken(n){let e=n.text||n.raw||"";if(!e.trim())return null;if(typeof window>"u")return n.block?{type:"paragraph",content:[{type:"text",text:e}]}:{type:"text",text:e};if(this.isUnrecognizedHtml(e))return this.htmlAsLiteralText(e,!!n.block);try{let t=Fd(e,this.baseExtensions);return t.type==="doc"&&t.content?n.block?t.content:t.content.length===1&&t.content[0].type==="paragraph"&&t.content[0].content?t.content[0].content:t.content:t}catch(t){throw new Error(`Failed to parse HTML in markdown: ${t}`)}}isUnrecognizedHtml(n){if(typeof window>"u"||typeof window.DOMParser>"u")return!1;let t=new window.DOMParser().parseFromString(`${n}`,"text/html").body.querySelectorAll("*");if(t.length===0)return!1;let r=this.getSchemaParseDomTags();return Array.from(t).some(s=>{if(!k1(s))return!1;let i=s.tagName.toLowerCase();return!r.has(i)})}getSchemaParseDomTags(){if(this.schemaParseDomTagsCache)return this.schemaParseDomTagsCache;let n=new Set;try{let e=gl(this.baseExtensions),t=r=>{let s=r?.parseDOM;Array.isArray(s)&&s.forEach(i=>{if(typeof i?.tag=="string"){let o=i.tag.match(/^[a-zA-Z][\w-]*/);o&&n.add(o[0].toLowerCase())}})};Object.values(e.nodes).forEach(r=>t(r.spec)),Object.values(e.marks).forEach(r=>t(r.spec))}catch{}return this.schemaParseDomTagsCache=n,n}htmlAsLiteralText(n,e){let t=n.replace(/\s+$/,"");return t?e?{type:"paragraph",content:[{type:"text",text:t}]}:{type:"text",text:t}:null}encodeTextForMarkdown(n,e,t){return t?.type!=null&&this.codeTypes.has(t.type)||(e.marks||[]).some(s=>this.codeTypes.has(typeof s=="string"?s:s.type))?n:this.escapeMarkdownSyntax(ch(n))}escapeMarkdownSyntax(n){return n.replace(/([\\`*_[\]~])/g,"\\$1")}renderNodeToMarkdown(n,e,t=0,r=0,s={}){var i;if(n.type==="text")return this.encodeTextForMarkdown(n.text||"",n,e);if(!n.type)return"";let o=this.getHandlerForToken(n.type);if(!o)return"";let l=Array.isArray(e?.content)&&t>0?e.content[t-1]:void 0,a={renderChildren:(d,h)=>{let f=o.isIndenting?r+1:r;return!Array.isArray(d)&&d.content?this.renderNodes(d.content,n,h||"",t,f):this.renderNodes(d,n,h||"",t,f)},renderChild:(d,h)=>{let f=o.isIndenting?r+1:r;return this.renderNodeToMarkdown(d,n,h,f)},indent:d=>this.indentString+d,wrapInBlock:h1},c={index:t,level:r,parentType:e?.type,previousNode:l,meta:{parentAttrs:e?.attrs,...s}};return((i=o.renderMarkdown)==null?void 0:i.call(o,n,a,c))||""}renderNodes(n,e,t="",r=0,s=0){return Array.isArray(n)?this.renderNodesWithMarkBoundaries(n,e,t,s):n.type?this.renderNodeToMarkdown(n,e,r,s):""}renderNodesWithMarkBoundaries(n,e,t="",r=0){let s=[],i=new Map,o=new Set,l=new Map;return n.forEach((a,c)=>{let u=c[x.type,x])),f=this.getMarksToOpenForSerialization(i,h,u),p=f1(h,u),m=p.filter(x=>i.has(x)),g=m.length>0&&f.length>0,y="";if(p.length>0&&!g){let x=d.match(/(\s+)$/);x&&(y=x[1],d=d.slice(0,-y.length))}g||p.slice().reverse().forEach(x=>{if(!i.has(x))return;let M=h.get(x),T=this.getMarkClosing(x,M,l.get(x));T&&(d+=T),i.has(x)&&(i.delete(x),l.delete(x))});let k="";if(f.length>0){let x=d.match(/^(\s+)/);x&&(k=x[1],d=d.slice(k.length))}f.forEach(({type:x,mark:M})=>{let T=o.has(x)?"html":"markdown",R=this.getMarkOpening(x,M,T);R&&(d=R+d),l.set(x,T),o.delete(x)}),g||f.slice().reverse().forEach(({type:x,mark:M})=>{i.set(x,M)}),d=k+d;let w;if(g){let x=new Set((u?.marks||[]).map(R=>R.type));f.forEach(({type:R})=>{x.has(R)&&this.getHtmlReopenTags(R)&&o.add(R)});let M=Array.from(i.keys()),T=m.slice().sort((R,P)=>M.indexOf(P)-M.indexOf(R));w=[...f.map(R=>R.type),...T]}else w=m1(i,h,u,this.markSetsEqual.bind(this));let C="";if(w.length>0){let x=d.match(/(\s+)$/);x&&(C=x[1],d=d.slice(0,-C.length))}w.forEach(x=>{var M;let T=(M=i.get(x))!=null?M:h.get(x),R=this.getMarkClosing(x,T,l.get(x));R&&(d+=R),i.delete(x),l.delete(x)}),d+=C,d+=y,s.push(d)}else{let d=new Set((a.marks||[]).map(y=>y.type)),h=new Map,f=new Map;i.forEach((y,k)=>{var w;d.has(k)&&(h.set(k,y),f.set(k,(w=l.get(k))!=null?w:"markdown"))});let p=g1(i,(y,k)=>this.getMarkClosing(y,k,l.get(y)));l.clear();let m=this.renderNodeToMarkdown(a,e,c,r),g=a.type==="hardBreak"?"":y1(h,i,(y,k)=>{var w;let C=(w=f.get(y))!=null?w:"markdown";return l.set(y,C),this.getMarkOpening(y,k,C)});s.push(p+m+g)}}),s.join(t)}getMarkOpening(n,e,t="markdown"){var r;if(t==="html")return((r=this.getHtmlReopenTags(n))==null?void 0:r.open)||"";let s=this.getHandlersForNodeType(n),i=s.length>0?s[0]:void 0;if(!i||!i.renderMarkdown)return"";let o="\uE000__TIPTAP_MARKDOWN_PLACEHOLDER__\uE001",l={type:n,attrs:e.attrs||{},content:[{type:"text",text:o}]};try{let a=i.renderMarkdown(l,{renderChildren:()=>o,renderChild:()=>o,indent:u=>u,wrapInBlock:(u,d)=>u+d},{index:0,level:0,parentType:"text",meta:{}}),c=a.indexOf(o);return c>=0?a.substring(0,c):""}catch(a){throw new Error(`Failed to get mark opening for ${n}: ${a}`)}}getMarkClosing(n,e,t="markdown"){var r;if(t==="html")return((r=this.getHtmlReopenTags(n))==null?void 0:r.close)||"";let s=this.getHandlersForNodeType(n),i=s.length>0?s[0]:void 0;if(!i||!i.renderMarkdown)return"";let o="\uE000__TIPTAP_MARKDOWN_PLACEHOLDER__\uE001",l={type:n,attrs:e.attrs||{},content:[{type:"text",text:o}]};try{let a=i.renderMarkdown(l,{renderChildren:()=>o,renderChild:()=>o,indent:d=>d,wrapInBlock:(d,h)=>d+h},{index:0,level:0,parentType:"text",meta:{}}),c=a.indexOf(o),u=c+o.length;return c>=0?a.substring(u):""}catch(a){throw new Error(`Failed to get mark closing for ${n}: ${a}`)}}getHtmlReopenTags(n){let e=this.getHandlersForNodeType(n),t=e.length>0?e[0]:void 0;return t?.htmlReopen}markSetsEqual(n,e){return n.size!==e.size?!1:Array.from(n.entries()).every(([t,r])=>{let s=e.get(t);return s&&Tt(r.attrs,s.attrs)})}getMarksToOpenForSerialization(n,e,t){let r=p1(n,e);if(r.length<=1)return r;let s=t?.marks||[],i=(c,u)=>s.some(d=>d.type===c&&Tt(d.attrs,u)),o=(c,u)=>{var d,h;let f=(d=this.extensionRanks.get(c.type))!=null?d:Number.MAX_SAFE_INTEGER,p=(h=this.extensionRanks.get(u.type))!=null?h:Number.MAX_SAFE_INTEGER;return f!==p?p-f:c.type.localeCompare(u.type)},l=r.filter(c=>!i(c.type,c.mark.attrs)).sort(o),a=r.filter(c=>i(c.type,c.mark.attrs)).sort(o);return[...l,...a]}},Fp=b1,x1=B.create({name:"markdown",addOptions(){return{indentation:{style:"space",size:2},marked:void 0,markedOptions:{}}},addCommands(){return{setContent:(n,e)=>{if(!e?.contentType)return Le.setContent(n,e);if(Vi(n,e?.contentType)!=="markdown"||!this.editor.markdown)return Le.setContent(n,e);let r=this.editor.markdown.parse(n);return Le.setContent(r,e)},insertContent:(n,e)=>{if(!e?.contentType)return Le.insertContent(n,e);if(Vi(n,e?.contentType)!=="markdown"||!this.editor.markdown)return Le.insertContent(n,e);let r=this.editor.markdown.parse(n);return Le.insertContent(r,e)},insertContentAt:(n,e,t)=>{if(!t?.contentType)return Le.insertContentAt(n,e,t);if(Vi(e,t?.contentType)!=="markdown"||!this.editor.markdown)return Le.insertContentAt(n,e,t);let s=this.editor.markdown.parse(e);return Le.insertContentAt(n,s,t)}}},addStorage(){return{manager:new Fp({indentation:this.options.indentation,marked:this.options.marked,markedOptions:this.options.markedOptions,extensions:[]})}},onBeforeCreate(){var n;if(this.editor.markdown){console.error("[tiptap][markdown]: There is already a `markdown` property on the editor instance. This might lead to unexpected behavior.");return}if(this.storage.manager=new Fp({indentation:this.options.indentation,marked:this.options.marked,markedOptions:this.options.markedOptions,extensions:this.editor.extensionManager.baseExtensions}),this.editor.markdown=this.storage.manager,this.editor.getMarkdown=()=>this.storage.manager.serialize(this.editor.getJSON()),!this.editor.options.contentType||Vi(this.editor.options.content,this.editor.options.contentType)!=="markdown")return;if(!this.editor.markdown)throw new Error('[tiptap][markdown]: The `contentType` option is set to "markdown", but the Markdown extension is not added to the editor. Please add the Markdown extension to use this feature.');if(this.editor.options.content===void 0||typeof this.editor.options.content!="string")throw new Error('[tiptap][markdown]: The `contentType` option is set to "markdown", but the initial content is not a string. Please provide the initial content as a markdown string.');let t=this.editor.markdown.parse(this.editor.options.content);(n=t.content)!=null&&n.length&&(this.editor.options.content=t)}});export{W as CellSelection,lk as Editor,Sx as Image,vb as Link,x1 as Markdown,bx as Placeholder,kx as StarterKit,Sp as Table,ww as TableCell,Sw as TableHeader,q as TableMap,xw as TableRow,vw as TaskItem,Cw as TaskList}; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 000000000..a0f6db612 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "celbridge", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} From 23a4e23a5f080c9d832c8fe06b8b6bdbe72589d1 Mon Sep 17 00:00:00 2001 From: Chris Gregan Date: Tue, 16 Jun 2026 11:40:42 +0100 Subject: [PATCH 14/21] Dialog automation: use DialogKind and related fixes Add dialog kind routing and robustness for automated dialog answers, plus a few ancillary fixes. - Dialog: DialogAnswerMessage now carries a DialogKind; DialogAnswerScheduler and all dialog views use the kind to target answers and the scheduler yields before broadcasting to avoid missing handlers. Unit tests updated to assert the Kind. - Tools/File: ComputeHashAsync result is checked for failure and the returned SHA-256 is normalized to lowercase to match git/sha256sum/workshop conventions. - Tools/Package: Before duplicate-checking an install, rescan project packages so the registry reflects packages installed earlier in the session; added required project service using. - PackageService: RescanProjectPackagesAsync simplified to refresh discovery only and documented why it does not re-fire initialization messages or rewrite project-load state. - Python tests: replace the skipped/placeholder test with an integration test asserting the tool rejects an invalid dialog kind. These changes improve test automation reliability and make package/hash behavior consistent across tools. --- .../Dialog/DialogMessages.cs | 12 ++++------ .../Tools/File/FileTools.GetInfo.cs | 18 +++++++++++--- .../Tools/Package/PackageTools.Install.cs | 11 +++++++++ .../Helpers/DialogAnswerScheduler.cs | 11 ++++++--- .../Views/Dialogs/AlertDialog.xaml.cs | 5 ++++ .../Views/Dialogs/ConfirmationDialog.xaml.cs | 5 ++++ .../Views/Dialogs/InputTextDialog.xaml.cs | 5 ++++ .../Dialogs/ResourcePickerDialog.xaml.cs | 5 ++++ .../UserInterface/DialogServiceAnswerTests.cs | 2 ++ .../Services/PackageService.cs | 22 +++++------------- .../Python/celbridge-0.1.0-py3-none-any.whl | Bin 51970 -> 51958 bytes .../integration_tests/test_answer_dialog.py | 15 ++++++------ 12 files changed, 74 insertions(+), 37 deletions(-) diff --git a/Source/Core/Celbridge.Foundation/Dialog/DialogMessages.cs b/Source/Core/Celbridge.Foundation/Dialog/DialogMessages.cs index cf5e57321..0fcb8bea3 100644 --- a/Source/Core/Celbridge.Foundation/Dialog/DialogMessages.cs +++ b/Source/Core/Celbridge.Foundation/Dialog/DialogMessages.cs @@ -1,11 +1,9 @@ namespace Celbridge.Dialog; /// -/// Broadcast by IDialogService to answer the currently-displayed modal dialog -/// on behalf of an automated test. The payload is interpreted by whichever -/// dialog is listening: an empty payload answers a confirmation dialog -/// affirmatively; a non-empty string is the text for an input-text dialog. -/// A dialog that receives a payload incompatible with its own contract logs -/// a warning and continues to block on the user. +/// Broadcast by IDialogService to deliver a scheduled automated answer to the +/// open modal dialog of the named kind. Kind identifies the target dialog; +/// Payload carries the answer data for that dialog. Used only by the debug-only +/// dialog test automation. /// -public record DialogAnswerMessage(string Payload); +public record DialogAnswerMessage(DialogKind Kind, string Payload); diff --git a/Source/Core/Celbridge.Tools/Tools/File/FileTools.GetInfo.cs b/Source/Core/Celbridge.Tools/Tools/File/FileTools.GetInfo.cs index 4292e9f76..d8b696dec 100644 --- a/Source/Core/Celbridge.Tools/Tools/File/FileTools.GetInfo.cs +++ b/Source/Core/Celbridge.Tools/Tools/File/FileTools.GetInfo.cs @@ -10,8 +10,10 @@ namespace Celbridge.Tools; /// Absence is signalled by sidecar_status = "none" with sidecar = null. /// IsReadOnly reflects the filesystem read-only attribute — a true value /// means write operations will fail until the attribute is cleared with -/// file_set_writeable. Hash is the uppercase-hex SHA-256 of the file's -/// bytes when computeHash was set; null otherwise. +/// file_set_writeable. Hash is the lowercase-hex SHA-256 of the file's +/// bytes when computeHash was set; null otherwise. Lowercase matches the +/// git / sha256sum / workshop content_hash convention, so the value compares +/// directly with those surfaces. ///
    public record class FileInfoResult( string Type, @@ -113,6 +115,16 @@ private async Task> ComputeFileHashAsync(ResourceKey resourceKey) } var resourceFileSystem = workspaceWrapper.WorkspaceService.ResourceService.FileSystem; - return await resourceFileSystem.ComputeHashAsync(resourceKey); + var hashResult = await resourceFileSystem.ComputeHashAsync(resourceKey); + if (hashResult.IsFailure) + { + return hashResult; + } + var hash = hashResult.Value; + + // Convert.ToHexString (the gateway's hash) is uppercase; lower it so the + // value matches the git / sha256sum / workshop content_hash convention an + // agent compares against. + return hash.ToLowerInvariant(); } } diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs index 0108cdcb4..fb8e532df 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs @@ -1,4 +1,5 @@ using System.Text.Json; +using Celbridge.Projects; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; using Path = System.IO.Path; @@ -64,6 +65,16 @@ public async partial Task Install( // copies. Refuse before downloading and name the existing location. if (packageFolder.Root == ResourceKey.DefaultRoot) { + // The registry is only populated at workspace load, so without a + // rescan a package installed earlier in this session would not be + // seen by the duplicate check below. + var projectService = GetRequiredService(); + var currentProject = projectService.CurrentProject; + if (currentProject is not null) + { + await workspaceService.PackageService.RescanProjectPackagesAsync(currentProject.ProjectFolderPath); + } + var duplicateCheck = CheckForDuplicateProjectPackage( workspaceService.PackageService, resourceRegistry, diff --git a/Source/Core/Celbridge.UserInterface/Helpers/DialogAnswerScheduler.cs b/Source/Core/Celbridge.UserInterface/Helpers/DialogAnswerScheduler.cs index 0e6a4af7a..f933ad926 100644 --- a/Source/Core/Celbridge.UserInterface/Helpers/DialogAnswerScheduler.cs +++ b/Source/Core/Celbridge.UserInterface/Helpers/DialogAnswerScheduler.cs @@ -81,10 +81,10 @@ public void OnDialogShown(DialogKind dialogKind) _delayMs = 0; } - _ = DelayThenBroadcastAsync(payload, delayMs); + _ = DelayThenBroadcastAsync(dialogKind, payload, delayMs); } - private async Task DelayThenBroadcastAsync(string payload, int delayMs) + private async Task DelayThenBroadcastAsync(DialogKind dialogKind, string payload, int delayMs) { // The await captures the calling SynchronizationContext (the UI thread // when OnDialogShown is invoked from a Show* method), so the broadcast @@ -92,11 +92,16 @@ private async Task DelayThenBroadcastAsync(string payload, int delayMs) // thread without explicit marshalling. try { + // Yield unconditionally before broadcasting. OnDialogShown runs + // before the dialog's Show* method registers its handler, so a + // delayMs of 0 would otherwise Send synchronously here, before any + // dialog is listening, and the answer would be lost. + await Task.Yield(); if (delayMs > 0) { await Task.Delay(delayMs); } - _messengerService.Send(new DialogAnswerMessage(payload)); + _messengerService.Send(new DialogAnswerMessage(dialogKind, payload)); } catch (Exception ex) { diff --git a/Source/Core/Celbridge.UserInterface/Views/Dialogs/AlertDialog.xaml.cs b/Source/Core/Celbridge.UserInterface/Views/Dialogs/AlertDialog.xaml.cs index 49eecd7fc..84dcd58c7 100644 --- a/Source/Core/Celbridge.UserInterface/Views/Dialogs/AlertDialog.xaml.cs +++ b/Source/Core/Celbridge.UserInterface/Views/Dialogs/AlertDialog.xaml.cs @@ -56,6 +56,11 @@ public async Task ShowDialogAsync() private void OnDialogAnswer(object recipient, DialogAnswerMessage message) { + if (message.Kind != DialogKind.Alert) + { + return; + } + _logger.LogInformation("Alert dialog answered automatically."); Hide(); } diff --git a/Source/Core/Celbridge.UserInterface/Views/Dialogs/ConfirmationDialog.xaml.cs b/Source/Core/Celbridge.UserInterface/Views/Dialogs/ConfirmationDialog.xaml.cs index a567b9252..25cee8568 100644 --- a/Source/Core/Celbridge.UserInterface/Views/Dialogs/ConfirmationDialog.xaml.cs +++ b/Source/Core/Celbridge.UserInterface/Views/Dialogs/ConfirmationDialog.xaml.cs @@ -63,6 +63,11 @@ public async Task ShowDialogAsync() private void OnDialogAnswer(object recipient, DialogAnswerMessage message) { + if (message.Kind != DialogKind.Confirmation) + { + return; + } + _autoAnswered = true; _logger.LogInformation("Confirmation dialog answered automatically."); Hide(); diff --git a/Source/Core/Celbridge.UserInterface/Views/Dialogs/InputTextDialog.xaml.cs b/Source/Core/Celbridge.UserInterface/Views/Dialogs/InputTextDialog.xaml.cs index 82d26bfd7..628f1e224 100644 --- a/Source/Core/Celbridge.UserInterface/Views/Dialogs/InputTextDialog.xaml.cs +++ b/Source/Core/Celbridge.UserInterface/Views/Dialogs/InputTextDialog.xaml.cs @@ -109,6 +109,11 @@ public void SetDefaultText(string defaultText, Range selectionRange) private void OnDialogAnswer(object recipient, DialogAnswerMessage message) { + if (message.Kind != DialogKind.InputText) + { + return; + } + _autoAnswered = true; ViewModel.InputText = message.Payload; _logger.LogInformation($"Input-text dialog answered automatically with '{message.Payload}'."); diff --git a/Source/Core/Celbridge.UserInterface/Views/Dialogs/ResourcePickerDialog.xaml.cs b/Source/Core/Celbridge.UserInterface/Views/Dialogs/ResourcePickerDialog.xaml.cs index 87dcf7717..322405e79 100644 --- a/Source/Core/Celbridge.UserInterface/Views/Dialogs/ResourcePickerDialog.xaml.cs +++ b/Source/Core/Celbridge.UserInterface/Views/Dialogs/ResourcePickerDialog.xaml.cs @@ -114,6 +114,11 @@ public async Task> ShowDialogAsync() private void OnDialogAnswer(object recipient, DialogAnswerMessage message) { + if (message.Kind != DialogKind.ResourcePicker) + { + return; + } + if (!ResourceKey.TryCreate(message.Payload, out var targetKey)) { _logger.LogWarning( diff --git a/Source/Tests/UserInterface/DialogServiceAnswerTests.cs b/Source/Tests/UserInterface/DialogServiceAnswerTests.cs index 9a968fee0..d40163e48 100644 --- a/Source/Tests/UserInterface/DialogServiceAnswerTests.cs +++ b/Source/Tests/UserInterface/DialogServiceAnswerTests.cs @@ -75,6 +75,7 @@ public async Task ShowingConfirmationDialog_FiresScheduledAnswer() var sent = _messengerService.Sent(); sent.Should().HaveCount(1); + sent[0].Kind.Should().Be(DialogKind.Confirmation); sent[0].Payload.Should().BeEmpty(); } @@ -89,6 +90,7 @@ await _dialogService.ShowInputTextDialogAsync( var sent = _messengerService.Sent(); sent.Should().HaveCount(1); + sent[0].Kind.Should().Be(DialogKind.InputText); sent[0].Payload.Should().Be("Renamed.txt"); } diff --git a/Source/Workspace/Celbridge.Packages/Services/PackageService.cs b/Source/Workspace/Celbridge.Packages/Services/PackageService.cs index 71177db29..44dc026fd 100644 --- a/Source/Workspace/Celbridge.Packages/Services/PackageService.cs +++ b/Source/Workspace/Celbridge.Packages/Services/PackageService.cs @@ -47,22 +47,12 @@ public async Task RegisterPackagesAsync(string projectFolderPath) public async Task RescanProjectPackagesAsync(string projectFolderPath) { - // A rescan re-runs discovery and refreshes the load report but skips - // PackagesInitializedMessage. Editor contributions are registered - // once at workspace load and re-firing the message would replay every - // factory registration, which is not idempotent and would surprise - // unrelated subscribers expecting one-shot initialization. - var report = await _registry.DiscoverPackagesAsync(projectFolderPath); - - _loadReporter.RecordPackageReport(report); - await _loadReporter.FlushAsync(); - - if (report.Failures.Count > 0) - { - var projectName = Path.GetFileName(projectFolderPath) ?? string.Empty; - var message = new ConsoleErrorMessage(ConsoleErrorType.PackageLoadError, projectName); - _messengerService.Send(message); - } + // Refreshes the registry mid-session (e.g. after a tool installs a + // package). Unlike RegisterPackagesAsync this is discovery only: it does + // not fire PackagesInitializedMessage (editor registration is one-shot at + // load), nor rewrite project-load.md or raise the error banner (those + // reflect the last actual load, not a tool-initiated rescan). + await _registry.DiscoverPackagesAsync(projectFolderPath); } public IReadOnlyList GetAllPackages() diff --git a/Source/Workspace/Celbridge.Python/Assets/Python/celbridge-0.1.0-py3-none-any.whl b/Source/Workspace/Celbridge.Python/Assets/Python/celbridge-0.1.0-py3-none-any.whl index eb7aa8e1589e241d113e6633880cf6b63fc9c01a..33e14dc0ffa6b682545ccd68ac8264882f1689b2 100644 GIT binary patch delta 2933 zcmZ9Oc{tQv8^_1k1|iw9Wyz8)dzL}=eU~lEFbF+NVX|cT88L-H`k}JRE_-&_8O9!x z3?YTY{mFIol>wVvIUH5f=zxVw)=Q`)F`$VRa?xv7dk!aI3T8uf|dWqanCWAl@ zXh3>xMF0^;)Vl!4|ANCd4OuSjwF=xH{KT@GfHF*f^vHse& znJaz-KUeE`JE6;+`-WS7cKEodj76$@V0~IidQy1~8h*)vanb}rtXKQK$FJS%Im<4{ zT;9AhWam#NP#KZgEG_~M$t==O3!T1Cv>*q=fTauSUSo%UOv}5eb3|J86+(A;++Zpg z@K?x+evUf*lyK0vtlcigLvul~7+p)2pdj}2xtn#@`!wV%k#RHSyLYr-C{@GAl$eU5 zntf-h58QD~og_Mfj$a_8G>4bh);uP&NY-vVSI$IlEB1o0ap=-+yAI2CG#cHCbM}w{ zc;oDEW0No2EW_GBc0&V3pA4Z@nZI)48&Pu|>BK^ETT%j4% zQ>bUQs#5COU`D6P8Ec&W;_*bx((}Wwe-j*Nd*Tr}W9E6W);wR?xJJy7@l(AC(~N5b zY)$uI1$&LaRNmOlKrv`99hj?;SRbreDDXq*k<(h__ei?+_mthTP3*;;SRZo0A<3#j zvGI8O!+e#tcsfKyYs2R;&`wETGshB1r)mlk8|W)H=i8g?X3y@g=(e;wH2&peagI9< z>ASbW$EjL>cS>fzU{tPhKPPjH-=Ss1a{j58g*k1A5XU@H9~`al#=zYkiE3IZ)UEFf z%92FHl?e$w5_VI^IK`>vt^>_`!QO`!u$0)Dm$7bJR)<%?l5jDHUvcy>WtA|tgoph~ zZJOGOrOU$pyWQWl-}iteMT_ci8y9Lh4yE^>~-2Zx-BMGtQIsd$?Hj9O3NBv`pKCmtQ=N@_bjX3kxT>#E7-#sVY?j! zt5hhGyPH#Wt4}O+RJE6_bCE)K$dpylJ$pMg5l+1FO+%ed9C3~7XMVPs{@bgF+8|lF zQ+o!RTkw4o^3gyvHQSVd-_|LK)FJBu{Kc`+;s_z0Eaa{%-+_-;bnL{FYh66kG@|?2 zd@C}1Q({gMT1WMXMdQH7HR;Jy&5sO+_w+(42K}E0JjTABh^O_hx#)J&I8dIVKQBu31e)5T%Krq$3- z;4-yWN#$z3MSUKS)rm{DdXZXnZ`us~nyPz@_S#6>8X(=6)zqw!aI)we&%Rr2;_`rV ziG0b$wp;S;VVI1Ye-m?WDYl?r)FmZ5X}4^+>aQ=F7*oUH#x?|Me!cVCaEONgbuZ66 zfaFc0ewYs_#jUY7^bJ_j-vWHmoO+(6S^~~1td;y=lo5rjO-1h}FjAF$zEYrg!~vPiVTedX zbz}MX*mkpjjBx3ZwigEuZfIE@;|RZK;bFzm9u6#7tkG5rRHMVPQmOl*AFJ13azK@q z{QCBtyg8y|+Ck=@AF#mUryoez#EbVvs6ooof>pw-29D1Qr5oorjmwEG6cl!Dccl=m z2yvIlU%8glTOi5gu#A#7_GuMU!8C;{YYV3zzVSdZjaI?$Kt;!PHkvo?4|yt&eAO1* zs56pCTq~|kT;clS$yljAd>s=$C2%9OfjNHV_eT|eMytuc0sYZ3sD2;QaB zIFBi+q!XpXpM-fQiDL(&F6V%_${EF_Hwroozp_4dS3pTQi%@-5aS|uFna5B^dwEfk?z9rr9J`rvAb8stN0WYPnCva+WyB!5r-N|||Aw6bx>lMHvn9$qSHQ1Slheh-+Q02g+ zRFlivL@sxlPd-0_T63-MEeYq18sbsGXaZXitxJo+aVD}`S~YoXPU!f%Xze@miWTy! z*DxoSPQR0x%ZOD`3dfcPAe0>~jRO}tl7O}bB3v))mDxjG-{QCL1>_BH&Pnj3WfIfz z*D~AzXJBntJ0I%0!6{l({*r)O6S$}qNAroG-Wz3_XsBLcci*d3m`yTrIcjs?OI8Pt z@o8_Y8#%(vQzU$R@*G}f$r_Ye|7lGZs8Cj-Y3Q?DHNrV7#QwtM8W$dno@k=ok2U;j z@^k6TM>G64t^7>KXg}=4v8ZUaO+9r(A#jnla$t*b{W-;DTAZcJ3n%DYc9HJ2Kj zlFvAEN~QI^-1OOx8oybeQXNBFV&;lz;hW%z3T*gu9JS>V0OotWOXdT}jrhHWA{fLv zfy&ia`M9mH8+;O32xz6ZO>+@(z=#tIur~fW32sAS)88&l>D!>WP{lpA2}dz`FBgycW_swj*iq7}^y^^6+rc zy_pVX$*#MS?9X!3Si?baSM5zrY=RtuN7?W}yYah-o0*=-8UP*wVz z@NlQgHp%RXc+cL`(r*6}9E?%0*QH$gfMl?Bu~{ZPQ4d|<7G}qj-mTfaXX%@`^-ksF zP6O(ODkTR=bPp}F4GooTKIk`G^(Fl2c0|MKYn@ebE+qfRB}7h`u{myj7KVj=RWOeh z+819Abh1D07$_mQ#()u7Q+V(=o+EVpwzhL?gK zdjDIJfg*jo7Rl#@tYkXyuwMk~ zm49vx2rN9mXNO~P@{B$y1VRggK={ty@XxBp?9_u11WE4SkdV=GtixHy{w%ovAm|co(*t7SzAE07BNtN;KKRQ;xSJiUpS+v&qC;bc0K(Ka)L<%Z0F$l{33@y YY|luZ{{m|CKzq>Uc-oJi85N0(JoOU7eRzU{FFqZ-H_2q5L}EBK}L&F zXOyQT(M1=c*IZ=;PrP;4y6?TS)?WKNdw<_vd!2v2v!4QqOodcZ=rE|b4cH;$wW$oC zAP^TFmQhCq=!+FlVj?L0P-DHp&b2;$4AoBUmNaZTtz1iNsj3+^`_Nb^u3+hTHo41$ zX;U{yoZvl(udZId0^Z>!Sr_8Air|)}tnacjs5JH5k;gn$wgU2Rv;yo4v)7Zvw0p=3 zhIAt*!JDsd+(gpkl4olXiGhQ%1H0=B(G3jrhWq6}E%gKBnf~9==f^2satjpk z4mU^VMUbfjl|HFm#;_ghz0HfNyOtDKu;W_wBxA(-8JkO#NSYyHuS_IXJ>ToQ3NkQW z^zAXtX(6Bd1kMFeZWG;Q*eEUjr2YH&+l|CUZfR?KQ}ek9xISHPbry^KSfU(~(jLf- zp2s(z9LzgC|_eBdS(`Hm9ds{E8+m&9IL#Oe`~%ZY^*-K&F9sIRi!cemrwrkmkbBNW=1M$CBiVN)(1mcygUKI1#19|E6whl{x?dJK zi+SxR^PVsDq3ir!2=qhX63{So);`Wmb}wsM-u+meV7b<#WJHX;_xs9N8`q&kKk$-D zH5N-{IKLej+&lZDE&_MLpjBI}cjw_u@~Y2C%)Jk-kTW1d;$@$-(o?gkNh}X zyHi_WHIKWE@xK!L+4jr0nlZdGHY9mz+XsY-(ghJ3=(%EF+#3TDls1u>hc->)4#K$A z8$t_=>+yMP4!$;rVXSu<9fQ15pS|yO?Eb@2ulOiIF!;9|C9Vzo=bvT0A2ln|0S7Vc z=INv|H$OcMl!T7S2%9sjfxnt|XnD>inQ&0AeJgNKJl z2F&Pt|JoD0JwBNeN}C@PJtf0p9Z}&k>h;`3cMpRYkXnSJ4W{RRY>1L; z`}k6KNYUwOyYkOwV}_Tfh4*R}^GQKnL77#!VB^~q7R#{xxhdDn^>v?Yz2;z@vy-(y z9q*qsY#a``D~f-;rEMA$45Zg79y1J3b6GiNp!kkuteVqc{rHT5--^S#y z8N=x7FWA^a`*es;O$Pk6D#jNk$>y?7(7u!K99@?h7Uljd^qM&A&}%k5AtHGpwu zWc0zwx=D(O``msg&Fg2(bb8F1(ly-{XVbt7$=^}S-l_6Ke5)Deo6UDNFEz}4dDDTO z94xl}U4qAoR)B*SIID(M*bnbFOW-_{s_qGRNI6IPduzI3`iP@`#W4%z??q?!xcQr< z?PlQV;d`3tmeGhhmf}#>fLm-@xuYcn-Hu~|1!#5RPRVbaJfi-K3%2-WzZPhA2OtEJ z);YFG`Hn+nU{=}6d8Py`7;I6hQzW9oSw%S-v-{9$>n#A8afX_l!jsNC5JN~%7OTS1 zX}S6me^Q$FL?o39``S+p4s|{IE#t)}B7=FKbi9IB^B9+y^D)^+)e<=<@>jg9l5Yqh zU%=P=9_Q2TrD6LCBZ}UNSMm}KX5Y4`SPWFZY;koM~ai~3~V8zYi@rGROyc`TG)TWptfSUGx$hCu>3MS;2maz9MM1qi|-{8|F zW|CR#U7nMz)W&UK|<9he5wY|Tj+nvS;DHW35 z0BDQWbfBEu0xibkPbeGM6c)A>Di(lC91+%JT`-1e(|MjrDsYwKs>~@3RX`^_009nX(2&!zUU2dhv4 zLRM|5K3>da%<+s8b%z4X3StF;K-8e>%vMpz|Na+0clc1e#A*>Oz%TD$^GE{N2BHiY zZhHZlNDAQ3b{Ei-q(D)M%_UiaslCn%!dTu;SqderX{R&z+?0Rma>Hq`bDaoqPvpf_ zx=S96kGX&`U2 Date: Wed, 17 Jun 2026 07:55:37 +0100 Subject: [PATCH 15/21] Store Workshop Key in editor settings Migrate Workshop credential storage from a standalone file to user-scoped IEditorSettings: WorkshopKeyProtected (ciphertext) and WorkshopKeyHint (non-secret display hint). Rename ApplicationKey concepts to WorkshopKey across APIs and resources; update ICredentialService methods (summary/get/set/clear) to operate on the Workshop Key string and add clearer error messages. Introduce fixed entropy bound for credential protection and require an entropy parameter on ICredentialProtector (and DPAPI implementation) so ciphertexts are scoped. Refactor CredentialService to use IEditorSettings and ICredentialProtector Protect/Unprotect helpers, remove the JSON store logic, and add helper methods for base64 protection/unprotection. Add StatusSeverity enum and converter, add Workshop settings view/viewmodel/tests, update service registrations to use WorkshopSettingsViewModel, and change publish-author resolution to read WorkshopAuthor from IEditorSettings (remove PublishAuthor helper). Adjust package version resolution semantics and update related docs and resource strings (various wording and key renames). Update tests and client/service code to match the new credential handling and naming. --- .../Resources/Strings/en-US/Resources.resw | 56 ++- .../Credentials/CredentialConstants.cs | 4 +- .../Credentials/ICredentialService.cs | 46 +-- .../Packages/IPackageApiClient.cs | 4 +- .../Settings/IEditorSettings.cs | 33 ++ .../UserInterface/StatusSeverity.cs | 29 ++ .../Services/CredentialService.cs | 373 +++++------------- .../Services/DpapiCredentialProtector.cs | 8 +- .../Services/EditorSettings.cs | 24 ++ .../Services/ICredentialProtector.cs | 13 +- .../Guides/Tools/package_delete.md | 2 +- .../Guides/Tools/package_install.md | 4 +- .../Guides/Tools/package_list.md | 2 +- .../Helpers/PackageVersionResolver.cs | 33 +- .../Celbridge.Tools/Helpers/PublishAuthor.cs | 33 -- .../Tools/Package/PackageTools.Delete.cs | 2 +- .../Tools/Package/PackageTools.Install.cs | 2 +- .../Tools/Package/PackageTools.Publish.cs | 1 - .../Tools/Package/PackageTools.cs | 25 +- .../Celbridge.Tools/Tools/Page/PageTools.cs | 27 +- .../Converters/StatusSeverityConverter.cs | 23 ++ .../Helpers/WorkshopConnectionValidation.cs | 6 +- .../ServiceConfiguration.cs | 2 +- .../Controls/WorkshopSettingsViewModel.cs | 346 ++++++++++++++++ .../ViewModels/Pages/SettingsPageViewModel.cs | 283 ------------- .../Views/Controls/WorkshopSettingsView.xaml | 107 +++++ .../Controls/WorkshopSettingsView.xaml.cs | 138 +++++++ .../Views/Pages/SettingsPage.xaml | 97 +---- .../Views/Pages/SettingsPage.xaml.cs | 27 +- .../Tests/Packages/PackageApiClientTests.cs | 44 ++- Source/Tests/Packages/PageApiClientTests.cs | 29 +- .../Tests/Settings/CredentialServiceTests.cs | 230 +++-------- .../Settings/DpapiCredentialProtectorTests.cs | 23 +- .../Tools/PackageVersionResolverTests.cs | 82 ++-- .../SettingsPageViewModelTests.cs | 231 ----------- .../WorkshopSettingsViewModelTests.cs | 349 ++++++++++++++++ .../Services/PackageApiClient.cs | 17 +- .../Services/PageApiClient.cs | 9 +- .../Services/WorkshopApiSender.cs | 43 +- 39 files changed, 1454 insertions(+), 1353 deletions(-) create mode 100644 Source/Core/Celbridge.Foundation/UserInterface/StatusSeverity.cs delete mode 100644 Source/Core/Celbridge.Tools/Helpers/PublishAuthor.cs create mode 100644 Source/Core/Celbridge.UserInterface/Converters/StatusSeverityConverter.cs create mode 100644 Source/Core/Celbridge.UserInterface/ViewModels/Controls/WorkshopSettingsViewModel.cs delete mode 100644 Source/Core/Celbridge.UserInterface/ViewModels/Pages/SettingsPageViewModel.cs create mode 100644 Source/Core/Celbridge.UserInterface/Views/Controls/WorkshopSettingsView.xaml create mode 100644 Source/Core/Celbridge.UserInterface/Views/Controls/WorkshopSettingsView.xaml.cs delete mode 100644 Source/Tests/UserInterface/SettingsPageViewModelTests.cs create mode 100644 Source/Tests/UserInterface/WorkshopSettingsViewModelTests.cs diff --git a/Source/Celbridge/Resources/Strings/en-US/Resources.resw b/Source/Celbridge/Resources/Strings/en-US/Resources.resw index 781c39162..0965242d7 100644 --- a/Source/Celbridge/Resources/Strings/en-US/Resources.resw +++ b/Source/Celbridge/Resources/Strings/en-US/Resources.resw @@ -414,16 +414,19 @@ Workshop + + Connect to a Workshop to publish and install packages and pages. + Workshop URL The web address of your Workshop server, where packages and pages are published. - - Application Key + + Workshop Key - + The secret key issued by your Workshop. Keep it private; it authenticates your requests. @@ -435,10 +438,7 @@ Your name - - Save - - + Clear @@ -451,28 +451,43 @@ Credential storage is not available on this platform. The Workshop connection cannot be configured. - The stored Workshop connection could not be read. Enter the Workshop URL and Application Key again, or clear the connection. + The stored Workshop connection could not be read. Enter the Workshop URL and Workshop Key again, or clear the stored key. The Workshop URL must be an https:// address. http:// is only allowed for localhost. - - Enter an Application Key. + + Enter a valid Workshop URL. + + + Enter a Workshop Key. + + + Add an Author Name to publish packages and pages. Failed to save the Workshop connection. - - Failed to clear the Workshop connection. + + Failed to clear the Workshop Key. Workshop connection saved. - - Workshop connection saved. The Application Key does not start with 'kpf_', so check that it was entered correctly. + + Workshop Key cleared. Enter a new key to reconnect. - - Workshop connection cleared. + + Checking connection to the workshop... + + + Connected to the workshop. + + + Couldn't reach the workshop. Check the Workshop URL and Workshop Key. + + + The Workshop Key is invalid. Create directory for project @@ -1065,16 +1080,16 @@ Do you wish to continue? Delete Package Version - Delete version {0} of package '{1}' from the workshop? Its content is removed permanently and cannot be recovered. + Delete version {0} of package '{1}' from the workshop? Its content will be permanently removed and cannot be recovered. - Delete version {0} of package '{1}' from the workshop? Its content is removed permanently and cannot be recovered. Aliases pointing at it will be left dangling or repointed: {2}. + Delete version {0} of package '{1}' from the workshop? Its content will be permanently removed and cannot be recovered. These aliases will be left pointing at the deleted version: {2}. Unpublish Package - Unpublish package '{0}' from the workshop? The package and all its versions are removed permanently and cannot be recovered. + Unpublish package '{0}' from the workshop? The package and all its versions will be permanently removed and cannot be recovered. Publish Page @@ -1091,6 +1106,9 @@ Do you wish to continue? Cannot Publish + + No Author is set. Add one on the Settings page, then try again. + Spreadsheet Editor diff --git a/Source/Core/Celbridge.Foundation/Credentials/CredentialConstants.cs b/Source/Core/Celbridge.Foundation/Credentials/CredentialConstants.cs index 9c4674079..13ae5e4a0 100644 --- a/Source/Core/Celbridge.Foundation/Credentials/CredentialConstants.cs +++ b/Source/Core/Celbridge.Foundation/Credentials/CredentialConstants.cs @@ -6,8 +6,8 @@ namespace Celbridge.Credentials; public static class CredentialConstants { /// - /// The prefix of a well-formed Workshop Application Key, shaped like + /// The prefix of a well-formed Workshop Key, shaped like /// "kpf_(prefix)_(secret)". The prefix identifies the key and is not secret. /// - public const string ApplicationKeyPrefix = "kpf_"; + public const string WorkshopKeyPrefix = "kpf_"; } diff --git a/Source/Core/Celbridge.Foundation/Credentials/ICredentialService.cs b/Source/Core/Celbridge.Foundation/Credentials/ICredentialService.cs index 105e0b68e..4d3b3d9e5 100644 --- a/Source/Core/Celbridge.Foundation/Credentials/ICredentialService.cs +++ b/Source/Core/Celbridge.Foundation/Credentials/ICredentialService.cs @@ -1,23 +1,19 @@ namespace Celbridge.Credentials; /// -/// A Workshop server URL, the Application Key issued by that server, and the -/// Author name recorded as the publisher of packages and pages. +/// Summary of the stored Workshop Key, readable without decrypting it. +/// KeyHint is the identifying prefix of the stored key, or empty when the key +/// has no recognisable prefix or the stored entry is unreadable. /// -public record WorkshopConnection(string WorkshopUrl, string ApplicationKey, string Author = ""); +public record WorkshopKeySummary(bool IsStored, string KeyHint); /// -/// Summary of the stored Workshop connection, readable without decrypting it. -/// KeyHint is the identifying prefix of the stored Application Key, or empty -/// when the key has no recognisable prefix or the stored entry is unreadable. -/// -public record WorkshopConnectionSummary(bool IsStored, string KeyHint); - -/// -/// Application-scoped store for sensitive credentials, encrypted at rest. -/// Stored values are retrievable only by host-side services through this typed -/// API and must never appear on agent-readable surfaces such as tool results, -/// log messages, the WebView, scripting APIs, or subprocess environments. +/// Application-scoped store for secret credentials, encrypted at rest. The store +/// is general purpose, with one typed accessor per credential. Stored values are +/// retrievable only by host-side services through this typed API and must never +/// appear on agent-readable surfaces such as tool results, log messages, the +/// WebView, scripting APIs, or subprocess environments. Only secrets belong here; +/// non-secret configuration belongs in settings. /// public interface ICredentialService { @@ -29,25 +25,25 @@ public interface ICredentialService bool IsAvailable { get; } /// - /// Gets a summary of the stored Workshop connection without decrypting it, - /// so display surfaces can identify the stored key. Reports a stored entry - /// even when it is corrupt, so callers can offer clear and replace. + /// Gets a summary of the stored Workshop Key without decrypting it, so + /// display surfaces can identify the stored key. Reports a stored entry even + /// when it is corrupt, so callers can offer clear and replace. /// - Task> GetWorkshopConnectionSummaryAsync(); + Task> GetWorkshopKeySummaryAsync(); /// - /// Gets the stored Workshop connection. Fails with an actionable message - /// when no connection is stored or the stored entry cannot be read. + /// Gets the stored Workshop Key. Fails with an actionable message when no + /// key is stored or the stored entry cannot be read. /// - Task> GetWorkshopConnectionAsync(); + Task> GetWorkshopKeyAsync(); /// - /// Stores the Workshop connection, replacing any existing one. + /// Stores the Workshop Key, replacing any existing one. /// - Task SetWorkshopConnectionAsync(WorkshopConnection connection); + Task SetWorkshopKeyAsync(string workshopKey); /// - /// Removes the stored Workshop connection. Succeeds when no connection is stored. + /// Removes the stored Workshop Key. Succeeds when none is stored. /// - Task ClearWorkshopConnectionAsync(); + Task ClearWorkshopKeyAsync(); } diff --git a/Source/Core/Celbridge.Foundation/Packages/IPackageApiClient.cs b/Source/Core/Celbridge.Foundation/Packages/IPackageApiClient.cs index b5ab0daf3..680a6feaf 100644 --- a/Source/Core/Celbridge.Foundation/Packages/IPackageApiClient.cs +++ b/Source/Core/Celbridge.Foundation/Packages/IPackageApiClient.cs @@ -53,8 +53,8 @@ public record RemotePublishReceipt( string ContentHash); /// -/// Client for the workshop server's package REST API. The Workshop URL and -/// Application Key are read from the credential store at request time; +/// Client for the workshop server's package REST API. The Workshop URL is read +/// from settings and the Workshop Key from the credential store at request time; /// credential values never appear in parameters, results, or error messages. /// Destructive operations (deleting a version, deleting a package) remove /// content outright; Celbridge does not model the server's tombstone state. diff --git a/Source/Core/Celbridge.Foundation/Settings/IEditorSettings.cs b/Source/Core/Celbridge.Foundation/Settings/IEditorSettings.cs index d178ca4b2..0d4f3ea3b 100644 --- a/Source/Core/Celbridge.Foundation/Settings/IEditorSettings.cs +++ b/Source/Core/Celbridge.Foundation/Settings/IEditorSettings.cs @@ -125,6 +125,39 @@ public interface IEditorSettings : INotifyPropertyChanged /// ApplicationColorTheme Theme { get; set; } + // ======================================== + // Workshop Connection + // User-scoped: these belong to the user and their installation, never to + // a project, so they must not be moved to per-project storage. + // ======================================== + + /// + /// The Workshop server URL. Empty when no Workshop is configured. + /// + string WorkshopUrl { get; set; } + + /// + /// The Author name recorded as the publisher of packages and pages. Empty + /// when none is set. + /// + string WorkshopAuthor { get; set; } + + /// + /// The Workshop Key in encrypted form (base64 of the platform-protected + /// ciphertext). Empty when no key is stored. Managed by ICredentialService + /// and not intended for direct consumption: read and write the key through + /// the service so encryption stays the only path in and out. + /// + string WorkshopKeyProtected { get; set; } + + /// + /// The non-secret display hint for the stored Workshop Key (the prefix of + /// the key up to the second underscore, e.g. "kpf_abc123"). Empty when no + /// hint can be derived. Managed by ICredentialService alongside + /// WorkshopKeyProtected. + /// + string WorkshopKeyHint { get; set; } + // ======================================== // Search Panel Options // ======================================== diff --git a/Source/Core/Celbridge.Foundation/UserInterface/StatusSeverity.cs b/Source/Core/Celbridge.Foundation/UserInterface/StatusSeverity.cs new file mode 100644 index 000000000..5390b7a05 --- /dev/null +++ b/Source/Core/Celbridge.Foundation/UserInterface/StatusSeverity.cs @@ -0,0 +1,29 @@ +namespace Celbridge.UserInterface; + +/// +/// Severity of a status message shown to the user, e.g. in an InfoBar. +/// UI-agnostic so a view model can set it without referencing a presentation +/// framework; a converter maps it to the control's own severity type. +/// +public enum StatusSeverity +{ + /// + /// Neutral information, or an in-progress state. + /// + Informational, + + /// + /// An operation completed successfully. + /// + Success, + + /// + /// A non-blocking caution the user should notice. + /// + Warning, + + /// + /// An operation failed, or input is invalid. + /// + Error +} diff --git a/Source/Core/Celbridge.Settings/Services/CredentialService.cs b/Source/Core/Celbridge.Settings/Services/CredentialService.cs index bace75a3b..2b177e763 100644 --- a/Source/Core/Celbridge.Settings/Services/CredentialService.cs +++ b/Source/Core/Celbridge.Settings/Services/CredentialService.cs @@ -1,387 +1,194 @@ using System.Text; -using System.Text.Json; using Celbridge.Credentials; -using Celbridge.FileSystem; using Celbridge.Logging; namespace Celbridge.Settings.Services; /// -/// On-disk shape of the credential store file: a version number and one -/// protected entry per credential. New credential types are added as nullable -/// entry properties without a version bump; a missing property deserializes as -/// null and reports as not configured. Adding a second entry requires changing -/// Set and Clear from whole-file rewrite and delete to read-modify-write. -/// Version increments only when the document is reshaped, paired with a -/// read-side migration. -/// -internal sealed record CredentialStoreDocument(int Version, WorkshopConnectionEntry? WorkshopConnection); - -/// -/// Stored form of the Workshop connection: the protected connection payload as -/// base64, plus the unprotected key prefix used as a display hint. -/// -internal sealed record WorkshopConnectionEntry(string ProtectedData, string KeyHint); - -/// -/// Stores credentials as platform-protected blobs in a JSON document in the -/// application data folder. All file access is serialized so concurrent -/// callers cannot interleave reads and writes. +/// Stores credentials as platform-protected ciphertext in user-scoped settings. +/// The encrypted bytes live in IEditorSettings (WorkshopKeyProtected) alongside +/// a non-secret display hint (WorkshopKeyHint); encryption is provided by the +/// ICredentialProtector. The settings backing is user-and-installation scoped +/// (Windows LocalSettings), so credentials never travel with a project folder. /// internal sealed class CredentialService : ICredentialService { - private const int StoreVersion = 1; - private const string UnavailableMessage = "Credential storage is not available on this platform"; - private const string NotConfiguredMessage = "No Workshop connection is configured. Enter the Workshop URL and Application Key on the Settings page."; - private const string CorruptStoreMessage = "The stored Workshop connection could not be read. Enter the Workshop URL and Application Key again on the Settings page."; - private const string NewerStoreVersionMessage = "The credential store was written by a newer version of Celbridge and cannot be read by this version."; + private const string NotConfiguredMessage = "No Workshop Key is configured. Enter it on the Settings page."; + private const string UnreadableMessage = "A stored credential could not be read. Enter it again on the Settings page."; - private static readonly JsonSerializerOptions DocumentJsonOptions = new() - { - WriteIndented = true - }; + // Fixed entropy bound into the Workshop Key ciphertext. Not a secret in + // itself; it scopes our protected blob so that a different DPAPI consumer + // running as the same user cannot Unprotect it by accident. The "v1" + // suffix is reserved for future rotation if we ever need to invalidate + // every stored Workshop Key in one step. + private static readonly byte[] WorkshopKeyEntropy = Encoding.UTF8.GetBytes("Celbridge.WorkshopKey.v1"); private readonly ILogger _logger; - private readonly ILocalFileSystem _fileSystem; private readonly ICredentialProtector _protector; - private readonly string _credentialsFilePath; - private readonly SemaphoreSlim _storeSemaphore = new(1, 1); + private readonly IEditorSettings _editorSettings; public CredentialService( ILogger logger, - ILocalFileSystem fileSystem, - ICredentialProtector protector) - : this(logger, fileSystem, protector, GetDefaultCredentialsFilePath()) - {} - - internal CredentialService( - ILogger logger, - ILocalFileSystem fileSystem, ICredentialProtector protector, - string credentialsFilePath) + IEditorSettings editorSettings) { _logger = logger; - _fileSystem = fileSystem; _protector = protector; - _credentialsFilePath = credentialsFilePath; + _editorSettings = editorSettings; } public bool IsAvailable => _protector.IsAvailable; - public async Task> GetWorkshopConnectionSummaryAsync() + public async Task> GetWorkshopKeySummaryAsync() { + await Task.CompletedTask; + if (!IsAvailable) { return Result.Fail(UnavailableMessage); } - await _storeSemaphore.WaitAsync(); - try - { - var infoResult = await _fileSystem.GetInfoAsync(_credentialsFilePath); - if (infoResult.IsFailure) - { - return Result.Fail("Failed to query the credential store file") - .WithErrors(infoResult); - } - - var storeInfo = infoResult.Value; - if (storeInfo.Kind != StorageItemKind.File) - { - return new WorkshopConnectionSummary(false, string.Empty); - } - - var readResult = await _fileSystem.ReadAllTextAsync(_credentialsFilePath); - if (readResult.IsFailure) - { - return Result.Fail("Failed to read the credential store file") - .WithErrors(readResult); - } - - var documentText = readResult.Value; - var document = ParseDocument(documentText); - if (document is null) - { - // An unparseable store still counts as a stored entry so that - // display surfaces can offer clear and replace as recovery. - return new WorkshopConnectionSummary(true, string.Empty); - } - - var entry = document.WorkshopConnection; - if (entry is null || - string.IsNullOrEmpty(entry.ProtectedData)) - { - return new WorkshopConnectionSummary(false, string.Empty); - } - - return new WorkshopConnectionSummary(true, entry.KeyHint ?? string.Empty); - } - finally + var protectedData = _editorSettings.WorkshopKeyProtected; + if (string.IsNullOrEmpty(protectedData)) { - _storeSemaphore.Release(); + return new WorkshopKeySummary(false, string.Empty); } + + return new WorkshopKeySummary(true, _editorSettings.WorkshopKeyHint ?? string.Empty); } - public async Task> GetWorkshopConnectionAsync() + public async Task> GetWorkshopKeyAsync() { + await Task.CompletedTask; + if (!IsAvailable) { return Result.Fail(UnavailableMessage); } - await _storeSemaphore.WaitAsync(); - try + var protectedData = _editorSettings.WorkshopKeyProtected; + if (string.IsNullOrEmpty(protectedData)) { - var infoResult = await _fileSystem.GetInfoAsync(_credentialsFilePath); - if (infoResult.IsFailure) - { - return Result.Fail("Failed to query the credential store file") - .WithErrors(infoResult); - } - - var storeInfo = infoResult.Value; - if (storeInfo.Kind != StorageItemKind.File) - { - return Result.Fail(NotConfiguredMessage); - } - - var readResult = await _fileSystem.ReadAllTextAsync(_credentialsFilePath); - if (readResult.IsFailure) - { - return Result.Fail("Failed to read the credential store file") - .WithErrors(readResult); - } - - var documentText = readResult.Value; - var document = ParseDocument(documentText); - if (document is null) - { - return Result.Fail(CorruptStoreMessage); - } - - if (document.Version > StoreVersion) - { - return Result.Fail(NewerStoreVersionMessage); - } - - var entry = document.WorkshopConnection; - if (entry is null || - string.IsNullOrEmpty(entry.ProtectedData)) - { - return Result.Fail(NotConfiguredMessage); - } - - byte[] protectedData; - try - { - protectedData = Convert.FromBase64String(entry.ProtectedData); - } - catch (FormatException) - { - _logger.LogError("The stored Workshop connection entry is not valid base64"); - - return Result.Fail(CorruptStoreMessage); - } - - var unprotectResult = _protector.Unprotect(protectedData); - if (unprotectResult.IsFailure) - { - _logger.LogError(unprotectResult, "Failed to unprotect the stored Workshop connection"); - - return Result.Fail(CorruptStoreMessage); - } - - var plainData = unprotectResult.Value; - var connection = ParseConnectionPayload(plainData); - if (connection is null || - string.IsNullOrEmpty(connection.WorkshopUrl) || - string.IsNullOrEmpty(connection.ApplicationKey)) - { - // The decrypted payload is sensitive, so no parse detail is - // attached to the error or written to the log. - _logger.LogError("The stored Workshop connection payload is invalid"); - - return Result.Fail(CorruptStoreMessage); - } - - // Connections saved before the Author field was added have no value - // for it; normalize the missing case to empty so callers gate on it - // uniformly rather than guarding against null. - if (connection.Author is null) - { - connection = connection with { Author = string.Empty }; - } - - return connection; + return Result.Fail(NotConfiguredMessage); } - finally - { - _storeSemaphore.Release(); - } - } - public async Task SetWorkshopConnectionAsync(WorkshopConnection connection) - { - if (!IsAvailable) + var valueResult = UnprotectFromBase64(protectedData); + if (valueResult.IsFailure) { - return Result.Fail(UnavailableMessage); + return valueResult; } - if (connection is null) + var workshopKey = valueResult.Value; + if (string.IsNullOrEmpty(workshopKey)) { - return Result.Fail("Workshop connection is required"); + _logger.LogError("The stored Workshop Key is empty"); + + return Result.Fail(UnreadableMessage); } - if (string.IsNullOrWhiteSpace(connection.WorkshopUrl)) + return workshopKey; + } + + public async Task SetWorkshopKeyAsync(string workshopKey) + { + await Task.CompletedTask; + + if (!IsAvailable) { - return Result.Fail("Workshop URL must not be empty"); + return Result.Fail(UnavailableMessage); } - if (string.IsNullOrWhiteSpace(connection.ApplicationKey)) + if (string.IsNullOrWhiteSpace(workshopKey)) { - return Result.Fail("Application Key must not be empty"); + return Result.Fail("Workshop Key must not be empty"); } - var payloadJson = JsonSerializer.Serialize(connection); - var payloadData = Encoding.UTF8.GetBytes(payloadJson); - - var protectResult = _protector.Protect(payloadData); + var protectResult = ProtectToBase64(workshopKey); if (protectResult.IsFailure) { - return Result.Fail("Failed to protect the Workshop connection") - .WithErrors(protectResult); + return Result.Fail("Failed to protect the Workshop Key").WithErrors(protectResult); } - var protectedData = protectResult.Value; - var entry = new WorkshopConnectionEntry( - Convert.ToBase64String(protectedData), - GetKeyDisplayHint(connection.ApplicationKey)); + // The ciphertext is written before the hint, so a crash between the two + // writes leaves a usable key with a stale hint (cosmetic only) rather + // than a stored hint pointing at no key. + _editorSettings.WorkshopKeyProtected = protectResult.Value; + _editorSettings.WorkshopKeyHint = GetKeyDisplayHint(workshopKey); - var document = new CredentialStoreDocument(StoreVersion, entry); - var documentText = JsonSerializer.Serialize(document, DocumentJsonOptions); - - await _storeSemaphore.WaitAsync(); - try - { - var folderPath = Path.GetDirectoryName(_credentialsFilePath); - if (!string.IsNullOrEmpty(folderPath)) - { - var createFolderResult = await _fileSystem.CreateFolderAsync(folderPath); - if (createFolderResult.IsFailure) - { - return Result.Fail("Failed to create the credential store folder") - .WithErrors(createFolderResult); - } - } - - var writeResult = await _fileSystem.WriteAllTextAsync(_credentialsFilePath, documentText); - if (writeResult.IsFailure) - { - return Result.Fail("Failed to write the credential store file") - .WithErrors(writeResult); - } - - return Result.Ok(); - } - finally - { - _storeSemaphore.Release(); - } + return Result.Ok(); } - public async Task ClearWorkshopConnectionAsync() + public async Task ClearWorkshopKeyAsync() { + await Task.CompletedTask; + if (!IsAvailable) { return Result.Fail(UnavailableMessage); } - await _storeSemaphore.WaitAsync(); - try - { - var infoResult = await _fileSystem.GetInfoAsync(_credentialsFilePath); - if (infoResult.IsFailure) - { - return Result.Fail("Failed to query the credential store file") - .WithErrors(infoResult); - } - - var storeInfo = infoResult.Value; - if (storeInfo.Kind != StorageItemKind.File) - { - return Result.Ok(); - } - - // The Workshop connection is the only entry today, so clearing it - // removes the whole store file. This also recovers from a - // corrupted file without needing to parse it. - var deleteResult = await _fileSystem.DeleteFileAsync(_credentialsFilePath); - if (deleteResult.IsFailure) - { - return Result.Fail("Failed to delete the credential store file") - .WithErrors(deleteResult); - } - - return Result.Ok(); - } - finally - { - _storeSemaphore.Release(); - } + _editorSettings.WorkshopKeyProtected = string.Empty; + _editorSettings.WorkshopKeyHint = string.Empty; + + return Result.Ok(); } - private static string GetDefaultCredentialsFilePath() + private Result ProtectToBase64(string plaintext) { - var appDataFolderPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + var data = Encoding.UTF8.GetBytes(plaintext); + var protectResult = _protector.Protect(data, WorkshopKeyEntropy); + if (protectResult.IsFailure) + { + return Result.Fail(protectResult); + } - return Path.Combine(appDataFolderPath, "Celbridge", "credentials.json"); + return Convert.ToBase64String(protectResult.Value); } - private static CredentialStoreDocument? ParseDocument(string documentText) + private Result UnprotectFromBase64(string base64) { + byte[] protectedData; try { - return JsonSerializer.Deserialize(documentText); + protectedData = Convert.FromBase64String(base64); } - catch (JsonException) + catch (FormatException) { - return null; + _logger.LogError("A stored credential entry is not valid base64"); + + return Result.Fail(UnreadableMessage); } - } - private static WorkshopConnection? ParseConnectionPayload(byte[] plainData) - { - try + var unprotectResult = _protector.Unprotect(protectedData, WorkshopKeyEntropy); + if (unprotectResult.IsFailure) { - var payloadJson = Encoding.UTF8.GetString(plainData); + _logger.LogError(unprotectResult, "Failed to unprotect a stored credential"); - return JsonSerializer.Deserialize(payloadJson); - } - catch (JsonException) - { - return null; + return Result.Fail(UnreadableMessage); } + + return Encoding.UTF8.GetString(unprotectResult.Value); } /// - /// Returns the identifying prefix of an Application Key shaped like + /// Returns the identifying prefix of a Workshop Key shaped like /// "kpf_(prefix)_(secret)", or an empty string when the key does not match /// that shape, so that no secret material can leak into the hint. /// - private static string GetKeyDisplayHint(string applicationKey) + private static string GetKeyDisplayHint(string workshopKey) { - if (!applicationKey.StartsWith(CredentialConstants.ApplicationKeyPrefix, StringComparison.Ordinal)) + if (!workshopKey.StartsWith(CredentialConstants.WorkshopKeyPrefix, StringComparison.Ordinal)) { return string.Empty; } - var separatorIndex = applicationKey.IndexOf('_', CredentialConstants.ApplicationKeyPrefix.Length); + var separatorIndex = workshopKey.IndexOf('_', CredentialConstants.WorkshopKeyPrefix.Length); if (separatorIndex < 0) { return string.Empty; } - return applicationKey.Substring(0, separatorIndex); + return workshopKey.Substring(0, separatorIndex); } } diff --git a/Source/Core/Celbridge.Settings/Services/DpapiCredentialProtector.cs b/Source/Core/Celbridge.Settings/Services/DpapiCredentialProtector.cs index e495d4e09..1c16106d1 100644 --- a/Source/Core/Celbridge.Settings/Services/DpapiCredentialProtector.cs +++ b/Source/Core/Celbridge.Settings/Services/DpapiCredentialProtector.cs @@ -10,7 +10,7 @@ internal sealed class DpapiCredentialProtector : ICredentialProtector { public bool IsAvailable => OperatingSystem.IsWindows(); - public Result Protect(byte[] plainData) + public Result Protect(byte[] plainData, byte[] entropy) { if (!OperatingSystem.IsWindows()) { @@ -19,7 +19,7 @@ public Result Protect(byte[] plainData) try { - var protectedData = ProtectedData.Protect(plainData, optionalEntropy: null, DataProtectionScope.CurrentUser); + var protectedData = ProtectedData.Protect(plainData, entropy, DataProtectionScope.CurrentUser); return protectedData; } @@ -30,7 +30,7 @@ public Result Protect(byte[] plainData) } } - public Result Unprotect(byte[] protectedData) + public Result Unprotect(byte[] protectedData, byte[] entropy) { if (!OperatingSystem.IsWindows()) { @@ -39,7 +39,7 @@ public Result Unprotect(byte[] protectedData) try { - var plainData = ProtectedData.Unprotect(protectedData, optionalEntropy: null, DataProtectionScope.CurrentUser); + var plainData = ProtectedData.Unprotect(protectedData, entropy, DataProtectionScope.CurrentUser); return plainData; } diff --git a/Source/Core/Celbridge.Settings/Services/EditorSettings.cs b/Source/Core/Celbridge.Settings/Services/EditorSettings.cs index 58d8dbe25..c77c1d4c8 100644 --- a/Source/Core/Celbridge.Settings/Services/EditorSettings.cs +++ b/Source/Core/Celbridge.Settings/Services/EditorSettings.cs @@ -116,6 +116,30 @@ public ApplicationColorTheme Theme set => SetValue(nameof(Theme), value); } + public string WorkshopUrl + { + get => GetValue(nameof(WorkshopUrl), string.Empty); + set => SetValue(nameof(WorkshopUrl), value); + } + + public string WorkshopAuthor + { + get => GetValue(nameof(WorkshopAuthor), string.Empty); + set => SetValue(nameof(WorkshopAuthor), value); + } + + public string WorkshopKeyProtected + { + get => GetValue(nameof(WorkshopKeyProtected), string.Empty); + set => SetValue(nameof(WorkshopKeyProtected), value); + } + + public string WorkshopKeyHint + { + get => GetValue(nameof(WorkshopKeyHint), string.Empty); + set => SetValue(nameof(WorkshopKeyHint), value); + } + public bool SearchMatchCase { get => GetValue(nameof(SearchMatchCase), false); diff --git a/Source/Core/Celbridge.Settings/Services/ICredentialProtector.cs b/Source/Core/Celbridge.Settings/Services/ICredentialProtector.cs index 35a7c38c0..7e76036bf 100644 --- a/Source/Core/Celbridge.Settings/Services/ICredentialProtector.cs +++ b/Source/Core/Celbridge.Settings/Services/ICredentialProtector.cs @@ -15,12 +15,17 @@ internal interface ICredentialProtector bool IsAvailable { get; } /// - /// Encrypts the given data for the current user. + /// Encrypts the given data for the current user. The entropy byte array is + /// bound into the ciphertext; the same value must be supplied to Unprotect. + /// Each credential type uses its own fixed entropy so a different DPAPI + /// consumer running as the same user cannot decrypt our data by accident. /// - Result Protect(byte[] plainData); + Result Protect(byte[] plainData, byte[] entropy); /// - /// Decrypts data previously returned by Protect for the same user. + /// Decrypts data previously returned by Protect for the same user. The + /// entropy must match the value passed to Protect; otherwise unprotection + /// fails. /// - Result Unprotect(byte[] protectedData); + Result Unprotect(byte[] protectedData, byte[] entropy); } diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_delete.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_delete.md index ad2ca0787..5902fd564 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/package_delete.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_delete.md @@ -21,7 +21,7 @@ A JSON object echoing `packageName`, the resolved `version`, and `deleted: true` ## Gotchas - **No default target.** Calling without a version is an error; name the number or alias explicitly. -- **Aliases that point at the deleted version are surfaced in the confirmation.** `latest` is server-managed and repoints to the highest remaining version; whether a publisher alias such as `stable` is detached or repointed is a server behaviour. The prompt lists the affected aliases so the consequence is clear before you confirm. +- **Aliases that point at the deleted version are surfaced in the confirmation.** Aliases are static pointers: deleting a version leaves any alias still pointing at it, so installing through that alias afterwards resolves to the deleted version and then fails at download. `latest` is the exception — it is resolved client-side to the highest live version, so it always skips deleted ones. The prompt lists the affected aliases so the consequence is clear before you confirm. - **Deleting an already-deleted version reports that state** rather than failing silently. - **Durability is the consumer's responsibility.** The workshop does not promise eternal availability; a consumer who needs a version permanently should vendor it. See `packages_overview`. - To remove the entire package and every version at once, use `package_unpublish`. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_install.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_install.md index daeae712c..121122c3a 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/package_install.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_install.md @@ -10,7 +10,7 @@ The name as published on the workshop (lowercase alphanumeric with single hyphen ### version -Which version to install. Accepts a version number (e.g. `3`), an alias name (e.g. `stable`), or `latest` (the default), which selects the highest live version. A tombstoned version cannot be installed. +Which version to install. Accepts a version number (e.g. `3`), an alias name (e.g. `stable`), or `latest` (the default), which selects the highest live version. A deleted version cannot be installed: a number or alias resolves to its target, but the download then reports that the version has been deleted. `latest` always skips deleted versions. ### destination @@ -37,5 +37,5 @@ Installing over an existing package folder completely replaces its contents — - Installing into `project:` fails before downloading if another manifest already claims the same package name at a *different* path — move, rename, or remove it first, or reinstall over the existing folder to replace it. Use `package_status` to see what is installed where. Copies under non-loading roots (e.g. `temp:`) are exempt because they never load. - The downloaded zip is staged briefly under `temp:` and removed after extraction, even if the extract fails partway. -- A package whose versions have all been tombstoned has no live version and cannot be installed. +- A package whose versions have all been deleted has no live version, so `latest` cannot resolve and the install fails. - `HISTORY.md` is generated metadata, not package content; `package_publish` never uploads it. diff --git a/Source/Core/Celbridge.Tools/Guides/Tools/package_list.md b/Source/Core/Celbridge.Tools/Guides/Tools/package_list.md index ebbc05f21..e214e8589 100644 --- a/Source/Core/Celbridge.Tools/Guides/Tools/package_list.md +++ b/Source/Core/Celbridge.Tools/Guides/Tools/package_list.md @@ -18,6 +18,6 @@ The array is in the order returned by the workshop; it is not sorted alphabetica Until the server delete-contract alignment lands (tracked in the migration follow-ups), `latestVersion` is **not** filtered to live versions: - After `package_delete` removes the highest version, the server may still report it under `latestVersion` until the next publish. -- After `package_unpublish` removes every version, every entry's `latestVersion` is non-null but installing that version returns "has been deleted and cannot be installed." +- After `package_unpublish` removes every version, every entry's `latestVersion` is non-null but installing that version fails because its content has been deleted. When you need certainty, call `package_info(packageName)` and select the highest `version` whose `deleted` is false. The version resolver inside `package_install` already does this for `latest`, so resolving `latest` continues to work correctly — only consumers reading `package_list` directly need the caveat. diff --git a/Source/Core/Celbridge.Tools/Helpers/PackageVersionResolver.cs b/Source/Core/Celbridge.Tools/Helpers/PackageVersionResolver.cs index ec4e8b31c..51e4c1ef1 100644 --- a/Source/Core/Celbridge.Tools/Helpers/PackageVersionResolver.cs +++ b/Source/Core/Celbridge.Tools/Helpers/PackageVersionResolver.cs @@ -10,25 +10,12 @@ namespace Celbridge.Tools; internal static class PackageVersionResolver { /// - /// Resolves a version for install. A deleted version cannot be downloaded, - /// so a deleted target is rejected with a clear error rather than resolved. + /// Resolves a requested version string to a concrete version number. 'latest' + /// selects the highest live version; a version number or alias dereferences to + /// its target whether or not that version is deleted, leaving the download or + /// delete as the single authority on liveness. /// - public static Result ResolveForInstall(RemotePackageDetails details, string requestedVersion) - { - return Resolve(details, requestedVersion, rejectDeleted: true); - } - - /// - /// Resolves a version for delete. A deleted target is not pre-rejected here. - /// The client reports the already-deleted state instead. The latest alias - /// still selects the highest live version, never a dead one. - /// - public static Result ResolveForDelete(RemotePackageDetails details, string requestedVersion) - { - return Resolve(details, requestedVersion, rejectDeleted: false); - } - - private static Result Resolve(RemotePackageDetails details, string requestedVersion, bool rejectDeleted) + public static Result Resolve(RemotePackageDetails details, string requestedVersion) { if (string.Equals(requestedVersion, PackageConstants.LatestAlias, StringComparison.OrdinalIgnoreCase)) { @@ -50,11 +37,6 @@ private static Result Resolve(RemotePackageDetails details, string requeste { return Result.Fail($"Version {explicitVersion} not found for package '{details.Name}'."); } - if (rejectDeleted - && match.Deleted) - { - return Result.Fail($"Version {explicitVersion} of package '{details.Name}' has been deleted and cannot be installed."); - } return explicitVersion; } @@ -71,11 +53,6 @@ private static Result Resolve(RemotePackageDetails details, string requeste { return Result.Fail($"Alias '{requestedVersion}' points at version {alias.Version}, which does not exist."); } - if (rejectDeleted - && aliasTarget.Deleted) - { - return Result.Fail($"Alias '{requestedVersion}' points at version {alias.Version}, which has been deleted."); - } return alias.Version; } diff --git a/Source/Core/Celbridge.Tools/Helpers/PublishAuthor.cs b/Source/Core/Celbridge.Tools/Helpers/PublishAuthor.cs deleted file mode 100644 index 5d90fedb9..000000000 --- a/Source/Core/Celbridge.Tools/Helpers/PublishAuthor.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Celbridge.Credentials; - -namespace Celbridge.Tools; - -/// -/// Resolves the publisher Author from the stored Workshop connection. Every -/// publish records who published it; until a user-account login exists the -/// Author is set once on the Settings page. Returns a clear, actionable failure -/// when no connection or no Author is configured, so the publish tools can both -/// surface it to the agent and alert the user. -/// -internal static class PublishAuthor -{ - public static async Task> ResolveAsync(ICredentialService credentialService) - { - var connectionResult = await credentialService.GetWorkshopConnectionAsync(); - if (connectionResult.IsFailure) - { - return Result.Fail( - "Cannot publish: no workshop connection is configured. Set it up on the Settings page, then try again.") - .WithErrors(connectionResult); - } - var connection = connectionResult.Value; - - if (string.IsNullOrWhiteSpace(connection.Author)) - { - return Result.Fail( - "Cannot publish: no Author is set. Add one on the Settings page, then try again."); - } - - return connection.Author.Trim(); - } -} diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Delete.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Delete.cs index 57db31430..d649fce1a 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Delete.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Delete.cs @@ -36,7 +36,7 @@ public async partial Task Delete(string packageName, string vers } var packageDetails = detailsResult.Value; - var resolveResult = PackageVersionResolver.ResolveForDelete(packageDetails, version.Trim()); + var resolveResult = PackageVersionResolver.Resolve(packageDetails, version.Trim()); if (resolveResult.IsFailure) { return ToolResponse.Error(resolveResult); diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs index fb8e532df..3607fb50c 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Install.cs @@ -96,7 +96,7 @@ public async partial Task Install( var packageDetails = detailsResult.Value; var requestedVersion = string.IsNullOrWhiteSpace(version) ? PackageConstants.LatestAlias : version.Trim(); - var resolveVersionResult = PackageVersionResolver.ResolveForInstall(packageDetails, requestedVersion); + var resolveVersionResult = PackageVersionResolver.Resolve(packageDetails, requestedVersion); if (resolveVersionResult.IsFailure) { return ToolResponse.Error(resolveVersionResult); diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs index 43e2cc36e..cc63c8ab6 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.Publish.cs @@ -1,6 +1,5 @@ using System.IO.Compression; using System.Text.Json; -using Celbridge.Credentials; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; using Tomlyn; diff --git a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs index 3c9104b86..b7ecdf8f2 100644 --- a/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs +++ b/Source/Core/Celbridge.Tools/Tools/Package/PackageTools.cs @@ -1,4 +1,4 @@ -using Celbridge.Credentials; +using Celbridge.Settings; using ModelContextProtocol.Server; namespace Celbridge.Tools; @@ -62,20 +62,21 @@ private async Task ConfirmActionAsync(string title, string message) // returned to the agent. private async Task> ResolvePublishAuthorAsync(bool confirmWithUser) { - var credentialService = GetRequiredService(); - var authorResult = await PublishAuthor.ResolveAsync(credentialService); - if (authorResult.IsFailure) + var editorSettings = GetRequiredService(); + var author = editorSettings.WorkshopAuthor.Trim(); + if (author.Length > 0) { - if (confirmWithUser) - { - var localizerService = GetRequiredService(); - var title = localizerService.GetString("Workshop_PublishBlocked_Title"); - await ShowAlertAsync(title, authorResult.FirstErrorMessage); - } + return author; + } - return authorResult; + var localizerService = GetRequiredService(); + var message = localizerService.GetString("Workshop_PublishBlocked_Message"); + if (confirmWithUser) + { + var title = localizerService.GetString("Workshop_PublishBlocked_Title"); + await ShowAlertAsync(title, message); } - return authorResult.Value; + return Result.Fail(message); } } diff --git a/Source/Core/Celbridge.Tools/Tools/Page/PageTools.cs b/Source/Core/Celbridge.Tools/Tools/Page/PageTools.cs index ed1223e74..4ba0abe8c 100644 --- a/Source/Core/Celbridge.Tools/Tools/Page/PageTools.cs +++ b/Source/Core/Celbridge.Tools/Tools/Page/PageTools.cs @@ -1,4 +1,4 @@ -using Celbridge.Credentials; +using Celbridge.Settings; using ModelContextProtocol.Server; namespace Celbridge.Tools; @@ -33,20 +33,21 @@ private async Task ConfirmActionAsync(string title, string message) // returned to the agent. private async Task> ResolvePublishAuthorAsync(bool confirmWithUser) { - var credentialService = GetRequiredService(); - var authorResult = await PublishAuthor.ResolveAsync(credentialService); - if (authorResult.IsFailure) + var editorSettings = GetRequiredService(); + var author = editorSettings.WorkshopAuthor.Trim(); + if (author.Length > 0) { - if (confirmWithUser) - { - var localizerService = GetRequiredService(); - var title = localizerService.GetString("Workshop_PublishBlocked_Title"); - await ShowAlertAsync(title, authorResult.FirstErrorMessage); - } - - return authorResult; + return author; } - return authorResult.Value; + var localizerService = GetRequiredService(); + var message = localizerService.GetString("Workshop_PublishBlocked_Message"); + if (confirmWithUser) + { + var title = localizerService.GetString("Workshop_PublishBlocked_Title"); + await ShowAlertAsync(title, message); + } + + return Result.Fail(message); } } diff --git a/Source/Core/Celbridge.UserInterface/Converters/StatusSeverityConverter.cs b/Source/Core/Celbridge.UserInterface/Converters/StatusSeverityConverter.cs new file mode 100644 index 000000000..f7471f053 --- /dev/null +++ b/Source/Core/Celbridge.UserInterface/Converters/StatusSeverityConverter.cs @@ -0,0 +1,23 @@ +namespace Celbridge.UserInterface.Converters; + +/// +/// Maps a StatusSeverity to the InfoBarSeverity used by InfoBar controls. +/// +public class StatusSeverityConverter : IValueConverter +{ + public object Convert(object value, Type targetType, object parameter, string language) + { + return value switch + { + StatusSeverity.Success => InfoBarSeverity.Success, + StatusSeverity.Warning => InfoBarSeverity.Warning, + StatusSeverity.Error => InfoBarSeverity.Error, + _ => InfoBarSeverity.Informational + }; + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } +} diff --git a/Source/Core/Celbridge.UserInterface/Helpers/WorkshopConnectionValidation.cs b/Source/Core/Celbridge.UserInterface/Helpers/WorkshopConnectionValidation.cs index 9ed358608..8629abf72 100644 --- a/Source/Core/Celbridge.UserInterface/Helpers/WorkshopConnectionValidation.cs +++ b/Source/Core/Celbridge.UserInterface/Helpers/WorkshopConnectionValidation.cs @@ -28,12 +28,12 @@ public static bool IsValidWorkshopUrl(string workshopUrl) } /// - /// Returns true when the key starts with the expected Application Key + /// Returns true when the key starts with the expected Workshop Key /// prefix. A typo guard for display, not a gate: keys without the prefix /// are still accepted. /// - public static bool HasExpectedKeyPrefix(string applicationKey) + public static bool HasExpectedKeyPrefix(string workshopKey) { - return applicationKey.StartsWith(CredentialConstants.ApplicationKeyPrefix, StringComparison.Ordinal); + return workshopKey.StartsWith(CredentialConstants.WorkshopKeyPrefix, StringComparison.Ordinal); } } diff --git a/Source/Core/Celbridge.UserInterface/ServiceConfiguration.cs b/Source/Core/Celbridge.UserInterface/ServiceConfiguration.cs index 7a604b4ed..ed6b42bad 100644 --- a/Source/Core/Celbridge.UserInterface/ServiceConfiguration.cs +++ b/Source/Core/Celbridge.UserInterface/ServiceConfiguration.cs @@ -61,7 +61,7 @@ public static void ConfigureServices(IServiceCollection services) services.AddTransient(); services.AddTransient(); - services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/Source/Core/Celbridge.UserInterface/ViewModels/Controls/WorkshopSettingsViewModel.cs b/Source/Core/Celbridge.UserInterface/ViewModels/Controls/WorkshopSettingsViewModel.cs new file mode 100644 index 000000000..df0c95814 --- /dev/null +++ b/Source/Core/Celbridge.UserInterface/ViewModels/Controls/WorkshopSettingsViewModel.cs @@ -0,0 +1,346 @@ +using Celbridge.Credentials; +using Celbridge.Packages; +using Celbridge.Settings; + +namespace Celbridge.UserInterface.ViewModels.Controls; + +public partial class WorkshopSettingsViewModel : ObservableObject +{ + private const string MaskedKeyDisplay = "********"; + + private readonly Logging.ILogger _logger; + private readonly IEditorSettings _editorSettings; + private readonly ICredentialService _credentialService; + private readonly IPackageApiClient _packageApiClient; + private readonly IStringLocalizer _stringLocalizer; + + [ObservableProperty] + private string _workshopUrl = string.Empty; + + [ObservableProperty] + private string _author = string.Empty; + + [ObservableProperty] + private string _workshopKey = string.Empty; + + [ObservableProperty] + private string _storedKeyDisplay = string.Empty; + + [ObservableProperty] + private bool _isStoreAvailable; + + [ObservableProperty] + private bool _isKeyEntryVisible; + + [ObservableProperty] + private bool _isStoredKeyVisible; + + [ObservableProperty] + private bool _isCancelReplaceVisible; + + [ObservableProperty] + private bool _isStatusVisible; + + [ObservableProperty] + private string _statusMessage = string.Empty; + + [ObservableProperty] + private StatusSeverity _statusSeverity; + + private bool _isKeyStored; + private bool _isReplacingKey; + + // Bumped on each connection check so the result of a slow check that is + // superseded by a newer save does not overwrite the newer status. + private int _connectionCheckId; + + /// + /// True while the view model is updating bound fields itself (load, clear, + /// post-save reset), so the view can tell a programmatic change from a user + /// edit and not trigger an auto-save. + /// + public bool IsApplyingProgrammaticChange { get; private set; } + + public WorkshopSettingsViewModel( + Logging.ILogger logger, + IEditorSettings editorSettings, + ICredentialService credentialService, + IPackageApiClient packageApiClient, + IStringLocalizer stringLocalizer) + { + _logger = logger; + _editorSettings = editorSettings; + _credentialService = credentialService; + _packageApiClient = packageApiClient; + _stringLocalizer = stringLocalizer; + } + + public async Task InitializeAsync() + { + IsStoreAvailable = _credentialService.IsAvailable; + + // URL and Author are ordinary settings, independent of the key store, so + // they load (and the section displays them) even when no key is stored. + ApplyProgrammatic(() => + { + WorkshopUrl = _editorSettings.WorkshopUrl; + Author = _editorSettings.WorkshopAuthor; + }); + + if (!IsStoreAvailable) + { + ShowStatus(StatusSeverity.Error, _stringLocalizer.GetString("SettingsPage_CredentialStoreUnavailable")); + UpdateViewState(); + return; + } + + var summaryResult = await _credentialService.GetWorkshopKeySummaryAsync(); + if (summaryResult.IsFailure) + { + _logger.LogError(summaryResult, "Failed to read the Workshop Key summary"); + ShowStatus(StatusSeverity.Error, _stringLocalizer.GetString("SettingsPage_StoredConnectionUnreadable")); + UpdateViewState(); + return; + } + + var summary = summaryResult.Value; + _isKeyStored = summary.IsStored; + if (_isKeyStored) + { + StoredKeyDisplay = FormatStoredKeyDisplay(summary.KeyHint); + } + + UpdateViewState(); + + // A stored key with no Author cannot publish; surface it up front rather + // than waiting for the first publish to fail. + if (_isKeyStored + && string.IsNullOrWhiteSpace(Author)) + { + ShowStatus(StatusSeverity.Warning, _stringLocalizer.GetString("SettingsPage_AuthorRequired")); + } + } + + /// + /// Persists the current field values. The Workshop URL and Author are saved as + /// ordinary settings, always and independently of the key. A newly entered key + /// is saved to the credential store. When checkConnection is set and a key is + /// stored, the connection is verified against the workshop and the result + /// shown; the view requests a check only when a connection-affecting field + /// changed. + /// + public async Task SaveWorkshopConnectionAsync(bool checkConnection = true) + { + if (!IsStoreAvailable) + { + return; + } + + // The URL and Author are non-secret; persist them as settings on every + // commit, so they are never coupled to the presence of a key. + _editorSettings.WorkshopUrl = WorkshopUrl.Trim(); + _editorSettings.WorkshopAuthor = Author.Trim(); + + // Persist a newly entered key to the credential store. + var enteringKey = !_isKeyStored || + _isReplacingKey; + var keyPrefixUnexpected = false; + if (enteringKey) + { + var workshopKey = WorkshopKey.Trim(); + if (!string.IsNullOrEmpty(workshopKey)) + { + var setResult = await _credentialService.SetWorkshopKeyAsync(workshopKey); + if (setResult.IsFailure) + { + _logger.LogError(setResult, "Failed to store the Workshop Key"); + ShowStatus(StatusSeverity.Error, _stringLocalizer.GetString("SettingsPage_SaveConnectionFailed")); + return; + } + + keyPrefixUnexpected = !WorkshopConnectionValidation.HasExpectedKeyPrefix(workshopKey); + ApplyProgrammatic(() => WorkshopKey = string.Empty); + _isKeyStored = true; + _isReplacingKey = false; + await RefreshStoredKeyDisplayAsync(); + UpdateViewState(); + } + } + + // The URL value is already persisted; validate it for the connection + // status shown below. + var workshopUrl = WorkshopUrl.Trim(); + if (string.IsNullOrEmpty(workshopUrl)) + { + ShowStatus(StatusSeverity.Error, _stringLocalizer.GetString("SettingsPage_EmptyWorkshopUrl")); + return; + } + if (!WorkshopConnectionValidation.IsValidWorkshopUrl(workshopUrl)) + { + ShowStatus(StatusSeverity.Error, _stringLocalizer.GetString("SettingsPage_InvalidWorkshopUrl")); + return; + } + + if (!_isKeyStored) + { + ShowStatus(StatusSeverity.Informational, _stringLocalizer.GetString("SettingsPage_EmptyWorkshopKey")); + return; + } + + if (checkConnection) + { + await CheckConnectionAsync(keyPrefixUnexpected); + } + else + { + ShowConnectionOkStatus("SettingsPage_ConnectionSaved"); + } + } + + // The connection is stored and (where checked) reachable. Publishing also + // needs an Author, so a missing one is surfaced as a warning in place of the + // success message rather than waiting for the first publish to fail. + private void ShowConnectionOkStatus(string successMessageKey) + { + if (string.IsNullOrWhiteSpace(Author)) + { + ShowStatus(StatusSeverity.Warning, _stringLocalizer.GetString("SettingsPage_AuthorRequired")); + } + else + { + ShowStatus(StatusSeverity.Success, _stringLocalizer.GetString(successMessageKey)); + } + } + + // Verifies the connection by making one authenticated request to the + // workshop. List-packages is reused as a lightweight probe until a dedicated + // health endpoint exists; only its success or failure is used. + private async Task CheckConnectionAsync(bool keyPrefixUnexpected) + { + var checkId = ++_connectionCheckId; + ShowStatus(StatusSeverity.Informational, _stringLocalizer.GetString("SettingsPage_CheckingConnection")); + + var listResult = await _packageApiClient.ListPackagesAsync(); + + // A newer save started its own check while this one was in flight; let + // the newer one own the final status. + if (checkId != _connectionCheckId) + { + return; + } + + if (listResult.IsSuccess) + { + ShowConnectionOkStatus("SettingsPage_ConnectionVerified"); + return; + } + + // A malformed-looking key that the workshop also rejected is reported as + // invalid; a well-formed key that failed could be the wrong key or an + // unreachable server, so it points at both the URL and the key. + var messageKey = keyPrefixUnexpected + ? "SettingsPage_InvalidWorkshopKey" + : "SettingsPage_ConnectionCheckFailed"; + + ShowStatus(StatusSeverity.Error, _stringLocalizer.GetString(messageKey)); + } + + [RelayCommand] + private async Task ClearWorkshopKeyAsync() + { + var clearResult = await _credentialService.ClearWorkshopKeyAsync(); + if (clearResult.IsFailure) + { + _logger.LogError(clearResult, "Failed to clear the Workshop Key"); + ShowStatus(StatusSeverity.Error, _stringLocalizer.GetString("SettingsPage_ClearWorkshopKeyFailed")); + return; + } + + // Only the secret is removed; the URL and Author stay as settings so a new + // key can be entered without retyping them. + _isKeyStored = false; + _isReplacingKey = false; + ApplyProgrammatic(() => WorkshopKey = string.Empty); + StoredKeyDisplay = string.Empty; + + ShowStatus(StatusSeverity.Informational, _stringLocalizer.GetString("SettingsPage_WorkshopKeyCleared")); + UpdateViewState(); + } + + [RelayCommand] + private void ReplaceWorkshopKey() + { + _isReplacingKey = true; + ClearStatus(); + UpdateViewState(); + } + + [RelayCommand] + private void CancelReplaceWorkshopKey() + { + _isReplacingKey = false; + ApplyProgrammatic(() => WorkshopKey = string.Empty); + ClearStatus(); + UpdateViewState(); + } + + private async Task RefreshStoredKeyDisplayAsync() + { + var summaryResult = await _credentialService.GetWorkshopKeySummaryAsync(); + if (summaryResult.IsSuccess) + { + var summary = summaryResult.Value; + StoredKeyDisplay = FormatStoredKeyDisplay(summary.KeyHint); + } + } + + private void UpdateViewState() + { + IsKeyEntryVisible = IsStoreAvailable && + (!_isKeyStored || _isReplacingKey); + IsStoredKeyVisible = IsStoreAvailable && + _isKeyStored && + !_isReplacingKey; + IsCancelReplaceVisible = IsStoreAvailable && + _isKeyStored && + _isReplacingKey; + } + + private void ShowStatus(StatusSeverity severity, string message) + { + StatusSeverity = severity; + StatusMessage = message; + IsStatusVisible = true; + } + + private void ClearStatus() + { + IsStatusVisible = false; + StatusMessage = string.Empty; + } + + // Runs an update to bound fields with the programmatic-change flag set, so the + // view's auto-save trigger ignores changes the view model makes itself. + private void ApplyProgrammatic(Action action) + { + IsApplyingProgrammaticChange = true; + try + { + action(); + } + finally + { + IsApplyingProgrammaticChange = false; + } + } + + private static string FormatStoredKeyDisplay(string keyHint) + { + if (string.IsNullOrEmpty(keyHint)) + { + return MaskedKeyDisplay; + } + + return $"{keyHint}_..."; + } +} diff --git a/Source/Core/Celbridge.UserInterface/ViewModels/Pages/SettingsPageViewModel.cs b/Source/Core/Celbridge.UserInterface/ViewModels/Pages/SettingsPageViewModel.cs deleted file mode 100644 index d6ae09a8e..000000000 --- a/Source/Core/Celbridge.UserInterface/ViewModels/Pages/SettingsPageViewModel.cs +++ /dev/null @@ -1,283 +0,0 @@ -using Celbridge.Credentials; - -namespace Celbridge.UserInterface.ViewModels.Pages; - -public partial class SettingsPageViewModel : ObservableObject -{ - private const string MaskedKeyDisplay = "********"; - - private readonly Logging.ILogger _logger; - private readonly ICredentialService _credentialService; - private readonly IStringLocalizer _stringLocalizer; - - [ObservableProperty] - private string _workshopUrl = string.Empty; - - [ObservableProperty] - private string _author = string.Empty; - - [ObservableProperty] - private string _applicationKey = string.Empty; - - [ObservableProperty] - private string _storedKeyDisplay = string.Empty; - - [ObservableProperty] - private bool _isStoreAvailable; - - [ObservableProperty] - private bool _isKeyEntryVisible; - - [ObservableProperty] - private bool _isStoredKeyVisible; - - [ObservableProperty] - private bool _isClearVisible; - - [ObservableProperty] - private bool _isCancelReplaceVisible; - - [ObservableProperty] - private string _statusText = string.Empty; - - [ObservableProperty] - private bool _isStatusVisible; - - [ObservableProperty] - private bool _isWarningVisible; - - [ObservableProperty] - private string _errorText = string.Empty; - - [ObservableProperty] - private bool _isErrorVisible; - - private bool _isConnectionStored; - private bool _isReplacingKey; - - public SettingsPageViewModel( - Logging.ILogger logger, - ICredentialService credentialService, - IStringLocalizer stringLocalizer) - { - _logger = logger; - _credentialService = credentialService; - _stringLocalizer = stringLocalizer; - } - - public async Task InitializeAsync() - { - IsStoreAvailable = _credentialService.IsAvailable; - if (!IsStoreAvailable) - { - ShowError(_stringLocalizer.GetString("SettingsPage_CredentialStoreUnavailable")); - UpdateViewState(); - return; - } - - var summaryResult = await _credentialService.GetWorkshopConnectionSummaryAsync(); - if (summaryResult.IsFailure) - { - _logger.LogError(summaryResult, "Failed to read the Workshop connection summary"); - ShowError(_stringLocalizer.GetString("SettingsPage_StoredConnectionUnreadable")); - UpdateViewState(); - return; - } - - var summary = summaryResult.Value; - _isConnectionStored = summary.IsStored; - - if (_isConnectionStored) - { - StoredKeyDisplay = FormatStoredKeyDisplay(summary.KeyHint); - - var getResult = await _credentialService.GetWorkshopConnectionAsync(); - if (getResult.IsSuccess) - { - var connection = getResult.Value; - WorkshopUrl = connection.WorkshopUrl; - Author = connection.Author; - } - else - { - // A stored entry that cannot be decrypted. The clear and - // replace affordances stay active so the user can recover. - _logger.LogError(getResult, "Failed to read the stored Workshop connection"); - ShowError(_stringLocalizer.GetString("SettingsPage_StoredConnectionUnreadable")); - } - } - - UpdateViewState(); - } - - [RelayCommand] - private async Task SaveWorkshopConnectionAsync() - { - ClearMessages(); - - var workshopUrl = WorkshopUrl.Trim(); - if (!WorkshopConnectionValidation.IsValidWorkshopUrl(workshopUrl)) - { - ShowError(_stringLocalizer.GetString("SettingsPage_InvalidWorkshopUrl")); - return; - } - - string applicationKey; - var isNewKey = !_isConnectionStored || - _isReplacingKey; - if (isNewKey) - { - applicationKey = ApplicationKey.Trim(); - if (string.IsNullOrEmpty(applicationKey)) - { - ShowError(_stringLocalizer.GetString("SettingsPage_EmptyApplicationKey")); - return; - } - } - else - { - // A URL-only update reuses the stored key, read at the point of use. - var getResult = await _credentialService.GetWorkshopConnectionAsync(); - if (getResult.IsFailure) - { - _logger.LogError(getResult, "Failed to read the stored Workshop connection"); - ShowError(_stringLocalizer.GetString("SettingsPage_StoredConnectionUnreadable")); - return; - } - - var storedConnection = getResult.Value; - applicationKey = storedConnection.ApplicationKey; - } - - var connection = new WorkshopConnection(workshopUrl, applicationKey, Author.Trim()); - var setResult = await _credentialService.SetWorkshopConnectionAsync(connection); - if (setResult.IsFailure) - { - _logger.LogError(setResult, "Failed to store the Workshop connection"); - ShowError(_stringLocalizer.GetString("SettingsPage_SaveConnectionFailed")); - return; - } - - ApplicationKey = string.Empty; - _isConnectionStored = true; - _isReplacingKey = false; - - await RefreshStoredKeyDisplayAsync(); - - // The prefix check is a typo guard, not a gate: the connection is - // saved either way and the warning invites a double check. - if (isNewKey && - !WorkshopConnectionValidation.HasExpectedKeyPrefix(applicationKey)) - { - ShowWarning(_stringLocalizer.GetString("SettingsPage_ConnectionSavedPrefixWarning")); - } - else - { - ShowStatus(_stringLocalizer.GetString("SettingsPage_ConnectionSaved")); - } - - UpdateViewState(); - } - - [RelayCommand] - private async Task ClearWorkshopConnectionAsync() - { - ClearMessages(); - - var clearResult = await _credentialService.ClearWorkshopConnectionAsync(); - if (clearResult.IsFailure) - { - _logger.LogError(clearResult, "Failed to clear the Workshop connection"); - ShowError(_stringLocalizer.GetString("SettingsPage_ClearConnectionFailed")); - return; - } - - _isConnectionStored = false; - _isReplacingKey = false; - WorkshopUrl = string.Empty; - Author = string.Empty; - ApplicationKey = string.Empty; - StoredKeyDisplay = string.Empty; - - ShowStatus(_stringLocalizer.GetString("SettingsPage_ConnectionCleared")); - UpdateViewState(); - } - - [RelayCommand] - private void ReplaceApplicationKey() - { - ClearMessages(); - _isReplacingKey = true; - UpdateViewState(); - } - - [RelayCommand] - private void CancelReplaceApplicationKey() - { - ClearMessages(); - _isReplacingKey = false; - ApplicationKey = string.Empty; - UpdateViewState(); - } - - private async Task RefreshStoredKeyDisplayAsync() - { - var summaryResult = await _credentialService.GetWorkshopConnectionSummaryAsync(); - if (summaryResult.IsSuccess) - { - var summary = summaryResult.Value; - StoredKeyDisplay = FormatStoredKeyDisplay(summary.KeyHint); - } - } - - private void UpdateViewState() - { - IsKeyEntryVisible = IsStoreAvailable && - (!_isConnectionStored || _isReplacingKey); - IsStoredKeyVisible = IsStoreAvailable && - _isConnectionStored && - !_isReplacingKey; - IsClearVisible = IsStoreAvailable && - _isConnectionStored; - IsCancelReplaceVisible = IsStoreAvailable && - _isConnectionStored && - _isReplacingKey; - } - - private void ShowStatus(string statusText) - { - StatusText = statusText; - IsStatusVisible = true; - } - - private void ShowWarning(string warningText) - { - StatusText = warningText; - IsWarningVisible = true; - } - - private void ShowError(string errorText) - { - ErrorText = errorText; - IsErrorVisible = true; - } - - private void ClearMessages() - { - StatusText = string.Empty; - IsStatusVisible = false; - IsWarningVisible = false; - ErrorText = string.Empty; - IsErrorVisible = false; - } - - private static string FormatStoredKeyDisplay(string keyHint) - { - if (string.IsNullOrEmpty(keyHint)) - { - return MaskedKeyDisplay; - } - - return $"{keyHint}_..."; - } -} diff --git a/Source/Core/Celbridge.UserInterface/Views/Controls/WorkshopSettingsView.xaml b/Source/Core/Celbridge.UserInterface/Views/Controls/WorkshopSettingsView.xaml new file mode 100644 index 000000000..918627801 --- /dev/null +++ b/Source/Core/Celbridge.UserInterface/Views/Controls/WorkshopSettingsView.xaml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +