diff --git a/assets/localization/en.json b/assets/localization/en.json index 631baaa624..88d79e78e3 100644 --- a/assets/localization/en.json +++ b/assets/localization/en.json @@ -24,7 +24,10 @@ "%settings_platform_interfaceLanguage_label%": "Interface Language", "%project_settings_platform_group1_label%": "Platform Settings", "%project_settings_platform_group1_description%": "Project settings pertaining to the software overall", + "%project_settings_platform_name_label%": "Project Short Name", + "%project_name_missing%": "_NoName_", "%project_settings_platform_fullName_label%": "Project Full Name", + "%project_full_name_missing%": "*Name Missing*", "%project_settings_platform_language_label%": "Project Primary Language", "%project_settings_platform_isEditable_label%": "Is Editable", "%project_settings_platform_isEditable_description%": "Whether this project is editable. A project that is not editable is sometimes called a resource." diff --git a/c-sharp-tests/DummyLocalParatextProjects.cs b/c-sharp-tests/DummyLocalParatextProjects.cs index 7567ad6ce8..b586046d85 100644 --- a/c-sharp-tests/DummyLocalParatextProjects.cs +++ b/c-sharp-tests/DummyLocalParatextProjects.cs @@ -1,14 +1,16 @@ using System.Diagnostics.CodeAnalysis; using Paranext.DataProvider.Projects; +using Paratext.Data; namespace TestParanextDataProvider { [ExcludeFromCodeCoverage] internal class DummyLocalParatextProjects : LocalParatextProjects { - public void FakeAddProject(ProjectDetails details) + public void FakeAddProject(ProjectDetails details, ScrText? scrText = null) { - _projectDetailsMap[details.Metadata.ID] = details; + scrText ??= new DummyScrText(details); + ScrTextCollection.Add(scrText, true); } public override void Initialize(bool shouldIncludePT9ProjectsOnWindows) diff --git a/c-sharp-tests/DummyScrText.cs b/c-sharp-tests/DummyScrText.cs index aa4951466b..638beece7a 100644 --- a/c-sharp-tests/DummyScrText.cs +++ b/c-sharp-tests/DummyScrText.cs @@ -1,6 +1,7 @@ using System.Diagnostics.CodeAnalysis; using System.Text; using System.Xml; +using Paranext.DataProvider.Projects; using Paratext.Data; using Paratext.Data.Languages; using Paratext.Data.ProjectFileAccess; @@ -15,18 +16,29 @@ internal class DummyScrText : ScrText { private readonly HexId _id; - public DummyScrText() : base(RegistrationInfo.DefaultUser) + public DummyScrText(ProjectDetails projectDetails) + : base( + new ProjectName + { + ShortName = projectDetails.Name, + ProjectPath = projectDetails.HomeDirectory + }, + RegistrationInfo.DefaultUser + ) { - _id = HexId.CreateNew(); - projectName = new ProjectName(); - projectName.ShortName = "Dummy" + _id; + _id = HexId.FromStr(projectDetails.Metadata.ID); + projectName = new ProjectName + { + ShortName = projectDetails.Name + _id, + ProjectPath = projectDetails.HomeDirectory + }; Settings.Editable = true; Settings.UsfmVersion = UsfmVersionOption.Version3; cachedDefaultStylesheet.Set(new DummyScrStylesheet()); cachedFrontBackStylesheet.Set(cachedDefaultStylesheet); - + LanguageId langId = new("dmy", null, null, null); DummyScrLanguage language = new(this); language.SetLanguageId(langId); @@ -36,6 +48,15 @@ public DummyScrText() : base(RegistrationInfo.DefaultUser) language.ForceSaveLdml(this); } + public DummyScrText() + : this( + new ProjectDetails( + "Dummy", + new ProjectMetadata(HexId.CreateNew().ToString(), []), + "" + ) + ) { } + protected override void Load(bool ignoreLoadErrors = false) { // Nothing to do @@ -45,15 +66,16 @@ protected override ProjectFileManager CreateFileManager() { return new InMemoryFileManager(this); } - + protected override ProjectSettings CreateProjectSettings(bool ignoreFileMissing) { - ProjectSettings settings = new(this, true) - { - FullName = "Test ScrText", - MinParatextDataVersion = ParatextInfo.MinSupportedParatextDataVersion, - Guid = _id - }; + ProjectSettings settings = + new(this, true) + { + FullName = "Test ScrText", + MinParatextDataVersion = ParatextInfo.MinSupportedParatextDataVersion, + Guid = _id + }; return settings; } @@ -64,9 +86,8 @@ private sealed class InMemoryFileManager : ProjectFileManager private static readonly Encoding s_utf8NoBOM = new UTF8Encoding(false); private readonly Dictionary _fileSystem = new(); - public InMemoryFileManager(ScrText scrText) : base(scrText) - { - } + public InMemoryFileManager(ScrText scrText) + : base(scrText) { } // Implementation shamelessly stolen from the Paratext test code and then simplified @@ -85,7 +106,9 @@ public override void Delete(string relFilePath) public override void DeleteDirectory(string relDirPath) { - string[] filesToBeRemoved = _fileSystem.Keys.Where(k => Path.GetDirectoryName(k) == relDirPath).ToArray(); + string[] filesToBeRemoved = _fileSystem + .Keys.Where(k => Path.GetDirectoryName(k) == relDirPath) + .ToArray(); foreach (string file in filesToBeRemoved) Delete(file); } @@ -102,22 +125,35 @@ public override void CopyFile(string absSourceFilePath, string dstRelPath) throw new NotImplementedException(); } - public override IEnumerable ProjectFiles(string searchPattern, string? relDirPath = null) + public override IEnumerable ProjectFiles( + string searchPattern, + string? relDirPath = null + ) { return Enumerable.Empty(); } - public override IEnumerable ProjectDirectories(string searchPattern, string? relDirPath = null) + public override IEnumerable ProjectDirectories( + string searchPattern, + string? relDirPath = null + ) { return Enumerable.Empty(); } - public override void WriteFileCreatingBackup(string relFilePath, Action writeFile, Action? validateFile = null) + public override void WriteFileCreatingBackup( + string relFilePath, + Action writeFile, + Action? validateFile = null + ) { writeFile(relFilePath); } - public override TextReader OpenFileForRead(string relFilePath, Encoding? encoding = null) + public override TextReader OpenFileForRead( + string relFilePath, + Encoding? encoding = null + ) { // ENHANCE: Keep track of file locking via a custom reader to further increase testing accuracy. if (encoding == null) @@ -136,7 +172,10 @@ public override XmlTextReader OpenFileForXmlRead(string relFilePath) return new XmlTextReader(new MemoryStream(GetFile(relFilePath))); } - public override TextWriter OpenFileForWrite(string relFilePath, Encoding? encoding = null) + public override TextWriter OpenFileForWrite( + string relFilePath, + Encoding? encoding = null + ) { if (encoding == null) encoding = s_utf8NoBOM; @@ -150,7 +189,9 @@ public override BinaryWriter OpenFileForByteWrite(string relFilePath) public override void SetXml(T obj, string relFilePath) { - _fileSystem[relFilePath] = new InMemoryFile(s_utf8NoBOM.GetBytes(Memento.ToXmlString(obj))); + _fileSystem[relFilePath] = new InMemoryFile( + s_utf8NoBOM.GetBytes(Memento.ToXmlString(obj)) + ); } public override T GetXml(string relFilePath) @@ -203,8 +244,12 @@ private sealed class DummyStreamWriter : StreamWriter private readonly InMemoryFileManager _owner; private readonly string _relFilePath; - public DummyStreamWriter(InMemoryFileManager owner, string relFilePath, Encoding encoding) : - base(new MemoryStream(), encoding) + public DummyStreamWriter( + InMemoryFileManager owner, + string relFilePath, + Encoding encoding + ) + : base(new MemoryStream(), encoding) { _owner = owner; _relFilePath = relFilePath; @@ -214,7 +259,9 @@ protected override void Dispose(bool disposing) { Flush(); - _owner._fileSystem[_relFilePath] = new InMemoryFile(((MemoryStream)BaseStream).ToArray()); + _owner._fileSystem[_relFilePath] = new InMemoryFile( + ((MemoryStream)BaseStream).ToArray() + ); base.Dispose(disposing); } @@ -230,7 +277,8 @@ private sealed class DummyBinaryWriter : BinaryWriter private readonly InMemoryFileManager _owner; private readonly string _relFilePath; - public DummyBinaryWriter(InMemoryFileManager owner, string relFilePath) : base(new MemoryStream()) + public DummyBinaryWriter(InMemoryFileManager owner, string relFilePath) + : base(new MemoryStream()) { _owner = owner; _relFilePath = relFilePath; @@ -239,7 +287,9 @@ public DummyBinaryWriter(InMemoryFileManager owner, string relFilePath) : base(n protected override void Dispose(bool disposing) { base.Dispose(disposing); - _owner._fileSystem[_relFilePath] = new InMemoryFile(((MemoryStream)BaseStream).ToArray()); + _owner._fileSystem[_relFilePath] = new InMemoryFile( + ((MemoryStream)BaseStream).ToArray() + ); } } #endregion diff --git a/c-sharp-tests/PapiTestBase.cs b/c-sharp-tests/PapiTestBase.cs index 987e032646..377d0ec8dd 100644 --- a/c-sharp-tests/PapiTestBase.cs +++ b/c-sharp-tests/PapiTestBase.cs @@ -22,13 +22,15 @@ internal abstract class PapiTestBase #region Test setup/teardown [SetUp] - public virtual async Task TestSetup() + public virtual Task TestSetup() { if (OperatingSystem.IsMacOS()) Assert.Ignore("Mac is missing ICU support so these tests will not work"); _projects = new DummyLocalParatextProjects(); _client = new DummyPapiClient(); + + return Task.CompletedTask; } [TearDown] @@ -73,12 +75,11 @@ protected DummyLocalParatextProjects ParatextProjects #region Helper methods to create test data /// - /// Creates a new dummy project for testing purposes. The project is added to the ScrTextCollection. + /// Creates a new dummy project for testing purposes /// protected static DummyScrText CreateDummyProject() { DummyScrText scrText = new(); - ScrTextCollection.Add(scrText, true); return scrText; } @@ -100,8 +101,8 @@ protected static ProjectDetails CreateProjectDetails( List? projectInterfaces = null ) { - ProjectMetadata metadata = new(id, name, projectInterfaces ?? []); - return new ProjectDetails(metadata, "testDirectoryThatDoesNotExist"); + ProjectMetadata metadata = new(id, projectInterfaces ?? []); + return new ProjectDetails(name, metadata, "testDirectoryThatDoesNotExist"); } /// diff --git a/c-sharp-tests/Projects/FixtureSetup.cs b/c-sharp-tests/Projects/FixtureSetup.cs index 61a7cc5e06..2b287ee1ca 100644 --- a/c-sharp-tests/Projects/FixtureSetup.cs +++ b/c-sharp-tests/Projects/FixtureSetup.cs @@ -7,7 +7,12 @@ namespace TestParanextDataProvider.Projects [ExcludeFromCodeCoverage] public class FixtureSetup { - private static readonly string s_testFolder = Path.Combine(Path.GetTempPath(), "Platform.Bible.Tests"); + private static readonly string s_testFolder = Path.Combine( + Path.GetTempPath(), + "Platform.Bible.Tests" + ); + + public static string TestFolderPath => s_testFolder; [OneTimeSetUp] public void RunBeforeAnyTests() @@ -26,7 +31,6 @@ public void RunAfterAnyTests() // Clean up after the tests are run. if (Directory.Exists(s_testFolder)) Directory.Delete(s_testFolder, true); - } } } diff --git a/c-sharp-tests/Projects/LocalParatextProjectsTests.cs b/c-sharp-tests/Projects/LocalParatextProjectsTests.cs index 43805f3239..f27b496c6b 100644 --- a/c-sharp-tests/Projects/LocalParatextProjectsTests.cs +++ b/c-sharp-tests/Projects/LocalParatextProjectsTests.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Paranext.DataProvider.ParatextUtils; using Paranext.DataProvider.Projects; namespace TestParanextDataProvider.Projects; @@ -7,7 +8,6 @@ namespace TestParanextDataProvider.Projects; [TestFixture] public class LocalParatextProjectsTests { - private const string TEST_ID = "441f1e41ffb8d319650847df35f4ffb78f12914e"; private LocalParatextProjects _localProjects; [SetUp] @@ -16,7 +16,8 @@ public void SetUp() if (OperatingSystem.IsMacOS()) Assert.Ignore("Mac is missing ICU support so some of these tests will not work"); - _localProjects = new TestLocalParatextProjectsInTempDir(); + var localProjects = new TestLocalParatextProjectsInTempDir(); + _localProjects = localProjects; } [TearDown] @@ -26,84 +27,38 @@ public void TearDown() disposable.Dispose(); } - #region Tests originally for now defunct DoesFolderMatchMetadata method - // Previously, local project folder names had to follow the pattern shortname_id. Now this - // has been relaxed, and folders can be named with any name allowed by the OS. - - [TestCase("-ABC_441f1e41ffb8d319650847df35f4ffb78f12914e")] - [TestCase("-_441f1e41ffb8d319650847df35f4ffb78f12914e")] - [TestCase("ABC@_441f1e41ffb8d319650847df35f4ffb78f12914e")] - [TestCase("A_B_C")] - [TestCase("ABC_")] - [TestCase("ABC_441f1e41ffb8d319650847df35f4ffb78f1291 e")] - [TestCase("_441f1e41ffb8d319650847df35f4ffb78f12914e")] - [TestCase("ABC")] - public void Initialize_NonStandardFolderName_ProjectIsRetrievable(string folder) - { - CreateTempProject(folder, CreateParatextProjectMetadata("ABC")); - - _localProjects.Initialize(false); - var details = _localProjects.GetAllProjectDetails().Single(); - Assert.That(details, Is.EqualTo(_localProjects.GetProjectDetails(TEST_ID))); - Assert.That(details.HomeDirectory, Does.EndWith(folder)); - Assert.That(details.Metadata.Name, Is.EqualTo("ABC")); - Assert.That(details.Metadata.ID.Equals(TEST_ID, StringComparison.OrdinalIgnoreCase)); - } - - [TestCase("ABC_441f1e41ffb8d319650847df35f4ffb78f12914e", "ABD")] - [TestCase("AB-C_441f1e41ffb8d319650847df35f4ffb78f12914e", "AB-D")] - [TestCase("A-B-C_441f1e41ffb8d319650847df35f4ffb78f12914e", "ABC")] - public void Initialize_NameDoesNotMatch_ProjectIsRetrievable(string folder, string name) - { - var metadata = CreateParatextProjectMetadata(name); - CreateTempProject(folder, metadata); - _localProjects.Initialize(false); - var details = _localProjects.GetAllProjectDetails().Single(); - Assert.That(details, Is.EqualTo(_localProjects.GetProjectDetails(TEST_ID))); - Assert.That(details.HomeDirectory, Does.EndWith(folder)); - Assert.That(details.Metadata.Name, Is.EqualTo(name)); - Assert.That(details.Metadata.ID.Equals(TEST_ID, StringComparison.OrdinalIgnoreCase)); - } - - [TestCase("ABC_541f1e41ffb8d319650847df35f4ffb78f12914e", "ABC")] - [TestCase("AB-C_541f1e41ffb8d319650847df35f4ffb78f12914e", "AB-C")] - public void Initialize_IdDoesNotMatch_ProjectIsRetrievable(string folder, string name) + [TestCase("-ABC_441f1e41ffb8d319650847df35f4ffb78f12914e", "HIMOM", "1234")] + [TestCase("-_441f1e41ffb8d319650847df35f4ffb78f12914e", "MONKEY", "abc123")] + [TestCase("ABC@_441f1e41ffb8d319650847df35f4ffb78f12914e", "ABC@_441", "abe5")] + [TestCase("A_B_C", "123", "affff00990")] + [TestCase("ABC_", "123", "a236")] + [TestCase("_441f1e41ffb8d319650847df35f4ffb78f12914e", "abs", "0626")] + [TestCase("ABC", "sdlkfjasdf", "1606")] + public void Initialize_SingleProject_ProjectIsRetrievable(string folder, string name, string id) { - var metadata = CreateParatextProjectMetadata(name); - CreateTempProject(folder, metadata); - _localProjects.Initialize(false); - var details = _localProjects.GetAllProjectDetails().Single(); - Assert.That(details, Is.EqualTo(_localProjects.GetProjectDetails(TEST_ID))); - Assert.That(details.HomeDirectory, Does.EndWith(folder)); - Assert.That(details.Metadata.Name, Is.EqualTo(name)); - Assert.That(details.Metadata.ID.Equals(TEST_ID, StringComparison.OrdinalIgnoreCase)); - } + CreateTempProject(folder, CreateParatextProjectDetails(folder, name, id)); - [TestCase("ABC_441F1E41FFB8D319650847DF35F4FFB78F12914E", "ABC")] - [TestCase("ABC_441F1E41FFB8D319650847DF35F4FFB78F12914E", "abc")] - [TestCase("AB-C_441f1e41ffb8d319650847df35f4ffb78f12914e", "AB-C")] - [TestCase("A-B-C_441f1e41ffb8d319650847df35f4ffb78f12914e", "A-B-C")] - public void Initialize_IdAndNameMatch_ProjectIsRetrievable(string folder, string name) - { - var metadata = CreateParatextProjectMetadata(name); - CreateTempProject(folder, metadata); _localProjects.Initialize(false); var details = _localProjects.GetAllProjectDetails().Single(); - Assert.That(details, Is.EqualTo(_localProjects.GetProjectDetails(TEST_ID))); + Assert.That(details, Is.EqualTo(_localProjects.GetProjectDetails(id))); Assert.That(details.HomeDirectory, Does.EndWith(folder)); - Assert.That(details.Metadata.Name, Is.EqualTo(name)); - Assert.That(details.Metadata.ID.Equals(TEST_ID, StringComparison.OrdinalIgnoreCase)); + // ScrText always prioritizes the folder name over the Name setting as the "name" even when + // accessing scrText.Settings.Name. So basically name here doesn't get set to anything. + Assert.That(details.Name, Is.EqualTo(folder)); + Assert.That(details.Metadata.ID.Equals(id, StringComparison.OrdinalIgnoreCase)); } - #endregion - - private void CreateTempProject(string folder, ProjectMetadata metadata) + private void CreateTempProject(string folder, ProjectDetails details) { - ((TestLocalParatextProjectsInTempDir)_localProjects).CreateTempProject(folder, metadata); + ((TestLocalParatextProjectsInTempDir)_localProjects).CreateTempProject(folder, details); } - private static ProjectMetadata CreateParatextProjectMetadata(string name) + private static ProjectDetails CreateParatextProjectDetails( + string folder, + string name, + string id + ) { - return new ProjectMetadata(TEST_ID, name, ["paratext"]); + return new ProjectDetails(name, new ProjectMetadata(id, ["paratext"]), folder); } } diff --git a/c-sharp-tests/Projects/MinimalParatextProjectSettings.cs b/c-sharp-tests/Projects/MinimalParatextProjectSettings.cs index 9ad21b82b1..e627c31506 100644 --- a/c-sharp-tests/Projects/MinimalParatextProjectSettings.cs +++ b/c-sharp-tests/Projects/MinimalParatextProjectSettings.cs @@ -7,5 +7,7 @@ public class MinimalParatextProjectSettings { public string? Name { get; set; } public string? Guid { get; set; } + public string? LanguageIsoCode { get; set; } + public string? MinParatextVersion { get; set; } } } diff --git a/c-sharp-tests/Projects/ParatextDataProviderTests.cs b/c-sharp-tests/Projects/ParatextDataProviderTests.cs index c15c53630a..3540bd4f2e 100644 --- a/c-sharp-tests/Projects/ParatextDataProviderTests.cs +++ b/c-sharp-tests/Projects/ParatextDataProviderTests.cs @@ -30,7 +30,7 @@ public override async Task TestSetup() _scrText = CreateDummyProject(); _projectDetails = CreateProjectDetails(_scrText); - ParatextProjects.FakeAddProject(_projectDetails); + ParatextProjects.FakeAddProject(_projectDetails, _scrText); var settingsService = new DummySettingsService(Client); await settingsService.RegisterDataProvider(); diff --git a/c-sharp-tests/Projects/ParatextProjectDataProviderFactoryTests.cs b/c-sharp-tests/Projects/ParatextProjectDataProviderFactoryTests.cs index 6aae110ae4..32a209c75e 100644 --- a/c-sharp-tests/Projects/ParatextProjectDataProviderFactoryTests.cs +++ b/c-sharp-tests/Projects/ParatextProjectDataProviderFactoryTests.cs @@ -10,7 +10,7 @@ namespace TestParanextDataProvider.Projects internal class ParatextProjectDataProviderFactoryTests : PapiTestBase { private const string PDB_FACTORY_GET_REQUEST = - $"object:platform.pdpFactory-{ParatextProjectDataProviderFactory.PDPF_NAME}.function"; + $"object:platform.{ParatextProjectDataProviderFactory.PDPF_NAME}-pdpf.function"; [SetUp] public override async Task TestSetup() @@ -22,18 +22,21 @@ public override async Task TestSetup() settingsService.AddSettingValue(Settings.INCLUDE_MY_PARATEXT_9_PROJECTS, true); } - [Test] - public async Task InvalidProjectId_ReturnsError() + [TestCase("0000", "Paratext.Data.ProjectNotFoundException: Project not found: 0000")] + // HexId-related problems + [TestCase("12345", "System.ArgumentException: Input must have even number of characters")] + [TestCase( + "abcdefgh", + "System.ArgumentException: String must contain only hexadecimal characters: abcdefgh" + )] + public async Task InvalidProjectId_ReturnsError(string projectId, string startOfError) { const int requesterId = 47192; ParatextProjectDataProviderFactory factory = new(Client, ParatextProjects); await factory.Initialize(); - JsonElement serverMessage = CreateRequestMessage( - "getProjectDataProviderId", - "unknownProj" - ); + JsonElement serverMessage = CreateRequestMessage("getProjectDataProviderId", projectId); Message result = Client .FakeMessageFromServer( @@ -43,7 +46,7 @@ public async Task InvalidProjectId_ReturnsError() Assert.That(result.Type, Is.EqualTo(MessageType.RESPONSE)); MessageResponse response = (MessageResponse)result; - Assert.That(response.ErrorMessage, Is.EqualTo("Unknown project ID: unknownProj")); + Assert.That(response.ErrorMessage, Does.StartWith(startOfError)); Assert.That(response.RequestId, Is.EqualTo(requesterId)); Assert.That(response.Contents, Is.Null); } @@ -51,7 +54,7 @@ public async Task InvalidProjectId_ReturnsError() [Test] public async Task WrongNumberOfParameters_ReturnsError() { - const string projId = "monkey"; + const string projId = "ABC123"; const int requesterId = 47281; ParatextProjects.FakeAddProject(CreateProjectDetails(projId, "Monkey Soup")); @@ -81,7 +84,7 @@ public async Task WrongNumberOfParameters_ReturnsError() [Test] public async Task InvalidFunction_ReturnsError() { - const string projId = "monkey"; + const string projId = "CDE539"; const int requesterId = 47192; ParatextProjects.FakeAddProject(CreateProjectDetails(projId, "Monkey Soup")); @@ -110,7 +113,7 @@ public async Task InvalidFunction_ReturnsError() [Test] public async Task GetProjectDataProviderID_ReturnsIdForProvider() { - const string projId = "monkey"; + const string projId = "305531"; const int requesterId1 = 47281; const int requesterId2 = 18542; diff --git a/c-sharp-tests/Projects/TestLocalParatextProjectsInTempDir.cs b/c-sharp-tests/Projects/TestLocalParatextProjectsInTempDir.cs index 6af4ec35ac..85bf904cf7 100644 --- a/c-sharp-tests/Projects/TestLocalParatextProjectsInTempDir.cs +++ b/c-sharp-tests/Projects/TestLocalParatextProjectsInTempDir.cs @@ -17,19 +17,28 @@ public TestLocalParatextProjectsInTempDir() public void Dispose() { _folder.Dispose(); - ScrTextCollection.RefreshScrTexts(); + // Reset ScrTextCollection's folder to be the global test project folder + ParatextData.Initialize(FixtureSetup.TestFolderPath, false); } protected override string ProjectRootFolder => _folder.Path; - internal void CreateTempProject(string folder, ProjectMetadata projectMetadata) + public string TestProjectRootFolder => _folder.Path; + + internal void CreateTempProject(string folder, ProjectDetails projectDetails) { var folderPath = Path.Combine(ProjectRootFolder, folder); CreateDirectory(folderPath); var settings = new MinimalParatextProjectSettings { - Name = projectMetadata.Name, - Guid = projectMetadata.ID + Name = projectDetails.Name, + Guid = projectDetails.Metadata.ID, + // Baked-in functional language code. Just needed something that worked for ScrText + // to load. Feel free to change this for testing purposes + LanguageIsoCode = "en:::", + // Baked-in functional Paratext version. Just needed something that worked for ScrText + // to load. Feel free to change this for testing purposes + MinParatextVersion = "8.0.100.76" }; var settingsPath = Path.Join(folderPath, "Settings.xml"); XmlSerializationHelper.SerializeToFileWithWriteThrough(settingsPath, settings); diff --git a/c-sharp/JsonUtils/ProjectMetadataConverter.cs b/c-sharp/JsonUtils/ProjectMetadataConverter.cs index e9fc6a77ef..024eb8a5a4 100644 --- a/c-sharp/JsonUtils/ProjectMetadataConverter.cs +++ b/c-sharp/JsonUtils/ProjectMetadataConverter.cs @@ -1,4 +1,3 @@ -using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Paranext.DataProvider.Projects; @@ -7,7 +6,6 @@ namespace Paranext.DataProvider.JsonUtils internal static class ProjectMetadataConverter { private const string ID = "id"; - private const string NAME = "name"; private const string PROJECT_INTERFACES = "projectInterfaces"; public static bool TryGetMetadata( @@ -20,9 +18,8 @@ out string errorMessage { JObject parsedArgs = JObject.Parse(jsonString); string id = Get(parsedArgs, ID); - string name = Get(parsedArgs, NAME); List projectInterfaces = GetStrings(parsedArgs, PROJECT_INTERFACES); - projectMetadata = new ProjectMetadata(id, name, projectInterfaces); + projectMetadata = new ProjectMetadata(id, projectInterfaces); } catch (Exception ex) { @@ -62,17 +59,15 @@ public static string ToJsonString(ProjectMetadata projectMetadata) return new JObject { [ID] = projectMetadata.ID, - [NAME] = projectMetadata.Name, [PROJECT_INTERFACES] = JToken.FromObject(projectMetadata.ProjectInterfaces) }.ToString(); } - public static string ToJsonString(string id, string name, List projectInterfaces) + public static string ToJsonString(string id, List projectInterfaces) { return new JObject { [ID] = id, - [NAME] = name, [PROJECT_INTERFACES] = JToken.FromObject(projectInterfaces) }.ToString(); } diff --git a/c-sharp/NetworkObjects/UsfmDataProvider.cs b/c-sharp/NetworkObjects/UsfmDataProvider.cs deleted file mode 100644 index 6e252441bb..0000000000 --- a/c-sharp/NetworkObjects/UsfmDataProvider.cs +++ /dev/null @@ -1,174 +0,0 @@ -using System.Text.Json; -using System.Text.Json.Nodes; -using System.Xml; -using System.Xml.XPath; -using Paranext.DataProvider.JsonUtils; -using Paranext.DataProvider.MessageHandlers; -using Paranext.DataProvider.MessageTransports; -using Paranext.DataProvider.ParatextUtils; -using Paratext.Data; -using SIL.Scripture; - -namespace Paranext.DataProvider.NetworkObjects; - -internal class UsfmDataProvider : DataProvider -{ - private readonly string _collectionName; - private readonly List _supportedFunctions = - new() - { - "getBookNames", - "getBookUsx", - "getChapter", - "getChapterUsx", - "getVerse", - "setChapterUsx" - }; - private ScrText? _scrText; - - public UsfmDataProvider(PapiClient papiClient, string dataFolderPath, string collectionName) - : base("usfm", papiClient) - { - _collectionName = collectionName; - ParatextGlobals.Initialize(dataFolderPath); - } - - protected override Task StartDataProvider() - { - _scrText = ScrTextCollection.Find(_collectionName); - return Task.CompletedTask; - } - - protected override List GetFunctionNames() - { - return _supportedFunctions; - } - - protected override ResponseToRequest HandleRequest(string functionName, JsonArray args) - { - if (_scrText == null) - { - Console.Error.WriteLine("StartDataProvider must be called first"); - return ResponseToRequest.Failed("Data provider must be started first"); - } - - try - { - return functionName switch - { - "getBookNames" => GetBookNames(), - "getChapter" => GetChapter(args[0]!.ToJsonString()), - "getChapterUsx" => GetUsx(args[0]!.ToJsonString()), - "setChapterUsx" => SetUsx(args[0]!.ToJsonString(), args[1]!.ToString()), - "getBookUsx" => GetUsx(args[0]!.ToJsonString()), - "getVerse" => GetVerse(args[0]!.ToJsonString()), - _ => ResponseToRequest.Failed($"Unexpected function: {functionName}") - }; - } - catch (Exception e) - { - Console.Error.WriteLine(e.ToString()); - return ResponseToRequest.Failed(e.ToString()); - } - } - - private static ResponseToRequest GetBookNames() - { - return ResponseToRequest.Succeeded(JsonSerializer.Serialize(Canon.AllBookIds)); - } - - private ResponseToRequest GetChapter(string args) - { - return VerseRefConverter.TryCreateVerseRef(args, out var verseRef, out string errorMsg) - ? ResponseToRequest.Succeeded(_scrText!.GetText(verseRef, true, true)) - : ResponseToRequest.Failed(errorMsg); - } - - private ResponseToRequest GetUsx(string args) - { - return VerseRefConverter.TryCreateVerseRef(args, out var verseRef, out string errorMsg) - ? ResponseToRequest.Succeeded(GetChapterOrBookUsx(verseRef)) - : ResponseToRequest.Failed(errorMsg); - } - - private ResponseToRequest SetUsx(string argVref, string argNewUsx) - { - return VerseRefConverter.TryCreateVerseRef(argVref, out var verseRef, out string errorMsg) - ? SetChapterOrBookUsx(verseRef, argNewUsx) - : ResponseToRequest.Failed(errorMsg); - } - - private ResponseToRequest GetVerse(string args) - { - return VerseRefConverter.TryCreateVerseRef(args, out var verseRef, out string errorMsg) - ? ResponseToRequest.Succeeded(_scrText!.GetVerseText(verseRef)) - : ResponseToRequest.Failed(errorMsg); - } - - private string GetChapterOrBookUsx(VerseRef vref) - { - XmlDocument usx = ConvertUsfmToUsx(GetUsfm(vref.BookNum, vref.ChapterNum), vref.BookNum); - string contents = usx.OuterXml ?? string.Empty; - return contents; - } - - private ResponseToRequest SetChapterOrBookUsx(VerseRef vref, string newUsx) - { - try - { - XmlDocument doc = new() { PreserveWhitespace = true }; - doc.LoadXml(newUsx); - if (doc.FirstChild?.Name != "usx") - return ResponseToRequest.Failed("Invalid USX"); - - UsxFragmenter.FindFragments( - _scrText!.ScrStylesheet(vref.BookNum), - doc.CreateNavigator(), - XPathExpression.Compile("*[false()]"), - out string usfm - ); - - usfm = UsfmToken.NormalizeUsfm(_scrText, vref.BookNum, usfm); - _scrText.PutText(vref.BookNum, vref.ChapterNum, false, usfm, null); - SendDataUpdateEvent("*"); - } - catch (Exception e) - { - return ResponseToRequest.Failed(e.ToString()); - } - - return ResponseToRequest.Succeeded(); - } - - /// - /// Converts usfm to usx, but does not annotate - /// - private XmlDocument ConvertUsfmToUsx(string usfm, int bookNum) - { - ScrStylesheet scrStylesheet = _scrText!.ScrStylesheet(bookNum); - // Tokenize usfm - List tokens = UsfmToken.Tokenize(scrStylesheet, usfm ?? string.Empty, true); - - XmlDocument doc = new XmlDocument(); - using (XmlWriter xmlw = doc.CreateNavigator()!.AppendChild()) - { - // Convert to XML - UsfmToUsx.ConvertToXmlWriter(scrStylesheet, tokens, xmlw, false); - xmlw.Flush(); - } - return doc; - } - - /// - /// Gets USFM for a book or chapter. - /// - /// The book for which to get USFM - /// The chapter for which to get USFM. Do not specify or specify `null` for the whole book - /// USFM - private string GetUsfm(int bookNum, int? chapterNum = null) - { - VerseRef vref = new(bookNum, chapterNum ?? 0, 0, _scrText!.Settings.Versification); - ScrText projectToUse = _scrText!.GetJoinedText(bookNum); - return projectToUse.GetText(vref, chapterNum.HasValue, true); - } -} diff --git a/c-sharp/ParatextUtils/ParatextGlobals.cs b/c-sharp/ParatextUtils/ParatextGlobals.cs index 240cba4342..196259a864 100644 --- a/c-sharp/ParatextUtils/ParatextGlobals.cs +++ b/c-sharp/ParatextUtils/ParatextGlobals.cs @@ -16,7 +16,12 @@ public static void Initialize(string dataFolderPath) return; if (s_initialized) + { + // Update the paratext data path to make sure we're using the latest path passed in + // For now, this is only used in tests + SetParatextDataPath(dataFolderPath); return; + } lock (s_locker) { @@ -34,10 +39,15 @@ public static void Initialize(string dataFolderPath) ICUDllLocator.Initialize(false, false); // Now tell Paratext.Data to use the specified folder - dataFolderPath = Path.GetFullPath(dataFolderPath); // Make sure path is rooted - ParatextData.Initialize(dataFolderPath, false); - s_initialized = true; + SetParatextDataPath(dataFolderPath); } } + + private static void SetParatextDataPath(string dataFolderPath) + { + dataFolderPath = Path.GetFullPath(dataFolderPath); // Make sure path is rooted + ParatextData.Initialize(dataFolderPath, false); + s_initialized = true; + } } } diff --git a/c-sharp/Program.cs b/c-sharp/Program.cs index 58ff779f4e..b2a2bab355 100644 --- a/c-sharp/Program.cs +++ b/c-sharp/Program.cs @@ -23,10 +23,9 @@ public static async Task Main() return; } - var sdp = new UsfmDataProvider(papi, "assets", "WEB"); var paratextProjects = new LocalParatextProjects(); var paratextFactory = new ParatextProjectDataProviderFactory(papi, paratextProjects); - await Task.WhenAll(sdp.RegisterDataProvider(), paratextFactory.Initialize()); + await Task.WhenAll(paratextFactory.Initialize()); // Things that only run in our "noisy dev mode" go here var noisyDevModeEnvVar = Environment.GetEnvironmentVariable("DEV_NOISY"); diff --git a/c-sharp/Projects/LocalParatextProjects.cs b/c-sharp/Projects/LocalParatextProjects.cs index 7ab99f8299..3688e785bc 100644 --- a/c-sharp/Projects/LocalParatextProjects.cs +++ b/c-sharp/Projects/LocalParatextProjects.cs @@ -1,5 +1,5 @@ -using System.Collections.Concurrent; using System.Xml; +using Paranext.DataProvider.ParatextUtils; using Paratext.Data; using Paratext.Data.Users; @@ -15,14 +15,16 @@ internal class LocalParatextProjects // Inside of each project's "home" directory, these are the subdirectories and files protected const string PROJECT_SETTINGS_FILE = "Settings.xml"; - protected static readonly List _paratextProjectInterfaces = [ProjectInterfaces.Paratext]; - /// /// Directory inside a project's root directory where Platform.Bible's extension data is stored /// public const string EXTENSION_DATA_SUBDIRECTORY = "shared/platform.bible/extensions"; - protected readonly ConcurrentDictionary _projectDetailsMap = new(); + private bool _isInitialized = false; + + private List _requiredProjectRootFiles = ["usfm.sty", "Attribution.md"]; + + private static readonly List _paratextProjectInterfaces = [ProjectInterfaces.Base, ProjectInterfaces.USFM_BookChapterVerse, ProjectInterfaces.USX_Chapter]; public LocalParatextProjects() { @@ -43,42 +45,54 @@ public LocalParatextProjects() public virtual void Initialize(bool shouldIncludePT9ProjectsOnWindows) { - if (!_projectDetailsMap.IsEmpty) + if (_isInitialized) return; - CreateDirectory(ProjectRootFolder); + // Make sure the necessary directory and files exist for the project root folder + SetUpProjectRootFolder(); - IEnumerable allProjectDetails = LoadAllProjectDetails(shouldIncludePT9ProjectsOnWindows); + // Set up the ScrTextCollection and read the projects in that folder + ParatextGlobals.Initialize(ProjectRootFolder); - if (!allProjectDetails.Any()) - { - SetUpSampleProject(); + Console.WriteLine($"Projects loaded from {ProjectRootFolder}: {GetScrTexts().Select(scrText => scrText.Name)}"); - allProjectDetails = LoadAllProjectDetails(shouldIncludePT9ProjectsOnWindows); - } + // Read the projects in any locations other than project root folder + IEnumerable otherProjectDetails = LoadOtherProjectDetails(shouldIncludePT9ProjectsOnWindows); + + if (otherProjectDetails.Any()) + Console.WriteLine($"Projects found in other locations: {otherProjectDetails.Select(projectDetails => projectDetails.Name)}"); - foreach (ProjectDetails projectDetails in allProjectDetails) + foreach (ProjectDetails projectDetails in otherProjectDetails) { try { - AddProjectToMaps(projectDetails); - Console.WriteLine($"Loaded project metadata: {projectDetails}"); + AddProjectToScrTextCollection(projectDetails); + Console.WriteLine($"Loaded project details: {projectDetails}"); } catch (Exception ex) { Console.WriteLine($"Failed to load project for {projectDetails}: {ex}"); } } + + // If there are no projects available anywhere, throw in the sample WEB one + if (!GetScrTexts().Any()) + { + Console.WriteLine("No projects found. Setting up sample WEB project"); + SetUpSampleProject(); + + ScrTextCollection.RefreshScrTexts(); + } } - public IList GetAllProjectDetails() + public IEnumerable GetAllProjectDetails() { - return _projectDetailsMap.Values.ToList(); + return GetScrTexts().Select(scrText => scrText.GetProjectDetails()); } public ProjectDetails GetProjectDetails(string projectId) { - return _projectDetailsMap[projectId.ToUpperInvariant()]; + return GetParatextProject(projectId).GetProjectDetails(); } public static ScrText GetParatextProject(string projectId) @@ -86,6 +100,11 @@ public static ScrText GetParatextProject(string projectId) return ScrTextCollection.GetById(HexId.FromStr(projectId)); } + public static List GetParatextProjectInterfaces() + { + return new List(_paratextProjectInterfaces); + } + #endregion #region Protected properties and methods @@ -111,21 +130,17 @@ protected static void CreateDirectory(string dir) #region Private properties and methods - private void AddProjectToMaps(ProjectDetails projectDetails) + private static IEnumerable GetScrTexts() { + return ScrTextCollection.ScrTexts(IncludeProjects.ScriptureOnly); + } + + private static void AddProjectToScrTextCollection(ProjectDetails projectDetails) { var projectPath = projectDetails.HomeDirectory; - var id = projectDetails.Metadata.ID; - Console.WriteLine( - _projectDetailsMap.ContainsKey(id) - ? $"Replacing Paratext project in map: {id} = {projectPath}" - : $"Adding Paratext project in map: {id} = {projectPath}" - ); - _projectDetailsMap[id.ToUpperInvariant()] = projectDetails; - var projectName = new ProjectName { - ShortName = projectDetails.Metadata.Name, + ShortName = projectDetails.Name, ProjectPath = projectPath }; ScrTextCollection.Add(new ScrText(projectName, RegistrationInfo.DefaultUser)); @@ -135,40 +150,41 @@ private void AddProjectToMaps(ProjectDetails projectDetails) /// Return projects that are available on disk on the local machine /// /// Enumeration of (ProjectMetadata, project directory) tuples for all projects - private IEnumerable LoadAllProjectDetails(bool shouldIncludePT9ProjectsOnWindows) + private IEnumerable LoadOtherProjectDetails(bool shouldIncludePT9ProjectsOnWindows) { - List projectRootFolders = [ProjectRootFolder]; - if (OperatingSystem.IsWindows() && shouldIncludePT9ProjectsOnWindows && Directory.Exists(Paratext9ProjectsFolder)) projectRootFolders.Add(Paratext9ProjectsFolder); + // Get project info for projects outside of the normal project root folder + List nonPT9ProjectRootFolders = []; + if (OperatingSystem.IsWindows() && shouldIncludePT9ProjectsOnWindows && Directory.Exists(Paratext9ProjectsFolder)) nonPT9ProjectRootFolders.Add(Paratext9ProjectsFolder); - foreach (var rootFolder in projectRootFolders) + foreach (var rootFolder in nonPT9ProjectRootFolders) { foreach (var dir in Directory.EnumerateDirectories(rootFolder)) { // There are a lot of folders with underscores in the name that we should ignore in // My Paratext 9 Projects - if (rootFolder == Paratext9ProjectsFolder && Path.GetFileName(dir).StartsWith('_')) continue; + if (rootFolder == Paratext9ProjectsFolder && Path.GetFileNameWithoutExtension(dir).StartsWith('_')) continue; - ProjectMetadata? projectMetadata; + ProjectDetails? projectDetails; string errorMessage; try { - projectMetadata = LoadProjectMetadata(dir, out errorMessage); + projectDetails = LoadProjectDetails(dir, out errorMessage); } catch (Exception ex) { - Console.WriteLine($"Error while getting project metadata from {dir}: {ex}"); + Console.WriteLine($"Error while getting project details from {dir}: {ex}"); continue; } - if (projectMetadata == null) + if (projectDetails == null) Console.WriteLine(errorMessage); else - yield return new ProjectDetails(projectMetadata, dir); + yield return projectDetails; } } } - private static ProjectMetadata? LoadProjectMetadata( + private static ProjectDetails? LoadProjectDetails( string projectHomeDir, out string errorMessage ) @@ -183,13 +199,13 @@ out string errorMessage var settings = new XmlDocument(); settings.Load(settingsFilePath); - var nameNode = settings.SelectSingleNode("/ScriptureText/Name"); - if (nameNode == null) - { - errorMessage = $"Could not find Name in Settings.xml of {projectHomeDir}"; - return null; - } - var shortName = nameNode.InnerText; + // ScrText always prioritizes the folder name over the Name setting as the "name" even when + // accessing scrText.Settings.Name. So we're copying Paratext's functionality here and using + // the folder name instead of Settings.Name. + // https://github.com/ubsicap/Paratext/blob/aaadecd828a9b02e6f55d18e4c5dda8703ce2429/ParatextData/ScrText.cs#L258 + // Removing extension twice because file may be in form name.id.ext to match Paratext + // https://github.com/ubsicap/Paratext/blob/aaadecd828a9b02e6f55d18e4c5dda8703ce2429/ParatextData/ScrTextCollection.cs#L1661 + var shortName = Path.GetFileNameWithoutExtension(Path.GetFileNameWithoutExtension(projectHomeDir)); var idNode = settings.SelectSingleNode("/ScriptureText/Guid"); if (idNode == null) @@ -199,10 +215,25 @@ out string errorMessage } var id = idNode.InnerText; - var metadata = new ProjectMetadata(id, shortName, _paratextProjectInterfaces); + var metadata = new ProjectMetadata(id, GetParatextProjectInterfaces()); + + var details = new ProjectDetails(shortName, metadata, projectHomeDir); errorMessage = ""; - return metadata; + return details; + } + + private void SetUpProjectRootFolder() + { + CreateDirectory(ProjectRootFolder); + + // Add usfm.sty and Attribution.md + foreach (string requiredFile in _requiredProjectRootFiles) + { + var dest = Path.Join(ProjectRootFolder, requiredFile); + if (!File.Exists(dest)) + File.Copy(Path.Join("assets", requiredFile), dest); + } } private void SetUpSampleProject() diff --git a/c-sharp/Projects/ParatextProjectDataProvider.cs b/c-sharp/Projects/ParatextProjectDataProvider.cs index 0e1aa4dd37..06ebcdc5cf 100644 --- a/c-sharp/Projects/ParatextProjectDataProvider.cs +++ b/c-sharp/Projects/ParatextProjectDataProvider.cs @@ -229,6 +229,13 @@ public ResponseToRequest GetProjectSetting(string jsonKey) settingName; var scrText = LocalParatextProjects.GetParatextProject(ProjectDetails.Metadata.ID); + // ScrText always prioritizes the folder name over the Name setting as the "name" even when + // accessing scrText.Settings.Name. So we're copying Paratext's functionality here and using + // the folder name instead of Settings.Name. + // https://github.com/ubsicap/Paratext/blob/aaadecd828a9b02e6f55d18e4c5dda8703ce2429/ParatextData/ProjectSettingsAccess/ProjectSettings.cs#L1438 + if (settingName == ProjectSettings.PT_NAME) + return ResponseToRequest.Succeeded(scrText.Name); + if (scrText.Settings.ParametersDictionary.TryGetValue(settingName, out string? settingValue)) { // Paratext project setting value found, so return the value with the appropriate type if (ProjectSettings.IsParatextSettingABoolean(settingName)) @@ -281,19 +288,47 @@ public ResponseToRequest SetProjectSetting(string jsonKey, string value) // Now actually write the setting string? errorMessage = null; - RunWithinLock( - WriteScope.AllSettingsFiles(), - _ => { - try - { - scrText.Settings.SetSetting(paratextSettingName, value); - scrText.Settings.Save(); - } - catch (Exception ex) - { - errorMessage = ex.Message; - } - }); + + // ScrText always prioritizes the folder name over the Name setting as the "name" even when + // accessing scrText.Settings.Name. So we're copying Paratext's functionality here and using + // the folder name instead of Settings.Name. + // https://github.com/ubsicap/Paratext/blob/aaadecd828a9b02e6f55d18e4c5dda8703ce2429/ParatextData/ScrText.cs#L259 + if (paratextSettingName == ProjectSettings.PT_NAME) + { + // Lock the whole project because this is literally moving the whole folder (chances + // this will actually succeed are very slim as the project must only have Settings.xml + // and the ldml file for this not to instantly throw) + // https://github.com/ubsicap/Paratext/blob/aaadecd828a9b02e6f55d18e4c5dda8703ce2429/ParatextData/ScrText.cs#L1793 + RunWithinLock( + WriteScope.AllSettingsFiles(), + _ => { + try + { + scrText.Name = value; + } + catch (Exception ex) + { + errorMessage = ex.Message; + } + }); + } + else + { + RunWithinLock( + WriteScope.AllSettingsFiles(), + _ => { + try + { + scrText.Settings.SetSetting(paratextSettingName, value); + scrText.Settings.Save(); + } + catch (Exception ex) + { + errorMessage = ex.Message; + } + }); + } + return (errorMessage != null) ? ResponseToRequest.Failed(errorMessage) : ResponseToRequest.Succeeded(ProjectDataType.SETTING); diff --git a/c-sharp/Projects/ParatextProjectDataProviderFactory.cs b/c-sharp/Projects/ParatextProjectDataProviderFactory.cs index 278fe65026..7c1358e159 100644 --- a/c-sharp/Projects/ParatextProjectDataProviderFactory.cs +++ b/c-sharp/Projects/ParatextProjectDataProviderFactory.cs @@ -17,7 +17,7 @@ public ParatextProjectDataProviderFactory( PapiClient papiClient, LocalParatextProjects paratextProjects ) - : base([ProjectInterfaces.Paratext], PDPF_NAME, papiClient) + : base(LocalParatextProjects.GetParatextProjectInterfaces(), PDPF_NAME, papiClient) { _paratextProjects = paratextProjects; } @@ -27,7 +27,10 @@ protected override Task StartFactory() bool? shouldIncludePT9ProjectsOnWindows = false; if (OperatingSystem.IsWindows()) { - shouldIncludePT9ProjectsOnWindows = SettingsService.GetSettingValue(PapiClient, Settings.INCLUDE_MY_PARATEXT_9_PROJECTS); + shouldIncludePT9ProjectsOnWindows = SettingsService.GetSettingValue( + PapiClient, + Settings.INCLUDE_MY_PARATEXT_9_PROJECTS + ); if (!shouldIncludePT9ProjectsOnWindows.HasValue) throw new Exception($"Setting {Settings.INCLUDE_MY_PARATEXT_9_PROJECTS} was null!"); } diff --git a/c-sharp/Projects/ProjectDataProvider.cs b/c-sharp/Projects/ProjectDataProvider.cs index c77ecac298..2d803a8206 100644 --- a/c-sharp/Projects/ProjectDataProvider.cs +++ b/c-sharp/Projects/ProjectDataProvider.cs @@ -49,7 +49,7 @@ out string errorMessage throw new Exception("No data scope provided"); dataScope.ProjectID = ProjectDetails.Metadata.ID; - dataScope.ProjectName = ProjectDetails.Metadata.Name; + dataScope.ProjectName = ProjectDetails.Name; return dataScope; } diff --git a/c-sharp/Projects/ProjectDataProviderFactory.cs b/c-sharp/Projects/ProjectDataProviderFactory.cs index ce8297de38..6ffb84122d 100644 --- a/c-sharp/Projects/ProjectDataProviderFactory.cs +++ b/c-sharp/Projects/ProjectDataProviderFactory.cs @@ -25,7 +25,7 @@ protected ProjectDataProviderFactory(List projectInterfaces, string pdpf public async Task Initialize() { await StartFactory(); - var name = $"platform.pdpFactory-{_pdpfName}"; + var name = $"platform.{_pdpfName}-pdpf"; await RegisterNetworkObject( name, new MessageEventProjectDataProviderFactoryCreated( diff --git a/c-sharp/Projects/ProjectDetails.cs b/c-sharp/Projects/ProjectDetails.cs index edfeffed96..377b663017 100644 --- a/c-sharp/Projects/ProjectDetails.cs +++ b/c-sharp/Projects/ProjectDetails.cs @@ -5,12 +5,15 @@ namespace Paranext.DataProvider.Projects; /// internal class ProjectDetails { - public ProjectDetails(ProjectMetadata metadata, string homeDirectory) + public ProjectDetails(string name, ProjectMetadata metadata, string homeDirectory) { + Name = name; Metadata = metadata; HomeDirectory = homeDirectory; } + public string Name { get; } + public ProjectMetadata Metadata { get; } /// diff --git a/c-sharp/Projects/ProjectInterfaces.cs b/c-sharp/Projects/ProjectInterfaces.cs index f96544b0f8..3f1eb151a2 100644 --- a/c-sharp/Projects/ProjectInterfaces.cs +++ b/c-sharp/Projects/ProjectInterfaces.cs @@ -5,5 +5,11 @@ namespace Paranext.DataProvider.Projects; /// public static class ProjectInterfaces { - public const string Paratext = "ParatextStandard"; + /// + /// The name of the `projectInterface` representing the essential methods every Base Project Data + /// Provider must implement + /// + public const string Base = "platform.base"; + public const string USFM_BookChapterVerse = "platformScripture.USFM_BookChapterVerse"; + public const string USX_Chapter = "platformScripture.USX_Chapter"; } diff --git a/c-sharp/Projects/ProjectMetadata.cs b/c-sharp/Projects/ProjectMetadata.cs index 96487fc9c9..e780b320c1 100644 --- a/c-sharp/Projects/ProjectMetadata.cs +++ b/c-sharp/Projects/ProjectMetadata.cs @@ -9,7 +9,7 @@ namespace Paranext.DataProvider.Projects; /// Returned from Project Data Provider Factories in order to inform others about what projects they /// support in what form. /// -public class ProjectMetadata(string id, string name, List projectInterfaces) +public class ProjectMetadata(string id, List projectInterfaces) { /// /// ID of the project (must be unique and case-insensitive) @@ -17,12 +17,6 @@ public class ProjectMetadata(string id, string name, List projectInterfa [JsonProperty("id")] public string ID { get; } = id.ToUpperInvariant(); - /// - /// Short name of the project (not necessarily unique) - /// - [JsonProperty("name")] - public string Name { get; } = name; - /// /// Indicates what sort of project this is which implies its data shape (e.g., what data streams should be available) /// @@ -31,6 +25,6 @@ public class ProjectMetadata(string id, string name, List projectInterfa public override string ToString() { - return $"[{Name} ({ID}): {string.Join(',', ProjectInterfaces)}]"; + return $"[({ID}): {string.Join(',', ProjectInterfaces)}]"; } } diff --git a/c-sharp/Projects/RawDirectoryProjectStreamManager.cs b/c-sharp/Projects/RawDirectoryProjectStreamManager.cs index a3d9faf32d..896a10940e 100644 --- a/c-sharp/Projects/RawDirectoryProjectStreamManager.cs +++ b/c-sharp/Projects/RawDirectoryProjectStreamManager.cs @@ -25,7 +25,7 @@ public void Initialize() // TODO: This doesn't seem to be used { if (!Directory.Exists(_projectDetails.HomeDirectory)) throw new Exception( - $"Project contents missing for {_projectDetails.Metadata.Name} ({_projectDetails.Metadata.ID})" + $"Project contents missing for {_projectDetails.Name} ({_projectDetails.Metadata.ID})" ); } diff --git a/c-sharp/Projects/ScrTextExtensions.cs b/c-sharp/Projects/ScrTextExtensions.cs new file mode 100644 index 0000000000..c41a20b35f --- /dev/null +++ b/c-sharp/Projects/ScrTextExtensions.cs @@ -0,0 +1,18 @@ +using Paratext.Data; + +namespace Paranext.DataProvider.Projects; + +internal static class ScrTextExtensions +{ + internal static ProjectDetails GetProjectDetails(this ScrText scrText) + { + return new( + scrText.Name, + new( + scrText.Guid.ToString().ToUpperInvariant(), + LocalParatextProjects.GetParatextProjectInterfaces() + ), + scrText.Directory + ); + } +} diff --git a/c-sharp/Services/ProjectSettings.cs b/c-sharp/Services/ProjectSettings.cs index a0c54f2a52..2fda42f142 100644 --- a/c-sharp/Services/ProjectSettings.cs +++ b/c-sharp/Services/ProjectSettings.cs @@ -5,6 +5,9 @@ public sealed class ProjectSettings public const string PB_BOOKS_PRESENT = "platformScripture.booksPresent"; public const string PT_BOOKS_PRESENT = "BooksPresent"; + public const string PB_NAME = "platform.name"; + public const string PT_NAME = "Name"; + public const string PB_FULL_NAME = "platform.fullName"; public const string PT_FULL_NAME = "FullName"; @@ -29,6 +32,7 @@ public sealed class ProjectSettings { PB_BOOKS_PRESENT, PT_BOOKS_PRESENT }, { PB_FULL_NAME, PT_FULL_NAME }, { PB_LANGUAGE, PT_LANGUAGE }, + { PB_NAME, PT_NAME }, { PB_VERSIFICATION, PT_VERSIFICATION }, { PB_IS_EDITABLE, PT_IS_EDITABLE }, }; diff --git a/extensions/package.json b/extensions/package.json index 03cd922865..30cf5e1b79 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -79,7 +79,6 @@ "webpack-merge": "^5.10.0", "zip-folder-promise": "^1.2.0" }, - "workspaces": ["src/*"], "volta": { "extends": "../package.json" } diff --git a/extensions/src/hello-world/src/main.ts b/extensions/src/hello-world/src/main.ts index 7264ef5550..7dff132558 100644 --- a/extensions/src/hello-world/src/main.ts +++ b/extensions/src/hello-world/src/main.ts @@ -109,7 +109,9 @@ const helloWorldProjectWebViewProvider: IWebViewProviderWithType = { return { title: projectId ? `Hello World Project: ${ - (await papi.projectLookup.getMetadataForProject(projectId)).name ?? projectId + (await ( + await papi.projectDataProviders.get('platform.base', projectId) + ).getSetting('platform.name')) ?? projectId }` : 'Hello World Project', ...savedWebView, @@ -175,7 +177,9 @@ const helloWorldProjectViewerProvider: IWebViewProviderWithType = { return { title: projectId ? `Hello World Project Viewer: ${ - (await papi.projectLookup.getMetadataForProject(projectId)).name ?? projectId + (await ( + await papi.projectDataProviders.get('platform.base', projectId) + ).getSetting('platform.name')) ?? projectId }` : 'Hello World Project Viewer', ...savedWebView, @@ -232,6 +236,7 @@ export async function activate(context: ExecutionActivationContext): Promise - implements IProjectDataProviderEngine + extends BaseProjectDataProviderEngine + implements IBaseProjectDataProviderEngine { private saveProjectData: () => Promise; @@ -53,11 +56,15 @@ class HelloWorldProjectDataProviderEngine async getSetting( key: ProjectSettingName, ): Promise { + if (key === 'platform.name') + // TypeScript doesn't realize ProjectSettingName is 'platform.name' in this case for some reason + // eslint-disable-next-line no-type-assertion/no-type-assertion + return this.projectData.projectName as ProjectSettingTypes[ProjectSettingName]; // We are checking in this same line that it is there. TypeScript :/ // eslint-disable-next-line no-type-assertion/no-type-assertion if (key in this.projectData.settings) return this.projectData.settings[key]!; - return papi.projectSettings.getDefault(key, HELLO_WORLD_PROJECT_INTERFACES); + return papi.projectSettings.getDefault(key); } async setSetting( @@ -67,6 +74,11 @@ class HelloWorldProjectDataProviderEngine if (!(await papi.projectSettings.isValid(key, newSetting, await this.getSetting(key)))) return false; + if (key === 'platform.name') + // TypeScript doesn't realize ProjectSettingName is 'platform.name' in this case for some reason + // eslint-disable-next-line no-type-assertion/no-type-assertion + this.projectData.projectName = newSetting as ProjectSettingTypes['platform.name']; + this.projectData.settings[key] = newSetting; await this.saveProjectData(); return true; diff --git a/extensions/src/hello-world/src/types/hello-world.d.ts b/extensions/src/hello-world/src/types/hello-world.d.ts index 7e8efe8259..412ee19c3a 100644 --- a/extensions/src/hello-world/src/types/hello-world.d.ts +++ b/extensions/src/hello-world/src/types/hello-world.d.ts @@ -1,6 +1,6 @@ declare module 'hello-world' { import type { DataProviderDataType, MandatoryProjectDataTypes } from '@papi/core'; - import type { IProjectDataProvider } from 'papi-shared-types'; + import type { IBaseProjectDataProvider } from 'papi-shared-types'; export type HelloWorldProjectDataTypes = MandatoryProjectDataTypes & { /** @@ -24,8 +24,8 @@ declare module 'hello-world' { removeName(name: string): Promise; }; - export type HelloWorldProjectDataProvider = IProjectDataProvider & - HelloWorldProjectDataProviderMethods; + export type IHelloWorldProjectDataProvider = + IBaseProjectDataProvider & HelloWorldProjectDataProviderMethods; /** Event containing information about `helloWorld` */ type HelloWorldEvent = { @@ -182,7 +182,7 @@ declare module 'hello-world' { } declare module 'papi-shared-types' { - import type { HelloWorldProjectDataProvider, HTMLColorNames } from 'hello-world'; + import type { IHelloWorldProjectDataProvider, HTMLColorNames } from 'hello-world'; export interface CommandHandlers { 'helloWorld.helloWorld': () => string; @@ -234,7 +234,7 @@ declare module 'papi-shared-types' { } export interface ProjectDataProviderInterfaces { - helloWorld: HelloWorldProjectDataProvider; + helloWorld: IHelloWorldProjectDataProvider; } export interface SettingTypes { diff --git a/extensions/src/hello-world/src/web-views/hello-world-project/hello-world-project-viewer.web-view.tsx b/extensions/src/hello-world/src/web-views/hello-world-project/hello-world-project-viewer.web-view.tsx index e6e15bae7a..c2b8bc1cc8 100644 --- a/extensions/src/hello-world/src/web-views/hello-world-project/hello-world-project-viewer.web-view.tsx +++ b/extensions/src/hello-world/src/web-views/hello-world-project/hello-world-project-viewer.web-view.tsx @@ -7,14 +7,9 @@ const namesDefault: string[] = []; globalThis.webViewComponent = function HelloWorldProjectViewer({ projectId }: WebViewProps) { const [names] = useProjectData('helloWorld', projectId).Names(undefined, namesDefault); - const [headerSize] = useProjectSetting('helloWorld', projectId, 'helloWorld.headerSize', 15); + const [headerSize] = useProjectSetting(projectId, 'helloWorld.headerSize', 15); - const [headerColor] = useProjectSetting( - 'helloWorld', - projectId, - 'helloWorld.headerColor', - 'Black', - ); + const [headerColor] = useProjectSetting(projectId, 'helloWorld.headerColor', 'Black'); const headerStyle = useMemo( () => ({ fontSize: `${headerSize}pt`, color: headerColor }), diff --git a/extensions/src/hello-world/src/web-views/hello-world-project/hello-world-project.web-view.tsx b/extensions/src/hello-world/src/web-views/hello-world-project/hello-world-project.web-view.tsx index b8666dc495..6fe7be7bb2 100644 --- a/extensions/src/hello-world/src/web-views/hello-world-project/hello-world-project.web-view.tsx +++ b/extensions/src/hello-world/src/web-views/hello-world-project/hello-world-project.web-view.tsx @@ -38,7 +38,7 @@ globalThis.webViewComponent = function HelloWorldProjectWebView({ setCurrentName(''); }, [pdp, currentName, setCurrentName]); - const helloWorldProjectSettings = useHelloWorldProjectSettings('helloWorld', pdp); + const helloWorldProjectSettings = useHelloWorldProjectSettings(pdp); const { headerStyle } = helloWorldProjectSettings; return ( diff --git a/extensions/src/hello-world/src/web-views/hello-world-project/use-hello-world-project-settings.hook.ts b/extensions/src/hello-world/src/web-views/hello-world-project/use-hello-world-project-settings.hook.ts index 1c6d61253a..6017523300 100644 --- a/extensions/src/hello-world/src/web-views/hello-world-project/use-hello-world-project-settings.hook.ts +++ b/extensions/src/hello-world/src/web-views/hello-world-project/use-hello-world-project-settings.hook.ts @@ -1,20 +1,19 @@ import { useProjectSetting } from '@papi/frontend/react'; -import type { ProjectDataProviderInterfaces, ProjectInterfaces } from 'papi-shared-types'; +import type { IBaseProjectDataProvider } from 'papi-shared-types'; import { CSSProperties, useMemo } from 'react'; -function useHelloWorldProjectSettings( - projectInterface: ProjectInterface, - projectDataProviderSource: string | ProjectDataProviderInterfaces[ProjectInterface] | undefined, +function useHelloWorldProjectSettings( + // Any Base PDP type works. Without `any`, the DataProviderUpdateInstructions types are incompatible + // eslint-disable-next-line @typescript-eslint/no-explicit-any + projectDataProviderSource: string | IBaseProjectDataProvider | undefined, ) { const [headerSize, setHeaderSize, resetHeaderSize] = useProjectSetting( - projectInterface, projectDataProviderSource, 'helloWorld.headerSize', 15, ); const [headerColor, setHeaderColor, resetHeaderColor] = useProjectSetting( - projectInterface, projectDataProviderSource, 'helloWorld.headerColor', 'Black', diff --git a/extensions/src/hello-world/src/web-views/hello-world.web-view.tsx b/extensions/src/hello-world/src/web-views/hello-world.web-view.tsx index c3f47c8e34..13859ee988 100644 --- a/extensions/src/hello-world/src/web-views/hello-world.web-view.tsx +++ b/extensions/src/hello-world/src/web-views/hello-world.web-view.tsx @@ -1,4 +1,4 @@ -import { ScrVers, VerseRef } from '@sillsdev/scripture'; +import { VerseRef } from '@sillsdev/scripture'; import papi, { logger } from '@papi/frontend'; import { useData, @@ -105,7 +105,7 @@ globalThis.webViewComponent = function HelloWorld({ // Test ref parameter properly getting latest value currentRender: currentRender.current, optionsSource: 'hook', - includeProjectInterfaces: '^ParatextStandard$', + includeProjectInterfaces: ['platformScripture.USFM_BookChapterVerse'], }, useCallback( (selectedProject, _dialogType, { currentRender: dialogRender, optionsSource }) => { @@ -150,7 +150,7 @@ globalThis.webViewComponent = function HelloWorld({ iconUrl: 'papi-extension://helloWorld/assets/offline.svg', title: 'Select List of Hello World Projects', selectedProjectIds: projects, - includeProjectInterfaces: '^ParatextStandard$', + includeProjectInterfaces: ['platformScripture.USFM_BookChapterVerse'], }), [projects], ), @@ -193,22 +193,12 @@ globalThis.webViewComponent = function HelloWorld({ const [personAge] = useData('helloSomeone.people').Age(name, -1); - const [psalm1] = useData('usfm').Chapter( - useMemo(() => new VerseRef('PSA', '1', '1', ScrVers.English), []), - 'Loading Psalm 1...', - ); - - const [john11] = useData('usfm').Verse( - useMemo(() => new VerseRef('JHN 1:1'), []), - 'Loading John 1:1...', - ); - const [currentProjectVerse] = useProjectData( - 'ParatextStandard', + 'platformScripture.USFM_BookChapterVerse', projectId ?? undefined, ).VerseUSFM(verseRef, 'Loading Verse'); - const helloWorldProjectSettings = useHelloWorldProjectSettings('ParatextStandard', projectId); + const helloWorldProjectSettings = useHelloWorldProjectSettings(projectId); const { headerStyle } = helloWorldProjectSettings; const [localizedStrings] = useLocalizedStrings( useMemo(() => ['%submitButton%'], []), @@ -258,10 +248,6 @@ globalThis.webViewComponent = function HelloWorld({
{personGreeting}
{personAge}
-

John 1:1

-
{john11}
-

Psalm 1

-
{psalm1}

Selected Project: {projectId ?? 'None'}
diff --git a/extensions/src/platform-scripture-editor/src/main.ts b/extensions/src/platform-scripture-editor/src/main.ts index 34f95e7dc6..fcd2407aa7 100644 --- a/extensions/src/platform-scripture-editor/src/main.ts +++ b/extensions/src/platform-scripture-editor/src/main.ts @@ -42,12 +42,12 @@ async function open( const projectForWebView = { projectId, isEditable: !isReadOnly }; if (!projectForWebView.projectId) { // Get a list of projects that are editable or not editable to show in the select project dialog - const projectMetadatas = (await papi.projectLookup.getMetadataForAllProjects()).filter( - (projectMetadata) => projectMetadata.projectInterfaces.includes('ParatextStandard'), - ); + const projectMetadatas = await papi.projectLookup.getMetadataForAllProjects({ + includeProjectInterfaces: ['platformScripture.USJ_Chapter'], + }); const projectsWithEditable = await Promise.all( projectMetadatas.map(async (projectMetadata) => { - const pdp = await papi.projectDataProviders.get('ParatextStandard', projectMetadata.id); + const pdp = await papi.projectDataProviders.get('platform.base', projectMetadata.id); return { projectId: projectMetadata.id, isEditable: await pdp.getSetting('platform.isEditable'), @@ -55,24 +55,29 @@ async function open( }), ); - projectForWebView.projectId = await papi.dialogs.selectProject({ - title: isReadOnly - ? '%platformScriptureEditor_dialog_openResourceViewer_title%' - : '%platformScriptureEditor_dialog_openScriptureEditor_title%', - prompt: isReadOnly - ? '%platformScriptureEditor_dialog_openResourceViewer_prompt%' - : '%platformScriptureEditor_dialog_openScriptureEditor_prompt%', - // Include projects whose editable matches readonly - includeProjectIds: projectsWithEditable - .filter(({ isEditable }) => isEditable !== isReadOnly) - .map(({ projectId: pId }) => pId), - }); + const projectIdsMatchingReadonly = projectsWithEditable + .filter(({ isEditable }) => isEditable !== isReadOnly) + .map(({ projectId: pId }) => pId); + + if (projectIdsMatchingReadonly.length > 0) { + projectForWebView.projectId = await papi.dialogs.selectProject({ + title: isReadOnly + ? '%platformScriptureEditor_dialog_openResourceViewer_title%' + : '%platformScriptureEditor_dialog_openScriptureEditor_title%', + prompt: isReadOnly + ? '%platformScriptureEditor_dialog_openResourceViewer_prompt%' + : '%platformScriptureEditor_dialog_openScriptureEditor_prompt%', + // Include projects whose editable matches readonly + includeProjectIds: projectIdsMatchingReadonly, + }); + } else { + logger.warn( + `Open ${isReadOnly ? 'Resource Viewer' : 'Scripture Editor'} did not find any projects with matching isEditable! Would show a prompt or something if there were a dialog available to do so`, + ); + } } else { // Get whether the provided project is editable - const pdp = await papi.projectDataProviders.get( - 'ParatextStandard', - projectForWebView.projectId, - ); + const pdp = await papi.projectDataProviders.get('platform.base', projectForWebView.projectId); projectForWebView.isEditable = await pdp.getSetting('platform.isEditable'); } if (projectForWebView.projectId) { @@ -103,7 +108,11 @@ const scriptureEditorWebViewProvider: IWebViewProvider = { const isReadOnly = getWebViewOptions.isReadOnly || savedWebView.state?.isReadOnly; let title = ''; if (projectId) { - title = `${(await papi.projectLookup.getMetadataForProject(projectId)).name ?? projectId}${isReadOnly ? '' : ' (Editable)'}`; + title = `${ + (await ( + await papi.projectDataProviders.get('platform.base', projectId) + ).getSetting('platform.name')) ?? projectId + }${isReadOnly ? '' : ' (Editable)'}`; } else title = isReadOnly ? 'Resource Viewer' : 'Scripture Editor'; return { diff --git a/extensions/src/platform-scripture-editor/src/platform-scripture-editor.web-view.tsx b/extensions/src/platform-scripture-editor/src/platform-scripture-editor.web-view.tsx index cc6c5d884c..95e48ae1a6 100644 --- a/extensions/src/platform-scripture-editor/src/platform-scripture-editor.web-view.tsx +++ b/extensions/src/platform-scripture-editor/src/platform-scripture-editor.web-view.tsx @@ -5,7 +5,7 @@ import { Marginal, MarginalRef, } from '@biblionexus-foundation/platform-editor'; -import { Usj, usjToUsxString, usxStringToUsj } from '@biblionexus-foundation/scripture-utilities'; +import { Usj } from '@biblionexus-foundation/scripture-utilities'; import { VerseRef } from '@sillsdev/scripture'; import { JSX, useCallback, useEffect, useMemo, useRef } from 'react'; import type { WebViewProps } from '@papi/core'; @@ -28,6 +28,8 @@ const defaultScrRef: ScriptureReference = { verseNum: 1, }; +const usjDocumentDefault: Usj = { type: 'USJ', version: '0.2.1', content: [] }; + function scrollToScrRef(scrRef: ScriptureReference) { const verseElement = document.querySelector( `.editor-container span[data-marker="v"][data-number="${scrRef.verseNum}"]`, @@ -81,31 +83,28 @@ globalThis.webViewComponent = function PlatformScriptureEditor({ */ const hasFirstRetrievedScripture = useRef(false); - const [usx, setUsx] = useProjectData('ParatextStandard', projectId).ChapterUSX( + const [usj, setUsj] = useProjectData('platformScripture.USJ_Chapter', projectId).ChapterUSJ( useMemo(() => new VerseRef(scrRef.bookNum, scrRef.chapterNum, scrRef.verseNum), [scrRef]), - '', + usjDocumentDefault, ); - const debouncedSetUsx = useMemo( - () => debounce((usj: Usj) => setUsx?.(usjToUsxString(usj)), 300), - [setUsx], - ); + const debouncedSetUsx = useMemo(() => debounce((newUsj: Usj) => setUsj?.(newUsj), 300), [setUsj]); // TODO: remove debounce when issue #826 is done. const onChange = useCallback(debouncedSetUsx, [debouncedSetUsx]); useEffect(() => { - if (usx) editorRef.current?.setUsj(usxStringToUsj(usx)); - }, [usx]); + if (usj) editorRef.current?.setUsj(usj); + }, [usj]); useEffect(() => { - if (usx && !hasFirstRetrievedScripture.current) { + if (usj && !hasFirstRetrievedScripture.current) { hasFirstRetrievedScripture.current = true; // Wait before scrolling to make sure there is time for the editor to load // TODO: hook into the editor and detect when it has loaded somehow setTimeout(() => scrollToScrRef(scrRef), EDITOR_LOAD_DELAY_TIME); } - }, [usx, scrRef]); + }, [usj, scrRef]); // Scroll the selected verse into view useEffect(() => { diff --git a/extensions/src/platform-scripture/package.json b/extensions/src/platform-scripture/package.json index 980c906d0c..dcbc86d7a7 100644 --- a/extensions/src/platform-scripture/package.json +++ b/extensions/src/platform-scripture/package.json @@ -35,6 +35,7 @@ "platform-bible-utils": "file:../../../lib/platform-bible-utils" }, "devDependencies": { + "@biblionexus-foundation/scripture-utilities": "^0.0.2", "@swc/core": "^1.4.11", "@types/node": "^20.12.2", "@types/react": "^18.2.73", diff --git a/extensions/src/platform-scripture/src/main.ts b/extensions/src/platform-scripture/src/main.ts index 34229d88a1..8c1d0c41cc 100644 --- a/extensions/src/platform-scripture/src/main.ts +++ b/extensions/src/platform-scripture/src/main.ts @@ -1,9 +1,12 @@ import papi, { logger } from '@papi/backend'; import { ExecutionActivationContext, ProjectSettingValidator } from '@papi/core'; +import ScriptureExtenderProjectDataProviderEngineFactory, { + SCRIPTURE_EXTENDER_PDPF_ID, +} from './project-data-provider/platform-scripture-extender-pdpef.model'; +import { SCRIPTURE_EXTENDER_PROJECT_INTERFACES } from './project-data-provider/platform-scripture-extender-pdpe.model'; // #region Project Setting Validators -// Based on https://github.com/paranext/paranext-core/blob/5c403e272b002ddd8970f735bc119f335c78c509/extensions/src/usfm-data-provider/index.d.ts#L401 // Should be 123 characters long const booksPresentValidator: ProjectSettingValidator<'platformScripture.booksPresent'> = async ( newValue: string, @@ -11,7 +14,6 @@ const booksPresentValidator: ProjectSettingValidator<'platformScripture.booksPre return newValue.length === 123 && newValue.replace(/[01]/g, '').length === 0; }; -// Based on https://github.com/paranext/paranext-core/blob/5c403e272b002ddd8970f735bc119f335c78c509/extensions/src/usfm-data-provider/index.d.ts#L391 // There are 7 options in the enum const versificationValidator: ProjectSettingValidator<'platformScripture.versification'> = async ( newValue: number, @@ -26,6 +28,13 @@ const versificationValidator: ProjectSettingValidator<'platformScripture.versifi export async function activate(context: ExecutionActivationContext) { logger.info('platformScripture is activating!'); + const scriptureExtenderPdpefPromise = + papi.projectDataProviders.registerProjectDataProviderEngineFactory( + SCRIPTURE_EXTENDER_PDPF_ID, + SCRIPTURE_EXTENDER_PROJECT_INTERFACES, + new ScriptureExtenderProjectDataProviderEngineFactory(), + ); + const includeProjectsCommandPromise = papi.commands.registerCommand( 'platformScripture.toggleIncludeMyParatext9Projects', async (shouldInclude) => { @@ -52,6 +61,7 @@ export async function activate(context: ExecutionActivationContext) { ); context.registrations.add( + await scriptureExtenderPdpefPromise, await includeProjectsCommandPromise, await includeProjectsValidatorPromise, await booksPresentPromise, diff --git a/extensions/src/platform-scripture/src/project-data-provider/platform-scripture-extender-pdpe.model.ts b/extensions/src/platform-scripture/src/project-data-provider/platform-scripture-extender-pdpe.model.ts new file mode 100644 index 0000000000..401a7c16af --- /dev/null +++ b/extensions/src/platform-scripture/src/project-data-provider/platform-scripture-extender-pdpe.model.ts @@ -0,0 +1,107 @@ +/** The `projectInterface`s the pdpf serves */ +// TypeScript is upset without `satisfies` here because `as const` makes the array readonly but it + +import papi, { ProjectDataProviderEngine, logger } from '@papi/backend'; +import { DataProviderUpdateInstructions, IProjectDataProviderEngine } from '@papi/core'; +import { VerseRef } from '@sillsdev/scripture'; +import type { ProjectDataProviderInterfaces } from 'papi-shared-types'; +import { UnsubscriberAsync, UnsubscriberAsyncList } from 'platform-bible-utils'; +import { USJChapterProjectInterfaceDataTypes } from 'platform-scripture'; +import { Usj, usjToUsxString, usxStringToUsj } from '@biblionexus-foundation/scripture-utilities'; + +/** The `projectInterface`s the Scripture Extender PDPF serves */ +// TypeScript is upset without `satisfies` here because `as const` makes the array readonly but it +// needs to be used in ProjectMetadata as not readonly :p +export const SCRIPTURE_EXTENDER_PROJECT_INTERFACES = [ + 'platformScripture.USJ_Chapter', +] as const satisfies ['platformScripture.USJ_Chapter']; + +/** The project interfaces the Scripture Extender Layering PDPF layers over */ +// TypeScript is upset without `satisfies` here because `as const` makes the array readonly but it +// needs to be used in ProjectMetadata as not readonly :p +export const SCRIPTURE_EXTENDER_OVERLAY_PROJECT_INTERFACES = [ + 'platformScripture.USX_Chapter', +] as const satisfies ['platformScripture.USX_Chapter']; + +export type ScriptureExtenderOverlayPDPs = { + [ProjectInterface in (typeof SCRIPTURE_EXTENDER_OVERLAY_PROJECT_INTERFACES)[number]]: ProjectDataProviderInterfaces[ProjectInterface]; +}; + +class ScriptureExtenderProjectDataProviderEngine + extends ProjectDataProviderEngine + implements IProjectDataProviderEngine +{ + /** The PDPs this layering PDP needs to function */ + private readonly pdps: ScriptureExtenderOverlayPDPs; + + private chapterUSXUnsubscriberPromise: Promise; + + constructor( + private readonly projectId: string, + pdpsToOverlay: ScriptureExtenderOverlayPDPs, + ) { + super(); + this.pdps = pdpsToOverlay; + + // Set up subscriptions to listen for changes to the project data to overlay and update + // our own subscribers + this.chapterUSXUnsubscriberPromise = this.pdps[ + 'platformScripture.USX_Chapter' + ].subscribeChapterUSX( + // Just picked a key for no reason in particular because we don't need anything in particular + // here because we're listening for all updates + new VerseRef(1, 1, 1), + () => { + this.notifyUpdate('ChapterUSJ'); + }, + { whichUpdates: '*', retrieveDataImmediately: false }, + ); + + // Synchronously set up an error logger because an IIFE says this.chapterUSXUnsubscriberPromise + // is being used before it is defined (most likely because it's a separate function running + // inside the constructor) + // code-review-disable-next-line complain-about-promise-chains ;) + this.chapterUSXUnsubscriberPromise.catch((e) => { + logger.error( + `Scripture Extender PDP for project ${this.projectId} failed to subscribe to ChapterUSX! ${e}`, + ); + }); + } + + // Do not emit update events when running this method because we are subscribing to data updates + // and sending out update events in the constructor + @papi.dataProviders.decorators.doNotNotify + async setChapterUSJ( + verseRef: VerseRef, + chapterUsj: Usj, + ): Promise> { + const didSucceed = await this.pdps['platformScripture.USX_Chapter'].setChapterUSX( + verseRef, + usjToUsxString(chapterUsj), + ); + if (didSucceed) return true; + return false; + } + + async getChapterUSJ(verseRef: VerseRef): Promise { + const usx = await this.pdps['platformScripture.USX_Chapter'].getChapterUSX(verseRef); + return usx ? usxStringToUsj(usx) : undefined; + } + + /** + * Disposes of this Project Data Provider Engine. Unsubscribes from listening to overlaid PDPs + * + * @returns `true` if successfully unsubscribed + */ + async dispose(): Promise { + const unsubscriberList = new UnsubscriberAsyncList( + `Scripture Extender PDP Engine ${this.projectId} Overlaid PDP Unsubscribers`, + ); + + unsubscriberList.add(await this.chapterUSXUnsubscriberPromise); + + return unsubscriberList.runAllUnsubscribers(); + } +} + +export default ScriptureExtenderProjectDataProviderEngine; diff --git a/extensions/src/platform-scripture/src/project-data-provider/platform-scripture-extender-pdpef.model.ts b/extensions/src/platform-scripture/src/project-data-provider/platform-scripture-extender-pdpef.model.ts new file mode 100644 index 0000000000..668ab4bd99 --- /dev/null +++ b/extensions/src/platform-scripture/src/project-data-provider/platform-scripture-extender-pdpef.model.ts @@ -0,0 +1,74 @@ +import { + IProjectDataProviderEngine, + IProjectDataProviderEngineFactory, + ProjectMetadataWithoutFactoryInfo, +} from '@papi/core'; +import papi from '@papi/backend'; +import { escapeStringRegexp } from 'platform-bible-utils'; +import ScriptureExtenderProjectDataProviderEngine, { + SCRIPTURE_EXTENDER_OVERLAY_PROJECT_INTERFACES, + SCRIPTURE_EXTENDER_PROJECT_INTERFACES, + ScriptureExtenderOverlayPDPs, +} from './platform-scripture-extender-pdpe.model'; + +/** PDP Factory ID for the Scripture Extender PDPF */ +export const SCRIPTURE_EXTENDER_PDPF_ID = 'platformScripture.scriptureExtenderPdpf'; +const SCRIPTURE_EXTENDER_PDPF_ID_REGEX_STRING = escapeStringRegexp(SCRIPTURE_EXTENDER_PDPF_ID); + +/** Regex strings for the project interfaces the Scripture Extender Layering PDPF layers over */ +const SCRIPTURE_EXTENDER_OVERLAY_PROJECT_INTERFACES_REGEX_STRINGS = + SCRIPTURE_EXTENDER_OVERLAY_PROJECT_INTERFACES.map((projectInterface) => + escapeStringRegexp(projectInterface), + ); + +class ScriptureExtenderProjectDataProviderEngineFactory + implements IProjectDataProviderEngineFactory +{ + // Implementing an interface method, so can't be static + // eslint-disable-next-line class-methods-use-this + async getAvailableProjects(): Promise { + try { + const projectsToOverlayMetadata = await papi.projectLookup.getMetadataForAllProjects({ + includeProjectInterfaces: SCRIPTURE_EXTENDER_OVERLAY_PROJECT_INTERFACES_REGEX_STRINGS, + // The `projectInterface`s we provide are excluded because we don't need to provide them + // again if they're already provided. Wrapped in another array so we only exclude projects + // that already serve all the `projectInterface`s we support + excludeProjectInterfaces: [SCRIPTURE_EXTENDER_PROJECT_INTERFACES], + excludePdpFactoryIds: SCRIPTURE_EXTENDER_PDPF_ID_REGEX_STRING, + }); + return projectsToOverlayMetadata.map((projectMetadataToOverlay) => { + const projectMetadata: ProjectMetadataWithoutFactoryInfo & { pdpFactoryInfo?: unknown } = + projectMetadataToOverlay; + delete projectMetadata.pdpFactoryInfo; + projectMetadata.projectInterfaces = SCRIPTURE_EXTENDER_PROJECT_INTERFACES; + return projectMetadata; + }); + } catch (e) { + throw new Error( + `${SCRIPTURE_EXTENDER_PDPF_ID} was not able to get metadata for projects to overlay. ${e}`, + ); + } + } + + // Implementing an interface method, so can't be static + // eslint-disable-next-line class-methods-use-this + async createProjectDataProviderEngine( + projectId: string, + ): Promise> { + // We're creating a ScriptureExtenderOverlayPDPs. Seems Object.fromEntries doesn't support + // mapped types very well + // eslint-disable-next-line no-type-assertion/no-type-assertion + const pdpsToOverlay = Object.fromEntries( + await Promise.all( + SCRIPTURE_EXTENDER_OVERLAY_PROJECT_INTERFACES.map(async (projectInterface) => { + const pdp = await papi.projectDataProviders.get(projectInterface, projectId); + return [projectInterface, pdp] as const; + }), + ), + ) as ScriptureExtenderOverlayPDPs; + + return new ScriptureExtenderProjectDataProviderEngine(projectId, pdpsToOverlay); + } +} + +export default ScriptureExtenderProjectDataProviderEngineFactory; diff --git a/extensions/src/platform-scripture/src/types/platform-scripture.d.ts b/extensions/src/platform-scripture/src/types/platform-scripture.d.ts index 5f5221cd5f..231080017e 100644 --- a/extensions/src/platform-scripture/src/types/platform-scripture.d.ts +++ b/extensions/src/platform-scripture/src/types/platform-scripture.d.ts @@ -1,9 +1,337 @@ declare module 'platform-scripture' { - // Add extension types exposed on the papi for other extensions to use here - // More instructions can be found in the README + import { VerseRef } from '@sillsdev/scripture'; + import type { + DataProviderDataType, + DataProviderSubscriberOptions, + DataProviderUpdateInstructions, + ExtensionDataScope, + } from '@papi/core'; + import type { IProjectDataProvider } from 'papi-shared-types'; + import { UnsubscriberAsync } from 'platform-bible-utils'; + import type { Usj } from '@biblionexus-foundation/scripture-utilities'; + + /** Provides Scripture data in USFM format by book, chapter, or verse */ + export type USFMBookChapterVerseProjectInterfaceDataTypes = { + /** Gets/sets the "raw" USFM data for the specified book */ + BookUSFM: DataProviderDataType; + /** Gets/sets the "raw" USFM data for the specified chapter */ + ChapterUSFM: DataProviderDataType; + /** Gets/sets the "raw" USFM data for the specified verse */ + VerseUSFM: DataProviderDataType; + }; + + /** Provides Scripture data in USX format by chapter */ + export type USXChapterProjectInterfaceDataTypes = { + /** Gets/sets the data in USX form for the specified chapter */ + ChapterUSX: DataProviderDataType; + }; + + /** Provides Scripture data in USJ format by chapter */ + export type USJChapterProjectInterfaceDataTypes = { + /** + * Gets the tokenized USJ data for the specified chapter + * + * WARNING: USJ is in very early stages of proposal, so it will likely change over time. + */ + ChapterUSJ: DataProviderDataType; + }; + + /** + * Provides project data for Scripture projects. + * + * WARNING: there are many possible Scripture-related data types. We have only implemented some of + * them. Following are a number of others that may be implemented at some point. This is not yet a + * complete list of the data types available from Scripture projects. + */ + type UnfinishedScriptureProjectDataTypes = { + /** + * Gets the tokenized USJ data for the specified book + * + * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change + * over time. Additionally, USJ is in very early stages of proposal, so it will likely also + * change over time. + */ + BookUSJ: DataProviderDataType; + /** + * Gets the tokenized USJ data for the specified verse + * + * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change + * over time. Additionally, USJ is in very early stages of proposal, so it will likely also + * change over time. + */ + VerseUSJ: DataProviderDataType; + }; + + /** + * Provides project data for Scripture projects. + * + * WARNING: there are many possible Scripture-related data types. We have only implemented some of + * them. Following are a number of others that may be implemented at some point. This is not yet a + * complete list of the data types available from Scripture projects. + */ + type UnfinishedProjectDataProviderExpanded = + IProjectDataProvider & { + /** + * Gets the tokenized USJ data for the specified book + * + * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change + * over time. Additionally, USJ is in very early stages of proposal, so it will likely also + * change over time. + */ + getBookUSJ(verseRef: VerseRef): Promise; + /** + * Sets the tokenized USJ data for the specified book + * + * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change + * over time. Additionally, USJ is in very early stages of proposal, so it will likely also + * change over time. + */ + setBookUSJ( + verseRef: VerseRef, + usj: Usj, + ): Promise>; + /** + * Subscribe to run a callback function when the tokenized USJ data is changed + * + * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change + * over time. Additionally, USJ is in very early stages of proposal, so it will likely also + * change over time. + * + * @param verseRef Tells the provider what changes to listen for + * @param callback Function to run with the updated USJ for this selector + * @param options Various options to adjust how the subscriber emits updates + * @returns Unsubscriber function (run to unsubscribe from listening for updates) + */ + subscribeBookUSJ( + verseRef: VerseRef, + callback: (usj: Usj | undefined) => void, + options?: DataProviderSubscriberOptions, + ): Promise; + + /** + * Gets the tokenized USJ data for the specified verse + * + * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change + * over time. Additionally, USJ is in very early stages of proposal, so it will likely also + * change over time. + */ + getVerseUSJ(verseRef: VerseRef): Promise; + /** + * Sets the tokenized USJ data for the specified verse + * + * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change + * over time. Additionally, USJ is in very early stages of proposal, so it will likely also + * change over time. + */ + setVerseUSJ( + verseRef: VerseRef, + usj: Usj, + ): Promise>; + /** + * Subscribe to run a callback function when the tokenized USJ data is changed + * + * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change + * over time. Additionally, USJ is in very early stages of proposal, so it will likely also + * change over time. + * + * @param verseRef Tells the provider what changes to listen for + * @param callback Function to run with the updated USJ for this selector + * @param options Various options to adjust how the subscriber emits updates + * @returns Unsubscriber function (run to unsubscribe from listening for updates) + */ + subscribeVerseUSJ( + verseRef: VerseRef, + callback: (usj: Usj | undefined) => void, + options?: DataProviderSubscriberOptions, + ): Promise; + + /** + * Gets an extension's serialized project data (so the extension can provide and manipulate + * its project data) + * + * @example `{ extensionName: 'biblicalTerms', dataQualifier: 'renderings' }` + * + * @param dataScope Contains the name of the extension requesting the data and which data it + * is requesting + * @returns Promise that resolves to the requested extension project data + */ + getExtensionData(dataScope: ExtensionDataScope): Promise; + /** + * Sets an extension's serialized project data (so the extension can provide and manipulate + * its project data) + * + * @example `{ extensionName: 'biblicalTerms', dataQualifier: 'renderings' }` + * + * @param dataScope Contains the name of the extension requesting the data and which data it + * is requesting + * @param extensionData The new project data for this extension + * @returns Promise that resolves indicating which data types received updates + */ + setExtensionData( + dataScope: ExtensionDataScope, + extensionData: string | undefined, + ): Promise>; + /** + * Subscribe to run a callback function when an extension's serialized project data is changed + * + * @example `{ extensionName: 'biblicalTerms', dataQualifier: 'renderings' }` + * + * @param dataScope Contains the name of the extension requesting the data and which data it + * is requesting + * @param callback Function to run with the updated extension data for this selector + * @param options Various options to adjust how the subscriber emits updates + * @returns Unsubscriber function (run to unsubscribe from listening for updates) + */ + subscribeExtensionData( + dataScope: ExtensionDataScope, + callback: (extensionData: string | undefined) => void, + options?: DataProviderSubscriberOptions, + ): Promise; + }; + + /** Provides Scripture data in USFM format by book, chapter, or verse */ + export type IUSFMBookChapterVerseProjectDataProvider = + IProjectDataProvider & { + /** Gets the "raw" USFM data for the specified book */ + getBookUSFM(verseRef: VerseRef): Promise; + /** Sets the "raw" USFM data for the specified book */ + setBookUSFM( + verseRef: VerseRef, + usfm: string, + ): Promise>; + /** + * Subscribe to run a callback function when the "raw" USFM data is changed + * + * @param verseRef Tells the provider what changes to listen for + * @param callback Function to run with the updated USFM for this selector + * @param options Various options to adjust how the subscriber emits updates + * @returns Unsubscriber function (run to unsubscribe from listening for updates) + */ + subscribeBookUSFM( + verseRef: VerseRef, + callback: (usfm: string | undefined) => void, + options?: DataProviderSubscriberOptions, + ): Promise; + + /** Gets the "raw" USFM data for the specified chapter */ + getChapterUSFM(verseRef: VerseRef): Promise; + /** Sets the "raw" USFM data for the specified chapter */ + setChapterUSFM( + verseRef: VerseRef, + usfm: string, + ): Promise>; + /** + * Subscribe to run a callback function when the "raw" USFM data is changed + * + * @param verseRef Tells the provider what changes to listen for + * @param callback Function to run with the updated USFM for this selector + * @param options Various options to adjust how the subscriber emits updates + * @returns Unsubscriber function (run to unsubscribe from listening for updates) + */ + subscribeChapterUSFM( + verseRef: VerseRef, + callback: (usfm: string | undefined) => void, + options?: DataProviderSubscriberOptions, + ): Promise; + + /** Gets the "raw" USFM data for the specified verse */ + getVerseUSFM(verseRef: VerseRef): Promise; + /** Sets the "raw" USFM data for the specified verse */ + setVerseUSFM( + verseRef: VerseRef, + usfm: string, + ): Promise>; + /** + * Subscribe to run a callback function when the "raw" USFM data is changed + * + * @param verseRef Tells the provider what changes to listen for + * @param callback Function to run with the updated USFM for this selector + * @param options Various options to adjust how the subscriber emits updates + * @returns Unsubscriber function (run to unsubscribe from listening for updates) + */ + subscribeVerseUSFM( + verseRef: VerseRef, + callback: (usfm: string | undefined) => void, + options?: DataProviderSubscriberOptions, + ): Promise; + }; + + /** Provides Scripture data in USX format by chapter */ + export type IUSXChapterProjectDataProvider = + IProjectDataProvider & { + /** Gets the Scripture text in USX format for the specified chapter */ + getChapterUSX(verseRef: VerseRef): Promise; + /** Sets the Scripture text in USX format for the specified chapter */ + setChapterUSX( + verseRef: VerseRef, + usx: string, + ): Promise>; + /** + * Subscribe to run a callback function when the USX data is changed + * + * @param verseRef Tells the provider what changes to listen for + * @param callback Function to run with the updated USX for this selector + * @param options Various options to adjust how the subscriber emits updates + * @returns Unsubscriber function (run to unsubscribe from listening for updates) + */ + subscribeChapterUSX( + verseRef: VerseRef, + callback: (usx: string | undefined) => void, + options?: DataProviderSubscriberOptions, + ): Promise; + }; + + /** Provides Scripture data in USJ format by chapter */ + export type IUSJChapterProjectDataProvider = + IProjectDataProvider & { + /** + * Gets the tokenized USJ data for the specified chapter + * + * WARNING: USJ is in very early stages of proposal, so it will likely change over time. + */ + getChapterUSJ(verseRef: VerseRef): Promise; + /** + * Sets the tokenized USJ data for the specified chapter + * + * WARNING: USJ is in very early stages of proposal, so it will likely change over time. + */ + setChapterUSJ( + verseRef: VerseRef, + usj: Usj, + ): Promise>; + /** + * Subscribe to run a callback function when the tokenized USJ data is changed + * + * WARNING: USJ is in very early stages of proposal, so it will likely change over time. + * + * @param verseRef Tells the provider what changes to listen for + * @param callback Function to run with the updated USJ for this selector + * @param options Various options to adjust how the subscriber emits updates + * @returns Unsubscriber function (run to unsubscribe from listening for updates) + */ + subscribeChapterUSJ( + verseRef: VerseRef, + callback: (usj: Usj | undefined) => void, + options?: DataProviderSubscriberOptions, + ): Promise; + }; + + // #region USJ types } declare module 'papi-shared-types' { + import type { + IUSFMBookChapterVerseProjectDataProvider, + IUSXChapterProjectDataProvider, + IUSJChapterProjectDataProvider, + } from 'platform-scripture'; + + export interface ProjectDataProviderInterfaces { + 'platformScripture.USFM_BookChapterVerse': IUSFMBookChapterVerseProjectDataProvider; + 'platformScripture.USX_Chapter': IUSXChapterProjectDataProvider; + 'platformScripture.USJ_Chapter': IUSJChapterProjectDataProvider; + } + export interface CommandHandlers { /** * Toggle the `platformScripture.includeMyParatext9Projects` setting on or off diff --git a/extensions/src/quick-verse/src/main.ts b/extensions/src/quick-verse/src/main.ts index 041df721a0..58bdba8a09 100644 --- a/extensions/src/quick-verse/src/main.ts +++ b/extensions/src/quick-verse/src/main.ts @@ -67,7 +67,10 @@ class QuickVerseDataProviderEngine /** Latest updated verse reference */ latestVerseRef = 'JHN 11:35'; - usfmDataProviderPromise = papi.dataProviders.get('usfm'); + usfmDataProviderPromise = papi.projectDataProviders.get( + 'platformScripture.USFM_BookChapterVerse', + '32664dc3288a28df2e2bb75ded887fc8f17a15fb', + ); /** Number of times any verse has been modified by a user this session */ heresyCount = 0; @@ -185,7 +188,7 @@ class QuickVerseDataProviderEngine try { const usfmDataProvider = await this.usfmDataProviderPromise; if (!usfmDataProvider) throw Error('Unable to get USFM data provider'); - const verseData = usfmDataProvider.getVerse(new VerseRef(selector)); + const verseData = usfmDataProvider.getVerseUSFM(new VerseRef(selector)); responseVerse = { text: (await verseData) ?? `${selector} not found` }; // Cache the verse text, track the latest cached verse, and send an update this.verses[selector] = responseVerse; diff --git a/extensions/src/usfm-data-provider/index.d.ts b/extensions/src/usfm-data-provider/index.d.ts index 02c75e2b6a..e42780da3a 100644 --- a/extensions/src/usfm-data-provider/index.d.ts +++ b/extensions/src/usfm-data-provider/index.d.ts @@ -1,15 +1,6 @@ declare module 'usfm-data-provider' { import { VerseRef } from '@sillsdev/scripture'; - import type { - DataProviderDataType, - DataProviderSubscriberOptions, - DataProviderUpdateInstructions, - ExtensionDataScope, - IDataProvider, - MandatoryProjectDataTypes, - } from '@papi/core'; - import type { IProjectDataProvider } from 'papi-shared-types'; - import { UnsubscriberAsync } from 'platform-bible-utils'; + import type { DataProviderDataType, IDataProvider } from '@papi/core'; export type UsfmProviderDataTypes = { BookNames: DataProviderDataType; @@ -20,356 +11,10 @@ declare module 'usfm-data-provider' { }; export type UsfmDataProvider = IDataProvider; - - /** - * Provides project data for Paratext Scripture projects. - * - * This is not yet a complete list of the data types available from Paratext projects. - */ - export type ParatextStandardProjectDataTypes = MandatoryProjectDataTypes & { - /** Gets/sets the "raw" USFM data for the specified book */ - BookUSFM: DataProviderDataType; - /** Gets/sets the "raw" USFM data for the specified chapter */ - ChapterUSFM: DataProviderDataType; - /** Gets/sets the "raw" USFM data for the specified verse */ - VerseUSFM: DataProviderDataType; - /** Gets/sets the data in USX form for the specified chapter */ - ChapterUSX: DataProviderDataType; - /** - * Gets the tokenized USJ data for the specified book - * - * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change - * over time. Additionally, USJ is in very early stages of proposal, so it will likely also - * change over time. - */ - BookUSJ: DataProviderDataType; - /** - * Gets the tokenized USJ data for the specified chapter - * - * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change - * over time. Additionally, USJ is in very early stages of proposal, so it will likely also - * change over time. - */ - ChapterUSJ: DataProviderDataType; - /** - * Gets the tokenized USJ data for the specified verse - * - * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change - * over time. Additionally, USJ is in very early stages of proposal, so it will likely also - * change over time. - */ - VerseUSJ: DataProviderDataType; - }; - - /** - * Scripture data represented in JSON format. Transformation from USX - * - * [See more information - * here](https://github.com/paranext/paranext-core/issues/480#issuecomment-1751094148) - */ - type USJDocument = { - /** The Scripture data serialization format used for this document */ - type: 'USJ'; - /** The USJ spec version */ - version: '0.0.1-alpha.2'; - /** Scripture contents laid out in a linear fashion */ - content: MarkerContent[]; - }; - - /** One piece of Scripture content. Can be a simple string or a marker and its contents */ - type MarkerContent = string | MarkerObject; - - /** A Scripture Marker and its contents */ - type MarkerObject = { - /** - * The kind of node or element this is, corresponding to each marker in USFM or each node in USX - * - * Its format is `type:style` - * - * @example `para:p`, `verse:v`, `char:nd` - */ - type: `${string}:${string}`; - /** This marker's contents laid out in a linear fashion */ - content?: MarkerContent[]; - /** Indicates the Book-chapter-verse value in the paragraph based structure */ - sid?: string; - /** Chapter number or verse number */ - number?: string; - /** The 3-letter book code in the id element */ - code?: BookCode; - /** Alternate chapter number or verse number */ - altnumber?: string; - /** Published character of chapter or verse */ - pubnumber?: string; - /** Caller character for footnotes and cross-refs */ - caller?: string; - /** Alignment of table cells */ - align?: string; - /** Category of extended study bible sections */ - category?: string; - }; - - /** Three-letter Scripture book code */ - // prettier-ignore - type BookCode = "GEN" | "EXO" | "LEV" | "NUM" | "DEU" | "JOS" | "JDG" | "RUT" | "1SA" | "2SA" | "1KI" | "2KI" | "1CH" | "2CH" | "EZR" | "NEH" | "EST" | "JOB" | "PSA" | "PRO" | "ECC" | "SNG" | "ISA" | "JER" | "LAM" | "EZK" | "DAN" | "HOS" | "JOL" | "AMO" | "OBA" | "JON" | "MIC" | "NAM" | "HAB" | "ZEP" | "HAG" | "ZEC" | "MAL" | "MAT" | "MRK" | "LUK" | "JHN" | "ACT" | "ROM" | "1CO" | "2CO" | "GAL" | "EPH" | "PHP" | "COL" | "1TH" | "2TH" | "1TI" | "2TI" | "TIT" | "PHM" | "HEB" | "JAS" | "1PE" | "2PE" | "1JN" | "2JN" | "3JN" | "JUD" | "REV" | "TOB" | "JDT" | "ESG" | "WIS" | "SIR" | "BAR" | "LJE" | "S3Y" | "SUS" | "BEL" | "1MA" | "2MA" | "3MA" | "4MA" | "1ES" | "2ES" | "MAN" | "PS2" | "ODA" | "PSS" | "EZA" | "5EZ" | "6EZ" | "DAG" | "PS3" | "2BA" | "LBA" | "JUB" | "ENO" | "1MQ" | "2MQ" | "3MQ" | "REP" | "4BA" | "LAO" | "FRT" | "BAK" | "OTH" | "INT" | "CNC" | "GLO" | "TDX" | "NDX" | "XXA" | "XXB" | "XXC" | "XXD" | "XXE" | "XXF" | "XXG"; - - /** - * Provides project data for Paratext Scripture projects. One is created for each project that is - * used - * - * This is a hand-written baked-out version of `ParatextStandardProjectDataProvider` for ease of - * reading - */ - type ParatextStandardProjectDataProviderExpanded = { - /** Gets the "raw" USFM data for the specified book */ - getBookUSFM(verseRef: VerseRef): Promise; - /** Sets the "raw" USFM data for the specified book */ - setBookUSFM( - verseRef: VerseRef, - usfm: string, - ): Promise>; - /** - * Subscribe to run a callback function when the "raw" USFM data is changed - * - * @param verseRef Tells the provider what changes to listen for - * @param callback Function to run with the updated USFM for this selector - * @param options Various options to adjust how the subscriber emits updates - * @returns Unsubscriber function (run to unsubscribe from listening for updates) - */ - subscribeBookUSFM( - verseRef: VerseRef, - callback: (usfm: string | undefined) => void, - options?: DataProviderSubscriberOptions, - ): Promise; - - /** Gets the "raw" USFM data for the specified chapter */ - getChapterUSFM(verseRef: VerseRef): Promise; - /** Sets the "raw" USFM data for the specified chapter */ - setChapterUSFM( - verseRef: VerseRef, - usfm: string, - ): Promise>; - /** - * Subscribe to run a callback function when the "raw" USFM data is changed - * - * @param verseRef Tells the provider what changes to listen for - * @param callback Function to run with the updated USFM for this selector - * @param options Various options to adjust how the subscriber emits updates - * @returns Unsubscriber function (run to unsubscribe from listening for updates) - */ - subscribeChapterUSFM( - verseRef: VerseRef, - callback: (usfm: string | undefined) => void, - options?: DataProviderSubscriberOptions, - ): Promise; - - /** Gets the "raw" USFM data for the specified verse */ - getVerseUSFM(verseRef: VerseRef): Promise; - /** Sets the "raw" USFM data for the specified verse */ - setVerseUSFM( - verseRef: VerseRef, - usfm: string, - ): Promise>; - /** - * Subscribe to run a callback function when the "raw" USFM data is changed - * - * @param verseRef Tells the provider what changes to listen for - * @param callback Function to run with the updated USFM for this selector - * @param options Various options to adjust how the subscriber emits updates - * @returns Unsubscriber function (run to unsubscribe from listening for updates) - */ - subscribeVerseUSFM( - verseRef: VerseRef, - callback: (usfm: string | undefined) => void, - options?: DataProviderSubscriberOptions, - ): Promise; - - /** Gets the Scripture text in USX format for the specified chapter */ - getChapterUSX(verseRef: VerseRef): Promise; - /** Sets the Scripture text in USX format for the specified chapter */ - setChapterUSX( - verseRef: VerseRef, - usx: string, - ): Promise>; - /** - * Subscribe to run a callback function when the USX data is changed - * - * @param verseRef Tells the provider what changes to listen for - * @param callback Function to run with the updated USX for this selector - * @param options Various options to adjust how the subscriber emits updates - * @returns Unsubscriber function (run to unsubscribe from listening for updates) - */ - subscribeChapterUSX( - verseRef: VerseRef, - callback: (usx: string | undefined) => void, - options?: DataProviderSubscriberOptions, - ): Promise; - - /** - * Gets the tokenized USJ data for the specified book - * - * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change - * over time. Additionally, USJ is in very early stages of proposal, so it will likely also - * change over time. - */ - getBookUSJ(verseRef: VerseRef): Promise; - /** - * Sets the tokenized USJ data for the specified book - * - * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change - * over time. Additionally, USJ is in very early stages of proposal, so it will likely also - * change over time. - */ - setBookUSJ( - verseRef: VerseRef, - usj: USJDocument, - ): Promise>; - /** - * Subscribe to run a callback function when the tokenized USJ data is changed - * - * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change - * over time. Additionally, USJ is in very early stages of proposal, so it will likely also - * change over time. - * - * @param verseRef Tells the provider what changes to listen for - * @param callback Function to run with the updated USJ for this selector - * @param options Various options to adjust how the subscriber emits updates - * @returns Unsubscriber function (run to unsubscribe from listening for updates) - */ - subscribeBookUSJ( - verseRef: VerseRef, - callback: (usj: USJDocument | undefined) => void, - options?: DataProviderSubscriberOptions, - ): Promise; - - /** - * Gets the tokenized USJ data for the specified chapter - * - * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change - * over time. Additionally, USJ is in very early stages of proposal, so it will likely also - * change over time. - */ - getChapterUSJ(verseRef: VerseRef): Promise; - /** - * Sets the tokenized USJ data for the specified chapter - * - * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change - * over time. Additionally, USJ is in very early stages of proposal, so it will likely also - * change over time. - */ - setChapterUSJ( - verseRef: VerseRef, - usj: USJDocument, - ): Promise>; - /** - * Subscribe to run a callback function when the tokenized USJ data is changed - * - * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change - * over time. Additionally, USJ is in very early stages of proposal, so it will likely also - * change over time. - * - * @param verseRef Tells the provider what changes to listen for - * @param callback Function to run with the updated USJ for this selector - * @param options Various options to adjust how the subscriber emits updates - * @returns Unsubscriber function (run to unsubscribe from listening for updates) - */ - subscribeChapterUSJ( - verseRef: VerseRef, - callback: (usj: USJDocument | undefined) => void, - options?: DataProviderSubscriberOptions, - ): Promise; - - /** - * Gets the tokenized USJ data for the specified verse - * - * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change - * over time. Additionally, USJ is in very early stages of proposal, so it will likely also - * change over time. - */ - getVerseUSJ(verseRef: VerseRef): Promise; - /** - * Sets the tokenized USJ data for the specified verse - * - * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change - * over time. Additionally, USJ is in very early stages of proposal, so it will likely also - * change over time. - */ - setVerseUSJ( - verseRef: VerseRef, - usj: USJDocument, - ): Promise>; - /** - * Subscribe to run a callback function when the tokenized USJ data is changed - * - * WARNING: USJ is one of many possible tokenized formats that we may use, so this may change - * over time. Additionally, USJ is in very early stages of proposal, so it will likely also - * change over time. - * - * @param verseRef Tells the provider what changes to listen for - * @param callback Function to run with the updated USJ for this selector - * @param options Various options to adjust how the subscriber emits updates - * @returns Unsubscriber function (run to unsubscribe from listening for updates) - */ - subscribeVerseUSJ( - verseRef: VerseRef, - callback: (usj: USJDocument | undefined) => void, - options?: DataProviderSubscriberOptions, - ): Promise; - - /** - * Gets an extension's serialized project data (so the extension can provide and manipulate its - * project data) - * - * @example `{ extensionName: 'biblicalTerms', dataQualifier: 'renderings' }` - * - * @param dataScope Contains the name of the extension requesting the data and which data it is - * requesting - * @returns Promise that resolves to the requested extension project data - */ - getExtensionData(dataScope: ExtensionDataScope): Promise; - /** - * Sets an extension's serialized project data (so the extension can provide and manipulate its - * project data) - * - * @example `{ extensionName: 'biblicalTerms', dataQualifier: 'renderings' }` - * - * @param dataScope Contains the name of the extension requesting the data and which data it is - * requesting - * @param extensionData The new project data for this extension - * @returns Promise that resolves indicating which data types received updates - */ - setExtensionData( - dataScope: ExtensionDataScope, - extensionData: string | undefined, - ): Promise>; - /** - * Subscribe to run a callback function when an extension's serialized project data is changed - * - * @example `{ extensionName: 'biblicalTerms', dataQualifier: 'renderings' }` - * - * @param dataScope Contains the name of the extension requesting the data and which data it is - * requesting - * @param callback Function to run with the updated extension data for this selector - * @param options Various options to adjust how the subscriber emits updates - * @returns Unsubscriber function (run to unsubscribe from listening for updates) - */ - subscribeExtensionData( - dataScope: ExtensionDataScope, - callback: (extensionData: string | undefined) => void, - options?: DataProviderSubscriberOptions, - ): Promise; - } & ParatextStandardProjectDataProvider; - - export type ParatextStandardProjectDataProvider = - IProjectDataProvider; } declare module 'papi-shared-types' { - import type { ParatextStandardProjectDataProvider, UsfmDataProvider } from 'usfm-data-provider'; - - export interface ProjectDataProviderInterfaces { - ParatextStandard: ParatextStandardProjectDataProvider; - } + import type { UsfmDataProvider } from 'usfm-data-provider'; export interface DataProviders { usfm: UsfmDataProvider; diff --git a/lib/papi-dts/edit-papi-d-ts.ts b/lib/papi-dts/edit-papi-d-ts.ts index dbae97160d..38fed2a74c 100644 --- a/lib/papi-dts/edit-papi-d-ts.ts +++ b/lib/papi-dts/edit-papi-d-ts.ts @@ -112,13 +112,6 @@ papiDTS = papiDTS.replace( "\n$2// @ts-ignore TypeScript pretends it can't find `$3`, but it works just fine$1", ); -// Add ignore error messages to the `UnionToIntersection` on generics extending -// `DataProviderDataTypes` -papiDTS = papiDTS.replace( - /WithProjectDataProviderEngine(ExtensionData|Setting)Methods<(?)/g, - 'WithProjectDataProviderEngine$1Methods<\n$3// @ts-ignore TypeScript thinks there is some unknown data type getting in, but there is not$2', -); - // Fix all the path-aliased imports. For some reason, generating `papi.d.ts` removes the @ from path // aliases on module declarations and static imports but not on dynamic imports to other modules. // Though we could go either way, we will remove the @ on dynamic imports to avoid confusing core diff --git a/lib/papi-dts/papi.d.ts b/lib/papi-dts/papi.d.ts index 89a0987d84..d2786670de 100644 --- a/lib/papi-dts/papi.d.ts +++ b/lib/papi-dts/papi.d.ts @@ -603,6 +603,12 @@ declare module 'shared/data/internal-connection.model' { * to be used outside of NetworkConnectors and ConnectionService.ts */ import { ComplexRequest, ComplexResponse, SerializedRequestType } from 'shared/utils/util'; + /** + * Represents when the request router does not know to which client id the request belongs. Server + * should try to determine the correct client id through other means, and client should just send to + * server + */ + export const CLIENT_ID_UNKNOWN = -2; /** Represents when the client id has not been assigned by the server */ export const CLIENT_ID_UNASSIGNED = -1; /** "Client id" for the server */ @@ -1896,6 +1902,11 @@ declare module 'shared/models/project-data-provider.model' { DataProviderDataTypes, DataProviderUpdateInstructions, } from 'shared/models/data-provider.model'; + /** + * The name of the `projectInterface` representing the essential methods every Base Project Data + * Provider must implement + */ + export const PROJECT_INTERFACE_PLATFORM_BASE = 'platform.base'; /** Indicates to a PDP what extension data is being referenced */ export type ExtensionDataScope = { /** Name of an extension as provided in its manifest */ @@ -2268,6 +2279,7 @@ declare module 'papi-shared-types' { } from 'shared/models/data-provider.model'; import type { MandatoryProjectDataTypes, + PROJECT_INTERFACE_PLATFORM_BASE, WithProjectDataProviderEngineExtensionDataMethods, } from 'shared/models/project-data-provider.model'; import type { IDisposableDataProvider } from 'shared/models/data-provider.interface'; @@ -2376,6 +2388,13 @@ declare module 'papi-shared-types' { * @example 'English' */ 'platform.language': string; + /** + * Short name of the project (not necessarily unique). This will be displayed directly in the + * UI. + * + * @example 'WEB' + */ + 'platform.name': string; /** * Localized full name of the project. This will be displayed directly in the UI. * @@ -2465,33 +2484,55 @@ declare module 'papi-shared-types' { * available, a Project Data Provider Factory that supports that project with some set of * `projectInterface`s creates a new instance of a PDP with the supported `projectInterface`s. * - * Every PDP **must** fulfill the requirements of all PDPs according to - * {@link MandatoryProjectDataTypes}. + * Often, these objects are Layering PDPs, meaning they manipulate data provided by Base PDPs + * which actually control the saving and loading of the data. Base PDPs must implement + * {@link IBaseProjectDataProvider}, which imposes additional requirements. + * + * See more information, including the difference between Base and Layering PDPs, at + * {@link ProjectDataProviderInterfaces}. */ - type IProjectDataProvider = IDataProvider< - TProjectDataTypes & MandatoryProjectDataTypes - > & - WithProjectDataProviderEngineSettingMethods & - WithProjectDataProviderEngineExtensionDataMethods & { - /** - * Subscribe to receive updates to the specified project setting. - * - * Note: By default, this `subscribeSetting` function automatically retrieves the current - * project setting value and runs the provided callback as soon as possible. That way, if you - * want to keep your data up-to-date, you do not also have to run `getSetting`. You can turn - * this functionality off in the `options` parameter. - * - * @param key The string id of the project setting for which to listen to changes - * @param callback Function to run with the updated project setting value - * @param options Various options to adjust how the subscriber emits updates - * @returns Unsubscriber to stop listening for updates - */ - subscribeSetting: ( - key: ProjectSettingName, - callback: (value: ProjectSettingTypes[ProjectSettingName]) => void, - options: DataProviderSubscriberOptions, - ) => Promise; - }; + type IProjectDataProvider = + IDataProvider; + /** + * An object on the papi for interacting with that project data. Created by the papi and layers + * over an {@link IBaseProjectDataProviderEngine} provided by an extension. Sometimes returned from + * getting a project data provider with `papi.projectDataProviders.get` (depending on if the PDP + * supports the `platform.base` `projectInterface`). + * + * Project Data Providers are a specialized version of {@link IDataProvider} that work with + * projects by exposing methods according to a set of `projectInterface`s. For each project + * available, a Project Data Provider Factory that supports that project with some set of + * `projectInterface`s creates a new instance of a PDP with the supported `projectInterface`s. + * + * Every Base PDP **must** fulfill the requirements of this interface in order to support the + * methods the PAPI requires for interacting with project data. + * + * See more information, including the difference between Base and Layering PDPs, at + * {@link ProjectDataProviderInterfaces}. + */ + type IBaseProjectDataProvider = + IProjectDataProvider & + WithProjectDataProviderEngineSettingMethods & + WithProjectDataProviderEngineExtensionDataMethods & { + /** + * Subscribe to receive updates to the specified project setting. + * + * Note: By default, this `subscribeSetting` function automatically retrieves the current + * project setting value and runs the provided callback as soon as possible. That way, if + * you want to keep your data up-to-date, you do not also have to run `getSetting`. You can + * turn this functionality off in the `options` parameter. + * + * @param key The string id of the project setting for which to listen to changes + * @param callback Function to run with the updated project setting value + * @param options Various options to adjust how the subscriber emits updates + * @returns Unsubscriber to stop listening for updates + */ + subscribeSetting: ( + key: ProjectSettingName, + callback: (value: ProjectSettingTypes[ProjectSettingName]) => void, + options: DataProviderSubscriberOptions, + ) => Promise; + }; /** This is just a simple example so we have more than one. It's not intended to be real. */ type NotesOnlyProjectDataTypes = MandatoryProjectDataTypes & { Notes: DataProviderDataType; @@ -2502,37 +2543,69 @@ declare module 'papi-shared-types' { * their `.d.ts` file and registering a Project Data Provider factory with the corresponding * `projectInterface`. * - * All Project Data Provider Interfaces' data types **must** extend - * {@link MandatoryProjectDataTypes} like the following example. Please see its documentation for - * information on how Project Data Providers can implement this interface. + * There are two types of Project Data Providers (and Project Data Provider Factories that serve + * them): + * + * 1. Base Project Data Provider - provides project data via some `projectInterface`s for its own + * projects with **its own unique project ids**. These PDPs **must support the `platform.base` + * `projectInterface` by implementing {@link IBaseProjectDataProvider}**. More information + * below. + * 2. Layering Project Data Provider - layers over other PDPs and provides additional + * `projectInterface`s for projects on other PDPs. Likely **does not provide its own unique + * project ids** but rather layers over base PDPs' project ids. These PDPs **do not need to + * support the `platform.base` `projectInterface` and should instead implement + * {@link IProjectDataProvider}**. Instead of providing projects themselves, they likely use the + * `ExtensionData` data type exposed via the `platform.base` `projectInterface` on Base PDPs to + * provide additional project data on top of Base PDPs. + * + * All Base Project Data Provider Interfaces' data types **must** implement + * {@link IBaseProjectDataProvider} (which extends {@link MandatoryProjectDataTypes}) like in the + * following example. Please see its documentation for information on how Project Data Providers + * can implement this interface. * * Note: The keys of this interface are the `projectInterface`s for the associated Project Data * Provider Interfaces. `projectInterface`s represent standardized sets of methods on a PDP. * - * WARNING: Each Project Data Provider **must** fulfill certain requirements for its `getSetting`, - * `setSetting`, and `resetSetting` methods. See {@link MandatoryProjectDataTypes} for more - * information. + * WARNING: Each Base Project Data Provider **must** fulfill certain requirements for its + * `getSetting`, `setSetting`, `resetSetting`, `getExtensionData`, and `setExtensionData` methods. + * See {@link IBaseProjectDataProvider} and {@link MandatoryProjectDataTypes} for more information. * * An extension can extend this interface to add types for the Project Data Provider Interfaces * its registered factory provides by adding the following to its `.d.ts` file (in this example, - * we are adding a Project Data Provider interface for the `MyExtensionProjectInterfaceName` - * `projectInterface`): + * we are adding a Base Project Data Provider interface for the `MyExtensionBaseProjectInterface` + * `projectInterface` and a Layering Project Data Provider interface for the + * `MyExtensionLayeringProjectInterface` `projectInterface`): * * @example * * ```typescript * declare module 'papi-shared-types' { - * export type MyProjectDataTypes = MandatoryProjectDataTypes & { + * export type MyBaseProjectDataTypes = { * MyProjectData: DataProviderDataType; * }; * + * export type MyLayeringProjectDataTypes = { + * MyOtherProjectData: DataProviderDataType; + * }; + * * export interface ProjectDataProviderInterfaces { - * MyExtensionProjectInterfaceName: IDataProvider; + * // Note that the base PDP implements `I**Base**ProjectDataProvider` + * MyExtensionBaseProjectInterface: IBaseProjectDataProvider; + * // Note that the layering PDP only implements `IProjectDataProvider` because the base PDP already + * // provides the `platform.base` data types + * MyExtensionLayeringProjectInterface: IProjectDataProvider; * } * } * ``` */ interface ProjectDataProviderInterfaces { + /** + * Base `projectInterface` that all PDPs that expose their own unique project ids must + * implement. + * + * There should be a PDP that provides `platform.base` for all available project ids. + */ + [PROJECT_INTERFACE_PLATFORM_BASE]: IBaseProjectDataProvider; 'platform.notesOnly': IProjectDataProvider; 'platform.placeholder': IProjectDataProvider; } @@ -2937,6 +3010,12 @@ declare module 'shared/services/web-view.service-model' { /** * Gets the saved properties on the WebView definition with the specified ID * + * Note: this only returns a representation of the current web view definition, not the actual web + * view definition itself. Changing properties on the returned definition does not affect the + * actual web view definition. You can possibly change the actual web view definition by calling + * {@link WebViewServiceType.getWebView} with certain `options`, depending on what options the web + * view provider has made available. + * * @param webViewId The ID of the WebView whose saved properties to get * @returns Saved properties of the WebView definition with the specified ID or undefined if not * found @@ -3455,8 +3534,6 @@ declare module 'shared/models/project-metadata.model' { export type ProjectMetadataWithoutFactoryInfo = { /** ID of the project (must be unique and case insensitive) */ id: string; - /** Short name of the project (not necessarily unique) */ - name: string; /** * All `projectInterface`s (aka standardized sets of methods on a PDP) that Project Data Providers * for this project support. Indicates what sort of project data should be available on this @@ -3499,21 +3576,11 @@ declare module 'shared/models/project-metadata.model' { }; } declare module 'shared/models/project-data-provider-engine.model' { - import { - ProjectInterfaces, - ProjectInterfaceDataTypes, - WithProjectDataProviderEngineSettingMethods, - ProjectSettingNames, - ProjectSettingTypes, - } from 'papi-shared-types'; - import { - MandatoryProjectDataTypes, - WithProjectDataProviderEngineExtensionDataMethods, - } from 'shared/models/project-data-provider.model'; + import { ProjectInterfaces, ProjectInterfaceDataTypes } from 'papi-shared-types'; import IDataProviderEngine, { DataProviderEngine, } from 'shared/models/data-provider-engine.model'; - import { DataProviderDataType } from 'shared/models/data-provider.model'; + import { DataProviderDataTypes } from 'shared/models/data-provider.model'; import { ProjectMetadataWithoutFactoryInfo } from 'shared/models/project-metadata.model'; import { UnionToIntersection } from 'platform-bible-utils'; /** @@ -3526,9 +3593,14 @@ declare module 'shared/models/project-data-provider-engine.model' { * {@link IProjectDataProvider}s. * * Project Data Provider Engine Factories create Project Data Provider Engines for specific - * `projectInterface`s. For each project available, a Project Data Provider Factory that supports - * that project with some set of `projectInterface`s creates a new instance of a PDP with the - * supported `projectInterface`s. + * `projectInterface`s. For each project id available on a Project Data Provider Factory, the + * factory that supports that project with some set of `projectInterface`s creates a new instance of + * a PDP with the supported `projectInterface`s. + * + * A PDP Factory can provide its own unique project ids (Base PDP Factory) or layer over other PDPFs + * and provide additional `projectInterface`s on those projects (Layering PDP Factory). Base PDP + * Factories must create PDPs that support the `platform.base` `projectInterface`. See + * {@link IBaseProjectDataProvider} and {@link ProjectDataProviderInterfaces} for more information. */ export interface IProjectDataProviderEngineFactory< SupportedProjectInterfaces extends ProjectInterfaces[], @@ -3598,17 +3670,8 @@ declare module 'shared/models/project-data-provider-engine.model' { */ export type IProjectDataProviderEngine = IDataProviderEngine< - UnionToIntersection & - MandatoryProjectDataTypes - > & - WithProjectDataProviderEngineSettingMethods< - // @ts-ignore TypeScript thinks there is some unknown data type getting in, but there is not - UnionToIntersection - > & - WithProjectDataProviderEngineExtensionDataMethods< - // @ts-ignore TypeScript thinks there is some unknown data type getting in, but there is not - UnionToIntersection - >; + UnionToIntersection & {} + >; /** * * Abstract class that provides a placeholder `notifyUpdate` for Project Data Provider Engine @@ -3626,8 +3689,107 @@ declare module 'shared/models/project-data-provider-engine.model' { */ export abstract class ProjectDataProviderEngine< SupportedProjectInterfaces extends ProjectInterfaces[], + AdditionalDataTypes extends DataProviderDataTypes = {}, > extends DataProviderEngine< - UnionToIntersection & { + UnionToIntersection & + AdditionalDataTypes + > {} +} +declare module 'shared/models/base-project-data-provider-engine.model' { + import { + ProjectInterfaces, + ProjectInterfaceDataTypes, + WithProjectDataProviderEngineSettingMethods, + ProjectSettingNames, + ProjectSettingTypes, + } from 'papi-shared-types'; + import { + PROJECT_INTERFACE_PLATFORM_BASE, + WithProjectDataProviderEngineExtensionDataMethods, + } from 'shared/models/project-data-provider.model'; + import { DataProviderDataType } from 'shared/models/data-provider.model'; + import { UnionToIntersection } from 'platform-bible-utils'; + import { + IProjectDataProviderEngine, + ProjectDataProviderEngine, + } from 'shared/models/project-data-provider-engine.model'; + /** + * The object to return from + * {@link IProjectDataProviderEngineFactory.createProjectDataProviderEngine} that the PAPI registers + * to create a Base Project Data Provider for a specific project. The ProjectDataProviderService + * creates an {@link IBaseProjectDataProvider} on the papi that layers over this engine, providing + * special functionality. + * + * See {@link DataProviderDataTypes} for information on how to make powerful types that work well + * with Intellisense. + * + * Note: papi creates a `notifyUpdate` function on the Project Data Provider Engine if one is not + * provided, so it is not necessary to provide one in order to call `this.notifyUpdate`. However, + * TypeScript does not understand that papi will create one as you are writing your Base Project + * Data Provider Engine, so you can avoid type errors with one of the following options: + * + * 1. If you are using a class to create a Base Project Data Provider Engine, you can extend the + * {@link BaseProjectDataProviderEngine} class, and it will provide `notifyUpdate` as well as + * inform Intellisense that you can run `notifyUpdate` with the `Setting` data type for you: + * + * ```typescript + * class MyPDPE extends BaseProjectDataProviderEngine<['MyProjectData']> implements IBaseProjectDataProviderEngine<['MyProjectData']> { + * ... + * } + * ``` + * + * 2. If you are using an object or class not extending {@link BaseProjectDataProviderEngine} to create + * a Base Project Data Provider Engine, you can add a `notifyUpdate` function (and, with an + * object, add the {@link WithNotifyUpdate} type) to your Base Project Data Provider Engine like + * so: + * + * ```typescript + * const myPDPE: IBaseProjectDataProviderEngine<['MyProjectData']> & WithNotifyUpdate = { + * notifyUpdate(updateInstructions) {}, + * ... + * } + * ``` + * + * OR + * + * ```typescript + * class MyPDPE implements IBaseProjectDataProviderEngine<['MyProjectData']> { + * notifyUpdate(updateInstructions?: DataProviderEngineNotifyUpdate) {} + * ... + * } + * ``` + */ + export type IBaseProjectDataProviderEngine< + SupportedProjectInterfaces extends ProjectInterfaces[], + > = IProjectDataProviderEngine< + [typeof PROJECT_INTERFACE_PLATFORM_BASE, ...SupportedProjectInterfaces] + > & + WithProjectDataProviderEngineSettingMethods< + UnionToIntersection & {} + > & + WithProjectDataProviderEngineExtensionDataMethods< + UnionToIntersection & {} + >; + /** + * + * Abstract class that provides a placeholder `notifyUpdate` for Base Project Data Provider Engine + * classes. If a Base Project Data Provider Engine class extends this class, it doesn't have to + * specify its own `notifyUpdate` function in order to use `notifyUpdate`. + * + * Additionally, extending this class informs Intellisense that you can run `notifyUpdate` with the + * `Setting` data type if needed like so: + * + * ```typescript + * this.notifyUpdate('Setting'); + * ``` + * + * @see {@link IBaseProjectDataProviderEngine} for more information on extending this class. + */ + export abstract class BaseProjectDataProviderEngine< + SupportedProjectInterfaces extends ProjectInterfaces[], + > extends ProjectDataProviderEngine< + [typeof PROJECT_INTERFACE_PLATFORM_BASE, ...SupportedProjectInterfaces], + { Setting: DataProviderDataType< ProjectSettingNames, ProjectSettingTypes[ProjectSettingNames], @@ -3646,7 +3808,10 @@ declare module 'shared/models/project-data-provider-factory.interface' { * TypeScript-extension-provided {@link IProjectDataProviderEngineFactory} or are created by * independent processes on the `papi`. * - * See {@link IProjectDataProvider} for more information. + * A PDP Factory can provide its own unique project ids (Base PDP Factory) or layer over other PDPFs + * and provide additional `projectInterface`s on those projects (Layering PDP Factory). Base PDP + * Factories must create PDPs that support the `platform.base` `projectInterface`. See + * {@link IBaseProjectDataProvider} and {@link ProjectDataProviderInterfaces} for more information. */ interface IProjectDataProviderFactory extends Dispose { /** Get data about all projects that can be created by this PDP factory */ @@ -3666,9 +3831,27 @@ declare module 'shared/models/project-data-provider-factory.interface' { export default IProjectDataProviderFactory; } declare module 'shared/models/project-lookup.service-model' { + import { + ProjectDataProviderFactoryMetadataInfo, + ProjectMetadata, + } from 'shared/models/project-metadata.model'; import { ProjectInterfaces } from 'papi-shared-types'; - import { ProjectMetadata } from 'shared/models/project-metadata.model'; import { ModifierProject } from 'platform-bible-utils'; + export const NETWORK_OBJECT_NAME_PROJECT_LOOKUP_SERVICE = 'ProjectLookupService'; + /** + * Transform the well-known pdp factory id into an id for its network object to use + * + * @param pdpFactoryId Id extensions use to identify this pdp factory + * @returns Id for then network object for this pdp factory + */ + export function getPDPFactoryNetworkObjectNameFromId(pdpFactoryId: string): string; + /** + * Transform a network object id for a pdp factory into its well-known pdp factory id + * + * @param pdpFactoryNetworkObjectName Id for then network object for this pdp factory + * @returns Id extensions use to identify this pdp factory + */ + export function getPDPFactoryIdFromNetworkObjectName(pdpFactoryNetworkObjectName: string): string; export type ProjectMetadataFilterOptions = ModifierProject & { /** Project IDs to include */ includeProjectIds?: string | string[]; @@ -3678,8 +3861,12 @@ declare module 'shared/models/project-lookup.service-model' { /** * * Provides metadata for projects known by the platform + * + * Note: this service runs locally everywhere in the TypeScript processes. It is also exposed on the + * PAPI websocket. Note these functions are all asynchronous on the PAPI websocket regardless of if + * their types are synchronous locally. */ - export interface ProjectLookupServiceType { + export type ProjectLookupServiceType = { /** * Provide metadata for all projects that have PDP factories * @@ -3689,9 +3876,13 @@ declare module 'shared/models/project-lookup.service-model' { * no id is specified) for the project will be returned. If you need `projectInterface`s supported * by specific PDP Factories, you can access it at {@link ProjectMetadata.pdpFactoryInfo}. * + * @param options Options for specifying filters for the project metadata retrieved. If a PDP + * Factory Id does not match the filter, it will not be contacted at all for this function call. + * As a result, a PDP factory that intends to layer over other PDP factories **must** specify + * its id in `options.excludePdpFactoryIds` to avoid an infinite loop of calling this function. * @returns ProjectMetadata for all projects stored on the local system */ - getMetadataForAllProjects: () => Promise; + getMetadataForAllProjects(options?: ProjectMetadataFilterOptions): Promise; /** * Look up metadata for a specific project ID * @@ -3708,1237 +3899,1248 @@ declare module 'shared/models/project-lookup.service-model' { * not provided, then look in all available PDP factories for the given project ID. * @returns ProjectMetadata for the given project */ - getMetadataForProject: ( + getMetadataForProject( projectId: string, projectInterface?: ProjectInterfaces, pdpFactoryId?: string, - ) => Promise; - } - export const projectLookupServiceNetworkObjectName = 'ProjectLookupService'; -} -declare module 'node/utils/crypto-util' { - export function createUuid(): string; - /** - * Create a cryptographically secure nonce that is at least 128 bits long. See nonce spec at - * https://w3c.github.io/webappsec-csp/#security-nonces - * - * @param encoding: "base64url" (HTML safe, shorter string) or "hex" (longer string) From - * https://base64.guru/standards/base64url, the purpose of this encoding is "the ability to use - * the encoding result as filename or URL address" - * @param numberOfBytes: Number of bytes the resulting nonce should contain - * @returns Cryptographically secure, pseudo-randomly generated value encoded as a string - */ - export function createNonce(encoding: 'base64url' | 'hex', numberOfBytes?: number): string; -} -declare module 'node/models/execution-token.model' { - /** For now this is just for extensions, but maybe we will want to expand this in the future */ - export type ExecutionTokenType = 'extension'; - /** Execution tokens can be passed into API calls to provide context about their identity */ - export class ExecutionToken { - readonly type: ExecutionTokenType; - readonly name: string; - readonly nonce: string; - constructor(tokenType: ExecutionTokenType, name: string); - getHash(): string; - } -} -declare module 'extension-host/extension-types/extension-activation-context.model' { - import { ExecutionToken } from 'node/models/execution-token.model'; - import { UnsubscriberAsyncList } from 'platform-bible-utils'; - /** An object of this type is passed into `activate()` for each extension during initialization */ - export type ExecutionActivationContext = { - /** Canonical name of the extension */ - name: string; - /** Used to save and load data from the storage service. */ - executionToken: ExecutionToken; - /** Tracks all registrations made by an extension so they can be cleaned up when it is unloaded */ - registrations: UnsubscriberAsyncList; - }; -} -declare module 'shared/models/dialog-options.model' { - import { LocalizeKey } from 'platform-bible-utils'; - /** General options to adjust dialogs (created from `papi.dialogs`) */ - export type DialogOptions = { - /** - * Dialog title to display in the header. If you provide a {@link LocalizeKey}, it will be - * localized before displaying. - * - * Default depends on the dialog - */ - title?: string | LocalizeKey; - /** Url of dialog icon to display in the header. Default is Platform.Bible logo */ - iconUrl?: string; - /** - * The message to show the user in the dialog. If you provide a {@link LocalizeKey}, it will be - * localized before displaying. - * - * Default depends on the dialog - */ - prompt?: string | LocalizeKey; - }; - /** Keys of properties on {@link DialogOptions} that should be localized if they are LocalizeKeys */ - export const DIALOG_OPTIONS_LOCALIZABLE_PROPERTY_KEYS: readonly ['title', 'prompt']; - /** Data in each tab that is a dialog. Added to DialogOptions in `dialog.service-host.ts` */ - export type DialogData = DialogOptions & { - isDialog: true; - }; -} -declare module 'renderer/components/dialogs/dialog-base.data' { - import { FloatSize, TabLoader, TabSaver } from 'shared/models/docking-framework.model'; - import { DialogData } from 'shared/models/dialog-options.model'; - import { ReactElement } from 'react'; - /** Base type for DialogDefinition. Contains reasonable defaults for dialogs */ - export type DialogDefinitionBase = Readonly<{ - /** Overwritten in {@link DialogDefinition}. Must be specified by all DialogDefinitions */ - tabType?: string; - /** Overwritten in {@link DialogDefinition}. Must be specified by all DialogDefinitions */ - Component?: (props: DialogProps) => ReactElement; - /** - * The default icon for this dialog. This may be overridden by the `DialogOptions.iconUrl` - * - * Defaults to the Platform.Bible logo - */ - defaultIconUrl?: string; + ): Promise; /** - * The default title for this dialog. This may be overridden by the `DialogOptions.title` + * Compare two project ids to determine if they are equal * - * Defaults to the DialogDefinition's `tabType` - */ - defaultTitle?: string; - /** The width and height at which the dialog will be loaded in CSS `px` units */ - initialSize: FloatSize; - /** The minimum width to which the dialog can be set in CSS `px` units */ - minWidth?: number; - /** The minimum height to which the dialog can be set in CSS `px` units */ - minHeight?: number; - /** - * The function used to load the dialog into the dock layout. Default uses the `Component` field - * and passes in the `DialogProps` - */ - loadDialog: TabLoader; - /** - * The function used to save the dialog into the dock layout + * We're treating project IDs as case insensitive strings. * - * Default does not save the dialog as they cannot properly be restored yet. + * From + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator: * - * TODO: preserve requests between refreshes - save the dialog info in such a way that it works - * when loading again after refresh + * Only strings that differ in base letters or accents and other diacritic marks compare as + * unequal. Examples: a ≠ b, a ≠ á, a = A. */ - saveDialog: TabSaver; - }>; - /** Props provided to the dialog component */ - export type DialogProps = DialogData & { + areProjectIdsEqual(projectIdA: string, projectIdB: string): boolean; + /** Filter an array of {@link ProjectMetadata} in various ways */ + filterProjectsMetadata( + projectsMetadata: ProjectMetadata[], + options: ProjectMetadataFilterOptions, + ): ProjectMetadata[]; /** - * Sends the data as a resolved response to the dialog request and closes the dialog + * Get the PDP Factory info whose `projectInterface`s are most minimally matching to the provided + * `projectInterface` * - * @param data Data with which to resolve the request - */ - submitDialog(data: TData): void; - /** Cancels the dialog request (resolves the response with `undefined`) and closes the dialog */ - cancelDialog(): void; - /** - * Rejects the dialog request with the specified message and closes the dialog + * Hopefully this will allow us to get the PDP that most closely matches the `projectInterface`s + * to avoid unnecessary redirects through layered PDPs * - * @param errorMessage Message to explain why the dialog request was rejected + * @param projectMetadata Metadata for project for which to get minimally matching PDPF + * @param projectInterface Which `projectInterface` to minimally match for + * @returns PDP Factory id whose `projectInterface`s minimally match the provided + * `projectInterface` if at least one PDP Factory was found that supports the `projectInterface` + * provided */ - rejectDialog(errorMessage: string): void; + getMinimalMatchPdpFactoryId( + projectMetadata: ProjectMetadata, + projectInterface: ProjectInterfaces, + ): string | undefined; }; + /** Local object of functions to run locally on each process as part of the project lookup service */ + export const projectLookupServiceBase: ProjectLookupServiceType; /** - * Set the functionality of submitting and canceling dialogs. This should be called specifically by - * `dialog.service-host.ts` immediately on startup and by nothing else. This is only here to - * mitigate a dependency cycle - * - * @param dialogServiceFunctions Functions from the dialog service host for resolving and rejecting - * dialogs + * Note: If there are multiple PDPs available whose metadata matches the conditions provided by the + * parameters, their project metadata will all be combined, so all available `projectInterface`s + * provided by the PDP Factory with the matching id (or all PDP Factories if no id is specified) for + * the project will be returned. If you need `projectInterface`s supported by specific PDP + * Factories, you can access it at {@link ProjectMetadata.pdpFactoryInfo}. */ - export function hookUpDialogService({ - resolveDialogRequest: resolve, - rejectDialogRequest: reject, - }: { - resolveDialogRequest: (id: string, data: unknown | undefined) => void; - rejectDialogRequest: (id: string, message: string) => void; - }): void; + function internalGetMetadata(options?: ProjectMetadataFilterOptions): Promise; + function transformGetMetadataForProjectParametersToFilter( + projectId?: string, + projectInterface?: ProjectInterfaces, + pdpFactoryId?: string, + ): { + includeProjectIds: string | undefined; + includeProjectInterfaces: string | undefined; + includePdpFactoryIds: string | undefined; + }; /** - * Static definition of a dialog that can be shown in Platform.Bible + * Compare function (for array sorting and such) that compares two PDPF Metadata infos by most + * minimal match to the `projectInterface` in question. * - * For good defaults, dialogs can include all the properties of this dialog. Dialogs must then - * specify `tabType` and `Component` in order to comply with `DialogDefinition` + * Hopefully this will allow us to get the PDP that most closely matches the `projectInterface`s to + * avoid unnecessary redirects through layered PDPs * - * Note: this is not a class that can be inherited because all properties would be static but then - * we would not be able to use the default `loadDialog` because it would be using a static reference - * to a nonexistent `Component`. Instead of inheriting this as a class, any dialog definition can - * spread this `{ ...DIALOG_BASE }` + * @param pdpFMetadataInfoA First ProjectDataProviderFactoryMetadataInfo to compare + * @param pdpFMetadataInfoB Second ProjectDataProviderFactoryMetadataInfo to compare + * @returns -1 if a is less than b, 0 if equal, and 1 otherwise */ - const DIALOG_BASE: DialogDefinitionBase; - export default DIALOG_BASE; -} -declare module 'renderer/components/dialogs/dialog-definition.model' { - import { DialogOptions } from 'shared/models/dialog-options.model'; - import { DialogDefinitionBase, DialogProps } from 'renderer/components/dialogs/dialog-base.data'; - import { ReactElement } from 'react'; - import { ProjectMetadataFilterOptions } from 'shared/models/project-lookup.service-model'; - /** The tabType for the select project dialog in `select-project.dialog.tsx` */ - export const SELECT_PROJECT_DIALOG_TYPE = 'platform.selectProject'; - /** The tabType for the select multiple projects dialog in `select-multiple-projects.dialog.tsx` */ - export const SELECT_MULTIPLE_PROJECTS_DIALOG_TYPE = 'platform.selectMultipleProjects'; - /** The tabType for the select books dialog in `select-books.dialog.tsx` */ - export const SELECT_BOOKS_DIALOG_TYPE = 'platform.selectBooks'; - type ProjectDialogOptionsBase = DialogOptions & ProjectMetadataFilterOptions; - /** Options to provide when showing the Select Project dialog */ - export type SelectProjectDialogOptions = ProjectDialogOptionsBase; - /** Options to provide when showing the Select Multiple Project dialog */ - export type SelectMultipleProjectsDialogOptions = ProjectDialogOptionsBase & { - /** Project IDs that should start selected in the dialog */ - selectedProjectIds?: string[]; - }; - /** Options to provide when showing the Select Books dialog */ - export type SelectBooksDialogOptions = DialogOptions & { - /** Books IDs that should start selected in the dialog */ - selectedBookIds?: string[]; + function compareProjectDataProviderFactoryMetadataInfoMinimalMatch( + pdpFMetadataInfoA: ProjectDataProviderFactoryMetadataInfo | undefined, + pdpFMetadataInfoB: ProjectDataProviderFactoryMetadataInfo | undefined, + ): -1 | 0 | 1; + /** This is an internal-only export for testing purposes and should not be used in development */ + export const testingProjectLookupService: { + internalGetMetadata: typeof internalGetMetadata; + compareProjectDataProviderFactoryMetadataInfoMinimalMatch: typeof compareProjectDataProviderFactoryMetadataInfoMinimalMatch; + transformGetMetadataForProjectParametersToFilter: typeof transformGetMetadataForProjectParametersToFilter; }; +} +declare module 'shared/services/project-lookup.service' { + const projectLookupService: import('shared/models/project-lookup.service-model').ProjectLookupServiceType; + export default projectLookupService; +} +declare module 'shared/services/project-data-provider.service' { + import { ProjectInterfaces, ProjectDataProviderInterfaces } from 'papi-shared-types'; + import { IProjectDataProviderEngineFactory } from 'shared/models/project-data-provider-engine.model'; + import { Dispose } from 'platform-bible-utils'; /** - * Mapped type for dialog functions to use in getting various types for dialogs + * Add a new Project Data Provider Factory to PAPI that uses the given engine. * - * Keys should be dialog names, and values should be {@link DialogDataTypes} + * @param pdpFactoryId Unique id for this PDP factory + * @param projectInterfaces The standardized sets of methods (`projectInterface`s) supported by the + * Project Data Provider Engines produced by this factory. Indicates what sort of project data + * should be available on the PDPEs created by this factory. + * @param pdpEngineFactory Used in a ProjectDataProviderFactory to create ProjectDataProviders + * @returns Promise that resolves to a disposable object when the registration operation completes + */ + export function registerProjectDataProviderEngineFactory< + SupportedProjectInterfaces extends ProjectInterfaces[], + >( + pdpFactoryId: string, + projectInterfaces: SupportedProjectInterfaces, + pdpEngineFactory: IProjectDataProviderEngineFactory, + ): Promise; + /** + * Get a Project Data Provider for the given project ID. * - * If you add a dialog here, you must also add it on {@link DIALOGS} + * @example + * + * ```typescript + * const pdp = await get('platformScripture.USFM_BookChapterVerse', 'ProjectID12345'); + * pdp.getVerse(new VerseRef('JHN', '1', '1')); + * ``` + * + * @param projectInterface `projectInterface` that the project to load must support. The TypeScript + * type for the returned project data provider will have the project data provider interface type + * associated with this `projectInterface`. If the project does not implement this + * `projectInterface` (according to its metadata), an error will be thrown. + * @param projectId ID for the project to load + * @param pdpFactoryId Optional ID of the PDP factory from which to get the project data provider if + * the PDP factory supports this project id and project interface. If not provided, then look in + * all available PDP factories for the given project ID. + * @returns Project data provider with types that are associated with the given `projectInterface` + * @throws If did not find a project data provider for the project id that supports the requested + * `projectInterface` (and from the requested PDP factory if specified) */ - export interface DialogTypes { - [SELECT_PROJECT_DIALOG_TYPE]: DialogDataTypes; - [SELECT_MULTIPLE_PROJECTS_DIALOG_TYPE]: DialogDataTypes< - SelectMultipleProjectsDialogOptions, - string[] - >; - [SELECT_BOOKS_DIALOG_TYPE]: DialogDataTypes; + export function get( + projectInterface: ProjectInterface, + projectId: string, + pdpFactoryId?: string, + ): Promise; + export interface PapiBackendProjectDataProviderService { + registerProjectDataProviderEngineFactory: typeof registerProjectDataProviderEngineFactory; + get: typeof get; } - /** Each type of dialog. These are the tab types used in the dock layout */ - export type DialogTabTypes = keyof DialogTypes; - /** Types related to a specific dialog */ - export type DialogDataTypes = { - /** - * The dialog options to specify when calling the dialog. Passed into `loadDialog` as - * SavedTabInfo.data - * - * The default implementation of `loadDialog` passes all the options down to the dialog component - * as props - */ - options: TOptions; - /** The type of the response to the dialog request */ - responseType: TReturnType; - /** Props provided to the dialog component */ - props: DialogProps & TOptions; - }; - export type DialogDefinition = Readonly< - DialogDefinitionBase & { - /** - * Type of tab - indicates what kind of built-in dock layout tab this dialog definition - * represents - */ - tabType: DialogTabType; - /** - * React component to render for this dialog - * - * This must be specified only if you do not overwrite the default `loadDialog` - * - * @param props Props that will be passed through from the dialog tab's data - * @returns React element to render - */ - Component: ( - props: DialogProps & - DialogTypes[DialogTabType]['options'], - ) => ReactElement; - } - >; -} -declare module 'shared/services/dialog.service-model' { - import { - DialogTabTypes, - DialogTypes, - SelectProjectDialogOptions, - } from 'renderer/components/dialogs/dialog-definition.model'; /** * - * Prompt the user for responses with dialogs + * Service that registers and gets project data providers */ - export interface DialogService { - /** - * Shows a dialog to the user and prompts the user to respond - * - * @type `TReturn` - The type of data the dialog responds with - * @param dialogType The type of dialog to show the user - * @param options Various options for configuring the dialog that shows - * @returns Returns the user's response or `undefined` if the user cancels - */ - showDialog( - dialogType: DialogTabType, - options?: DialogTypes[DialogTabType]['options'], - ): Promise; - /** - * Shows a select project dialog to the user and prompts the user to select a dialog - * - * @param options Various options for configuring the dialog that shows - * @returns Returns the user's selected project id or `undefined` if the user cancels - */ - selectProject(options?: SelectProjectDialogOptions): Promise; + export const papiBackendProjectDataProviderService: PapiBackendProjectDataProviderService; + export interface PapiFrontendProjectDataProviderService { + get: typeof get; } - /** Prefix on requests that indicates that the request is related to dialog operations */ - export const CATEGORY_DIALOG = 'dialog'; -} -declare module 'shared/services/dialog.service' { - import { DialogService } from 'shared/services/dialog.service-model'; - const dialogService: DialogService; - export default dialogService; -} -declare module 'renderer/hooks/papi-hooks/use-dialog-callback.hook' { - import { DialogTabTypes, DialogTypes } from 'renderer/components/dialogs/dialog-definition.model'; - export type UseDialogCallbackOptions = { - /** - * How many dialogs are allowed to be open at once from this dialog callback. Calling the callback - * when this number of maximum open dialogs has been reached does nothing. Set to -1 for - * unlimited. Defaults to 1. - */ - maximumOpenDialogs?: number; + /** + * + * Service that gets project data providers + */ + export const papiFrontendProjectDataProviderService: { + get: typeof get; }; +} +declare module 'shared/data/file-system.model' { + /** Types to use with file system operations */ /** + * Represents a path in file system or other. Has a scheme followed by :// followed by a relative + * path. If no scheme is provided, the app scheme is used. Available schemes are as follows: * - * Enables using `papi.dialogs.showDialog` in React more easily. Returns a callback to run that will - * open a dialog with the provided `dialogType` and `options` then run the `resolveCallback` with - * the dialog response or `rejectCallback` if there is an error. By default, only one dialog can be - * open at a time. + * - `app://` - goes to the `.platform.bible` directory inside the user's home directory. * - * If you need to open multiple dialogs and track which dialog is which, you can set - * `options.shouldOpenMultipleDialogs` to `true` and add a counter to the `options` when calling the - * callback. Then `resolveCallback` will be resolved with that options object including your - * counter. + * - On Linux and Mac, this is `$HOME/.platform.bible` + * - On Windows, this is `%USERPROFILE%/.platform.bible` + * - Note: In development, `app://` always goes to `paranext-core/dev-appdata` + * - `cache://` - goes to the app's temporary file cache at `app://cache` + * - `data://` - goes to the app's data storage location at `app://data` + * - `resources://` - goes to the `resources` directory inside the install directory * - * @type `DialogTabType` The dialog type you are using. Should be inferred by parameters - * @param dialogType Dialog type you want to show on the screen + * - Note: In development, `resources://` always goes to the repo root, `paranext-core`. Not all files + * are copied into the production `resources` folder, though. See `electron-builder.json5`'s + * `extraResources` for some that are copied. + * - `file://` - an absolute file path from drive root * - * Note: this parameter is internally assigned to a `ref`, so changing it will not cause any hooks - * to re-run with its new value. This means that updating this parameter will not cause a new - * callback to be returned. However, because of the nature of calling dialogs, this has no adverse - * effect on the functionality of this hook. Calling the callback will always use the latest - * `dialogType`. - * @param options Various options for configuring the dialog that shows and this hook. If an - * `options` parameter is also provided to the returned `showDialog` callback, those - * callback-provided `options` merge over these hook-provided `options` + * Note: projects are stored in the production version of `app://projects` regardless of whether you + * are in production or development + */ + export type Uri = string; +} +declare module 'node/utils/util' { + import { Uri } from 'shared/data/file-system.model'; + export const FILE_PROTOCOL = 'file://'; + export const RESOURCES_PROTOCOL = 'resources://'; + export function resolveHtmlPath(htmlFileName: string): string; + /** + * Gets the platform-specific user Platform.Bible folder for this application * - * Note: this parameter is internally assigned to a `ref`, so changing it will not cause any hooks - * to re-run with its new value. This means that updating this parameter will not cause a new - * callback to be returned. However, because of the nature of calling dialogs, this has no adverse - * effect on the functionality of this hook. Calling the callback will always use the latest - * `options`. - * @param resolveCallback `(response, dialogType, options)` The function that will be called if the - * dialog request resolves properly - * - * - `response` - the resolved value of the dialog call. Either the user's response or `undefined` if - * the user cancels - * - `dialogType` - the value of `dialogType` at the time that this dialog was called - * - `options` the `options` provided to the dialog at the time that this dialog was called. This - * consists of the `options` provided to the returned `showDialog` callback merged over the - * `options` provided to the hook and additionally contains {@link UseDialogCallbackOptions} - * properties + * When running in development: `/dev-appdata` * - * Note: this parameter is internally assigned to a `ref`, so changing it will not cause any hooks - * to re-run with its new value. This means that updating this parameter will not cause a new - * callback to be returned. However, because of the nature of calling dialogs, this has no adverse - * effect on the functionality of this hook. When the dialog resolves, it will always call the - * latest `resolveCallback`. - * @param rejectCallback `(error, dialogType, options)` The function that will be called if the - * dialog request throws an error + * When packaged: `/.platform.bible` + */ + export const getAppDir: import('memoize-one').MemoizedFn<() => string>; + /** + * Resolves the uri to a path * - * - `error` - the error thrown while calling the dialog - * - `dialogType` - the value of `dialogType` at the time that this dialog was called - * - `options` the `options` provided to the dialog at the time that this dialog was called. This - * consists of the `options` provided to the returned `showDialog` callback merged over the - * `options` provided to the hook and additionally contains {@link UseDialogCallbackOptions} - * properties + * @param uri The uri to resolve + * @returns Real path to the uri supplied + */ + export function getPathFromUri(uri: Uri): string; + /** + * Combines the uri passed in with the paths passed in to make one uri * - * Note: this parameter is internally assigned to a `ref`, so changing it will not cause any hooks - * to re-run with its new value. This means that updating this parameter will not cause a new - * callback to be returned. However, because of the nature of calling dialogs, this has no adverse - * effect on the functionality of this hook. If the dialog throws an error, it will always call - * the latest `rejectCallback`. - * @returns `showDialog(options?)` - callback to run to show the dialog to prompt the user for a - * response + * @param uri Uri to start from + * @param paths Paths to combine into the uri + * @returns One uri that combines the uri and the paths in left-to-right order + */ + export function joinUriPaths(uri: Uri, ...paths: string[]): Uri; + /** + * Determines if running in noisy dev mode * - * - `optionsOverrides?` - `options` object you may specify that will merge over the `options` you - * provide to the hook before passing to the dialog. All properties are optional, so you may - * specify as many or as few properties here as you want to overwrite the properties in the - * `options` you provide to the hook + * @returns True if the process is running in noisy dev mode, false otherwise */ - function useDialogCallback< - DialogTabType extends DialogTabTypes, - DialogOptions extends DialogTypes[DialogTabType]['options'], - >( - dialogType: DialogTabType, - options: DialogOptions & UseDialogCallbackOptions, - resolveCallback: ( - response: DialogTypes[DialogTabType]['responseType'] | undefined, - dialogType: DialogTabType, - options: DialogOptions, - ) => void, - rejectCallback: (error: unknown, dialogType: DialogTabType, options: DialogOptions) => void, - ): (optionOverrides?: Partial) => Promise; + export const isNoisyDevModeEnvVariableSet: () => boolean; +} +declare module 'node/services/node-file-system.service' { + /** File system calls from Node */ + import fs, { BigIntStats } from 'fs'; + import { Uri } from 'shared/data/file-system.model'; /** + * Read a text file * - * Enables using `papi.dialogs.showDialog` in React more easily. Returns a callback to run that will - * open a dialog with the provided `dialogType` and `options` then run the `resolveCallback` with - * the dialog response or `rejectCallback` if there is an error. By default, only one dialog can be - * open at a time. + * @param uri URI of file + * @returns Promise that resolves to the contents of the file + */ + export function readFileText(uri: Uri): Promise; + /** + * Read a binary file * - * If you need to open multiple dialogs and track which dialog is which, you can set - * `options.shouldOpenMultipleDialogs` to `true` and add a counter to the `options` when calling the - * callback. Then `resolveCallback` will be resolved with that options object including your - * counter. + * @param uri URI of file + * @returns Promise that resolves to the contents of the file + */ + export function readFileBinary(uri: Uri): Promise; + /** + * Write data to a file * - * @type `DialogTabType` The dialog type you are using. Should be inferred by parameters - * @param dialogType Dialog type you want to show on the screen + * @param uri URI of file + * @param fileContents String or Buffer to write into the file + * @returns Promise that resolves after writing the file + */ + export function writeFile(uri: Uri, fileContents: string | Buffer): Promise; + /** + * Copies a file from one location to another. Creates the path to the destination if it does not + * exist * - * Note: this parameter is internally assigned to a `ref`, so changing it will not cause any hooks - * to re-run with its new value. This means that updating this parameter will not cause a new - * callback to be returned. However, because of the nature of calling dialogs, this has no adverse - * effect on the functionality of this hook. Calling the callback will always use the latest - * `dialogType`. - * @param options Various options for configuring the dialog that shows and this hook. If an - * `options` parameter is also provided to the returned `showDialog` callback, those - * callback-provided `options` merge over these hook-provided `options` + * @param sourceUri The location of the file to copy + * @param destinationUri The uri to the file to create as a copy of the source file + * @param mode Bitwise modifiers that affect how the copy works. See + * [`fsPromises.copyFile`](https://nodejs.org/api/fs.html#fspromisescopyfilesrc-dest-mode) for + * more information + */ + export function copyFile( + sourceUri: Uri, + destinationUri: Uri, + mode?: Parameters[2], + ): Promise; + /** + * Delete a file if it exists * - * Note: this parameter is internally assigned to a `ref`, so changing it will not cause any hooks - * to re-run with its new value. This means that updating this parameter will not cause a new - * callback to be returned. However, because of the nature of calling dialogs, this has no adverse - * effect on the functionality of this hook. Calling the callback will always use the latest - * `options`. - * @param resolveCallback `(response, dialogType, options)` The function that will be called if the - * dialog request resolves properly + * @param uri URI of file + * @returns Promise that resolves when the file is deleted or determined to not exist + */ + export function deleteFile(uri: Uri): Promise; + /** + * Get stats about the file or directory. Note that BigInts are used instead of ints to avoid. + * https://en.wikipedia.org/wiki/Year_2038_problem * - * - `response` - the resolved value of the dialog call. Either the user's response or `undefined` if - * the user cancels - * - `dialogType` - the value of `dialogType` at the time that this dialog was called - * - `options` the `options` provided to the dialog at the time that this dialog was called. This - * consists of the `options` provided to the returned `showDialog` callback merged over the - * `options` provided to the hook and additionally contains {@link UseDialogCallbackOptions} - * properties + * @param uri URI of file or directory + * @returns Promise that resolves to object of type https://nodejs.org/api/fs.html#class-fsstats if + * file or directory exists, undefined if it doesn't + */ + export function getStats(uri: Uri): Promise; + /** + * Set the last modified and accessed times for the file or directory * - * Note: this parameter is internally assigned to a `ref`, so changing it will not cause any hooks - * to re-run with its new value. This means that updating this parameter will not cause a new - * callback to be returned. However, because of the nature of calling dialogs, this has no adverse - * effect on the functionality of this hook. When the dialog resolves, it will always call the - * latest `resolveCallback`. - * @param rejectCallback `(error, dialogType, options)` The function that will be called if the - * dialog request throws an error + * @param uri URI of file or directory + * @returns Promise that resolves once the touch operation finishes + */ + export function touch(uri: Uri, date: Date): Promise; + /** Type of file system item in a directory */ + export enum EntryType { + File = 'file', + Directory = 'directory', + Unknown = 'unknown', + } + /** All entries in a directory, mapped from entry type to array of uris for the entries */ + export type DirectoryEntries = Readonly<{ + [entryType in EntryType]: Uri[]; + }>; + /** + * Reads a directory and returns lists of entries in the directory by entry type. * - * - `error` - the error thrown while calling the dialog - * - `dialogType` - the value of `dialogType` at the time that this dialog was called - * - `options` the `options` provided to the dialog at the time that this dialog was called. This - * consists of the `options` provided to the returned `showDialog` callback merged over the - * `options` provided to the hook and additionally contains {@link UseDialogCallbackOptions} - * properties + * @param uri - URI of directory. + * @param entryFilter - Function to filter out entries in the directory based on their names. + * @returns Map of entry type to list of uris for each entry in the directory with that type. + */ + export function readDir( + uri: Uri, + entryFilter?: (entryName: string) => boolean, + ): Promise; + /** + * Create a directory in the file system if it does not exist. Does not throw if it already exists. * - * Note: this parameter is internally assigned to a `ref`, so changing it will not cause any hooks - * to re-run with its new value. This means that updating this parameter will not cause a new - * callback to be returned. However, because of the nature of calling dialogs, this has no adverse - * effect on the functionality of this hook. If the dialog throws an error, it will always call - * the latest `rejectCallback`. - * @returns `showDialog(options?)` - callback to run to show the dialog to prompt the user for a - * response + * @param uri URI of directory + * @returns Promise that resolves once the directory has been created + */ + export function createDir(uri: Uri): Promise; + /** + * Remove a directory and all its contents recursively from the file system * - * - `optionsOverrides?` - `options` object you may specify that will merge over the `options` you - * provide to the hook before passing to the dialog. All properties are optional, so you may - * specify as many or as few properties here as you want to overwrite the properties in the - * `options` you provide to the hook + * @param uri URI of directory + * @returns Promise that resolves when the delete operation finishes */ - function useDialogCallback< - DialogTabType extends DialogTabTypes, - DialogOptions extends DialogTypes[DialogTabType]['options'], - >( - dialogType: DialogTabType, - options: DialogOptions & UseDialogCallbackOptions, - resolveCallback: ( - response: DialogTypes[DialogTabType]['responseType'] | undefined, - dialogType: DialogTabType, - options: DialogOptions, - ) => void, - ): (optionOverrides?: Partial) => Promise; - export default useDialogCallback; + export function deleteDir(uri: Uri): Promise; } -declare module 'shared/services/localization.service-model' { - import IDataProvider from 'shared/models/data-provider.interface'; - import { - DataProviderDataType, - DataProviderUpdateInstructions, - } from 'shared/models/data-provider.model'; - import { LanguageStrings, LocalizeKey, OnDidDispose } from 'platform-bible-utils'; - export type LocalizationData = LanguageStrings; - export type LocalizationSelector = { - localizeKey: LocalizeKey; - locales?: string[]; - }; - export type LocalizationSelectors = { - localizeKeys: LocalizeKey[]; - locales?: string[]; - }; +declare module 'node/utils/crypto-util' { + export function createUuid(): string; /** + * Create a cryptographically secure nonce that is at least 128 bits long. See nonce spec at + * https://w3c.github.io/webappsec-csp/#security-nonces * - * This name is used to register the localization data provider on the papi. You can use this name - * to find the data provider when accessing it using the useData hook + * @param encoding: "base64url" (HTML safe, shorter string) or "hex" (longer string) From + * https://base64.guru/standards/base64url, the purpose of this encoding is "the ability to use + * the encoding result as filename or URL address" + * @param numberOfBytes: Number of bytes the resulting nonce should contain + * @returns Cryptographically secure, pseudo-randomly generated value encoded as a string */ - export const localizationServiceProviderName = 'platform.localizationDataServiceDataProvider'; - export const localizationServiceObjectToProxy: Readonly<{ - /** - * - * This name is used to register the localization data provider on the papi. You can use this name - * to find the data provider when accessing it using the useData hook - */ - dataProviderName: 'platform.localizationDataServiceDataProvider'; - }>; - export type LocalizationDataDataTypes = { - LocalizedString: DataProviderDataType; - LocalizedStrings: DataProviderDataType; + export function createNonce(encoding: 'base64url' | 'hex', numberOfBytes?: number): string; +} +declare module 'node/models/execution-token.model' { + /** For now this is just for extensions, but maybe we will want to expand this in the future */ + export type ExecutionTokenType = 'extension'; + /** Execution tokens can be passed into API calls to provide context about their identity */ + export class ExecutionToken { + readonly type: ExecutionTokenType; + readonly name: string; + readonly nonce: string; + constructor(tokenType: ExecutionTokenType, name: string); + getHash(): string; + } +} +declare module 'node/services/execution-token.service' { + import { ExecutionToken } from 'node/models/execution-token.model'; + /** + * This should be called when extensions are being loaded + * + * @param extensionName Name of the extension to register + * @returns Token that can be passed to `tokenIsValid` to authenticate or authorize API callers. It + * is important that the token is not shared to avoid impersonation of API callers. + */ + function registerExtension(extensionName: string): ExecutionToken; + /** + * Remove a registered token. Note that a hash of a token is what is needed to unregister, not the + * full token itself (notably not the nonce), so something can be delegated the ability to + * unregister a token without having been given the full token itself. + * + * @param extensionName Name of the extension that was originally registered + * @param tokenHash Value of `getHash()` of the token that was originally registered. + * @returns `true` if the token was successfully unregistered, `false` otherwise + */ + function unregisterExtension(extensionName: string, tokenHash: string): boolean; + /** + * This should only be needed by services that need to contextualize the response for the caller + * + * @param executionToken Token that was previously registered. + * @returns `true` if the token matches a token that was previous registered, `false` otherwise. + */ + function tokenIsValid(executionToken: ExecutionToken): boolean; + const executionTokenService: { + registerExtension: typeof registerExtension; + unregisterExtension: typeof unregisterExtension; + tokenIsValid: typeof tokenIsValid; }; - module 'papi-shared-types' { - interface DataProviders { - [localizationServiceProviderName]: ILocalizationService; - } + export default executionTokenService; +} +declare module 'extension-host/services/extension-storage.service' { + import { ExecutionToken } from 'node/models/execution-token.model'; + import { Buffer } from 'buffer'; + /** + * This is only intended to be called by the extension service. This service cannot call into the + * extension service or it causes a circular dependency. + */ + export function setExtensionUris(urisPerExtension: Map): void; + /** Return a path to the specified file within the extension's installation directory */ + export function buildExtensionPathFromName(extensionName: string, fileName: string): string; + /** + * Read a text file from the the extension's installation directory + * + * @param token ExecutionToken provided to the extension when `activate()` was called + * @param fileName Name of the file to be read + * @returns Promise for a string with the contents of the file + */ + function readTextFileFromInstallDirectory( + token: ExecutionToken, + fileName: string, + ): Promise; + /** + * Read a binary file from the the extension's installation directory + * + * @param token ExecutionToken provided to the extension when `activate()` was called + * @param fileName Name of the file to be read + * @returns Promise for a Buffer with the contents of the file + */ + function readBinaryFileFromInstallDirectory( + token: ExecutionToken, + fileName: string, + ): Promise; + /** + * Read data specific to the user (as identified by the OS) and extension (as identified by the + * ExecutionToken) + * + * @param token ExecutionToken provided to the extension when `activate()` was called + * @param key Unique identifier of the data + * @returns Promise for a string containing the data + */ + function readUserData(token: ExecutionToken, key: string): Promise; + /** + * Write data specific to the user (as identified by the OS) and extension (as identified by the + * ExecutionToken) + * + * @param token ExecutionToken provided to the extension when `activate()` was called + * @param key Unique identifier of the data + * @param data Data to be written + * @returns Promise that will resolve if the data is written successfully + */ + function writeUserData(token: ExecutionToken, key: string, data: string): Promise; + /** + * Delete data previously written that is specific to the user (as identified by the OS) and + * extension (as identified by the ExecutionToken) + * + * @param token ExecutionToken provided to the extension when `activate()` was called + * @param key Unique identifier of the data + * @returns Promise that will resolve if the data is deleted successfully + */ + function deleteUserData(token: ExecutionToken, key: string): Promise; + export interface ExtensionStorageService { + readTextFileFromInstallDirectory: typeof readTextFileFromInstallDirectory; + readBinaryFileFromInstallDirectory: typeof readBinaryFileFromInstallDirectory; + readUserData: typeof readUserData; + writeUserData: typeof writeUserData; + deleteUserData: typeof deleteUserData; } /** * - * Service that allows to get and store localizations + * This service provides extensions in the extension host the ability to read/write data based on + * the extension identity and current user (as identified by the OS). This service will not work + * within the renderer. */ - export type ILocalizationService = { + const extensionStorageService: ExtensionStorageService; + export default extensionStorageService; +} +declare module 'shared/models/dialog-options.model' { + import { LocalizeKey } from 'platform-bible-utils'; + /** General options to adjust dialogs (created from `papi.dialogs`) */ + export type DialogOptions = { /** - * Look up localized string for specific localizeKey + * Dialog title to display in the header. If you provide a {@link LocalizeKey}, it will be + * localized before displaying. * - * @param selector Made up of a string key that corresponds to a localized value and an array of - * BCP 47 language codes - * @returns Localized string + * Default depends on the dialog */ - getLocalizedString: (selector: LocalizationSelector) => Promise; + title?: string | LocalizeKey; + /** Url of dialog icon to display in the header. Default is Platform.Bible logo */ + iconUrl?: string; /** - * Look up localized strings for all localizeKeys provided + * The message to show the user in the dialog. If you provide a {@link LocalizeKey}, it will be + * localized before displaying. * - * @param selectors An array of LocalizationSelectors. A LocalizationSelector is made up of a - * string key that corresponds to a localized value and an array of BCP 47 language codes - * @returns Object whose keys are localizeKeys and values are localized strings + * Default depends on the dialog */ - getLocalizedStrings: (selectors: LocalizationSelectors) => Promise; + prompt?: string | LocalizeKey; + }; + /** Keys of properties on {@link DialogOptions} that should be localized if they are LocalizeKeys */ + export const DIALOG_OPTIONS_LOCALIZABLE_PROPERTY_KEYS: readonly ['title', 'prompt']; + /** Data in each tab that is a dialog. Added to DialogOptions in `dialog.service-host.ts` */ + export type DialogData = DialogOptions & { + isDialog: true; + }; +} +declare module 'renderer/components/dialogs/dialog-base.data' { + import { FloatSize, TabLoader, TabSaver } from 'shared/models/docking-framework.model'; + import { DialogData } from 'shared/models/dialog-options.model'; + import { ReactElement } from 'react'; + /** Base type for DialogDefinition. Contains reasonable defaults for dialogs */ + export type DialogDefinitionBase = Readonly<{ + /** Overwritten in {@link DialogDefinition}. Must be specified by all DialogDefinitions */ + tabType?: string; + /** Overwritten in {@link DialogDefinition}. Must be specified by all DialogDefinitions */ + Component?: (props: DialogProps) => ReactElement; /** - * This data cannot be changed. Trying to use this setter this will always throw + * The default icon for this dialog. This may be overridden by the `DialogOptions.iconUrl` * - * @returns Unsubscriber function + * Defaults to the Platform.Bible logo */ - setLocalizedString(): Promise>; + defaultIconUrl?: string; /** - * This data cannot be changed. Trying to use this setter this will always throw + * The default title for this dialog. This may be overridden by the `DialogOptions.title` * - * @returns Unsubscriber function + * Defaults to the DialogDefinition's `tabType` */ - setLocalizedStrings(): Promise>; - } & OnDidDispose & - typeof localizationServiceObjectToProxy & { - /** - * This function is used to take a book number from a verse ref and return the localized name of - * the book so that the book name can be displayed in the UI language within the UI - */ - getLocalizedIdFromBookNumber(bookNum: number, localizationLanguage: string): Promise; - } & IDataProvider; -} -declare module 'shared/services/settings.service-model' { - import { SettingNames, SettingTypes } from 'papi-shared-types'; - import { OnDidDispose, UnsubscriberAsync } from 'platform-bible-utils'; - import IDataProvider from 'shared/models/data-provider.interface'; - import { - DataProviderSubscriberOptions, - DataProviderUpdateInstructions, - } from 'shared/models/data-provider.model'; - /** Name prefix for registered commands that call settings validators */ - export const CATEGORY_EXTENSION_SETTING_VALIDATOR = 'extensionSettingValidator'; - /** - * - * This name is used to register the settings service data provider on the papi. You can use this - * name to find the data provider when accessing it using the useData hook - */ - export const settingsServiceDataProviderName = 'platform.settingsServiceDataProvider'; - export const settingsServiceObjectToProxy: Readonly<{ + defaultTitle?: string; + /** The width and height at which the dialog will be loaded in CSS `px` units */ + initialSize: FloatSize; + /** The minimum width to which the dialog can be set in CSS `px` units */ + minWidth?: number; + /** The minimum height to which the dialog can be set in CSS `px` units */ + minHeight?: number; /** - * - * This name is used to register the settings service data provider on the papi. You can use this - * name to find the data provider when accessing it using the useData hook + * The function used to load the dialog into the dock layout. Default uses the `Component` field + * and passes in the `DialogProps` */ - dataProviderName: 'platform.settingsServiceDataProvider'; + loadDialog: TabLoader; /** + * The function used to save the dialog into the dock layout * - * Registers a function that validates whether a new setting value is allowed to be set. + * Default does not save the dialog as they cannot properly be restored yet. * - * @param key The string id of the setting to validate - * @param validator Function to call to validate the new setting value - * @returns Unsubscriber that should be called whenever the providing extension is deactivated + * TODO: preserve requests between refreshes - save the dialog info in such a way that it works + * when loading again after refresh */ - registerValidator: ( - key: SettingName, - validator: SettingValidator, - ) => Promise; + saveDialog: TabSaver; }>; - /** - * SettingDataTypes handles getting and setting Platform.Bible core application and extension - * settings. - * - * Note: the unnamed (`''`) data type is not actually part of `SettingDataTypes` because the methods - * would not be able to create a generic type extending from `SettingNames` in order to return the - * specific setting type being requested. As such, `get`, `set`, `reset` and `subscribe` are all - * specified on {@link ISettingsService} instead. Unfortunately, as a result, using Intellisense with - * `useData` will not show the unnamed data type (`''`) as an option, but you can use `useSetting` - * instead. However, do note that the unnamed data type (`''`) is fully functional. - * - * The closest possible representation of the unnamed (````) data type follows: - * - * ```typescript - * '': DataProviderDataType; - * ``` - */ - export type SettingDataTypes = {}; - export type AllSettingsData = { - [SettingName in SettingNames]: SettingTypes[SettingName]; - }; - /** Function that validates whether a new setting value should be allowed to be set */ - export type SettingValidator = ( - newValue: SettingTypes[SettingName], - currentValue: SettingTypes[SettingName], - allChanges: Partial, - ) => Promise; - /** Validators for all settings. Keys are setting keys, values are functions to validate new settings */ - export type AllSettingsValidators = { - [SettingName in SettingNames]: SettingValidator; - }; - module 'papi-shared-types' { - interface DataProviders { - [settingsServiceDataProviderName]: ISettingsService; - } - } - /** */ - export type ISettingsService = { + /** Props provided to the dialog component */ + export type DialogProps = DialogData & { /** - * Retrieves the value of the specified setting + * Sends the data as a resolved response to the dialog request and closes the dialog * - * @param key The string id of the setting for which the value is being retrieved - * @returns The value of the specified setting, parsed to an object. Returns default setting if - * setting does not exist - * @throws If no default value is available for the setting. + * @param data Data with which to resolve the request */ - get(key: SettingName): Promise; + submitDialog(data: TData): void; + /** Cancels the dialog request (resolves the response with `undefined`) and closes the dialog */ + cancelDialog(): void; /** - * Sets the value of the specified setting + * Rejects the dialog request with the specified message and closes the dialog * - * @param key The string id of the setting for which the value is being set - * @param newSetting The value that is to be set for the specified key - * @returns Information that papi uses to interpret whether to send out updates. Defaults to - * `true` (meaning send updates only for this data type). - * @see {@link DataProviderUpdateInstructions} for more info on what to return + * @param errorMessage Message to explain why the dialog request was rejected */ - set( - key: SettingName, - newSetting: SettingTypes[SettingName], - ): Promise>; + rejectDialog(errorMessage: string): void; + }; + /** + * Set the functionality of submitting and canceling dialogs. This should be called specifically by + * `dialog.service-host.ts` immediately on startup and by nothing else. This is only here to + * mitigate a dependency cycle + * + * @param dialogServiceFunctions Functions from the dialog service host for resolving and rejecting + * dialogs + */ + export function hookUpDialogService({ + resolveDialogRequest: resolve, + rejectDialogRequest: reject, + }: { + resolveDialogRequest: (id: string, data: unknown | undefined) => void; + rejectDialogRequest: (id: string, message: string) => void; + }): void; + /** + * Static definition of a dialog that can be shown in Platform.Bible + * + * For good defaults, dialogs can include all the properties of this dialog. Dialogs must then + * specify `tabType` and `Component` in order to comply with `DialogDefinition` + * + * Note: this is not a class that can be inherited because all properties would be static but then + * we would not be able to use the default `loadDialog` because it would be using a static reference + * to a nonexistent `Component`. Instead of inheriting this as a class, any dialog definition can + * spread this `{ ...DIALOG_BASE }` + */ + const DIALOG_BASE: DialogDefinitionBase; + export default DIALOG_BASE; +} +declare module 'renderer/components/dialogs/dialog-definition.model' { + import { DialogOptions } from 'shared/models/dialog-options.model'; + import { DialogDefinitionBase, DialogProps } from 'renderer/components/dialogs/dialog-base.data'; + import { ReactElement } from 'react'; + import { ProjectMetadataFilterOptions } from 'shared/models/project-lookup.service-model'; + /** The tabType for the select project dialog in `select-project.dialog.tsx` */ + export const SELECT_PROJECT_DIALOG_TYPE = 'platform.selectProject'; + /** The tabType for the select multiple projects dialog in `select-multiple-projects.dialog.tsx` */ + export const SELECT_MULTIPLE_PROJECTS_DIALOG_TYPE = 'platform.selectMultipleProjects'; + /** The tabType for the select books dialog in `select-books.dialog.tsx` */ + export const SELECT_BOOKS_DIALOG_TYPE = 'platform.selectBooks'; + type ProjectDialogOptionsBase = DialogOptions & ProjectMetadataFilterOptions; + /** Options to provide when showing the Select Project dialog */ + export type SelectProjectDialogOptions = ProjectDialogOptionsBase; + /** Options to provide when showing the Select Multiple Project dialog */ + export type SelectMultipleProjectsDialogOptions = ProjectDialogOptionsBase & { + /** Project IDs that should start selected in the dialog */ + selectedProjectIds?: string[]; + }; + /** Options to provide when showing the Select Books dialog */ + export type SelectBooksDialogOptions = DialogOptions & { + /** Books IDs that should start selected in the dialog */ + selectedBookIds?: string[]; + }; + /** + * Mapped type for dialog functions to use in getting various types for dialogs + * + * Keys should be dialog names, and values should be {@link DialogDataTypes} + * + * If you add a dialog here, you must also add it on {@link DIALOGS} + */ + export interface DialogTypes { + [SELECT_PROJECT_DIALOG_TYPE]: DialogDataTypes; + [SELECT_MULTIPLE_PROJECTS_DIALOG_TYPE]: DialogDataTypes< + SelectMultipleProjectsDialogOptions, + string[] + >; + [SELECT_BOOKS_DIALOG_TYPE]: DialogDataTypes; + } + /** Each type of dialog. These are the tab types used in the dock layout */ + export type DialogTabTypes = keyof DialogTypes; + /** Types related to a specific dialog */ + export type DialogDataTypes = { /** - * Removes the setting from memory and resets it to its default value + * The dialog options to specify when calling the dialog. Passed into `loadDialog` as + * SavedTabInfo.data * - * @param key The string id of the setting for which the value is being removed - * @returns `true` if successfully reset the project setting. `false` otherwise + * The default implementation of `loadDialog` passes all the options down to the dialog component + * as props */ - reset(key: SettingName): Promise; + options: TOptions; + /** The type of the response to the dialog request */ + responseType: TReturnType; + /** Props provided to the dialog component */ + props: DialogProps & TOptions; + }; + export type DialogDefinition = Readonly< + DialogDefinitionBase & { + /** + * Type of tab - indicates what kind of built-in dock layout tab this dialog definition + * represents + */ + tabType: DialogTabType; + /** + * React component to render for this dialog + * + * This must be specified only if you do not overwrite the default `loadDialog` + * + * @param props Props that will be passed through from the dialog tab's data + * @returns React element to render + */ + Component: ( + props: DialogProps & + DialogTypes[DialogTabType]['options'], + ) => ReactElement; + } + >; +} +declare module 'shared/services/dialog.service-model' { + import { + DialogTabTypes, + DialogTypes, + SelectProjectDialogOptions, + } from 'renderer/components/dialogs/dialog-definition.model'; + /** + * + * Prompt the user for responses with dialogs + */ + export interface DialogService { /** - * Subscribes to updates of the specified setting. Whenever the value of the setting changes, the - * callback function is executed. + * Shows a dialog to the user and prompts the user to respond * - * @param key The string id of the setting for which the value is being subscribed to - * @param callback The function that will be called whenever the specified setting is updated - * @param options Various options to adjust how the subscriber emits updates - * @returns Unsubscriber that should be called whenever the subscription should be deleted + * @type `TReturn` - The type of data the dialog responds with + * @param dialogType The type of dialog to show the user + * @param options Various options for configuring the dialog that shows + * @returns Returns the user's response or `undefined` if the user cancels */ - subscribe( - key: SettingName, - callback: (newSetting: SettingTypes[SettingName]) => void, - options?: DataProviderSubscriberOptions, - ): Promise; + showDialog( + dialogType: DialogTabType, + options?: DialogTypes[DialogTabType]['options'], + ): Promise; /** + * Shows a select project dialog to the user and prompts the user to select a dialog * - * Registers a function that validates whether a new setting value is allowed to be set. - * - * @param key The string id of the setting to validate - * @param validator Function to call to validate the new setting value - * @returns Unsubscriber that should be called whenever the providing extension is deactivated + * @param options Various options for configuring the dialog that shows + * @returns Returns the user's selected project id or `undefined` if the user cancels */ - registerValidator( - key: SettingName, - validator: SettingValidator, - ): Promise; - } & OnDidDispose & - IDataProvider & - typeof settingsServiceObjectToProxy; + selectProject(options?: SelectProjectDialogOptions): Promise; + } + /** Prefix on requests that indicates that the request is related to dialog operations */ + export const CATEGORY_DIALOG = 'dialog'; } -declare module 'shared/services/project-settings.service-model' { - import { ProjectSettingNames, ProjectSettingTypes } from 'papi-shared-types'; - import { UnsubscriberAsync } from 'platform-bible-utils'; - /** Name prefix for registered commands that call project settings validators */ - export const CATEGORY_EXTENSION_PROJECT_SETTING_VALIDATOR = 'extensionProjectSettingValidator'; - export const projectSettingsServiceNetworkObjectName = 'ProjectSettingsService'; - export const projectSettingsServiceObjectToProxy: Readonly<{ +declare module 'shared/services/dialog.service' { + import { DialogService } from 'shared/services/dialog.service-model'; + const dialogService: DialogService; + export default dialogService; +} +declare module 'extension-host/extension-types/extension-activation-context.model' { + import { ExecutionToken } from 'node/models/execution-token.model'; + import { UnsubscriberAsyncList } from 'platform-bible-utils'; + /** An object of this type is passed into `activate()` for each extension during initialization */ + export type ExecutionActivationContext = { + /** Canonical name of the extension */ + name: string; + /** Used to save and load data from the storage service. */ + executionToken: ExecutionToken; + /** Tracks all registrations made by an extension so they can be cleaned up when it is unloaded */ + registrations: UnsubscriberAsyncList; + }; +} +declare module 'renderer/hooks/papi-hooks/use-dialog-callback.hook' { + import { DialogTabTypes, DialogTypes } from 'renderer/components/dialogs/dialog-definition.model'; + export type UseDialogCallbackOptions = { /** - * - * Registers a function that validates whether a new project setting value is allowed to be set. - * - * @param key The string id of the setting to validate - * @param validator Function to call to validate the new setting value - * @returns Unsubscriber that should be called whenever the providing extension is deactivated + * How many dialogs are allowed to be open at once from this dialog callback. Calling the callback + * when this number of maximum open dialogs has been reached does nothing. Set to -1 for + * unlimited. Defaults to 1. */ - registerValidator: ( - key: ProjectSettingName, - validator: ProjectSettingValidator, - ) => Promise; - }>; + maximumOpenDialogs?: number; + }; /** * - * Provides utility functions that project data providers should call when handling project settings - */ - export interface IProjectSettingsService { - /** - * Calls registered project settings validators to determine whether or not a project setting - * change is valid. - * - * Every Project Data Provider **must** run this function when it receives a request to set a - * project setting before changing the value of the setting. - * - * @param newValue The new value requested to set the project setting value to - * @param currentValue The current project setting value - * @param key The project setting key being set - * @param projectInterfaces The `projectInterface`s supported by the calling PDP for the project - * whose setting is being changed - * @param allChanges All project settings changes being set in one batch - * @returns `true` if change is valid, `false` otherwise - */ - isValid( - key: ProjectSettingName, - newValue: ProjectSettingTypes[ProjectSettingName], - currentValue: ProjectSettingTypes[ProjectSettingName], - allChanges?: SimultaneousProjectSettingsChanges, - ): Promise; - /** - * Gets default value for a project setting - * - * Every Project Data Providers **must** run this function when it receives a request to get a - * project setting if the project does not have a value for the project setting requested. It - * should return the response from this function directly, either the returned default value or - * throw. - * - * @param key The project setting key for which to get the default value - * @param projectInterfaces The `projectInterface`s supported by the calling PDP for the project - * for which to get the default setting value - * @returns The default value for the setting if a default value is registered - * @throws If a default value is not registered for the setting - */ - getDefault( - key: ProjectSettingName, - ): Promise; - /** - * - * Registers a function that validates whether a new project setting value is allowed to be set. - * - * @param key The string id of the setting to validate - * @param validator Function to call to validate the new setting value - * @returns Unsubscriber that should be called whenever the providing extension is deactivated - */ - registerValidator( - key: ProjectSettingName, - validatorCallback: ProjectSettingValidator, - ): Promise; - } - /** - * All project settings changes being set in one batch - * - * Project settings may be circularly dependent on one another, so multiple project settings may - * need to be changed at once in some cases - */ - export type SimultaneousProjectSettingsChanges = { - [ProjectSettingName in ProjectSettingNames]?: { - /** The new value requested to set the project setting value to */ - newValue: ProjectSettingTypes[ProjectSettingName]; - /** The current project setting value */ - currentValue: ProjectSettingTypes[ProjectSettingName]; - }; - }; - /** - * Function that validates whether a new project setting value should be allowed to be set - * - * @param newValue The new value requested to set the project setting value to - * @param currentValue The current project setting value - * @param allChanges All project settings changes being set in one batch - * @param projectInterfaces The `projectInterface`s supported by the calling PDP for the project - * whose setting is being changed - */ - export type ProjectSettingValidator = ( - newValue: ProjectSettingTypes[ProjectSettingName], - currentValue: ProjectSettingTypes[ProjectSettingName], - allChanges: SimultaneousProjectSettingsChanges, - ) => Promise; - /** - * Validators for all project settings. Keys are setting keys, values are functions to validate new - * settings - */ - export type AllProjectSettingsValidators = { - [ProjectSettingName in ProjectSettingNames]: ProjectSettingValidator; - }; -} -declare module '@papi/core' { - /** Exporting empty object so people don't have to put 'type' in their import statements */ - const core: {}; - export default core; - export type { ExecutionActivationContext } from 'extension-host/extension-types/extension-activation-context.model'; - export type { ExecutionToken } from 'node/models/execution-token.model'; - export type { DialogTypes } from 'renderer/components/dialogs/dialog-definition.model'; - export type { UseDialogCallbackOptions } from 'renderer/hooks/papi-hooks/use-dialog-callback.hook'; - export type { default as IDataProvider } from 'shared/models/data-provider.interface'; - export type { - DataProviderUpdateInstructions, - DataProviderDataType, - DataProviderSubscriberOptions, - } from 'shared/models/data-provider.model'; - export type { WithNotifyUpdate } from 'shared/models/data-provider-engine.model'; - export type { default as IDataProviderEngine } from 'shared/models/data-provider-engine.model'; - export type { DialogOptions } from 'shared/models/dialog-options.model'; - export type { - ExtensionDataScope, - MandatoryProjectDataTypes, - } from 'shared/models/project-data-provider.model'; - export type { - IProjectDataProviderEngine, - IProjectDataProviderEngineFactory, - } from 'shared/models/project-data-provider-engine.model'; - export type { default as IProjectDataProviderFactory } from 'shared/models/project-data-provider-factory.interface'; - export type { - ProjectDataProviderFactoryMetadataInfo, - ProjectMetadata, - ProjectMetadataWithoutFactoryInfo, - } from 'shared/models/project-metadata.model'; - export type { - LocalizationData, - LocalizationSelector, - LocalizationSelectors, - } from 'shared/services/localization.service-model'; - export type { SettingValidator } from 'shared/services/settings.service-model'; - export type { - GetWebViewOptions, - SavedWebViewDefinition, - UseWebViewStateHook, - WebViewContentType, - WebViewDefinition, - WebViewProps, - } from 'shared/models/web-view.model'; - export type { IWebViewProvider } from 'shared/models/web-view-provider.model'; - export type { - SimultaneousProjectSettingsChanges, - ProjectSettingValidator, - } from 'shared/services/project-settings.service-model'; -} -declare module 'shared/services/project-lookup.service' { - import { - ProjectLookupServiceType, - ProjectMetadataFilterOptions, - } from 'shared/models/project-lookup.service-model'; - import { - ProjectDataProviderFactoryMetadataInfo, - ProjectMetadata, - } from 'shared/models/project-metadata.model'; - import { ProjectInterfaces } from 'papi-shared-types'; - /** - * Note: If there are multiple PDPs available whose metadata matches the conditions provided by the - * parameters, their project metadata will all be combined, so all available `projectInterface`s - * provided by the PDP Factory with the matching id (or all PDP Factories if no id is specified) for - * the project will be returned. If you need `projectInterface`s supported by specific PDP - * Factories, you can access it at {@link ProjectMetadata.pdpFactoryInfo}. - */ - function internalGetMetadata( - onlyProjectId?: string, - onlyProjectInterface?: string, - onlyPdpFactoryId?: string, - ): Promise; - export function filterProjectsMetadata( - projectsMetadata: ProjectMetadata[], - options: ProjectMetadataFilterOptions, - ): ProjectMetadata[]; - /** - * Compare function (for array sorting and such) that compares two PDPF Metadata infos by most - * minimal match to the `projectInterface` in question. - * - * Hopefully this will allow us to get the PDP that most closely matches the `projectInterface`s to - * avoid unnecessary redirects through layered PDPs - * - * @param pdpFMetadataInfoA First ProjectDataProviderFactoryMetadataInfo to compare - * @param pdpFMetadataInfoB Second ProjectDataProviderFactoryMetadataInfo to compare - * @returns -1 if a is less than b, 0 if equal, and 1 otherwise - */ - function compareProjectDataProviderFactoryMetadataInfoMinimalMatch( - pdpFMetadataInfoA: ProjectDataProviderFactoryMetadataInfo | undefined, - pdpFMetadataInfoB: ProjectDataProviderFactoryMetadataInfo | undefined, - ): -1 | 0 | 1; - /** - * Get the PDP Factory info whose `projectInterface`s are most minimally matching to the provided - * `projectInterface` - * - * Hopefully this will allow us to get the PDP that most closely matches the `projectInterface`s to - * avoid unnecessary redirects through layered PDPs - * - * @param projectMetadata Metadata for project for which to get minimally matching PDPF - * @param projectInterface Which `projectInterface` to minimally match for - * @returns PDP Factory id whose `projectInterface`s minimally match the provided `projectInterface` - * if at least one PDP Factory was found that supports the `projectInterface` provided - */ - export function getMinimalMatchPdpFactoryId( - projectMetadata: ProjectMetadata, - projectInterface: ProjectInterfaces, - ): string | undefined; - /** This is an internal-only export for testing purposes and should not be used in development */ - export const testingProjectLookupService: { - internalGetMetadata: typeof internalGetMetadata; - compareProjectDataProviderFactoryMetadataInfoMinimalMatch: typeof compareProjectDataProviderFactoryMetadataInfoMinimalMatch; - }; - const projectLookupService: ProjectLookupServiceType; - export default projectLookupService; -} -declare module 'shared/services/project-data-provider.service' { - import { ProjectInterfaces, ProjectDataProviderInterfaces } from 'papi-shared-types'; - import { IProjectDataProviderEngineFactory } from 'shared/models/project-data-provider-engine.model'; - import { Dispose } from 'platform-bible-utils'; - /** - * Add a new Project Data Provider Factory to PAPI that uses the given engine. - * - * @param projectInterfaces The standardized sets of methods (`projectInterface`s) supported by the - * Project Data Provider Engines produced by this factory. Indicates what sort of project data - * should be available on the PDPEs created by this factory. - * @param pdpEngineFactory Used in a ProjectDataProviderFactory to create ProjectDataProviders - * @returns Promise that resolves to a disposable object when the registration operation completes - */ - export function registerProjectDataProviderEngineFactory< - SupportedProjectInterfaces extends ProjectInterfaces[], - >( - projectInterfaces: SupportedProjectInterfaces, - pdpEngineFactory: IProjectDataProviderEngineFactory, - ): Promise; - /** - * Get a Project Data Provider for the given project ID. - * - * @example - * - * ```typescript - * const pdp = await get('ParatextStandard', 'ProjectID12345'); - * pdp.getVerse(new VerseRef('JHN', '1', '1')); - * ``` - * - * @param projectInterface `projectInterface` that the project to load must support. The TypeScript - * type for the returned project data provider will have the project data provider interface type - * associated with this `projectInterface`. If the project does not implement this - * `projectInterface` (according to its metadata), an error will be thrown. - * @param projectId ID for the project to load - * @param pdpFactoryId Optional ID of the PDP factory from which to get the project data provider if - * the PDP factory supports this project id and project interface. If not provided, then look in - * all available PDP factories for the given project ID. - * @returns Project data provider with types that are associated with the given `projectInterface` - * @throws If did not find a project data provider for the project id that supports the requested - * `projectInterface` (and from the requested PDP factory if specified) - */ - export function get( - projectInterface: ProjectInterface, - projectId: string, - pdpFactoryId?: string, - ): Promise; - export interface PapiBackendProjectDataProviderService { - registerProjectDataProviderEngineFactory: typeof registerProjectDataProviderEngineFactory; - get: typeof get; - } - /** - * - * Service that registers and gets project data providers - */ - export const papiBackendProjectDataProviderService: PapiBackendProjectDataProviderService; - export interface PapiFrontendProjectDataProviderService { - get: typeof get; - } - /** + * Enables using `papi.dialogs.showDialog` in React more easily. Returns a callback to run that will + * open a dialog with the provided `dialogType` and `options` then run the `resolveCallback` with + * the dialog response or `rejectCallback` if there is an error. By default, only one dialog can be + * open at a time. * - * Service that gets project data providers - */ - export const papiFrontendProjectDataProviderService: { - get: typeof get; - }; -} -declare module 'shared/data/file-system.model' { - /** Types to use with file system operations */ - /** - * Represents a path in file system or other. Has a scheme followed by :// followed by a relative - * path. If no scheme is provided, the app scheme is used. Available schemes are as follows: + * If you need to open multiple dialogs and track which dialog is which, you can set + * `options.shouldOpenMultipleDialogs` to `true` and add a counter to the `options` when calling the + * callback. Then `resolveCallback` will be resolved with that options object including your + * counter. * - * - `app://` - goes to the `.platform.bible` directory inside the user's home directory. + * @type `DialogTabType` The dialog type you are using. Should be inferred by parameters + * @param dialogType Dialog type you want to show on the screen * - * - On Linux and Mac, this is `$HOME/.platform.bible` - * - On Windows, this is `%USERPROFILE%/.platform.bible` - * - Note: In development, `app://` always goes to `paranext-core/dev-appdata` - * - `cache://` - goes to the app's temporary file cache at `app://cache` - * - `data://` - goes to the app's data storage location at `app://data` - * - `resources://` - goes to the `resources` directory inside the install directory + * Note: this parameter is internally assigned to a `ref`, so changing it will not cause any hooks + * to re-run with its new value. This means that updating this parameter will not cause a new + * callback to be returned. However, because of the nature of calling dialogs, this has no adverse + * effect on the functionality of this hook. Calling the callback will always use the latest + * `dialogType`. + * @param options Various options for configuring the dialog that shows and this hook. If an + * `options` parameter is also provided to the returned `showDialog` callback, those + * callback-provided `options` merge over these hook-provided `options` * - * - Note: In development, `resources://` always goes to the repo root, `paranext-core`. Not all files - * are copied into the production `resources` folder, though. See `electron-builder.json5`'s - * `extraResources` for some that are copied. - * - `file://` - an absolute file path from drive root + * Note: this parameter is internally assigned to a `ref`, so changing it will not cause any hooks + * to re-run with its new value. This means that updating this parameter will not cause a new + * callback to be returned. However, because of the nature of calling dialogs, this has no adverse + * effect on the functionality of this hook. Calling the callback will always use the latest + * `options`. + * @param resolveCallback `(response, dialogType, options)` The function that will be called if the + * dialog request resolves properly * - * Note: projects are stored in the production version of `app://projects` regardless of whether you - * are in production or development - */ - export type Uri = string; -} -declare module 'node/utils/util' { - import { Uri } from 'shared/data/file-system.model'; - export const FILE_PROTOCOL = 'file://'; - export const RESOURCES_PROTOCOL = 'resources://'; - export function resolveHtmlPath(htmlFileName: string): string; - /** - * Gets the platform-specific user Platform.Bible folder for this application + * - `response` - the resolved value of the dialog call. Either the user's response or `undefined` if + * the user cancels + * - `dialogType` - the value of `dialogType` at the time that this dialog was called + * - `options` the `options` provided to the dialog at the time that this dialog was called. This + * consists of the `options` provided to the returned `showDialog` callback merged over the + * `options` provided to the hook and additionally contains {@link UseDialogCallbackOptions} + * properties * - * When running in development: `/dev-appdata` + * Note: this parameter is internally assigned to a `ref`, so changing it will not cause any hooks + * to re-run with its new value. This means that updating this parameter will not cause a new + * callback to be returned. However, because of the nature of calling dialogs, this has no adverse + * effect on the functionality of this hook. When the dialog resolves, it will always call the + * latest `resolveCallback`. + * @param rejectCallback `(error, dialogType, options)` The function that will be called if the + * dialog request throws an error * - * When packaged: `/.platform.bible` - */ - export const getAppDir: import('memoize-one').MemoizedFn<() => string>; - /** - * Resolves the uri to a path + * - `error` - the error thrown while calling the dialog + * - `dialogType` - the value of `dialogType` at the time that this dialog was called + * - `options` the `options` provided to the dialog at the time that this dialog was called. This + * consists of the `options` provided to the returned `showDialog` callback merged over the + * `options` provided to the hook and additionally contains {@link UseDialogCallbackOptions} + * properties * - * @param uri The uri to resolve - * @returns Real path to the uri supplied - */ - export function getPathFromUri(uri: Uri): string; - /** - * Combines the uri passed in with the paths passed in to make one uri + * Note: this parameter is internally assigned to a `ref`, so changing it will not cause any hooks + * to re-run with its new value. This means that updating this parameter will not cause a new + * callback to be returned. However, because of the nature of calling dialogs, this has no adverse + * effect on the functionality of this hook. If the dialog throws an error, it will always call + * the latest `rejectCallback`. + * @returns `showDialog(options?)` - callback to run to show the dialog to prompt the user for a + * response * - * @param uri Uri to start from - * @param paths Paths to combine into the uri - * @returns One uri that combines the uri and the paths in left-to-right order + * - `optionsOverrides?` - `options` object you may specify that will merge over the `options` you + * provide to the hook before passing to the dialog. All properties are optional, so you may + * specify as many or as few properties here as you want to overwrite the properties in the + * `options` you provide to the hook */ - export function joinUriPaths(uri: Uri, ...paths: string[]): Uri; + function useDialogCallback< + DialogTabType extends DialogTabTypes, + DialogOptions extends DialogTypes[DialogTabType]['options'], + >( + dialogType: DialogTabType, + options: DialogOptions & UseDialogCallbackOptions, + resolveCallback: ( + response: DialogTypes[DialogTabType]['responseType'] | undefined, + dialogType: DialogTabType, + options: DialogOptions, + ) => void, + rejectCallback: (error: unknown, dialogType: DialogTabType, options: DialogOptions) => void, + ): (optionOverrides?: Partial) => Promise; /** - * Determines if running in noisy dev mode * - * @returns True if the process is running in noisy dev mode, false otherwise - */ - export const isNoisyDevModeEnvVariableSet: () => boolean; -} -declare module 'node/services/node-file-system.service' { - /** File system calls from Node */ - import fs, { BigIntStats } from 'fs'; - import { Uri } from 'shared/data/file-system.model'; - /** - * Read a text file + * Enables using `papi.dialogs.showDialog` in React more easily. Returns a callback to run that will + * open a dialog with the provided `dialogType` and `options` then run the `resolveCallback` with + * the dialog response or `rejectCallback` if there is an error. By default, only one dialog can be + * open at a time. * - * @param uri URI of file - * @returns Promise that resolves to the contents of the file - */ - export function readFileText(uri: Uri): Promise; - /** - * Read a binary file + * If you need to open multiple dialogs and track which dialog is which, you can set + * `options.shouldOpenMultipleDialogs` to `true` and add a counter to the `options` when calling the + * callback. Then `resolveCallback` will be resolved with that options object including your + * counter. * - * @param uri URI of file - * @returns Promise that resolves to the contents of the file - */ - export function readFileBinary(uri: Uri): Promise; - /** - * Write data to a file + * @type `DialogTabType` The dialog type you are using. Should be inferred by parameters + * @param dialogType Dialog type you want to show on the screen * - * @param uri URI of file - * @param fileContents String or Buffer to write into the file - * @returns Promise that resolves after writing the file - */ - export function writeFile(uri: Uri, fileContents: string | Buffer): Promise; - /** - * Copies a file from one location to another. Creates the path to the destination if it does not - * exist + * Note: this parameter is internally assigned to a `ref`, so changing it will not cause any hooks + * to re-run with its new value. This means that updating this parameter will not cause a new + * callback to be returned. However, because of the nature of calling dialogs, this has no adverse + * effect on the functionality of this hook. Calling the callback will always use the latest + * `dialogType`. + * @param options Various options for configuring the dialog that shows and this hook. If an + * `options` parameter is also provided to the returned `showDialog` callback, those + * callback-provided `options` merge over these hook-provided `options` * - * @param sourceUri The location of the file to copy - * @param destinationUri The uri to the file to create as a copy of the source file - * @param mode Bitwise modifiers that affect how the copy works. See - * [`fsPromises.copyFile`](https://nodejs.org/api/fs.html#fspromisescopyfilesrc-dest-mode) for - * more information - */ - export function copyFile( - sourceUri: Uri, - destinationUri: Uri, - mode?: Parameters[2], - ): Promise; - /** - * Delete a file if it exists + * Note: this parameter is internally assigned to a `ref`, so changing it will not cause any hooks + * to re-run with its new value. This means that updating this parameter will not cause a new + * callback to be returned. However, because of the nature of calling dialogs, this has no adverse + * effect on the functionality of this hook. Calling the callback will always use the latest + * `options`. + * @param resolveCallback `(response, dialogType, options)` The function that will be called if the + * dialog request resolves properly * - * @param uri URI of file - * @returns Promise that resolves when the file is deleted or determined to not exist - */ - export function deleteFile(uri: Uri): Promise; - /** - * Get stats about the file or directory. Note that BigInts are used instead of ints to avoid. - * https://en.wikipedia.org/wiki/Year_2038_problem + * - `response` - the resolved value of the dialog call. Either the user's response or `undefined` if + * the user cancels + * - `dialogType` - the value of `dialogType` at the time that this dialog was called + * - `options` the `options` provided to the dialog at the time that this dialog was called. This + * consists of the `options` provided to the returned `showDialog` callback merged over the + * `options` provided to the hook and additionally contains {@link UseDialogCallbackOptions} + * properties * - * @param uri URI of file or directory - * @returns Promise that resolves to object of type https://nodejs.org/api/fs.html#class-fsstats if - * file or directory exists, undefined if it doesn't - */ - export function getStats(uri: Uri): Promise; - /** - * Set the last modified and accessed times for the file or directory + * Note: this parameter is internally assigned to a `ref`, so changing it will not cause any hooks + * to re-run with its new value. This means that updating this parameter will not cause a new + * callback to be returned. However, because of the nature of calling dialogs, this has no adverse + * effect on the functionality of this hook. When the dialog resolves, it will always call the + * latest `resolveCallback`. + * @param rejectCallback `(error, dialogType, options)` The function that will be called if the + * dialog request throws an error * - * @param uri URI of file or directory - * @returns Promise that resolves once the touch operation finishes - */ - export function touch(uri: Uri, date: Date): Promise; - /** Type of file system item in a directory */ - export enum EntryType { - File = 'file', - Directory = 'directory', - Unknown = 'unknown', - } - /** All entries in a directory, mapped from entry type to array of uris for the entries */ - export type DirectoryEntries = Readonly<{ - [entryType in EntryType]: Uri[]; - }>; - /** - * Reads a directory and returns lists of entries in the directory by entry type. + * - `error` - the error thrown while calling the dialog + * - `dialogType` - the value of `dialogType` at the time that this dialog was called + * - `options` the `options` provided to the dialog at the time that this dialog was called. This + * consists of the `options` provided to the returned `showDialog` callback merged over the + * `options` provided to the hook and additionally contains {@link UseDialogCallbackOptions} + * properties * - * @param uri - URI of directory. - * @param entryFilter - Function to filter out entries in the directory based on their names. - * @returns Map of entry type to list of uris for each entry in the directory with that type. + * Note: this parameter is internally assigned to a `ref`, so changing it will not cause any hooks + * to re-run with its new value. This means that updating this parameter will not cause a new + * callback to be returned. However, because of the nature of calling dialogs, this has no adverse + * effect on the functionality of this hook. If the dialog throws an error, it will always call + * the latest `rejectCallback`. + * @returns `showDialog(options?)` - callback to run to show the dialog to prompt the user for a + * response + * + * - `optionsOverrides?` - `options` object you may specify that will merge over the `options` you + * provide to the hook before passing to the dialog. All properties are optional, so you may + * specify as many or as few properties here as you want to overwrite the properties in the + * `options` you provide to the hook */ - export function readDir( - uri: Uri, - entryFilter?: (entryName: string) => boolean, - ): Promise; + function useDialogCallback< + DialogTabType extends DialogTabTypes, + DialogOptions extends DialogTypes[DialogTabType]['options'], + >( + dialogType: DialogTabType, + options: DialogOptions & UseDialogCallbackOptions, + resolveCallback: ( + response: DialogTypes[DialogTabType]['responseType'] | undefined, + dialogType: DialogTabType, + options: DialogOptions, + ) => void, + ): (optionOverrides?: Partial) => Promise; + export default useDialogCallback; +} +declare module 'shared/services/localization.service-model' { + import IDataProvider from 'shared/models/data-provider.interface'; + import { + DataProviderDataType, + DataProviderUpdateInstructions, + } from 'shared/models/data-provider.model'; + import { LanguageStrings, LocalizeKey, OnDidDispose } from 'platform-bible-utils'; + export type LocalizationData = LanguageStrings; + export type LocalizationSelector = { + localizeKey: LocalizeKey; + locales?: string[]; + }; + export type LocalizationSelectors = { + localizeKeys: LocalizeKey[]; + locales?: string[]; + }; /** - * Create a directory in the file system if it does not exist. Does not throw if it already exists. * - * @param uri URI of directory - * @returns Promise that resolves once the directory has been created + * This name is used to register the localization data provider on the papi. You can use this name + * to find the data provider when accessing it using the useData hook */ - export function createDir(uri: Uri): Promise; + export const localizationServiceProviderName = 'platform.localizationDataServiceDataProvider'; + export const localizationServiceObjectToProxy: Readonly<{ + /** + * + * This name is used to register the localization data provider on the papi. You can use this name + * to find the data provider when accessing it using the useData hook + */ + dataProviderName: 'platform.localizationDataServiceDataProvider'; + }>; + export type LocalizationDataDataTypes = { + LocalizedString: DataProviderDataType; + LocalizedStrings: DataProviderDataType; + }; + module 'papi-shared-types' { + interface DataProviders { + [localizationServiceProviderName]: ILocalizationService; + } + } /** - * Remove a directory and all its contents recursively from the file system * - * @param uri URI of directory - * @returns Promise that resolves when the delete operation finishes + * Service that allows to get and store localizations */ - export function deleteDir(uri: Uri): Promise; + export type ILocalizationService = { + /** + * Look up localized string for specific localizeKey + * + * @param selector Made up of a string key that corresponds to a localized value and an array of + * BCP 47 language codes + * @returns Localized string + */ + getLocalizedString: (selector: LocalizationSelector) => Promise; + /** + * Look up localized strings for all localizeKeys provided + * + * @param selectors An array of LocalizationSelectors. A LocalizationSelector is made up of a + * string key that corresponds to a localized value and an array of BCP 47 language codes + * @returns Object whose keys are localizeKeys and values are localized strings + */ + getLocalizedStrings: (selectors: LocalizationSelectors) => Promise; + /** + * This data cannot be changed. Trying to use this setter this will always throw + * + * @returns Unsubscriber function + */ + setLocalizedString(): Promise>; + /** + * This data cannot be changed. Trying to use this setter this will always throw + * + * @returns Unsubscriber function + */ + setLocalizedStrings(): Promise>; + } & OnDidDispose & + typeof localizationServiceObjectToProxy & { + /** + * This function is used to take a book number from a verse ref and return the localized name of + * the book so that the book name can be displayed in the UI language within the UI + */ + getLocalizedIdFromBookNumber(bookNum: number, localizationLanguage: string): Promise; + } & IDataProvider; } -declare module 'node/services/execution-token.service' { - import { ExecutionToken } from 'node/models/execution-token.model'; +declare module 'shared/services/settings.service-model' { + import { SettingNames, SettingTypes } from 'papi-shared-types'; + import { OnDidDispose, UnsubscriberAsync } from 'platform-bible-utils'; + import IDataProvider from 'shared/models/data-provider.interface'; + import { + DataProviderSubscriberOptions, + DataProviderUpdateInstructions, + } from 'shared/models/data-provider.model'; + /** Name prefix for registered commands that call settings validators */ + export const CATEGORY_EXTENSION_SETTING_VALIDATOR = 'extensionSettingValidator'; /** - * This should be called when extensions are being loaded * - * @param extensionName Name of the extension to register - * @returns Token that can be passed to `tokenIsValid` to authenticate or authorize API callers. It - * is important that the token is not shared to avoid impersonation of API callers. + * This name is used to register the settings service data provider on the papi. You can use this + * name to find the data provider when accessing it using the useData hook */ - function registerExtension(extensionName: string): ExecutionToken; + export const settingsServiceDataProviderName = 'platform.settingsServiceDataProvider'; + export const settingsServiceObjectToProxy: Readonly<{ + /** + * + * This name is used to register the settings service data provider on the papi. You can use this + * name to find the data provider when accessing it using the useData hook + */ + dataProviderName: 'platform.settingsServiceDataProvider'; + /** + * + * Registers a function that validates whether a new setting value is allowed to be set. + * + * @param key The string id of the setting to validate + * @param validator Function to call to validate the new setting value + * @returns Unsubscriber that should be called whenever the providing extension is deactivated + */ + registerValidator: ( + key: SettingName, + validator: SettingValidator, + ) => Promise; + }>; /** - * Remove a registered token. Note that a hash of a token is what is needed to unregister, not the - * full token itself (notably not the nonce), so something can be delegated the ability to - * unregister a token without having been given the full token itself. + * SettingDataTypes handles getting and setting Platform.Bible core application and extension + * settings. * - * @param extensionName Name of the extension that was originally registered - * @param tokenHash Value of `getHash()` of the token that was originally registered. - * @returns `true` if the token was successfully unregistered, `false` otherwise - */ - function unregisterExtension(extensionName: string, tokenHash: string): boolean; - /** - * This should only be needed by services that need to contextualize the response for the caller + * Note: the unnamed (`''`) data type is not actually part of `SettingDataTypes` because the methods + * would not be able to create a generic type extending from `SettingNames` in order to return the + * specific setting type being requested. As such, `get`, `set`, `reset` and `subscribe` are all + * specified on {@link ISettingsService} instead. Unfortunately, as a result, using Intellisense with + * `useData` will not show the unnamed data type (`''`) as an option, but you can use `useSetting` + * instead. However, do note that the unnamed data type (`''`) is fully functional. * - * @param executionToken Token that was previously registered. - * @returns `true` if the token matches a token that was previous registered, `false` otherwise. + * The closest possible representation of the unnamed (````) data type follows: + * + * ```typescript + * '': DataProviderDataType; + * ``` */ - function tokenIsValid(executionToken: ExecutionToken): boolean; - const executionTokenService: { - registerExtension: typeof registerExtension; - unregisterExtension: typeof unregisterExtension; - tokenIsValid: typeof tokenIsValid; + export type SettingDataTypes = {}; + export type AllSettingsData = { + [SettingName in SettingNames]: SettingTypes[SettingName]; }; - export default executionTokenService; + /** Function that validates whether a new setting value should be allowed to be set */ + export type SettingValidator = ( + newValue: SettingTypes[SettingName], + currentValue: SettingTypes[SettingName], + allChanges: Partial, + ) => Promise; + /** Validators for all settings. Keys are setting keys, values are functions to validate new settings */ + export type AllSettingsValidators = { + [SettingName in SettingNames]: SettingValidator; + }; + module 'papi-shared-types' { + interface DataProviders { + [settingsServiceDataProviderName]: ISettingsService; + } + } + /** */ + export type ISettingsService = { + /** + * Retrieves the value of the specified setting + * + * @param key The string id of the setting for which the value is being retrieved + * @returns The value of the specified setting, parsed to an object. Returns default setting if + * setting does not exist + * @throws If no default value is available for the setting. + */ + get(key: SettingName): Promise; + /** + * Sets the value of the specified setting + * + * @param key The string id of the setting for which the value is being set + * @param newSetting The value that is to be set for the specified key + * @returns Information that papi uses to interpret whether to send out updates. Defaults to + * `true` (meaning send updates only for this data type). + * @see {@link DataProviderUpdateInstructions} for more info on what to return + */ + set( + key: SettingName, + newSetting: SettingTypes[SettingName], + ): Promise>; + /** + * Removes the setting from memory and resets it to its default value + * + * @param key The string id of the setting for which the value is being removed + * @returns `true` if successfully reset the project setting. `false` otherwise + */ + reset(key: SettingName): Promise; + /** + * Subscribes to updates of the specified setting. Whenever the value of the setting changes, the + * callback function is executed. + * + * @param key The string id of the setting for which the value is being subscribed to + * @param callback The function that will be called whenever the specified setting is updated + * @param options Various options to adjust how the subscriber emits updates + * @returns Unsubscriber that should be called whenever the subscription should be deleted + */ + subscribe( + key: SettingName, + callback: (newSetting: SettingTypes[SettingName]) => void, + options?: DataProviderSubscriberOptions, + ): Promise; + /** + * + * Registers a function that validates whether a new setting value is allowed to be set. + * + * @param key The string id of the setting to validate + * @param validator Function to call to validate the new setting value + * @returns Unsubscriber that should be called whenever the providing extension is deactivated + */ + registerValidator( + key: SettingName, + validator: SettingValidator, + ): Promise; + } & OnDidDispose & + IDataProvider & + typeof settingsServiceObjectToProxy; } -declare module 'extension-host/services/extension-storage.service' { - import { ExecutionToken } from 'node/models/execution-token.model'; - import { Buffer } from 'buffer'; - /** - * This is only intended to be called by the extension service. This service cannot call into the - * extension service or it causes a circular dependency. - */ - export function setExtensionUris(urisPerExtension: Map): void; - /** Return a path to the specified file within the extension's installation directory */ - export function buildExtensionPathFromName(extensionName: string, fileName: string): string; - /** - * Read a text file from the the extension's installation directory - * - * @param token ExecutionToken provided to the extension when `activate()` was called - * @param fileName Name of the file to be read - * @returns Promise for a string with the contents of the file - */ - function readTextFileFromInstallDirectory( - token: ExecutionToken, - fileName: string, - ): Promise; - /** - * Read a binary file from the the extension's installation directory - * - * @param token ExecutionToken provided to the extension when `activate()` was called - * @param fileName Name of the file to be read - * @returns Promise for a Buffer with the contents of the file - */ - function readBinaryFileFromInstallDirectory( - token: ExecutionToken, - fileName: string, - ): Promise; +declare module 'shared/services/project-settings.service-model' { + import { ProjectSettingNames, ProjectSettingTypes } from 'papi-shared-types'; + import { UnsubscriberAsync } from 'platform-bible-utils'; + /** Name prefix for registered commands that call project settings validators */ + export const CATEGORY_EXTENSION_PROJECT_SETTING_VALIDATOR = 'extensionProjectSettingValidator'; + export const projectSettingsServiceNetworkObjectName = 'ProjectSettingsService'; + export const projectSettingsServiceObjectToProxy: Readonly<{ + /** + * + * Registers a function that validates whether a new project setting value is allowed to be set. + * + * @param key The string id of the setting to validate + * @param validator Function to call to validate the new setting value + * @returns Unsubscriber that should be called whenever the providing extension is deactivated + */ + registerValidator: ( + key: ProjectSettingName, + validator: ProjectSettingValidator, + ) => Promise; + }>; /** - * Read data specific to the user (as identified by the OS) and extension (as identified by the - * ExecutionToken) * - * @param token ExecutionToken provided to the extension when `activate()` was called - * @param key Unique identifier of the data - * @returns Promise for a string containing the data + * Provides utility functions that project data providers should call when handling project settings */ - function readUserData(token: ExecutionToken, key: string): Promise; + export interface IProjectSettingsService { + /** + * Calls registered project settings validators to determine whether or not a project setting + * change is valid. + * + * Every Project Data Provider **must** run this function when it receives a request to set a + * project setting before changing the value of the setting. + * + * @param newValue The new value requested to set the project setting value to + * @param currentValue The current project setting value + * @param key The project setting key being set + * @param projectInterfaces The `projectInterface`s supported by the calling PDP for the project + * whose setting is being changed + * @param allChanges All project settings changes being set in one batch + * @returns `true` if change is valid, `false` otherwise + */ + isValid( + key: ProjectSettingName, + newValue: ProjectSettingTypes[ProjectSettingName], + currentValue: ProjectSettingTypes[ProjectSettingName], + allChanges?: SimultaneousProjectSettingsChanges, + ): Promise; + /** + * Gets default value for a project setting + * + * Every Project Data Providers **must** run this function when it receives a request to get a + * project setting if the project does not have a value for the project setting requested. It + * should return the response from this function directly, either the returned default value or + * throw. + * + * @param key The project setting key for which to get the default value + * @returns The default value for the setting if a default value is registered + * @throws If a default value is not registered for the setting + */ + getDefault( + key: ProjectSettingName, + ): Promise; + /** + * + * Registers a function that validates whether a new project setting value is allowed to be set. + * + * @param key The string id of the setting to validate + * @param validator Function to call to validate the new setting value + * @returns Unsubscriber that should be called whenever the providing extension is deactivated + */ + registerValidator( + key: ProjectSettingName, + validatorCallback: ProjectSettingValidator, + ): Promise; + } /** - * Write data specific to the user (as identified by the OS) and extension (as identified by the - * ExecutionToken) + * All project settings changes being set in one batch * - * @param token ExecutionToken provided to the extension when `activate()` was called - * @param key Unique identifier of the data - * @param data Data to be written - * @returns Promise that will resolve if the data is written successfully + * Project settings may be circularly dependent on one another, so multiple project settings may + * need to be changed at once in some cases */ - function writeUserData(token: ExecutionToken, key: string, data: string): Promise; + export type SimultaneousProjectSettingsChanges = { + [ProjectSettingName in ProjectSettingNames]?: { + /** The new value requested to set the project setting value to */ + newValue: ProjectSettingTypes[ProjectSettingName]; + /** The current project setting value */ + currentValue: ProjectSettingTypes[ProjectSettingName]; + }; + }; /** - * Delete data previously written that is specific to the user (as identified by the OS) and - * extension (as identified by the ExecutionToken) + * Function that validates whether a new project setting value should be allowed to be set * - * @param token ExecutionToken provided to the extension when `activate()` was called - * @param key Unique identifier of the data - * @returns Promise that will resolve if the data is deleted successfully + * @param newValue The new value requested to set the project setting value to + * @param currentValue The current project setting value + * @param allChanges All project settings changes being set in one batch */ - function deleteUserData(token: ExecutionToken, key: string): Promise; - export interface ExtensionStorageService { - readTextFileFromInstallDirectory: typeof readTextFileFromInstallDirectory; - readBinaryFileFromInstallDirectory: typeof readBinaryFileFromInstallDirectory; - readUserData: typeof readUserData; - writeUserData: typeof writeUserData; - deleteUserData: typeof deleteUserData; - } + export type ProjectSettingValidator = ( + newValue: ProjectSettingTypes[ProjectSettingName], + currentValue: ProjectSettingTypes[ProjectSettingName], + allChanges: SimultaneousProjectSettingsChanges, + ) => Promise; /** - * - * This service provides extensions in the extension host the ability to read/write data based on - * the extension identity and current user (as identified by the OS). This service will not work - * within the renderer. + * Validators for all project settings. Keys are setting keys, values are functions to validate new + * settings */ - const extensionStorageService: ExtensionStorageService; - export default extensionStorageService; + export type AllProjectSettingsValidators = { + [ProjectSettingName in ProjectSettingNames]: ProjectSettingValidator; + }; +} +declare module '@papi/core' { + /** Exporting empty object so people don't have to put 'type' in their import statements */ + const core: {}; + export default core; + export type { ExecutionActivationContext } from 'extension-host/extension-types/extension-activation-context.model'; + export type { ExecutionToken } from 'node/models/execution-token.model'; + export type { DialogTypes } from 'renderer/components/dialogs/dialog-definition.model'; + export type { UseDialogCallbackOptions } from 'renderer/hooks/papi-hooks/use-dialog-callback.hook'; + export type { default as IDataProvider } from 'shared/models/data-provider.interface'; + export type { + DataProviderUpdateInstructions, + DataProviderDataType, + DataProviderSubscriberOptions, + } from 'shared/models/data-provider.model'; + export type { WithNotifyUpdate } from 'shared/models/data-provider-engine.model'; + export type { default as IDataProviderEngine } from 'shared/models/data-provider-engine.model'; + export type { DialogOptions } from 'shared/models/dialog-options.model'; + export type { + ExtensionDataScope, + MandatoryProjectDataTypes, + } from 'shared/models/project-data-provider.model'; + export type { + IProjectDataProviderEngine, + IProjectDataProviderEngineFactory, + } from 'shared/models/project-data-provider-engine.model'; + export type { IBaseProjectDataProviderEngine } from 'shared/models/base-project-data-provider-engine.model'; + export type { default as IProjectDataProviderFactory } from 'shared/models/project-data-provider-factory.interface'; + export type { + ProjectDataProviderFactoryMetadataInfo, + ProjectMetadata, + ProjectMetadataWithoutFactoryInfo, + } from 'shared/models/project-metadata.model'; + export type { + LocalizationData, + LocalizationSelector, + LocalizationSelectors, + } from 'shared/services/localization.service-model'; + export type { SettingValidator } from 'shared/services/settings.service-model'; + export type { + GetWebViewOptions, + SavedWebViewDefinition, + UseWebViewStateHook, + WebViewContentType, + WebViewDefinition, + WebViewProps, + } from 'shared/models/web-view.model'; + export type { IWebViewProvider } from 'shared/models/web-view-provider.model'; + export type { + SimultaneousProjectSettingsChanges, + ProjectSettingValidator, + } from 'shared/services/project-settings.service-model'; } declare module 'shared/services/menu-data.service-model' { import { @@ -5094,6 +5296,7 @@ declare module '@papi/backend' { import { DataProviderService } from 'shared/services/data-provider.service'; import { DataProviderEngine as PapiDataProviderEngine } from 'shared/models/data-provider-engine.model'; import { ProjectDataProviderEngine as PapiProjectDataProviderEngine } from 'shared/models/project-data-provider-engine.model'; + import { BaseProjectDataProviderEngine as PapiBaseProjectDataProviderEngine } from 'shared/models/base-project-data-provider-engine.model'; import { PapiBackendProjectDataProviderService } from 'shared/services/project-data-provider.service'; import { ExtensionStorageService } from 'extension-host/services/extension-storage.service'; import { ProjectLookupServiceType } from 'shared/models/project-lookup.service-model'; @@ -5128,6 +5331,22 @@ declare module '@papi/backend' { * @see {@link IProjectDataProviderEngine} for more information on extending this class. */ ProjectDataProviderEngine: typeof PapiProjectDataProviderEngine; + /** + * + * Abstract class that provides a placeholder `notifyUpdate` for Base Project Data Provider Engine + * classes. If a Base Project Data Provider Engine class extends this class, it doesn't have to + * specify its own `notifyUpdate` function in order to use `notifyUpdate`. + * + * Additionally, extending this class informs Intellisense that you can run `notifyUpdate` with the + * `Setting` data type if needed like so: + * + * ```typescript + * this.notifyUpdate('Setting'); + * ``` + * + * @see {@link IBaseProjectDataProviderEngine} for more information on extending this class. + */ + BaseProjectDataProviderEngine: typeof PapiBaseProjectDataProviderEngine; /** This is just an alias for internet.fetch */ fetch: typeof globalThis.fetch; /** @@ -5185,6 +5404,10 @@ declare module '@papi/backend' { /** * * Provides metadata for projects known by the platform + * + * Note: this service runs locally everywhere in the TypeScript processes. It is also exposed on the + * PAPI websocket. Note these functions are all asynchronous on the PAPI websocket regardless of if + * their types are synchronous locally. */ projectLookup: ProjectLookupServiceType; /** @@ -5238,6 +5461,22 @@ declare module '@papi/backend' { * @see {@link IProjectDataProviderEngine} for more information on extending this class. */ export const ProjectDataProviderEngine: typeof PapiProjectDataProviderEngine; + /** + * + * Abstract class that provides a placeholder `notifyUpdate` for Base Project Data Provider Engine + * classes. If a Base Project Data Provider Engine class extends this class, it doesn't have to + * specify its own `notifyUpdate` function in order to use `notifyUpdate`. + * + * Additionally, extending this class informs Intellisense that you can run `notifyUpdate` with the + * `Setting` data type if needed like so: + * + * ```typescript + * this.notifyUpdate('Setting'); + * ``` + * + * @see {@link IBaseProjectDataProviderEngine} for more information on extending this class. + */ + export const BaseProjectDataProviderEngine: typeof PapiBaseProjectDataProviderEngine; /** This is just an alias for internet.fetch */ export const fetch: typeof globalThis.fetch; /** @@ -5295,6 +5534,10 @@ declare module '@papi/backend' { /** * * Provides metadata for projects known by the platform + * + * Note: this service runs locally everywhere in the TypeScript processes. It is also exposed on the + * PAPI websocket. Note these functions are all asynchronous on the PAPI websocket regardless of if + * their types are synchronous locally. */ export const projectLookup: ProjectLookupServiceType; /** @@ -5684,7 +5927,8 @@ declare module 'renderer/hooks/papi-hooks/use-project-data.hook' { /** * React hook to use data from a Project Data Provider * - * @example `useProjectData('ParatextStandard', 'project id').VerseUSFM(...);` + * @example `useProjectData('platformScripture.USFM_BookChapterVerse', 'project + * id').VerseUSFM(...);` */ type UseProjectDataHook = { ( @@ -5745,12 +5989,13 @@ declare module 'renderer/hooks/papi-hooks/use-project-data.hook' { * Provider with `useProjectData('', '').` and use like any * other React hook. * - * _@example_ Subscribing to Verse USFM info at JHN 11:35 on a `ParatextStandard` project with - * projectId `32664dc3288a28df2e2bb75ded887fc8f17a15fb`: + * _@example_ Subscribing to Verse USFM info at JHN 11:35 on a + * `platformScripture.USFM_BookChapterVerse` project with projectId + * `32664dc3288a28df2e2bb75ded887fc8f17a15fb`: * * ```typescript * const [verse, setVerse, verseIsLoading] = useProjectData( - * 'ParatextStandard', + * 'platformScripture.USFM_BookChapterVerse', * '32664dc3288a28df2e2bb75ded887fc8f17a15fb', * ).VerseUSFM( * useMemo(() => new VerseRef('JHN', '11', '35', ScrVers.English), []), @@ -5796,20 +6041,20 @@ declare module 'renderer/hooks/papi-hooks/use-project-data.hook' { export default useProjectData; } declare module 'renderer/hooks/papi-hooks/use-project-setting.hook' { - import { ProjectDataProviderInterfaces, ProjectSettingTypes } from 'papi-shared-types'; + import { IBaseProjectDataProvider, ProjectSettingTypes } from 'papi-shared-types'; import { DataProviderSubscriberOptions } from 'shared/models/data-provider.model'; /** * Gets, sets and resets a project setting on the papi for a specified project. Also notifies * subscribers when the project setting changes and gets updated when the project setting is changed * by others. * - * @param projectInterface `projectInterface` that the project to load must support. The TypeScript - * type for the returned project data provider will have the project data provider interface type - * associated with this `projectInterface`. If the project does not implement this - * `projectInterface` (according to its metadata), an error will be thrown. * @param projectDataProviderSource `projectDataProviderSource` String name of the id of the project * to get OR projectDataProvider (result of `useProjectDataProvider` if you want to consolidate - * and only get the Project Data Provider once) + * and only get the Project Data Provider once). If you provide a project id, this hook will use a + * PDP for this project that supports the `platform.base` `projectInterface`. + * + * Note: If you provide a projectDataProvider directly, it must be an + * {@link IBaseProjectDataProvider} * @param key The string id of the project setting to interact with * * WARNING: MUST BE STABLE - const or wrapped in useState, useMemo, etc. The reference must not be @@ -5836,12 +6081,8 @@ declare module 'renderer/hooks/papi-hooks/use-project-setting.hook' { * @throws When subscription callback function is called with an update that has an unexpected * message type */ - const useProjectSetting: < - ProjectInterface extends keyof ProjectDataProviderInterfaces, - ProjectSettingName extends keyof ProjectSettingTypes, - >( - projectInterface: ProjectInterface, - projectDataProviderSource: string | ProjectDataProviderInterfaces[ProjectInterface] | undefined, + const useProjectSetting: ( + projectDataProviderSource: string | IBaseProjectDataProvider | undefined, key: ProjectSettingName, defaultValue: ProjectSettingTypes[ProjectSettingName], subscriberOptions?: DataProviderSubscriberOptions, @@ -6080,6 +6321,10 @@ declare module '@papi/frontend' { /** * * Provides metadata for projects known by the platform + * + * Note: this service runs locally everywhere in the TypeScript processes. It is also exposed on the + * PAPI websocket. Note these functions are all asynchronous on the PAPI websocket regardless of if + * their types are synchronous locally. */ projectLookup: ProjectLookupServiceType; /** @@ -6167,6 +6412,10 @@ declare module '@papi/frontend' { /** * * Provides metadata for projects known by the platform + * + * Note: this service runs locally everywhere in the TypeScript processes. It is also exposed on the + * PAPI websocket. Note these functions are all asynchronous on the PAPI websocket regardless of if + * their types are synchronous locally. */ export const projectLookup: ProjectLookupServiceType; /** diff --git a/lib/platform-bible-utils/dist/index.cjs b/lib/platform-bible-utils/dist/index.cjs index 6618a8dbad..0e748ed113 100644 --- a/lib/platform-bible-utils/dist/index.cjs +++ b/lib/platform-bible-utils/dist/index.cjs @@ -1,2 +1,2 @@ -"use strict";var Qe=Object.defineProperty;var Ye=(t,e,r)=>e in t?Qe(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r;var d=(t,e,r)=>(Ye(t,typeof e!="symbol"?e+"":e,r),r);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const et=require("async-mutex");class tt{constructor(e,r=1e4){d(this,"variableName");d(this,"promiseToValue");d(this,"resolver");d(this,"rejecter");this.variableName=e,this.promiseToValue=new Promise((s,n)=>{this.resolver=s,this.rejecter=n}),r>0&&setTimeout(()=>{this.rejecter&&(this.rejecter(`Timeout reached when waiting for ${this.variableName} to settle`),this.complete())},r),Object.seal(this)}get promise(){return this.promiseToValue}get hasSettled(){return Object.isFrozen(this)}resolveToValue(e,r=!1){if(this.resolver)console.debug(`${this.variableName} is being resolved now`),this.resolver(e),this.complete();else{if(r)throw Error(`${this.variableName} was already settled`);console.debug(`Ignoring subsequent resolution of ${this.variableName}`)}}rejectWithReason(e,r=!1){if(this.rejecter)console.debug(`${this.variableName} is being rejected now`),this.rejecter(e),this.complete();else{if(r)throw Error(`${this.variableName} was already settled`);console.debug(`Ignoring subsequent rejection of ${this.variableName}`)}}complete(){this.resolver=void 0,this.rejecter=void 0,Object.freeze(this)}}class rt{constructor(e,r){d(this,"collator");this.collator=new Intl.Collator(e,r)}compare(e,r){return this.collator.compare(e,r)}resolvedOptions(){return this.collator.resolvedOptions()}}class he{constructor(e,r){d(this,"dateTimeFormatter");this.dateTimeFormatter=new Intl.DateTimeFormat(e,r)}format(e){return this.dateTimeFormatter.format(e)}formatRange(e,r){return this.dateTimeFormatter.formatRange(e,r)}formatRangeToParts(e,r){return this.dateTimeFormatter.formatRangeToParts(e,r)}formatToParts(e){return this.dateTimeFormatter.formatToParts(e)}resolvedOptions(){return this.dateTimeFormatter.resolvedOptions()}}class pe{constructor(){d(this,"subscribe",this.event);d(this,"subscriptions");d(this,"lazyEvent");d(this,"isDisposed",!1);d(this,"dispose",()=>this.disposeFn());d(this,"emit",e=>{this.emitFn(e)})}get event(){return this.assertNotDisposed(),this.lazyEvent||(this.lazyEvent=e=>{if(!e||typeof e!="function")throw new Error("Event handler callback must be a function!");return this.subscriptions||(this.subscriptions=[]),this.subscriptions.push(e),()=>{if(!this.subscriptions)return!1;const r=this.subscriptions.indexOf(e);return r<0?!1:(this.subscriptions.splice(r,1),!0)}}),this.lazyEvent}emitFn(e){var r;this.assertNotDisposed(),(r=this.subscriptions)==null||r.forEach(s=>s(e))}assertNotDisposed(){if(this.isDisposed)throw new Error("Emitter is disposed")}disposeFn(){return this.assertNotDisposed(),this.isDisposed=!0,this.subscriptions=void 0,this.lazyEvent=void 0,Promise.resolve(!0)}}function st(){return"00-0-4-1-000".replace(/[^-]/g,t=>((Math.random()+~~t)*65536>>t).toString(16).padStart(4,"0"))}function de(t){return typeof t=="string"||t instanceof String}function A(t){return JSON.parse(JSON.stringify(t))}function nt(t,e=300){if(de(t))throw new Error("Tried to debounce a string! Could be XSS");let r;return(...s)=>{clearTimeout(r),r=setTimeout(()=>t(...s),e)}}function it(t,e,r){const s=new Map;return t.forEach(n=>{const i=e(n),o=s.get(i),a=r?r(n,i):n;o?o.push(a):s.set(i,[a])}),s}function ot(t){return typeof t=="object"&&t!==null&&"message"in t&&typeof t.message=="string"}function at(t){if(ot(t))return t;try{return new Error(JSON.stringify(t))}catch{return new Error(String(t))}}function ut(t){return at(t).message}function me(t){return new Promise(e=>setTimeout(e,t))}function lt(t,e){const r=me(e).then(()=>{});return Promise.any([r,t()])}function ct(t,e="obj"){const r=new Set;Object.getOwnPropertyNames(t).forEach(n=>{try{typeof t[n]=="function"&&r.add(n)}catch(i){console.debug(`Skipping ${n} on ${e} due to error: ${i}`)}});let s=Object.getPrototypeOf(t);for(;s&&Object.getPrototypeOf(s);)Object.getOwnPropertyNames(s).forEach(n=>{try{typeof t[n]=="function"&&r.add(n)}catch(i){console.debug(`Skipping ${n} on ${e}'s prototype due to error: ${i}`)}}),s=Object.getPrototypeOf(s);return r}function ft(t,e={}){return new Proxy(e,{get(r,s){return s in r?r[s]:async(...n)=>(await t())[s](...n)}})}class ge{constructor(e,r){d(this,"baseDocument");d(this,"contributions",new Map);d(this,"latestOutput");d(this,"options");d(this,"onDidRebuildEmitter",new pe);d(this,"onDidRebuild",this.onDidRebuildEmitter.subscribe);this.baseDocument=e,this.options=r,this.updateBaseDocument(e)}updateBaseDocument(e){return this.validateBaseDocument(e),this.baseDocument=this.options.copyDocuments?A(e):e,this.baseDocument=this.transformBaseDocumentAfterValidation(this.baseDocument),this.rebuild()}addOrUpdateContribution(e,r){this.validateContribution(e,r);const s=this.contributions.get(e);let n=this.options.copyDocuments&&r?A(r):r;n=this.transformContributionAfterValidation(e,n),this.contributions.set(e,n);try{return this.rebuild()}catch(i){throw s?this.contributions.set(e,s):this.contributions.delete(e),new Error(`Error when setting the document named ${e}: ${i}`)}}deleteContribution(e){const r=this.contributions.get(e);if(!r)throw new Error(`${e} does not exist`);this.contributions.delete(e);try{return this.rebuild()}catch(s){throw this.contributions.set(e,r),new Error(`Error when deleting the document named ${e}: ${s}`)}}deleteAllContributions(){if(this.contributions.size<=0)return this.latestOutput;const e=[...this.contributions.entries()];e.forEach(([r])=>this.contributions.delete(r));try{return this.rebuild()}catch(r){throw e.forEach(([s,n])=>this.contributions.set(s,n)),new Error(`Error when deleting all contributions: ${r}`)}}rebuild(){if(this.contributions.size===0){let r=A(this.baseDocument);return r=this.transformFinalOutputBeforeValidation(r),this.validateOutput(r),this.latestOutput=r,this.onDidRebuildEmitter.emit(void 0),this.latestOutput}let e=this.baseDocument;return this.contributions.forEach(r=>{e=ht(e,r,this.options.ignoreDuplicateProperties),this.validateOutput(e)}),e=this.transformFinalOutputBeforeValidation(e),this.validateOutput(e),this.latestOutput=e,this.onDidRebuildEmitter.emit(void 0),this.latestOutput}transformBaseDocumentAfterValidation(e){return e}transformContributionAfterValidation(e,r){return r}validateBaseDocument(e){}validateContribution(e,r){}validateOutput(e){}transformFinalOutputBeforeValidation(e){return e}}function Q(...t){let e=!0;return t.forEach(r=>{(!r||typeof r!="object"||Array.isArray(r))&&(e=!1)}),e}function Y(...t){let e=!0;return t.forEach(r=>{(!r||typeof r!="object"||!Array.isArray(r))&&(e=!1)}),e}function ht(t,e,r){const s=A(t);return e?be(s,A(e),r):s}function be(t,e,r){if(!e)return t;if(Q(t,e)){const s=t,n=e;Object.keys(n).forEach(i=>{if(Object.hasOwn(s,i)){if(Q(s[i],n[i]))s[i]=be(s[i],n[i],r);else if(Y(s[i],n[i]))s[i]=s[i].concat(n[i]);else if(!r)throw new Error(`Cannot merge objects: key "${i}" already exists in the target object`)}else s[i]=n[i]})}else Y(t,e)&&t.push(...e);return t}class ye extends et.Mutex{}class pt{constructor(){d(this,"mutexesByID",new Map)}get(e){let r=this.mutexesByID.get(e);return r||(r=new ye,this.mutexesByID.set(e,r),r)}}class dt extends ge{constructor(e,r){super(e,r)}get output(){return this.latestOutput}}class mt{constructor(e,r){d(this,"numberFormatter");this.numberFormatter=new Intl.NumberFormat(e,r)}format(e){return this.numberFormatter.format(e)}formatRange(e,r){return this.numberFormatter.formatRange(e,r)}formatRangeToParts(e,r){return this.numberFormatter.formatRangeToParts(e,r)}formatToParts(e){return this.numberFormatter.formatToParts(e)}resolvedOptions(){return this.numberFormatter.resolvedOptions()}}class gt{constructor(e="Anonymous"){d(this,"unsubscribers",new Set);this.name=e}add(...e){e.forEach(r=>{"dispose"in r?this.unsubscribers.add(r.dispose):this.unsubscribers.add(r)})}async runAllUnsubscribers(){const e=[...this.unsubscribers].map(s=>s()),r=await Promise.all(e);return this.unsubscribers.clear(),r.every((s,n)=>(s||console.error(`UnsubscriberAsyncList ${this.name}: Unsubscriber at index ${n} failed!`),s))}}var bt=Object.defineProperty,yt=(t,e,r)=>e in t?bt(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r,l=(t,e,r)=>(yt(t,typeof e!="symbol"?e+"":e,r),r);const j=["GEN","EXO","LEV","NUM","DEU","JOS","JDG","RUT","1SA","2SA","1KI","2KI","1CH","2CH","EZR","NEH","EST","JOB","PSA","PRO","ECC","SNG","ISA","JER","LAM","EZK","DAN","HOS","JOL","AMO","OBA","JON","MIC","NAM","HAB","ZEP","HAG","ZEC","MAL","MAT","MRK","LUK","JHN","ACT","ROM","1CO","2CO","GAL","EPH","PHP","COL","1TH","2TH","1TI","2TI","TIT","PHM","HEB","JAS","1PE","2PE","1JN","2JN","3JN","JUD","REV","TOB","JDT","ESG","WIS","SIR","BAR","LJE","S3Y","SUS","BEL","1MA","2MA","3MA","4MA","1ES","2ES","MAN","PS2","ODA","PSS","JSA","JDB","TBS","SST","DNT","BLT","XXA","XXB","XXC","XXD","XXE","XXF","XXG","FRT","BAK","OTH","3ES","EZA","5EZ","6EZ","INT","CNC","GLO","TDX","NDX","DAG","PS3","2BA","LBA","JUB","ENO","1MQ","2MQ","3MQ","REP","4BA","LAO"],G=["XXA","XXB","XXC","XXD","XXE","XXF","XXG","FRT","BAK","OTH","INT","CNC","GLO","TDX","NDX"],ve=["Genesis","Exodus","Leviticus","Numbers","Deuteronomy","Joshua","Judges","Ruth","1 Samuel","2 Samuel","1 Kings","2 Kings","1 Chronicles","2 Chronicles","Ezra","Nehemiah","Esther (Hebrew)","Job","Psalms","Proverbs","Ecclesiastes","Song of Songs","Isaiah","Jeremiah","Lamentations","Ezekiel","Daniel (Hebrew)","Hosea","Joel","Amos","Obadiah","Jonah","Micah","Nahum","Habakkuk","Zephaniah","Haggai","Zechariah","Malachi","Matthew","Mark","Luke","John","Acts","Romans","1 Corinthians","2 Corinthians","Galatians","Ephesians","Philippians","Colossians","1 Thessalonians","2 Thessalonians","1 Timothy","2 Timothy","Titus","Philemon","Hebrews","James","1 Peter","2 Peter","1 John","2 John","3 John","Jude","Revelation","Tobit","Judith","Esther Greek","Wisdom of Solomon","Sirach (Ecclesiasticus)","Baruch","Letter of Jeremiah","Song of 3 Young Men","Susanna","Bel and the Dragon","1 Maccabees","2 Maccabees","3 Maccabees","4 Maccabees","1 Esdras (Greek)","2 Esdras (Latin)","Prayer of Manasseh","Psalm 151","Odes","Psalms of Solomon","Joshua A. *obsolete*","Judges B. *obsolete*","Tobit S. *obsolete*","Susanna Th. *obsolete*","Daniel Th. *obsolete*","Bel Th. *obsolete*","Extra A","Extra B","Extra C","Extra D","Extra E","Extra F","Extra G","Front Matter","Back Matter","Other Matter","3 Ezra *obsolete*","Apocalypse of Ezra","5 Ezra (Latin Prologue)","6 Ezra (Latin Epilogue)","Introduction","Concordance ","Glossary ","Topical Index","Names Index","Daniel Greek","Psalms 152-155","2 Baruch (Apocalypse)","Letter of Baruch","Jubilees","Enoch","1 Meqabyan","2 Meqabyan","3 Meqabyan","Reproof (Proverbs 25-31)","4 Baruch (Rest of Baruch)","Laodiceans"],ee=It();function T(t,e=!0){return e&&(t=t.toUpperCase()),t in ee?ee[t]:0}function F(t){return T(t)>0}function vt(t){const e=typeof t=="string"?T(t):t;return e>=40&&e<=66}function Nt(t){return(typeof t=="string"?T(t):t)<=39}function Ne(t){return t<=66}function wt(t){const e=typeof t=="string"?T(t):t;return Se(e)&&!Ne(e)}function*Et(){for(let t=1;t<=j.length;t++)yield t}const St=1,we=j.length;function Ot(){return["XXA","XXB","XXC","XXD","XXE","XXF","XXG"]}function K(t,e="***"){const r=t-1;return r<0||r>=j.length?e:j[r]}function Ee(t){return t<=0||t>we?"******":ve[t-1]}function $t(t){return Ee(T(t))}function Se(t){const e=typeof t=="number"?K(t):t;return F(e)&&!G.includes(e)}function jt(t){const e=typeof t=="number"?K(t):t;return F(e)&&G.includes(e)}function Ct(t){return ve[t-1].includes("*obsolete*")}function It(){const t={};for(let e=0;e(t[t.Unknown=0]="Unknown",t[t.Original=1]="Original",t[t.Septuagint=2]="Septuagint",t[t.Vulgate=3]="Vulgate",t[t.English=4]="English",t[t.RussianProtestant=5]="RussianProtestant",t[t.RussianOrthodox=6]="RussianOrthodox",t))(S||{});const N=class{constructor(e){if(l(this,"name"),l(this,"fullPath"),l(this,"isPresent"),l(this,"hasVerseSegments"),l(this,"isCustomized"),l(this,"baseVersification"),l(this,"scriptureBooks"),l(this,"_type"),e!=null)typeof e=="string"?this.name=e:this._type=e;else throw new Error("Argument null")}get type(){return this._type}equals(e){return!e.type||!this.type?!1:e.type===this.type}};l(N,"Original",new N(S.Original)),l(N,"Septuagint",new N(S.Septuagint)),l(N,"Vulgate",new N(S.Vulgate)),l(N,"English",new N(S.English)),l(N,"RussianProtestant",new N(S.RussianProtestant)),l(N,"RussianOrthodox",new N(S.RussianOrthodox));let I=N;function te(t,e){const r=e[0];for(let s=1;s(t[t.Valid=0]="Valid",t[t.UnknownVersification=1]="UnknownVersification",t[t.OutOfRange=2]="OutOfRange",t[t.VerseOutOfOrder=3]="VerseOutOfOrder",t[t.VerseRepeated=4]="VerseRepeated",t))(Oe||{});const y=class f{constructor(e,r,s,n){if(l(this,"firstChapter"),l(this,"lastChapter"),l(this,"lastVerse"),l(this,"hasSegmentsDefined"),l(this,"text"),l(this,"BBBCCCVVVS"),l(this,"longHashCode"),l(this,"versification"),l(this,"rtlMark","‏"),l(this,"_bookNum",0),l(this,"_chapterNum",0),l(this,"_verseNum",0),l(this,"_verse"),s==null&&n==null)if(e!=null&&typeof e=="string"){const i=e,o=r!=null&&r instanceof I?r:void 0;this.setEmpty(o),this.parse(i)}else if(e!=null&&typeof e=="number"){const i=r!=null&&r instanceof I?r:void 0;this.setEmpty(i),this._verseNum=e%f.chapterDigitShifter,this._chapterNum=Math.floor(e%f.bookDigitShifter/f.chapterDigitShifter),this._bookNum=Math.floor(e/f.bookDigitShifter)}else if(r==null)if(e!=null&&e instanceof f){const i=e;this._bookNum=i.bookNum,this._chapterNum=i.chapterNum,this._verseNum=i.verseNum,this._verse=i.verse,this.versification=i.versification}else{if(e==null)return;const i=e instanceof I?e:f.defaultVersification;this.setEmpty(i)}else throw new Error("VerseRef constructor not supported.");else if(e!=null&&r!=null&&s!=null)if(typeof e=="string"&&typeof r=="string"&&typeof s=="string")this.setEmpty(n),this.updateInternal(e,r,s);else if(typeof e=="number"&&typeof r=="number"&&typeof s=="number")this._bookNum=e,this._chapterNum=r,this._verseNum=s,this.versification=n??f.defaultVersification;else throw new Error("VerseRef constructor not supported.");else throw new Error("VerseRef constructor not supported.")}static parse(e,r=f.defaultVersification){const s=new f(r);return s.parse(e),s}static isVerseParseable(e){return e.length>0&&"0123456789".includes(e[0])&&!e.endsWith(this.verseRangeSeparator)&&!e.endsWith(this.verseSequenceIndicator)}static tryParse(e){let r;try{return r=f.parse(e),{success:!0,verseRef:r}}catch(s){if(s instanceof R)return r=new f,{success:!1,verseRef:r};throw s}}static getBBBCCCVVV(e,r,s){return e%f.bcvMaxValue*f.bookDigitShifter+(r>=0?r%f.bcvMaxValue*f.chapterDigitShifter:0)+(s>=0?s%f.bcvMaxValue:0)}static tryGetVerseNum(e){let r;if(!e)return r=-1,{success:!0,vNum:r};r=0;let s;for(let n=0;n"9")return n===0&&(r=-1),{success:!1,vNum:r};if(r=r*10+ +s-+"0",r>f.bcvMaxValue)return r=-1,{success:!1,vNum:r}}return{success:!0,vNum:r}}get isDefault(){return this.bookNum===0&&this.chapterNum===0&&this.verseNum===0&&this.versification==null}get hasMultiple(){return this._verse!=null&&(this._verse.includes(f.verseRangeSeparator)||this._verse.includes(f.verseSequenceIndicator))}get book(){return E.bookNumberToId(this.bookNum,"")}set book(e){this.bookNum=E.bookIdToNumber(e)}get chapter(){return this.isDefault||this._chapterNum<0?"":this._chapterNum.toString()}set chapter(e){const r=+e;this._chapterNum=Number.isInteger(r)?r:-1}get verse(){return this._verse!=null?this._verse:this.isDefault||this._verseNum<0?"":this._verseNum.toString()}set verse(e){const{success:r,vNum:s}=f.tryGetVerseNum(e);this._verse=r?void 0:e.replace(this.rtlMark,""),this._verseNum=s,!(this._verseNum>=0)&&({vNum:this._verseNum}=f.tryGetVerseNum(this._verse))}get bookNum(){return this._bookNum}set bookNum(e){if(e<=0||e>E.lastBook)throw new R("BookNum must be greater than zero and less than or equal to last book");this._bookNum=e}get chapterNum(){return this._chapterNum}set chapterNum(e){this.chapterNum=e}get verseNum(){return this._verseNum}set verseNum(e){this._verseNum=e}get versificationStr(){var e;return(e=this.versification)==null?void 0:e.name}set versificationStr(e){this.versification=this.versification!=null?new I(e):void 0}get valid(){return this.validStatus===0}get validStatus(){return this.validateVerse(f.verseRangeSeparators,f.verseSequenceIndicators)}get BBBCCC(){return f.getBBBCCCVVV(this._bookNum,this._chapterNum,0)}get BBBCCCVVV(){return f.getBBBCCCVVV(this._bookNum,this._chapterNum,this._verseNum)}get isExcluded(){return!1}parse(e){if(e=e.replace(this.rtlMark,""),e.includes("/")){const i=e.split("/");if(e=i[0],i.length>1)try{const o=+i[1].trim();this.versification=new I(S[o])}catch{throw new R("Invalid reference : "+e)}}const r=e.trim().split(" ");if(r.length!==2)throw new R("Invalid reference : "+e);const s=r[1].split(":"),n=+s[0];if(s.length!==2||E.bookIdToNumber(r[0])===0||!Number.isInteger(n)||n<0||!f.isVerseParseable(s[1]))throw new R("Invalid reference : "+e);this.updateInternal(r[0],s[0],s[1])}simplify(){this._verse=void 0}clone(){return new f(this)}toString(){const e=this.book;return e===""?"":`${e} ${this.chapter}:${this.verse}`}equals(e){return e instanceof f?e._bookNum===this._bookNum&&e._chapterNum===this._chapterNum&&e._verseNum===this._verseNum&&e.verse===this.verse&&e.versification!=null&&this.versification!=null&&e.versification.equals(this.versification):!1}allVerses(e=!1,r=f.verseRangeSeparators,s=f.verseSequenceIndicators){if(this._verse==null||this.chapterNum<=0)return[this.clone()];const n=[],i=te(this._verse,s);for(const o of i.map(a=>te(a,r))){const a=this.clone();a.verse=o[0];const h=a.verseNum;if(n.push(a),o.length>1){const p=this.clone();if(p.verse=o[1],!e)for(let u=h+1;uo)return 3;if(s===o)return 4;s=o}return 0}get internalValid(){return this.versification==null?1:this._bookNum<=0||this._bookNum>E.lastBook?2:(E.isCanonical(this._bookNum),0)}setEmpty(e=f.defaultVersification){this._bookNum=0,this._chapterNum=-1,this._verse=void 0,this.versification=e}updateInternal(e,r,s){this.bookNum=E.bookIdToNumber(e),this.chapter=r,this.verse=s}};l(y,"defaultVersification",I.English),l(y,"verseRangeSeparator","-"),l(y,"verseSequenceIndicator",","),l(y,"verseRangeSeparators",[y.verseRangeSeparator]),l(y,"verseSequenceIndicators",[y.verseSequenceIndicator]),l(y,"chapterDigitShifter",1e3),l(y,"bookDigitShifter",y.chapterDigitShifter*y.chapterDigitShifter),l(y,"bcvMaxValue",y.chapterDigitShifter-1),l(y,"ValidStatusType",Oe);class R extends Error{}var re=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},C={},At=()=>{const t="\\ud800-\\udfff",e="\\u0300-\\u036f",r="\\ufe20-\\ufe2f",s="\\u20d0-\\u20ff",n="\\u1ab0-\\u1aff",i="\\u1dc0-\\u1dff",o=e+r+s+n+i,a="\\ufe0e\\ufe0f",h="\\uD83D\\uDC69\\uD83C\\uDFFB\\u200D\\uD83C\\uDF93",p=`[${t}]`,u=`[${o}]`,c="\\ud83c[\\udffb-\\udfff]",m=`(?:${u}|${c})`,v=`[^${t}]`,b="(?:\\uD83C[\\uDDE6-\\uDDFF]){2}",M="[\\ud800-\\udbff][\\udc00-\\udfff]",q="\\u200d",Le="(?:\\ud83c\\udff4\\udb40\\udc67\\udb40\\udc62\\udb40(?:\\udc65|\\udc73|\\udc77)\\udb40(?:\\udc6e|\\udc63|\\udc6c)\\udb40(?:\\udc67|\\udc74|\\udc73)\\udb40\\udc7f)",Ue=`[${h}]`,W=`${m}?`,Z=`[${a}]?`,He=`(?:${q}(?:${[v,b,M].join("|")})${Z+W})*`,We=Z+W+He,Ze=`(?:${[`${v}${u}?`,u,b,M,p,Ue].join("|")})`;return new RegExp(`${Le}|${c}(?=${c})|${Ze+We}`,"g")},Pt=re&&re.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(C,"__esModule",{value:!0});var k=Pt(At);function z(t){if(typeof t!="string")throw new Error("A string is expected as input");return t.match(k.default())||[]}var Tt=C.toArray=z;function X(t){if(typeof t!="string")throw new Error("Input must be a string");var e=t.match(k.default());return e===null?0:e.length}var Dt=C.length=X;function $e(t,e,r){if(e===void 0&&(e=0),typeof t!="string")throw new Error("Input must be a string");(typeof e!="number"||e<0)&&(e=0),typeof r=="number"&&r<0&&(r=0);var s=t.match(k.default());return s?s.slice(e,r).join(""):""}var Mt=C.substring=$e;function Rt(t,e,r){if(e===void 0&&(e=0),typeof t!="string")throw new Error("Input must be a string");var s=X(t);if(typeof e!="number"&&(e=parseInt(e,10)),e>=s)return"";e<0&&(e+=s);var n;typeof r>"u"?n=s:(typeof r!="number"&&(r=parseInt(r,10)),n=r>=0?r+e:e);var i=t.match(k.default());return i?i.slice(e,n).join(""):""}var Bt=C.substr=Rt;function xt(t,e,r,s){if(e===void 0&&(e=16),r===void 0&&(r="#"),s===void 0&&(s="right"),typeof t!="string"||typeof e!="number")throw new Error("Invalid arguments specified");if(["left","right"].indexOf(s)===-1)throw new Error("Pad position should be either left or right");typeof r!="string"&&(r=String(r));var n=X(t);if(n>e)return $e(t,0,e);if(n=s.length)return e===""?s.length:-1;if(e==="")return r;var n=z(e),i=!1,o;for(o=r;og(t)||e<-g(t)))return V(t,e,1)}function $(t,e){return e<0||e>g(t)-1?"":V(t,e,1)}function zt(t,e){if(!(e<0||e>g(t)-1))return V(t,e,1).codePointAt(0)}function Ce(t,e,r=g(t)){const s=Ae(t,e);return!(s===-1||s+g(e)!==r)}function _t(t,e,r){if(e<0)return-1;if(r){if($(t,e)==="}"&&$(t,e-1)==="\\")return e;const i=P(t,"\\}",e);return i>=0?i+1:i}let s=e;const n=g(t);for(;s=n?-1:s}function Jt(t,e){let r=t,s=0;for(;s=0){const i=w(r,s+1,n),o=i in e?e[i]:i;r=`${w(r,0,s)}${o}${w(r,n+1)}`,s=n+g(o)-g(i)-2}}else r=`${w(r,0,s-1)}${w(r,s)}`,s-=1;break;case"}":$(r,s-1)!=="\\"||(r=`${w(r,0,s-1)}${w(r,s)}`,s-=1);break}s+=1}return r}function Ie(t,e,r=0){const s=w(t,r);return P(s,e)!==-1}function P(t,e,r=0){return Vt(t,e,r)}function Ae(t,e,r){let s=r===void 0?g(t):r;s<0?s=0:s>=g(t)&&(s=g(t)-1);for(let n=s;n>=0;n--)if(V(t,n,g(e))===e)return n;return-1}function g(t){return Dt(t)}function Gt(t,e){const r=e.toUpperCase();return r==="NONE"?t:t.normalize(r)}function Ft(t,e,r){return t.localeCompare(e,"en",r)}function Kt(t,e,r=" "){return e<=g(t)?t:je(t,e,r,"right")}function Xt(t,e,r=" "){return e<=g(t)?t:je(t,e,r,"left")}function se(t,e){return e>t?t:e<-t?0:e<0?e+t:e}function Lt(t,e,r){const s=g(t);if(e>s||r&&(e>r&&!(e>=0&&e-s)||r<-s))return"";const n=se(s,e),i=r?se(s,r):void 0;return w(t,n,i)}function _(t,e,r){const s=[];if(r!==void 0&&r<=0)return[t];if(e==="")return Pe(t).slice(0,r);let n=e;(typeof e=="string"||e instanceof RegExp&&!Ie(e.flags,"g"))&&(n=new RegExp(e,"g"));const i=t.match(n);let o=0;if(!i)return[t];for(let a=0;a<(r?r-1:i.length);a++){const h=P(t,i[a],o),p=g(i[a]);if(s.push(w(t,o,h)),o=h+p,r!==void 0&&s.length===r)break}return s.push(w(t,o)),s}function L(t,e,r=0){return P(t,e,r)===r}function V(t,e=0,r=g(t)-e){return Bt(t,e,r)}function w(t,e,r=g(t)){return Mt(t,e,r)}function Pe(t){return Tt(t)}function Ut(t){return L(t,"%")&&Ce(t,"%")}const Te=[{shortName:"ERR",fullNames:["ERROR"],chapters:-1},{shortName:"GEN",fullNames:["Genesis"],chapters:50},{shortName:"EXO",fullNames:["Exodus"],chapters:40},{shortName:"LEV",fullNames:["Leviticus"],chapters:27},{shortName:"NUM",fullNames:["Numbers"],chapters:36},{shortName:"DEU",fullNames:["Deuteronomy"],chapters:34},{shortName:"JOS",fullNames:["Joshua"],chapters:24},{shortName:"JDG",fullNames:["Judges"],chapters:21},{shortName:"RUT",fullNames:["Ruth"],chapters:4},{shortName:"1SA",fullNames:["1 Samuel"],chapters:31},{shortName:"2SA",fullNames:["2 Samuel"],chapters:24},{shortName:"1KI",fullNames:["1 Kings"],chapters:22},{shortName:"2KI",fullNames:["2 Kings"],chapters:25},{shortName:"1CH",fullNames:["1 Chronicles"],chapters:29},{shortName:"2CH",fullNames:["2 Chronicles"],chapters:36},{shortName:"EZR",fullNames:["Ezra"],chapters:10},{shortName:"NEH",fullNames:["Nehemiah"],chapters:13},{shortName:"EST",fullNames:["Esther"],chapters:10},{shortName:"JOB",fullNames:["Job"],chapters:42},{shortName:"PSA",fullNames:["Psalm","Psalms"],chapters:150},{shortName:"PRO",fullNames:["Proverbs"],chapters:31},{shortName:"ECC",fullNames:["Ecclesiastes"],chapters:12},{shortName:"SNG",fullNames:["Song of Solomon","Song of Songs"],chapters:8},{shortName:"ISA",fullNames:["Isaiah"],chapters:66},{shortName:"JER",fullNames:["Jeremiah"],chapters:52},{shortName:"LAM",fullNames:["Lamentations"],chapters:5},{shortName:"EZK",fullNames:["Ezekiel"],chapters:48},{shortName:"DAN",fullNames:["Daniel"],chapters:12},{shortName:"HOS",fullNames:["Hosea"],chapters:14},{shortName:"JOL",fullNames:["Joel"],chapters:3},{shortName:"AMO",fullNames:["Amos"],chapters:9},{shortName:"OBA",fullNames:["Obadiah"],chapters:1},{shortName:"JON",fullNames:["Jonah"],chapters:4},{shortName:"MIC",fullNames:["Micah"],chapters:7},{shortName:"NAM",fullNames:["Nahum"],chapters:3},{shortName:"HAB",fullNames:["Habakkuk"],chapters:3},{shortName:"ZEP",fullNames:["Zephaniah"],chapters:3},{shortName:"HAG",fullNames:["Haggai"],chapters:2},{shortName:"ZEC",fullNames:["Zechariah"],chapters:14},{shortName:"MAL",fullNames:["Malachi"],chapters:4},{shortName:"MAT",fullNames:["Matthew"],chapters:28},{shortName:"MRK",fullNames:["Mark"],chapters:16},{shortName:"LUK",fullNames:["Luke"],chapters:24},{shortName:"JHN",fullNames:["John"],chapters:21},{shortName:"ACT",fullNames:["Acts"],chapters:28},{shortName:"ROM",fullNames:["Romans"],chapters:16},{shortName:"1CO",fullNames:["1 Corinthians"],chapters:16},{shortName:"2CO",fullNames:["2 Corinthians"],chapters:13},{shortName:"GAL",fullNames:["Galatians"],chapters:6},{shortName:"EPH",fullNames:["Ephesians"],chapters:6},{shortName:"PHP",fullNames:["Philippians"],chapters:4},{shortName:"COL",fullNames:["Colossians"],chapters:4},{shortName:"1TH",fullNames:["1 Thessalonians"],chapters:5},{shortName:"2TH",fullNames:["2 Thessalonians"],chapters:3},{shortName:"1TI",fullNames:["1 Timothy"],chapters:6},{shortName:"2TI",fullNames:["2 Timothy"],chapters:4},{shortName:"TIT",fullNames:["Titus"],chapters:3},{shortName:"PHM",fullNames:["Philemon"],chapters:1},{shortName:"HEB",fullNames:["Hebrews"],chapters:13},{shortName:"JAS",fullNames:["James"],chapters:5},{shortName:"1PE",fullNames:["1 Peter"],chapters:5},{shortName:"2PE",fullNames:["2 Peter"],chapters:3},{shortName:"1JN",fullNames:["1 John"],chapters:5},{shortName:"2JN",fullNames:["2 John"],chapters:1},{shortName:"3JN",fullNames:["3 John"],chapters:1},{shortName:"JUD",fullNames:["Jude"],chapters:1},{shortName:"REV",fullNames:["Revelation"],chapters:22}],De=1,Me=Te.length-1,Re=1,Be=1,xe=t=>{var e;return((e=Te[t])==null?void 0:e.chapters)??-1},Ht=(t,e)=>({bookNum:Math.max(De,Math.min(t.bookNum+e,Me)),chapterNum:1,verseNum:1}),Wt=(t,e)=>({...t,chapterNum:Math.min(Math.max(Re,t.chapterNum+e),xe(t.bookNum)),verseNum:1}),Zt=(t,e)=>({...t,verseNum:Math.max(Be,t.verseNum+e)});async function Qt(t,e,r){const s=E.bookNumberToId(t);if(!L(Intl.getCanonicalLocales(e)[0],"zh"))return r({localizeKey:`LocalizedId.${s}`,languagesToSearch:[e]});const n=await r({localizeKey:`Book.${s}`,languagesToSearch:[e]}),i=_(n,"-");return _(i[0],"ÿ08")[0].trim()}const Yt=t=>(...e)=>t.map(s=>s(...e)).every(s=>s),er=t=>async(...e)=>{const r=t.map(async s=>s(...e));return(await Promise.all(r)).every(s=>s)};var tr=Object.getOwnPropertyNames,rr=Object.getOwnPropertySymbols,sr=Object.prototype.hasOwnProperty;function ne(t,e){return function(s,n,i){return t(s,n,i)&&e(s,n,i)}}function x(t){return function(r,s,n){if(!r||!s||typeof r!="object"||typeof s!="object")return t(r,s,n);var i=n.cache,o=i.get(r),a=i.get(s);if(o&&a)return o===s&&a===r;i.set(r,s),i.set(s,r);var h=t(r,s,n);return i.delete(r),i.delete(s),h}}function ie(t){return tr(t).concat(rr(t))}var ke=Object.hasOwn||function(t,e){return sr.call(t,e)};function D(t,e){return t||e?t===e:t===e||t!==t&&e!==e}var Ve="_owner",oe=Object.getOwnPropertyDescriptor,ae=Object.keys;function nr(t,e,r){var s=t.length;if(e.length!==s)return!1;for(;s-- >0;)if(!r.equals(t[s],e[s],s,s,t,e,r))return!1;return!0}function ir(t,e){return D(t.getTime(),e.getTime())}function ue(t,e,r){if(t.size!==e.size)return!1;for(var s={},n=t.entries(),i=0,o,a;(o=n.next())&&!o.done;){for(var h=e.entries(),p=!1,u=0;(a=h.next())&&!a.done;){var c=o.value,m=c[0],v=c[1],b=a.value,M=b[0],q=b[1];!p&&!s[u]&&(p=r.equals(m,M,i,u,t,e,r)&&r.equals(v,q,m,M,t,e,r))&&(s[u]=!0),u++}if(!p)return!1;i++}return!0}function or(t,e,r){var s=ae(t),n=s.length;if(ae(e).length!==n)return!1;for(var i;n-- >0;)if(i=s[n],i===Ve&&(t.$$typeof||e.$$typeof)&&t.$$typeof!==e.$$typeof||!ke(e,i)||!r.equals(t[i],e[i],i,i,t,e,r))return!1;return!0}function B(t,e,r){var s=ie(t),n=s.length;if(ie(e).length!==n)return!1;for(var i,o,a;n-- >0;)if(i=s[n],i===Ve&&(t.$$typeof||e.$$typeof)&&t.$$typeof!==e.$$typeof||!ke(e,i)||!r.equals(t[i],e[i],i,i,t,e,r)||(o=oe(t,i),a=oe(e,i),(o||a)&&(!o||!a||o.configurable!==a.configurable||o.enumerable!==a.enumerable||o.writable!==a.writable)))return!1;return!0}function ar(t,e){return D(t.valueOf(),e.valueOf())}function ur(t,e){return t.source===e.source&&t.flags===e.flags}function le(t,e,r){if(t.size!==e.size)return!1;for(var s={},n=t.values(),i,o;(i=n.next())&&!i.done;){for(var a=e.values(),h=!1,p=0;(o=a.next())&&!o.done;)!h&&!s[p]&&(h=r.equals(i.value,o.value,i.value,o.value,t,e,r))&&(s[p]=!0),p++;if(!h)return!1}return!0}function lr(t,e){var r=t.length;if(e.length!==r)return!1;for(;r-- >0;)if(t[r]!==e[r])return!1;return!0}var cr="[object Arguments]",fr="[object Boolean]",hr="[object Date]",pr="[object Map]",dr="[object Number]",mr="[object Object]",gr="[object RegExp]",br="[object Set]",yr="[object String]",vr=Array.isArray,ce=typeof ArrayBuffer=="function"&&ArrayBuffer.isView?ArrayBuffer.isView:null,fe=Object.assign,Nr=Object.prototype.toString.call.bind(Object.prototype.toString);function wr(t){var e=t.areArraysEqual,r=t.areDatesEqual,s=t.areMapsEqual,n=t.areObjectsEqual,i=t.arePrimitiveWrappersEqual,o=t.areRegExpsEqual,a=t.areSetsEqual,h=t.areTypedArraysEqual;return function(u,c,m){if(u===c)return!0;if(u==null||c==null||typeof u!="object"||typeof c!="object")return u!==u&&c!==c;var v=u.constructor;if(v!==c.constructor)return!1;if(v===Object)return n(u,c,m);if(vr(u))return e(u,c,m);if(ce!=null&&ce(u))return h(u,c,m);if(v===Date)return r(u,c,m);if(v===RegExp)return o(u,c,m);if(v===Map)return s(u,c,m);if(v===Set)return a(u,c,m);var b=Nr(u);return b===hr?r(u,c,m):b===gr?o(u,c,m):b===pr?s(u,c,m):b===br?a(u,c,m):b===mr?typeof u.then!="function"&&typeof c.then!="function"&&n(u,c,m):b===cr?n(u,c,m):b===fr||b===dr||b===yr?i(u,c,m):!1}}function Er(t){var e=t.circular,r=t.createCustomConfig,s=t.strict,n={areArraysEqual:s?B:nr,areDatesEqual:ir,areMapsEqual:s?ne(ue,B):ue,areObjectsEqual:s?B:or,arePrimitiveWrappersEqual:ar,areRegExpsEqual:ur,areSetsEqual:s?ne(le,B):le,areTypedArraysEqual:s?B:lr};if(r&&(n=fe({},n,r(n))),e){var i=x(n.areArraysEqual),o=x(n.areMapsEqual),a=x(n.areObjectsEqual),h=x(n.areSetsEqual);n=fe({},n,{areArraysEqual:i,areMapsEqual:o,areObjectsEqual:a,areSetsEqual:h})}return n}function Sr(t){return function(e,r,s,n,i,o,a){return t(e,r,a)}}function Or(t){var e=t.circular,r=t.comparator,s=t.createState,n=t.equals,i=t.strict;if(s)return function(h,p){var u=s(),c=u.cache,m=c===void 0?e?new WeakMap:void 0:c,v=u.meta;return r(h,p,{cache:m,equals:n,meta:v,strict:i})};if(e)return function(h,p){return r(h,p,{cache:new WeakMap,equals:n,meta:void 0,strict:i})};var o={cache:void 0,equals:n,meta:void 0,strict:i};return function(h,p){return r(h,p,o)}}var $r=O();O({strict:!0});O({circular:!0});O({circular:!0,strict:!0});O({createInternalComparator:function(){return D}});O({strict:!0,createInternalComparator:function(){return D}});O({circular:!0,createInternalComparator:function(){return D}});O({circular:!0,createInternalComparator:function(){return D},strict:!0});function O(t){t===void 0&&(t={});var e=t.circular,r=e===void 0?!1:e,s=t.createInternalComparator,n=t.createState,i=t.strict,o=i===void 0?!1:i,a=Er(t),h=wr(a),p=s?s(h):Sr(h);return Or({circular:r,comparator:h,createState:n,equals:p,strict:o})}function qe(t,e){return $r(t,e)}function ze(t,e){if(typeof t!=typeof e)return!1;if(!t&&!e)return!0;if(Array.isArray(t)){const i=e,o=t;return i.length===0?!0:i.every(a=>o.includes(a))}if(typeof t!="object")return qe(t,e);const r=e,s=t;let n=!0;return Object.keys(r).forEach(i=>{n&&(Object.hasOwn(s,i)&&ze(s[i],r[i])||(n=!1))}),n}function J(t,e,r){return JSON.stringify(t,(n,i)=>{let o=i;return e&&(o=e(n,o)),o===void 0&&(o=null),o},r)}function _e(t,e){function r(n){return Object.keys(n).forEach(i=>{n[i]===null?n[i]=void 0:typeof n[i]=="object"&&(n[i]=r(n[i]))}),n}const s=JSON.parse(t,e);if(s!==null)return typeof s=="object"?r(s):s}function jr(t){try{const e=J(t);return e===J(_e(e))}catch{return!1}}const Cr=t=>t.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/");function Ir(){return typeof navigator<"u"&&navigator.languages?navigator.languages[0]:new he().resolvedOptions().locale}const U={projectSettingsContribution:{description:"The data an extension provides to inform Platform.Bible of the project settings it provides",anyOf:[{$ref:"#/$defs/projectSettingsGroup"},{type:"array",items:{$ref:"#/$defs/projectSettingsGroup"}}]},projectSettingsGroup:{description:"Group of related settings definitions",type:"object",properties:{label:{description:"localizeKey that displays in the project settings dialog as the group name",$ref:"#/$defs/localizeKey"},description:{description:"localizeKey that displays in the project settings dialog to describe the group",$ref:"#/$defs/localizeKey"},properties:{$ref:"#/$defs/projectSettingProperties"}},required:["label","properties"]},projectSettingProperties:{description:"Object whose keys are setting IDs and whose values are settings objects",type:"object",patternProperties:{"^[\\w\\-]+\\.[\\w\\-]+$":{$ref:"#/$defs/projectSetting"}},additionalProperties:!1},projectSetting:{description:"A description of an extension's setting entry",anyOf:[{$ref:"#/$defs/extensionControlledProjectSetting"}]},extensionControlledProjectSetting:{description:"Setting definition that is validated by the extension.",allOf:[{$ref:"#/$defs/projectSettingBase"},{$ref:"#/$defs/modifierExtensionControlled"}]},projectSettingBase:{description:"Base information needed to describe a project setting entry",allOf:[{$ref:"#/$defs/settingBase"},{$ref:"#/$defs/modifierProject"}]},modifierProject:{description:"Modifies setting type to be project setting",type:"object",properties:{includeProjectInterfaces:{description:"String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if they should be included.\n\nIf this is one string, it will be matched against `projectInterface`s. If this is an array, each entry is handled based on its type (at least one entry must match for this filter condition to pass):\n\n- If the entry is a string, it will be matched against each `projectInterface`. If any match, the project will pass this filter condition\n- If the entry is an array of strings, each will be matched against each `projectInterface`. If every string matches against at least one `projectInterface`, the project will pass this filter condition\n\nIn other words, each entry in the first-level array is `OR`'ed together. Each entry in second-level arrays (arrays within the first-level array) are `AND`'ed together.\n\nDefaults to all {@link ProjectInterfaces}, so all projects that do not match `excludeProjectInterfaces` will be included\n\n@example\n\n```typescript\nincludeProjectInterfaces: ['one', ['two', 'three']];\n```\n\nThis filter condition will succeed on projects whose `projectInterface`s fulfill at least one of the following conditions (At least one entry in the array must match):\n\n- Include `one`\n- Include both `two` and `three`.",anyOf:[{type:"null"},{type:"string"},{type:"array",items:{anyOf:[{type:"string"},{type:"array",items:{type:"string"}}]}}]},excludeProjectInterfaces:{description:"String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if they should absolutely not be included even if they match with `includeProjectInterfaces`.\n\nIf this is one string, it will be matched against `projectInterface`s. If this is an array, each entry is handled based on its type (at least one entry must match for this filter condition to exclude the project):\n\n- If the entry is a string, it will be matched against each `projectInterface`. If any match, the project will pass this filter condition and exclude the project\n- If the entry is an array of strings, each will be matched against each `projectInterface`. If every string matches against at least one `projectInterface`, the project will pass this filter condition and exclude the project\n\nIn other words, each entry in the first-level array is `OR`'ed together. Each entry in second-level arrays (arrays within the first-level array) are `AND`'ed together.\n\nDefaults to no {@link ProjectInterfaces}, so all projects that match `includeProjectInterfaces` will be included\n\n@example\n\n```typescript\nexcludeProjectInterfaces: ['one', ['two', 'three']];\n```\n\nThis filter condition will succeed and exclude projects whose `projectInterface`s fulfill at least one of the following conditions (At least one entry in the array must match):\n\n- Include `one`\n- Include both `two` and `three`.",anyOf:[{type:"null"},{type:"string"},{type:"array",items:{anyOf:[{type:"string"},{type:"array",items:{type:"string"}}]}}]},includePdpFactoryIds:{description:"String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory Ids that provided each project's metadata (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if the projects should be included.\n\nDefaults to all Project Data Provider Factory Ids, so all projects that do not match `excludePdpFactoryIds` will be included",anyOf:[{type:"null"},{type:"string"},{type:"array",items:{type:"string"}}]},excludePdpFactoryIds:{description:"String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory Ids that provided each project's metadata (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if the projects should absolutely not be included even if they match with `includeProjectInterfaces`.\n\nDefaults to none, so all projects that match `includePdpFactoryIds` will be included",anyOf:[{type:"null"},{type:"string"},{type:"array",items:{type:"string"}}]}}},settingsContribution:{description:"The data an extension provides to inform Platform.Bible of the settings it provides",anyOf:[{$ref:"#/$defs/settingsGroup"},{type:"array",items:{$ref:"#/$defs/settingsGroup"}}]},settingsGroup:{description:"Group of related settings definitions",type:"object",properties:{label:{description:"localizeKey that displays in the settings dialog as the group name",$ref:"#/$defs/localizeKey"},description:{description:"localizeKey that displays in the settings dialog to describe the group",$ref:"#/$defs/localizeKey"},properties:{$ref:"#/$defs/settingProperties"}},required:["label","properties"]},settingProperties:{description:"Object whose keys are setting IDs and whose values are settings objects",type:"object",patternProperties:{"^[\\w-]+\\.[\\w-]+$":{$ref:"#/$defs/setting"}},additionalProperties:!1},setting:{description:"A description of an extension's setting entry",anyOf:[{$ref:"#/$defs/extensionControlledSetting"}]},extensionControlledSetting:{description:"Setting definition that is validated by the extension.",allOf:[{$ref:"#/$defs/settingBase"},{$ref:"#/$defs/modifierExtensionControlled"}]},settingBase:{description:"Base information needed to describe a setting entry",allOf:[{$ref:"#/$defs/stateBase"},{type:"object",properties:{label:{description:"localizeKey that displays in the settings dialog as the setting name",$ref:"#/$defs/localizeKey"},description:{description:"localizeKey that displays in the settings dialog to describe the setting",$ref:"#/$defs/localizeKey"}},required:["label"]}]},projectStateContribution:{description:"The data an extension provides to inform Platform.Bible of the project state it provides",$ref:"#/$defs/userStateProperties"},userStateContribution:{description:"The data an extension provides to inform Platform.Bible of the user state it provides",$ref:"#/$defs/userStateProperties"},userStateProperties:{description:"Object whose keys are state IDs and whose values are state objects",type:"object",patternProperties:{"^[\\w\\-]+\\.[\\w\\-]+$":{$ref:"#/$defs/userState"}},additionalProperties:!1},userState:{description:"A description of an extension's user state entry",anyOf:[{$ref:"#/$defs/extensionControlledState"}]},extensionControlledState:{description:"State definition that is validated by the extension.",allOf:[{$ref:"#/$defs/stateBase"},{$ref:"#/$defs/modifierExtensionControlled"}]},modifierExtensionControlled:{description:'Modifies state/setting type to be extension-controlled. "Extension-controlled" means the extension provides the component and the validator for the state/setting, so the state/setting is controlled by the extension.',not:{anyOf:[{type:"object",required:["platformType"]},{type:"object",required:["type"]}]}},stateBase:{description:"Base information needed to describe a state entry",type:"object",properties:{default:{description:"default value for the state/setting",type:"any"},derivesFrom:{description:"a state/setting ID whose value to set to this state/setting's starting value the first time this state/setting is loaded",$ref:"#/$defs/id"}},required:["default"]},localizeKey:{description:"Identifier for a string that will be localized based on the user's UI language",type:"string",pattern:"^%[\\w\\-\\.]+%$",tsType:"LocalizeKey"},id:{description:"",type:"string",pattern:"^[\\w\\-]+\\.[\\w\\-]+$",tsType:"Id"}};function H(t){t&&Object.values(t).forEach(e=>{if(e.type){if("tsType"in e&&delete e.tsType,e.type==="any"){delete e.type;return}e.type==="object"&&H(e.properties)}})}H(U);const Je={$schema:"https://json-schema.org/draft/2020-12/schema",title:"Project Settings Contribution",description:"The data an extension provides to inform Platform.Bible of the project settings it provides",anyOf:[{$ref:"#/$defs/projectSettingsGroup"},{type:"array",items:{$ref:"#/$defs/projectSettingsGroup"}}],$defs:U};Object.freeze(Je);const Ge={$schema:"https://json-schema.org/draft/2020-12/schema",title:"Settings Contribution",description:"The data an extension provides to inform Platform.Bible of the settings it provides",anyOf:[{$ref:"#/$defs/settingsGroup"},{type:"array",items:{$ref:"#/$defs/settingsGroup"}}],$defs:U};Object.freeze(Ge);const Fe={languageStrings:{description:"Map whose keys are localized string keys and whose values provide information about how to localize strings for the localized string key",type:"object",patternProperties:{"^%[\\w\\-\\.]+%$":{$ref:"#/$defs/localizedStringValue"}},additionalProperties:!1},localizedStringValue:{description:"Localized string value associated with this key",type:"string"},stringsMetadata:{description:"Map whose keys are localized string keys and whose values provide additional non-locale-specific information about the localized string key",type:"object",patternProperties:{"^%[\\w\\-\\.]+%$":{$ref:"#/$defs/stringMetadata"}},additionalProperties:!1},stringMetadata:{description:"Additional non-locale-specific information about a localized string key",type:"object",properties:{fallbackKey:{description:"Localized string key from which to get this value if one does not exist in the specified language. If a new key/value pair needs to be made to replace an existing one, this could help smooth over the transition if the meanings are close enough",$ref:"#/$defs/localizeKey"},notes:{description:"Additional information provided by developers in English to help the translator to know how to translate this localized string accurately",type:"string"}}},localizeKey:{description:"Identifier for a string that will be localized based on the user's UI language",type:"string",pattern:"^%[\\w\\-\\.]+%$",tsType:"LocalizeKey"}};H(Fe);const Ke={$schema:"https://json-schema.org/draft/2020-12/schema",title:"Localized String Data Contribution",description:"The data an extension provides to inform Platform.Bible of the localized strings it provides.",type:"object",properties:{metadata:{$ref:"#/$defs/stringsMetadata"},localizedStrings:{type:"object",additionalProperties:{$ref:"#/$defs/languageStrings"}}},$defs:Fe};Object.freeze(Ke);const Xe={title:"Platform.Bible menus",type:"object",properties:{mainMenu:{description:"Top level menu for the application",$ref:"#/$defs/multiColumnMenu"},defaultWebViewTopMenu:{description:"Default top menu for web views that don't specify their own",$ref:"#/$defs/multiColumnMenu"},defaultWebViewContextMenu:{description:"Default context menu for web views that don't specify their own",$ref:"#/$defs/singleColumnMenu"},webViewMenus:{description:"Menus that apply per web view in the application",type:"object",patternProperties:{"^[\\w\\-]+\\.[\\w\\-]+$":{$ref:"#/$defs/menusForOneWebView"}},additionalProperties:!1}},required:["mainMenu","defaultWebViewTopMenu","defaultWebViewContextMenu","webViewMenus"],additionalProperties:!1,$defs:{localizeKey:{description:"Identifier for a string that will be localized in a menu based on the user's UI language",type:"string",pattern:"^%[\\w\\-\\.]+%$"},referencedItem:{description:"Name of some UI element (i.e., tab, column, group, menu item) or some PAPI object (i.e., command)",type:"string",pattern:"^[\\w\\-]+\\.[\\w\\-]+$"},columnsWithHeaders:{description:"Group of columns that can be combined with other columns to form a multi-column menu",type:"object",patternProperties:{"^[\\w\\-]+\\.[\\w\\-]+$":{description:"Single column with a header string",type:"object",properties:{label:{description:"Header text for this this column in the UI",$ref:"#/$defs/localizeKey"},localizeNotes:{description:"Additional information provided by developers to help people who perform localization",type:"string"},order:{description:"Relative order of this column compared to other columns (sorted ascending)",type:"number"},isExtensible:{description:"Defines whether contributions are allowed to add menu groups to this column",type:"boolean"}},required:["label","order"],additionalProperties:!1}},properties:{isExtensible:{description:"Defines whether contributions are allowed to add columns to this multi-column menu",type:"boolean"}}},menuGroups:{description:"Group of menu items that can be combined with other groups to form a single menu/submenu. Groups are separated using a line within the menu/submenu.",type:"object",patternProperties:{"^[\\w\\-]+\\.[\\w\\-]+$":{description:"Single group that contains menu items",type:"object",oneOf:[{properties:{column:{description:"Column where this group belongs, not required for single column menus",$ref:"#/$defs/referencedItem"},order:{description:"Relative order of this group compared to other groups in the same column or submenu (sorted ascending)",type:"number"},isExtensible:{description:"Defines whether contributions are allowed to add menu items to this menu group",type:"boolean"}},required:["order"],additionalProperties:!1},{properties:{menuItem:{description:"Menu item that anchors the submenu where this group belongs",$ref:"#/$defs/referencedItem"},order:{description:"Relative order of this group compared to other groups in the same column or submenu (sorted ascending)",type:"number"},isExtensible:{description:"Defines whether contributions are allowed to add menu items to this menu group",type:"boolean"}},required:["menuItem","order"],additionalProperties:!1}]}},additionalProperties:!1},menuItem:{description:"Single item in a menu that can be clicked on to take an action or can be the parent of a submenu",type:"object",oneOf:[{properties:{id:{description:"ID for this menu item that holds a submenu",$ref:"#/$defs/referencedItem"}},required:["id"]},{properties:{command:{description:"Name of the PAPI command to run when this menu item is selected.",$ref:"#/$defs/referencedItem"},iconPathBefore:{description:"Path to the icon to display before the menu text",type:"string"},iconPathAfter:{description:"Path to the icon to display after the menu text",type:"string"}},required:["command"]}],properties:{label:{description:"Key that represents the text of this menu item to display",$ref:"#/$defs/localizeKey"},tooltip:{description:"Key that represents the text to display if a mouse pointer hovers over the menu item",$ref:"#/$defs/localizeKey"},searchTerms:{description:"Key that represents additional words the platform should reference when users are searching for menu items",$ref:"#/$defs/localizeKey"},localizeNotes:{description:"Additional information provided by developers to help people who perform localization",type:"string"},group:{description:"Group to which this menu item belongs",$ref:"#/$defs/referencedItem"},order:{description:"Relative order of this menu item compared to other menu items in the same group (sorted ascending)",type:"number"}},required:["label","group","order"],unevaluatedProperties:!1},groupsAndItems:{description:"Core schema for a column",type:"object",properties:{groups:{description:"Groups that belong in this menu",$ref:"#/$defs/menuGroups"},items:{description:"List of menu items that belong in this menu",type:"array",items:{$ref:"#/$defs/menuItem"},uniqueItems:!0}},required:["groups","items"]},singleColumnMenu:{description:"Menu that contains a column without a header",type:"object",allOf:[{$ref:"#/$defs/groupsAndItems"}],unevaluatedProperties:!1},multiColumnMenu:{description:"Menu that can contain multiple columns with headers",type:"object",allOf:[{$ref:"#/$defs/groupsAndItems"},{properties:{columns:{description:"Columns that belong in this menu",$ref:"#/$defs/columnsWithHeaders"}},required:["columns"]}],unevaluatedProperties:!1},menusForOneWebView:{description:"Set of menus that are associated with a single tab",type:"object",properties:{includeDefaults:{description:"Indicates whether the platform default menus should be included for this webview",type:"boolean"},topMenu:{description:"Menu that opens when you click on the top left corner of a tab",$ref:"#/$defs/multiColumnMenu"},contextMenu:{description:"Menu that opens when you right click on the main body/area of a tab",$ref:"#/$defs/singleColumnMenu"}},additionalProperties:!1}}};Object.freeze(Xe);exports.AsyncVariable=tt;exports.Collator=rt;exports.DateTimeFormat=he;exports.DocumentCombiner=ge;exports.FIRST_SCR_BOOK_NUM=De;exports.FIRST_SCR_CHAPTER_NUM=Re;exports.FIRST_SCR_VERSE_NUM=Be;exports.LAST_SCR_BOOK_NUM=Me;exports.Mutex=ye;exports.MutexMap=pt;exports.NonValidatingDocumentCombiner=dt;exports.NumberFormat=mt;exports.PlatformEventEmitter=pe;exports.UnsubscriberAsyncList=gt;exports.aggregateUnsubscriberAsyncs=er;exports.aggregateUnsubscribers=Yt;exports.at=qt;exports.charAt=$;exports.codePointAt=zt;exports.createSyncProxyForAsyncObject=ft;exports.debounce=nt;exports.deepClone=A;exports.deepEqual=qe;exports.deserialize=_e;exports.endsWith=Ce;exports.formatReplacementString=Jt;exports.getAllObjectFunctionNames=ct;exports.getChaptersForBook=xe;exports.getCurrentLocale=Ir;exports.getErrorMessage=ut;exports.getLocalizedIdFromBookNumber=Qt;exports.groupBy=it;exports.htmlEncode=Cr;exports.includes=Ie;exports.indexOf=P;exports.isLocalizeKey=Ut;exports.isSerializable=jr;exports.isString=de;exports.isSubset=ze;exports.lastIndexOf=Ae;exports.localizedStringsDocumentSchema=Ke;exports.menuDocumentSchema=Xe;exports.newGuid=st;exports.normalize=Gt;exports.offsetBook=Ht;exports.offsetChapter=Wt;exports.offsetVerse=Zt;exports.ordinalCompare=Ft;exports.padEnd=Kt;exports.padStart=Xt;exports.projectSettingsDocumentSchema=Je;exports.serialize=J;exports.settingsDocumentSchema=Ge;exports.slice=Lt;exports.split=_;exports.startsWith=L;exports.stringLength=g;exports.substring=w;exports.toArray=Pe;exports.wait=me;exports.waitForDuration=lt; +"use strict";var Qe=Object.defineProperty;var Ye=(t,e,r)=>e in t?Qe(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r;var d=(t,e,r)=>(Ye(t,typeof e!="symbol"?e+"":e,r),r);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const et=require("async-mutex");class tt{constructor(e,r=1e4){d(this,"variableName");d(this,"promiseToValue");d(this,"resolver");d(this,"rejecter");this.variableName=e,this.promiseToValue=new Promise((s,n)=>{this.resolver=s,this.rejecter=n}),r>0&&setTimeout(()=>{this.rejecter&&(this.rejecter(`Timeout reached when waiting for ${this.variableName} to settle`),this.complete())},r),Object.seal(this)}get promise(){return this.promiseToValue}get hasSettled(){return Object.isFrozen(this)}resolveToValue(e,r=!1){if(this.resolver)console.debug(`${this.variableName} is being resolved now`),this.resolver(e),this.complete();else{if(r)throw Error(`${this.variableName} was already settled`);console.debug(`Ignoring subsequent resolution of ${this.variableName}`)}}rejectWithReason(e,r=!1){if(this.rejecter)console.debug(`${this.variableName} is being rejected now`),this.rejecter(e),this.complete();else{if(r)throw Error(`${this.variableName} was already settled`);console.debug(`Ignoring subsequent rejection of ${this.variableName}`)}}complete(){this.resolver=void 0,this.rejecter=void 0,Object.freeze(this)}}class rt{constructor(e,r){d(this,"collator");this.collator=new Intl.Collator(e,r)}compare(e,r){return this.collator.compare(e,r)}resolvedOptions(){return this.collator.resolvedOptions()}}class he{constructor(e,r){d(this,"dateTimeFormatter");this.dateTimeFormatter=new Intl.DateTimeFormat(e,r)}format(e){return this.dateTimeFormatter.format(e)}formatRange(e,r){return this.dateTimeFormatter.formatRange(e,r)}formatRangeToParts(e,r){return this.dateTimeFormatter.formatRangeToParts(e,r)}formatToParts(e){return this.dateTimeFormatter.formatToParts(e)}resolvedOptions(){return this.dateTimeFormatter.resolvedOptions()}}class pe{constructor(){d(this,"subscribe",this.event);d(this,"subscriptions");d(this,"lazyEvent");d(this,"isDisposed",!1);d(this,"dispose",()=>this.disposeFn());d(this,"emit",e=>{this.emitFn(e)})}get event(){return this.assertNotDisposed(),this.lazyEvent||(this.lazyEvent=e=>{if(!e||typeof e!="function")throw new Error("Event handler callback must be a function!");return this.subscriptions||(this.subscriptions=[]),this.subscriptions.push(e),()=>{if(!this.subscriptions)return!1;const r=this.subscriptions.indexOf(e);return r<0?!1:(this.subscriptions.splice(r,1),!0)}}),this.lazyEvent}emitFn(e){var r;this.assertNotDisposed(),(r=this.subscriptions)==null||r.forEach(s=>s(e))}assertNotDisposed(){if(this.isDisposed)throw new Error("Emitter is disposed")}disposeFn(){return this.assertNotDisposed(),this.isDisposed=!0,this.subscriptions=void 0,this.lazyEvent=void 0,Promise.resolve(!0)}}function st(){return"00-0-4-1-000".replace(/[^-]/g,t=>((Math.random()+~~t)*65536>>t).toString(16).padStart(4,"0"))}function de(t){return typeof t=="string"||t instanceof String}function A(t){return JSON.parse(JSON.stringify(t))}function nt(t,e=300){if(de(t))throw new Error("Tried to debounce a string! Could be XSS");let r;return(...s)=>{clearTimeout(r),r=setTimeout(()=>t(...s),e)}}function it(t,e,r){const s=new Map;return t.forEach(n=>{const i=e(n),o=s.get(i),a=r?r(n,i):n;o?o.push(a):s.set(i,[a])}),s}function ot(t){return typeof t=="object"&&t!==null&&"message"in t&&typeof t.message=="string"}function at(t){if(ot(t))return t;try{return new Error(JSON.stringify(t))}catch{return new Error(String(t))}}function ut(t){return at(t).message}function me(t){return new Promise(e=>setTimeout(e,t))}function lt(t,e){const r=me(e).then(()=>{});return Promise.any([r,t()])}function ct(t,e="obj"){const r=new Set;Object.getOwnPropertyNames(t).forEach(n=>{try{typeof t[n]=="function"&&r.add(n)}catch(i){console.debug(`Skipping ${n} on ${e} due to error: ${i}`)}});let s=Object.getPrototypeOf(t);for(;s&&Object.getPrototypeOf(s);)Object.getOwnPropertyNames(s).forEach(n=>{try{typeof t[n]=="function"&&r.add(n)}catch(i){console.debug(`Skipping ${n} on ${e}'s prototype due to error: ${i}`)}}),s=Object.getPrototypeOf(s);return r}function ft(t,e={}){return new Proxy(e,{get(r,s){return s in r?r[s]:async(...n)=>(await t())[s](...n)}})}class ge{constructor(e,r){d(this,"baseDocument");d(this,"contributions",new Map);d(this,"latestOutput");d(this,"options");d(this,"onDidRebuildEmitter",new pe);d(this,"onDidRebuild",this.onDidRebuildEmitter.subscribe);this.baseDocument=e,this.options=r,this.updateBaseDocument(e)}updateBaseDocument(e){return this.validateBaseDocument(e),this.baseDocument=this.options.copyDocuments?A(e):e,this.baseDocument=this.transformBaseDocumentAfterValidation(this.baseDocument),this.rebuild()}addOrUpdateContribution(e,r){this.validateContribution(e,r);const s=this.contributions.get(e);let n=this.options.copyDocuments&&r?A(r):r;n=this.transformContributionAfterValidation(e,n),this.contributions.set(e,n);try{return this.rebuild()}catch(i){throw s?this.contributions.set(e,s):this.contributions.delete(e),new Error(`Error when setting the document named ${e}: ${i}`)}}deleteContribution(e){const r=this.contributions.get(e);if(!r)throw new Error(`${e} does not exist`);this.contributions.delete(e);try{return this.rebuild()}catch(s){throw this.contributions.set(e,r),new Error(`Error when deleting the document named ${e}: ${s}`)}}deleteAllContributions(){if(this.contributions.size<=0)return this.latestOutput;const e=[...this.contributions.entries()];e.forEach(([r])=>this.contributions.delete(r));try{return this.rebuild()}catch(r){throw e.forEach(([s,n])=>this.contributions.set(s,n)),new Error(`Error when deleting all contributions: ${r}`)}}rebuild(){if(this.contributions.size===0){let r=A(this.baseDocument);return r=this.transformFinalOutputBeforeValidation(r),this.validateOutput(r),this.latestOutput=r,this.onDidRebuildEmitter.emit(void 0),this.latestOutput}let e=this.baseDocument;return this.contributions.forEach(r=>{e=ht(e,r,this.options.ignoreDuplicateProperties),this.validateOutput(e)}),e=this.transformFinalOutputBeforeValidation(e),this.validateOutput(e),this.latestOutput=e,this.onDidRebuildEmitter.emit(void 0),this.latestOutput}transformBaseDocumentAfterValidation(e){return e}transformContributionAfterValidation(e,r){return r}validateBaseDocument(e){}validateContribution(e,r){}validateOutput(e){}transformFinalOutputBeforeValidation(e){return e}}function Q(...t){let e=!0;return t.forEach(r=>{(!r||typeof r!="object"||Array.isArray(r))&&(e=!1)}),e}function Y(...t){let e=!0;return t.forEach(r=>{(!r||typeof r!="object"||!Array.isArray(r))&&(e=!1)}),e}function ht(t,e,r){const s=A(t);return e?be(s,A(e),r):s}function be(t,e,r){if(!e)return t;if(Q(t,e)){const s=t,n=e;Object.keys(n).forEach(i=>{if(Object.hasOwn(s,i)){if(Q(s[i],n[i]))s[i]=be(s[i],n[i],r);else if(Y(s[i],n[i]))s[i]=s[i].concat(n[i]);else if(!r)throw new Error(`Cannot merge objects: key "${i}" already exists in the target object`)}else s[i]=n[i]})}else Y(t,e)&&t.push(...e);return t}class ye extends et.Mutex{}class pt{constructor(){d(this,"mutexesByID",new Map)}get(e){let r=this.mutexesByID.get(e);return r||(r=new ye,this.mutexesByID.set(e,r),r)}}class dt extends ge{constructor(e,r){super(e,r)}get output(){return this.latestOutput}}class mt{constructor(e,r){d(this,"numberFormatter");this.numberFormatter=new Intl.NumberFormat(e,r)}format(e){return this.numberFormatter.format(e)}formatRange(e,r){return this.numberFormatter.formatRange(e,r)}formatRangeToParts(e,r){return this.numberFormatter.formatRangeToParts(e,r)}formatToParts(e){return this.numberFormatter.formatToParts(e)}resolvedOptions(){return this.numberFormatter.resolvedOptions()}}class gt{constructor(e="Anonymous"){d(this,"unsubscribers",new Set);this.name=e}add(...e){e.forEach(r=>{"dispose"in r?this.unsubscribers.add(r.dispose):this.unsubscribers.add(r)})}async runAllUnsubscribers(){const e=[...this.unsubscribers].map(s=>s()),r=await Promise.all(e);return this.unsubscribers.clear(),r.every((s,n)=>(s||console.error(`UnsubscriberAsyncList ${this.name}: Unsubscriber at index ${n} failed!`),s))}}var bt=Object.defineProperty,yt=(t,e,r)=>e in t?bt(t,e,{enumerable:!0,configurable:!0,writable:!0,value:r}):t[e]=r,l=(t,e,r)=>(yt(t,typeof e!="symbol"?e+"":e,r),r);const j=["GEN","EXO","LEV","NUM","DEU","JOS","JDG","RUT","1SA","2SA","1KI","2KI","1CH","2CH","EZR","NEH","EST","JOB","PSA","PRO","ECC","SNG","ISA","JER","LAM","EZK","DAN","HOS","JOL","AMO","OBA","JON","MIC","NAM","HAB","ZEP","HAG","ZEC","MAL","MAT","MRK","LUK","JHN","ACT","ROM","1CO","2CO","GAL","EPH","PHP","COL","1TH","2TH","1TI","2TI","TIT","PHM","HEB","JAS","1PE","2PE","1JN","2JN","3JN","JUD","REV","TOB","JDT","ESG","WIS","SIR","BAR","LJE","S3Y","SUS","BEL","1MA","2MA","3MA","4MA","1ES","2ES","MAN","PS2","ODA","PSS","JSA","JDB","TBS","SST","DNT","BLT","XXA","XXB","XXC","XXD","XXE","XXF","XXG","FRT","BAK","OTH","3ES","EZA","5EZ","6EZ","INT","CNC","GLO","TDX","NDX","DAG","PS3","2BA","LBA","JUB","ENO","1MQ","2MQ","3MQ","REP","4BA","LAO"],G=["XXA","XXB","XXC","XXD","XXE","XXF","XXG","FRT","BAK","OTH","INT","CNC","GLO","TDX","NDX"],ve=["Genesis","Exodus","Leviticus","Numbers","Deuteronomy","Joshua","Judges","Ruth","1 Samuel","2 Samuel","1 Kings","2 Kings","1 Chronicles","2 Chronicles","Ezra","Nehemiah","Esther (Hebrew)","Job","Psalms","Proverbs","Ecclesiastes","Song of Songs","Isaiah","Jeremiah","Lamentations","Ezekiel","Daniel (Hebrew)","Hosea","Joel","Amos","Obadiah","Jonah","Micah","Nahum","Habakkuk","Zephaniah","Haggai","Zechariah","Malachi","Matthew","Mark","Luke","John","Acts","Romans","1 Corinthians","2 Corinthians","Galatians","Ephesians","Philippians","Colossians","1 Thessalonians","2 Thessalonians","1 Timothy","2 Timothy","Titus","Philemon","Hebrews","James","1 Peter","2 Peter","1 John","2 John","3 John","Jude","Revelation","Tobit","Judith","Esther Greek","Wisdom of Solomon","Sirach (Ecclesiasticus)","Baruch","Letter of Jeremiah","Song of 3 Young Men","Susanna","Bel and the Dragon","1 Maccabees","2 Maccabees","3 Maccabees","4 Maccabees","1 Esdras (Greek)","2 Esdras (Latin)","Prayer of Manasseh","Psalm 151","Odes","Psalms of Solomon","Joshua A. *obsolete*","Judges B. *obsolete*","Tobit S. *obsolete*","Susanna Th. *obsolete*","Daniel Th. *obsolete*","Bel Th. *obsolete*","Extra A","Extra B","Extra C","Extra D","Extra E","Extra F","Extra G","Front Matter","Back Matter","Other Matter","3 Ezra *obsolete*","Apocalypse of Ezra","5 Ezra (Latin Prologue)","6 Ezra (Latin Epilogue)","Introduction","Concordance ","Glossary ","Topical Index","Names Index","Daniel Greek","Psalms 152-155","2 Baruch (Apocalypse)","Letter of Baruch","Jubilees","Enoch","1 Meqabyan","2 Meqabyan","3 Meqabyan","Reproof (Proverbs 25-31)","4 Baruch (Rest of Baruch)","Laodiceans"],ee=It();function T(t,e=!0){return e&&(t=t.toUpperCase()),t in ee?ee[t]:0}function F(t){return T(t)>0}function vt(t){const e=typeof t=="string"?T(t):t;return e>=40&&e<=66}function Nt(t){return(typeof t=="string"?T(t):t)<=39}function Ne(t){return t<=66}function wt(t){const e=typeof t=="string"?T(t):t;return Se(e)&&!Ne(e)}function*Et(){for(let t=1;t<=j.length;t++)yield t}const St=1,we=j.length;function Ot(){return["XXA","XXB","XXC","XXD","XXE","XXF","XXG"]}function K(t,e="***"){const r=t-1;return r<0||r>=j.length?e:j[r]}function Ee(t){return t<=0||t>we?"******":ve[t-1]}function $t(t){return Ee(T(t))}function Se(t){const e=typeof t=="number"?K(t):t;return F(e)&&!G.includes(e)}function jt(t){const e=typeof t=="number"?K(t):t;return F(e)&&G.includes(e)}function Ct(t){return ve[t-1].includes("*obsolete*")}function It(){const t={};for(let e=0;e(t[t.Unknown=0]="Unknown",t[t.Original=1]="Original",t[t.Septuagint=2]="Septuagint",t[t.Vulgate=3]="Vulgate",t[t.English=4]="English",t[t.RussianProtestant=5]="RussianProtestant",t[t.RussianOrthodox=6]="RussianOrthodox",t))(S||{});const N=class{constructor(e){if(l(this,"name"),l(this,"fullPath"),l(this,"isPresent"),l(this,"hasVerseSegments"),l(this,"isCustomized"),l(this,"baseVersification"),l(this,"scriptureBooks"),l(this,"_type"),e!=null)typeof e=="string"?this.name=e:this._type=e;else throw new Error("Argument null")}get type(){return this._type}equals(e){return!e.type||!this.type?!1:e.type===this.type}};l(N,"Original",new N(S.Original)),l(N,"Septuagint",new N(S.Septuagint)),l(N,"Vulgate",new N(S.Vulgate)),l(N,"English",new N(S.English)),l(N,"RussianProtestant",new N(S.RussianProtestant)),l(N,"RussianOrthodox",new N(S.RussianOrthodox));let I=N;function te(t,e){const r=e[0];for(let s=1;s(t[t.Valid=0]="Valid",t[t.UnknownVersification=1]="UnknownVersification",t[t.OutOfRange=2]="OutOfRange",t[t.VerseOutOfOrder=3]="VerseOutOfOrder",t[t.VerseRepeated=4]="VerseRepeated",t))(Oe||{});const y=class f{constructor(e,r,s,n){if(l(this,"firstChapter"),l(this,"lastChapter"),l(this,"lastVerse"),l(this,"hasSegmentsDefined"),l(this,"text"),l(this,"BBBCCCVVVS"),l(this,"longHashCode"),l(this,"versification"),l(this,"rtlMark","‏"),l(this,"_bookNum",0),l(this,"_chapterNum",0),l(this,"_verseNum",0),l(this,"_verse"),s==null&&n==null)if(e!=null&&typeof e=="string"){const i=e,o=r!=null&&r instanceof I?r:void 0;this.setEmpty(o),this.parse(i)}else if(e!=null&&typeof e=="number"){const i=r!=null&&r instanceof I?r:void 0;this.setEmpty(i),this._verseNum=e%f.chapterDigitShifter,this._chapterNum=Math.floor(e%f.bookDigitShifter/f.chapterDigitShifter),this._bookNum=Math.floor(e/f.bookDigitShifter)}else if(r==null)if(e!=null&&e instanceof f){const i=e;this._bookNum=i.bookNum,this._chapterNum=i.chapterNum,this._verseNum=i.verseNum,this._verse=i.verse,this.versification=i.versification}else{if(e==null)return;const i=e instanceof I?e:f.defaultVersification;this.setEmpty(i)}else throw new Error("VerseRef constructor not supported.");else if(e!=null&&r!=null&&s!=null)if(typeof e=="string"&&typeof r=="string"&&typeof s=="string")this.setEmpty(n),this.updateInternal(e,r,s);else if(typeof e=="number"&&typeof r=="number"&&typeof s=="number")this._bookNum=e,this._chapterNum=r,this._verseNum=s,this.versification=n??f.defaultVersification;else throw new Error("VerseRef constructor not supported.");else throw new Error("VerseRef constructor not supported.")}static parse(e,r=f.defaultVersification){const s=new f(r);return s.parse(e),s}static isVerseParseable(e){return e.length>0&&"0123456789".includes(e[0])&&!e.endsWith(this.verseRangeSeparator)&&!e.endsWith(this.verseSequenceIndicator)}static tryParse(e){let r;try{return r=f.parse(e),{success:!0,verseRef:r}}catch(s){if(s instanceof R)return r=new f,{success:!1,verseRef:r};throw s}}static getBBBCCCVVV(e,r,s){return e%f.bcvMaxValue*f.bookDigitShifter+(r>=0?r%f.bcvMaxValue*f.chapterDigitShifter:0)+(s>=0?s%f.bcvMaxValue:0)}static tryGetVerseNum(e){let r;if(!e)return r=-1,{success:!0,vNum:r};r=0;let s;for(let n=0;n"9")return n===0&&(r=-1),{success:!1,vNum:r};if(r=r*10+ +s-+"0",r>f.bcvMaxValue)return r=-1,{success:!1,vNum:r}}return{success:!0,vNum:r}}get isDefault(){return this.bookNum===0&&this.chapterNum===0&&this.verseNum===0&&this.versification==null}get hasMultiple(){return this._verse!=null&&(this._verse.includes(f.verseRangeSeparator)||this._verse.includes(f.verseSequenceIndicator))}get book(){return E.bookNumberToId(this.bookNum,"")}set book(e){this.bookNum=E.bookIdToNumber(e)}get chapter(){return this.isDefault||this._chapterNum<0?"":this._chapterNum.toString()}set chapter(e){const r=+e;this._chapterNum=Number.isInteger(r)?r:-1}get verse(){return this._verse!=null?this._verse:this.isDefault||this._verseNum<0?"":this._verseNum.toString()}set verse(e){const{success:r,vNum:s}=f.tryGetVerseNum(e);this._verse=r?void 0:e.replace(this.rtlMark,""),this._verseNum=s,!(this._verseNum>=0)&&({vNum:this._verseNum}=f.tryGetVerseNum(this._verse))}get bookNum(){return this._bookNum}set bookNum(e){if(e<=0||e>E.lastBook)throw new R("BookNum must be greater than zero and less than or equal to last book");this._bookNum=e}get chapterNum(){return this._chapterNum}set chapterNum(e){this.chapterNum=e}get verseNum(){return this._verseNum}set verseNum(e){this._verseNum=e}get versificationStr(){var e;return(e=this.versification)==null?void 0:e.name}set versificationStr(e){this.versification=this.versification!=null?new I(e):void 0}get valid(){return this.validStatus===0}get validStatus(){return this.validateVerse(f.verseRangeSeparators,f.verseSequenceIndicators)}get BBBCCC(){return f.getBBBCCCVVV(this._bookNum,this._chapterNum,0)}get BBBCCCVVV(){return f.getBBBCCCVVV(this._bookNum,this._chapterNum,this._verseNum)}get isExcluded(){return!1}parse(e){if(e=e.replace(this.rtlMark,""),e.includes("/")){const i=e.split("/");if(e=i[0],i.length>1)try{const o=+i[1].trim();this.versification=new I(S[o])}catch{throw new R("Invalid reference : "+e)}}const r=e.trim().split(" ");if(r.length!==2)throw new R("Invalid reference : "+e);const s=r[1].split(":"),n=+s[0];if(s.length!==2||E.bookIdToNumber(r[0])===0||!Number.isInteger(n)||n<0||!f.isVerseParseable(s[1]))throw new R("Invalid reference : "+e);this.updateInternal(r[0],s[0],s[1])}simplify(){this._verse=void 0}clone(){return new f(this)}toString(){const e=this.book;return e===""?"":`${e} ${this.chapter}:${this.verse}`}equals(e){return e instanceof f?e._bookNum===this._bookNum&&e._chapterNum===this._chapterNum&&e._verseNum===this._verseNum&&e.verse===this.verse&&e.versification!=null&&this.versification!=null&&e.versification.equals(this.versification):!1}allVerses(e=!1,r=f.verseRangeSeparators,s=f.verseSequenceIndicators){if(this._verse==null||this.chapterNum<=0)return[this.clone()];const n=[],i=te(this._verse,s);for(const o of i.map(a=>te(a,r))){const a=this.clone();a.verse=o[0];const h=a.verseNum;if(n.push(a),o.length>1){const p=this.clone();if(p.verse=o[1],!e)for(let u=h+1;uo)return 3;if(s===o)return 4;s=o}return 0}get internalValid(){return this.versification==null?1:this._bookNum<=0||this._bookNum>E.lastBook?2:(E.isCanonical(this._bookNum),0)}setEmpty(e=f.defaultVersification){this._bookNum=0,this._chapterNum=-1,this._verse=void 0,this.versification=e}updateInternal(e,r,s){this.bookNum=E.bookIdToNumber(e),this.chapter=r,this.verse=s}};l(y,"defaultVersification",I.English),l(y,"verseRangeSeparator","-"),l(y,"verseSequenceIndicator",","),l(y,"verseRangeSeparators",[y.verseRangeSeparator]),l(y,"verseSequenceIndicators",[y.verseSequenceIndicator]),l(y,"chapterDigitShifter",1e3),l(y,"bookDigitShifter",y.chapterDigitShifter*y.chapterDigitShifter),l(y,"bcvMaxValue",y.chapterDigitShifter-1),l(y,"ValidStatusType",Oe);class R extends Error{}var re=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},C={},At=()=>{const t="\\ud800-\\udfff",e="\\u0300-\\u036f",r="\\ufe20-\\ufe2f",s="\\u20d0-\\u20ff",n="\\u1ab0-\\u1aff",i="\\u1dc0-\\u1dff",o=e+r+s+n+i,a="\\ufe0e\\ufe0f",h="\\uD83D\\uDC69\\uD83C\\uDFFB\\u200D\\uD83C\\uDF93",p=`[${t}]`,u=`[${o}]`,c="\\ud83c[\\udffb-\\udfff]",m=`(?:${u}|${c})`,v=`[^${t}]`,b="(?:\\uD83C[\\uDDE6-\\uDDFF]){2}",M="[\\ud800-\\udbff][\\udc00-\\udfff]",q="\\u200d",Le="(?:\\ud83c\\udff4\\udb40\\udc67\\udb40\\udc62\\udb40(?:\\udc65|\\udc73|\\udc77)\\udb40(?:\\udc6e|\\udc63|\\udc6c)\\udb40(?:\\udc67|\\udc74|\\udc73)\\udb40\\udc7f)",Ue=`[${h}]`,W=`${m}?`,Z=`[${a}]?`,He=`(?:${q}(?:${[v,b,M].join("|")})${Z+W})*`,We=Z+W+He,Ze=`(?:${[`${v}${u}?`,u,b,M,p,Ue].join("|")})`;return new RegExp(`${Le}|${c}(?=${c})|${Ze+We}`,"g")},Pt=re&&re.__importDefault||function(t){return t&&t.__esModule?t:{default:t}};Object.defineProperty(C,"__esModule",{value:!0});var k=Pt(At);function z(t){if(typeof t!="string")throw new Error("A string is expected as input");return t.match(k.default())||[]}var Tt=C.toArray=z;function X(t){if(typeof t!="string")throw new Error("Input must be a string");var e=t.match(k.default());return e===null?0:e.length}var Dt=C.length=X;function $e(t,e,r){if(e===void 0&&(e=0),typeof t!="string")throw new Error("Input must be a string");(typeof e!="number"||e<0)&&(e=0),typeof r=="number"&&r<0&&(r=0);var s=t.match(k.default());return s?s.slice(e,r).join(""):""}var Mt=C.substring=$e;function Rt(t,e,r){if(e===void 0&&(e=0),typeof t!="string")throw new Error("Input must be a string");var s=X(t);if(typeof e!="number"&&(e=parseInt(e,10)),e>=s)return"";e<0&&(e+=s);var n;typeof r>"u"?n=s:(typeof r!="number"&&(r=parseInt(r,10)),n=r>=0?r+e:e);var i=t.match(k.default());return i?i.slice(e,n).join(""):""}var Bt=C.substr=Rt;function xt(t,e,r,s){if(e===void 0&&(e=16),r===void 0&&(r="#"),s===void 0&&(s="right"),typeof t!="string"||typeof e!="number")throw new Error("Invalid arguments specified");if(["left","right"].indexOf(s)===-1)throw new Error("Pad position should be either left or right");typeof r!="string"&&(r=String(r));var n=X(t);if(n>e)return $e(t,0,e);if(n=s.length)return e===""?s.length:-1;if(e==="")return r;var n=z(e),i=!1,o;for(o=r;og(t)||e<-g(t)))return V(t,e,1)}function $(t,e){return e<0||e>g(t)-1?"":V(t,e,1)}function zt(t,e){if(!(e<0||e>g(t)-1))return V(t,e,1).codePointAt(0)}function Ce(t,e,r=g(t)){const s=Ae(t,e);return!(s===-1||s+g(e)!==r)}function _t(t,e,r){if(e<0)return-1;if(r){if($(t,e)==="}"&&$(t,e-1)==="\\")return e;const i=P(t,"\\}",e);return i>=0?i+1:i}let s=e;const n=g(t);for(;s=n?-1:s}function Jt(t,e){let r=t,s=0;for(;s=0){const i=w(r,s+1,n),o=i in e?e[i]:i;r=`${w(r,0,s)}${o}${w(r,n+1)}`,s=n+g(o)-g(i)-2}}else r=`${w(r,0,s-1)}${w(r,s)}`,s-=1;break;case"}":$(r,s-1)!=="\\"||(r=`${w(r,0,s-1)}${w(r,s)}`,s-=1);break}s+=1}return r}function Ie(t,e,r=0){const s=w(t,r);return P(s,e)!==-1}function P(t,e,r=0){return Vt(t,e,r)}function Ae(t,e,r){let s=r===void 0?g(t):r;s<0?s=0:s>=g(t)&&(s=g(t)-1);for(let n=s;n>=0;n--)if(V(t,n,g(e))===e)return n;return-1}function g(t){return Dt(t)}function Gt(t,e){const r=e.toUpperCase();return r==="NONE"?t:t.normalize(r)}function Ft(t,e,r){return t.localeCompare(e,"en",r)}function Kt(t,e,r=" "){return e<=g(t)?t:je(t,e,r,"right")}function Xt(t,e,r=" "){return e<=g(t)?t:je(t,e,r,"left")}function se(t,e){return e>t?t:e<-t?0:e<0?e+t:e}function Lt(t,e,r){const s=g(t);if(e>s||r&&(e>r&&!(e>=0&&e-s)||r<-s))return"";const n=se(s,e),i=r?se(s,r):void 0;return w(t,n,i)}function _(t,e,r){const s=[];if(r!==void 0&&r<=0)return[t];if(e==="")return Pe(t).slice(0,r);let n=e;(typeof e=="string"||e instanceof RegExp&&!Ie(e.flags,"g"))&&(n=new RegExp(e,"g"));const i=t.match(n);let o=0;if(!i)return[t];for(let a=0;a<(r?r-1:i.length);a++){const h=P(t,i[a],o),p=g(i[a]);if(s.push(w(t,o,h)),o=h+p,r!==void 0&&s.length===r)break}return s.push(w(t,o)),s}function L(t,e,r=0){return P(t,e,r)===r}function V(t,e=0,r=g(t)-e){return Bt(t,e,r)}function w(t,e,r=g(t)){return Mt(t,e,r)}function Pe(t){return Tt(t)}function Ut(t){return L(t,"%")&&Ce(t,"%")}function Ht(t){if(typeof t!="string")throw new TypeError("Expected a string");return t.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&").replace(/-/g,"\\x2d")}const Te=[{shortName:"ERR",fullNames:["ERROR"],chapters:-1},{shortName:"GEN",fullNames:["Genesis"],chapters:50},{shortName:"EXO",fullNames:["Exodus"],chapters:40},{shortName:"LEV",fullNames:["Leviticus"],chapters:27},{shortName:"NUM",fullNames:["Numbers"],chapters:36},{shortName:"DEU",fullNames:["Deuteronomy"],chapters:34},{shortName:"JOS",fullNames:["Joshua"],chapters:24},{shortName:"JDG",fullNames:["Judges"],chapters:21},{shortName:"RUT",fullNames:["Ruth"],chapters:4},{shortName:"1SA",fullNames:["1 Samuel"],chapters:31},{shortName:"2SA",fullNames:["2 Samuel"],chapters:24},{shortName:"1KI",fullNames:["1 Kings"],chapters:22},{shortName:"2KI",fullNames:["2 Kings"],chapters:25},{shortName:"1CH",fullNames:["1 Chronicles"],chapters:29},{shortName:"2CH",fullNames:["2 Chronicles"],chapters:36},{shortName:"EZR",fullNames:["Ezra"],chapters:10},{shortName:"NEH",fullNames:["Nehemiah"],chapters:13},{shortName:"EST",fullNames:["Esther"],chapters:10},{shortName:"JOB",fullNames:["Job"],chapters:42},{shortName:"PSA",fullNames:["Psalm","Psalms"],chapters:150},{shortName:"PRO",fullNames:["Proverbs"],chapters:31},{shortName:"ECC",fullNames:["Ecclesiastes"],chapters:12},{shortName:"SNG",fullNames:["Song of Solomon","Song of Songs"],chapters:8},{shortName:"ISA",fullNames:["Isaiah"],chapters:66},{shortName:"JER",fullNames:["Jeremiah"],chapters:52},{shortName:"LAM",fullNames:["Lamentations"],chapters:5},{shortName:"EZK",fullNames:["Ezekiel"],chapters:48},{shortName:"DAN",fullNames:["Daniel"],chapters:12},{shortName:"HOS",fullNames:["Hosea"],chapters:14},{shortName:"JOL",fullNames:["Joel"],chapters:3},{shortName:"AMO",fullNames:["Amos"],chapters:9},{shortName:"OBA",fullNames:["Obadiah"],chapters:1},{shortName:"JON",fullNames:["Jonah"],chapters:4},{shortName:"MIC",fullNames:["Micah"],chapters:7},{shortName:"NAM",fullNames:["Nahum"],chapters:3},{shortName:"HAB",fullNames:["Habakkuk"],chapters:3},{shortName:"ZEP",fullNames:["Zephaniah"],chapters:3},{shortName:"HAG",fullNames:["Haggai"],chapters:2},{shortName:"ZEC",fullNames:["Zechariah"],chapters:14},{shortName:"MAL",fullNames:["Malachi"],chapters:4},{shortName:"MAT",fullNames:["Matthew"],chapters:28},{shortName:"MRK",fullNames:["Mark"],chapters:16},{shortName:"LUK",fullNames:["Luke"],chapters:24},{shortName:"JHN",fullNames:["John"],chapters:21},{shortName:"ACT",fullNames:["Acts"],chapters:28},{shortName:"ROM",fullNames:["Romans"],chapters:16},{shortName:"1CO",fullNames:["1 Corinthians"],chapters:16},{shortName:"2CO",fullNames:["2 Corinthians"],chapters:13},{shortName:"GAL",fullNames:["Galatians"],chapters:6},{shortName:"EPH",fullNames:["Ephesians"],chapters:6},{shortName:"PHP",fullNames:["Philippians"],chapters:4},{shortName:"COL",fullNames:["Colossians"],chapters:4},{shortName:"1TH",fullNames:["1 Thessalonians"],chapters:5},{shortName:"2TH",fullNames:["2 Thessalonians"],chapters:3},{shortName:"1TI",fullNames:["1 Timothy"],chapters:6},{shortName:"2TI",fullNames:["2 Timothy"],chapters:4},{shortName:"TIT",fullNames:["Titus"],chapters:3},{shortName:"PHM",fullNames:["Philemon"],chapters:1},{shortName:"HEB",fullNames:["Hebrews"],chapters:13},{shortName:"JAS",fullNames:["James"],chapters:5},{shortName:"1PE",fullNames:["1 Peter"],chapters:5},{shortName:"2PE",fullNames:["2 Peter"],chapters:3},{shortName:"1JN",fullNames:["1 John"],chapters:5},{shortName:"2JN",fullNames:["2 John"],chapters:1},{shortName:"3JN",fullNames:["3 John"],chapters:1},{shortName:"JUD",fullNames:["Jude"],chapters:1},{shortName:"REV",fullNames:["Revelation"],chapters:22}],De=1,Me=Te.length-1,Re=1,Be=1,xe=t=>{var e;return((e=Te[t])==null?void 0:e.chapters)??-1},Wt=(t,e)=>({bookNum:Math.max(De,Math.min(t.bookNum+e,Me)),chapterNum:1,verseNum:1}),Zt=(t,e)=>({...t,chapterNum:Math.min(Math.max(Re,t.chapterNum+e),xe(t.bookNum)),verseNum:1}),Qt=(t,e)=>({...t,verseNum:Math.max(Be,t.verseNum+e)});async function Yt(t,e,r){const s=E.bookNumberToId(t);if(!L(Intl.getCanonicalLocales(e)[0],"zh"))return r({localizeKey:`LocalizedId.${s}`,languagesToSearch:[e]});const n=await r({localizeKey:`Book.${s}`,languagesToSearch:[e]}),i=_(n,"-");return _(i[0],"ÿ08")[0].trim()}const er=t=>(...e)=>t.map(s=>s(...e)).every(s=>s),tr=t=>async(...e)=>{const r=t.map(async s=>s(...e));return(await Promise.all(r)).every(s=>s)};var rr=Object.getOwnPropertyNames,sr=Object.getOwnPropertySymbols,nr=Object.prototype.hasOwnProperty;function ne(t,e){return function(s,n,i){return t(s,n,i)&&e(s,n,i)}}function x(t){return function(r,s,n){if(!r||!s||typeof r!="object"||typeof s!="object")return t(r,s,n);var i=n.cache,o=i.get(r),a=i.get(s);if(o&&a)return o===s&&a===r;i.set(r,s),i.set(s,r);var h=t(r,s,n);return i.delete(r),i.delete(s),h}}function ie(t){return rr(t).concat(sr(t))}var ke=Object.hasOwn||function(t,e){return nr.call(t,e)};function D(t,e){return t||e?t===e:t===e||t!==t&&e!==e}var Ve="_owner",oe=Object.getOwnPropertyDescriptor,ae=Object.keys;function ir(t,e,r){var s=t.length;if(e.length!==s)return!1;for(;s-- >0;)if(!r.equals(t[s],e[s],s,s,t,e,r))return!1;return!0}function or(t,e){return D(t.getTime(),e.getTime())}function ue(t,e,r){if(t.size!==e.size)return!1;for(var s={},n=t.entries(),i=0,o,a;(o=n.next())&&!o.done;){for(var h=e.entries(),p=!1,u=0;(a=h.next())&&!a.done;){var c=o.value,m=c[0],v=c[1],b=a.value,M=b[0],q=b[1];!p&&!s[u]&&(p=r.equals(m,M,i,u,t,e,r)&&r.equals(v,q,m,M,t,e,r))&&(s[u]=!0),u++}if(!p)return!1;i++}return!0}function ar(t,e,r){var s=ae(t),n=s.length;if(ae(e).length!==n)return!1;for(var i;n-- >0;)if(i=s[n],i===Ve&&(t.$$typeof||e.$$typeof)&&t.$$typeof!==e.$$typeof||!ke(e,i)||!r.equals(t[i],e[i],i,i,t,e,r))return!1;return!0}function B(t,e,r){var s=ie(t),n=s.length;if(ie(e).length!==n)return!1;for(var i,o,a;n-- >0;)if(i=s[n],i===Ve&&(t.$$typeof||e.$$typeof)&&t.$$typeof!==e.$$typeof||!ke(e,i)||!r.equals(t[i],e[i],i,i,t,e,r)||(o=oe(t,i),a=oe(e,i),(o||a)&&(!o||!a||o.configurable!==a.configurable||o.enumerable!==a.enumerable||o.writable!==a.writable)))return!1;return!0}function ur(t,e){return D(t.valueOf(),e.valueOf())}function lr(t,e){return t.source===e.source&&t.flags===e.flags}function le(t,e,r){if(t.size!==e.size)return!1;for(var s={},n=t.values(),i,o;(i=n.next())&&!i.done;){for(var a=e.values(),h=!1,p=0;(o=a.next())&&!o.done;)!h&&!s[p]&&(h=r.equals(i.value,o.value,i.value,o.value,t,e,r))&&(s[p]=!0),p++;if(!h)return!1}return!0}function cr(t,e){var r=t.length;if(e.length!==r)return!1;for(;r-- >0;)if(t[r]!==e[r])return!1;return!0}var fr="[object Arguments]",hr="[object Boolean]",pr="[object Date]",dr="[object Map]",mr="[object Number]",gr="[object Object]",br="[object RegExp]",yr="[object Set]",vr="[object String]",Nr=Array.isArray,ce=typeof ArrayBuffer=="function"&&ArrayBuffer.isView?ArrayBuffer.isView:null,fe=Object.assign,wr=Object.prototype.toString.call.bind(Object.prototype.toString);function Er(t){var e=t.areArraysEqual,r=t.areDatesEqual,s=t.areMapsEqual,n=t.areObjectsEqual,i=t.arePrimitiveWrappersEqual,o=t.areRegExpsEqual,a=t.areSetsEqual,h=t.areTypedArraysEqual;return function(u,c,m){if(u===c)return!0;if(u==null||c==null||typeof u!="object"||typeof c!="object")return u!==u&&c!==c;var v=u.constructor;if(v!==c.constructor)return!1;if(v===Object)return n(u,c,m);if(Nr(u))return e(u,c,m);if(ce!=null&&ce(u))return h(u,c,m);if(v===Date)return r(u,c,m);if(v===RegExp)return o(u,c,m);if(v===Map)return s(u,c,m);if(v===Set)return a(u,c,m);var b=wr(u);return b===pr?r(u,c,m):b===br?o(u,c,m):b===dr?s(u,c,m):b===yr?a(u,c,m):b===gr?typeof u.then!="function"&&typeof c.then!="function"&&n(u,c,m):b===fr?n(u,c,m):b===hr||b===mr||b===vr?i(u,c,m):!1}}function Sr(t){var e=t.circular,r=t.createCustomConfig,s=t.strict,n={areArraysEqual:s?B:ir,areDatesEqual:or,areMapsEqual:s?ne(ue,B):ue,areObjectsEqual:s?B:ar,arePrimitiveWrappersEqual:ur,areRegExpsEqual:lr,areSetsEqual:s?ne(le,B):le,areTypedArraysEqual:s?B:cr};if(r&&(n=fe({},n,r(n))),e){var i=x(n.areArraysEqual),o=x(n.areMapsEqual),a=x(n.areObjectsEqual),h=x(n.areSetsEqual);n=fe({},n,{areArraysEqual:i,areMapsEqual:o,areObjectsEqual:a,areSetsEqual:h})}return n}function Or(t){return function(e,r,s,n,i,o,a){return t(e,r,a)}}function $r(t){var e=t.circular,r=t.comparator,s=t.createState,n=t.equals,i=t.strict;if(s)return function(h,p){var u=s(),c=u.cache,m=c===void 0?e?new WeakMap:void 0:c,v=u.meta;return r(h,p,{cache:m,equals:n,meta:v,strict:i})};if(e)return function(h,p){return r(h,p,{cache:new WeakMap,equals:n,meta:void 0,strict:i})};var o={cache:void 0,equals:n,meta:void 0,strict:i};return function(h,p){return r(h,p,o)}}var jr=O();O({strict:!0});O({circular:!0});O({circular:!0,strict:!0});O({createInternalComparator:function(){return D}});O({strict:!0,createInternalComparator:function(){return D}});O({circular:!0,createInternalComparator:function(){return D}});O({circular:!0,createInternalComparator:function(){return D},strict:!0});function O(t){t===void 0&&(t={});var e=t.circular,r=e===void 0?!1:e,s=t.createInternalComparator,n=t.createState,i=t.strict,o=i===void 0?!1:i,a=Sr(t),h=Er(a),p=s?s(h):Or(h);return $r({circular:r,comparator:h,createState:n,equals:p,strict:o})}function qe(t,e){return jr(t,e)}function ze(t,e){if(typeof t!=typeof e)return!1;if(!t&&!e)return!0;if(Array.isArray(t)){const i=e,o=t;return i.length===0?!0:i.every(a=>o.includes(a))}if(typeof t!="object")return qe(t,e);const r=e,s=t;let n=!0;return Object.keys(r).forEach(i=>{n&&(Object.hasOwn(s,i)&&ze(s[i],r[i])||(n=!1))}),n}function J(t,e,r){return JSON.stringify(t,(n,i)=>{let o=i;return e&&(o=e(n,o)),o===void 0&&(o=null),o},r)}function _e(t,e){function r(n){return Object.keys(n).forEach(i=>{n[i]===null?n[i]=void 0:typeof n[i]=="object"&&(n[i]=r(n[i]))}),n}const s=JSON.parse(t,e);if(s!==null)return typeof s=="object"?r(s):s}function Cr(t){try{const e=J(t);return e===J(_e(e))}catch{return!1}}const Ir=t=>t.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/");function Ar(){return typeof navigator<"u"&&navigator.languages?navigator.languages[0]:new he().resolvedOptions().locale}const U={projectSettingsContribution:{description:"The data an extension provides to inform Platform.Bible of the project settings it provides",anyOf:[{$ref:"#/$defs/projectSettingsGroup"},{type:"array",items:{$ref:"#/$defs/projectSettingsGroup"}}]},projectSettingsGroup:{description:"Group of related settings definitions",type:"object",properties:{label:{description:"localizeKey that displays in the project settings dialog as the group name",$ref:"#/$defs/localizeKey"},description:{description:"localizeKey that displays in the project settings dialog to describe the group",$ref:"#/$defs/localizeKey"},properties:{$ref:"#/$defs/projectSettingProperties"}},required:["label","properties"]},projectSettingProperties:{description:"Object whose keys are setting IDs and whose values are settings objects",type:"object",patternProperties:{"^[\\w\\-]+\\.[\\w\\-]+$":{$ref:"#/$defs/projectSetting"}},additionalProperties:!1},projectSetting:{description:"A description of an extension's setting entry",anyOf:[{$ref:"#/$defs/extensionControlledProjectSetting"}]},extensionControlledProjectSetting:{description:"Setting definition that is validated by the extension.",allOf:[{$ref:"#/$defs/projectSettingBase"},{$ref:"#/$defs/modifierExtensionControlled"}]},projectSettingBase:{description:"Base information needed to describe a project setting entry",allOf:[{$ref:"#/$defs/settingBase"},{$ref:"#/$defs/modifierProject"}]},modifierProject:{description:"Modifies setting type to be project setting",type:"object",properties:{includeProjectInterfaces:{description:"String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if they should be included.\n\nIf this is one string, it will be matched against `projectInterface`s. If this is an array, each entry is handled based on its type (at least one entry must match for this filter condition to pass):\n\n- If the entry is a string, it will be matched against each `projectInterface`. If any match, the project will pass this filter condition\n- If the entry is an array of strings, each will be matched against each `projectInterface`. If every string matches against at least one `projectInterface`, the project will pass this filter condition\n\nIn other words, each entry in the first-level array is `OR`'ed together. Each entry in second-level arrays (arrays within the first-level array) are `AND`'ed together.\n\nDefaults to all {@link ProjectInterfaces}, so all projects that do not match `excludeProjectInterfaces` will be included\n\n@example\n\n```typescript\nincludeProjectInterfaces: ['one', ['two', 'three']];\n```\n\nThis filter condition will succeed on projects whose `projectInterface`s fulfill at least one of the following conditions (At least one entry in the array must match):\n\n- Include `one`\n- Include both `two` and `three`.",anyOf:[{type:"null"},{type:"string"},{type:"array",items:{anyOf:[{type:"string"},{type:"array",items:{type:"string"}}]}}]},excludeProjectInterfaces:{description:"String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if they should absolutely not be included even if they match with `includeProjectInterfaces`.\n\nIf this is one string, it will be matched against `projectInterface`s. If this is an array, each entry is handled based on its type (at least one entry must match for this filter condition to exclude the project):\n\n- If the entry is a string, it will be matched against each `projectInterface`. If any match, the project will pass this filter condition and exclude the project\n- If the entry is an array of strings, each will be matched against each `projectInterface`. If every string matches against at least one `projectInterface`, the project will pass this filter condition and exclude the project\n\nIn other words, each entry in the first-level array is `OR`'ed together. Each entry in second-level arrays (arrays within the first-level array) are `AND`'ed together.\n\nDefaults to no {@link ProjectInterfaces}, so all projects that match `includeProjectInterfaces` will be included\n\n@example\n\n```typescript\nexcludeProjectInterfaces: ['one', ['two', 'three']];\n```\n\nThis filter condition will succeed and exclude projects whose `projectInterface`s fulfill at least one of the following conditions (At least one entry in the array must match):\n\n- Include `one`\n- Include both `two` and `three`.",anyOf:[{type:"null"},{type:"string"},{type:"array",items:{anyOf:[{type:"string"},{type:"array",items:{type:"string"}}]}}]},includePdpFactoryIds:{description:"String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory Ids that provided each project's metadata (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if the projects should be included.\n\nDefaults to all Project Data Provider Factory Ids, so all projects that do not match `excludePdpFactoryIds` will be included",anyOf:[{type:"null"},{type:"string"},{type:"array",items:{type:"string"}}]},excludePdpFactoryIds:{description:"String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory Ids that provided each project's metadata (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if the projects should absolutely not be included even if they match with `includeProjectInterfaces`.\n\nDefaults to none, so all projects that match `includePdpFactoryIds` will be included",anyOf:[{type:"null"},{type:"string"},{type:"array",items:{type:"string"}}]}}},settingsContribution:{description:"The data an extension provides to inform Platform.Bible of the settings it provides",anyOf:[{$ref:"#/$defs/settingsGroup"},{type:"array",items:{$ref:"#/$defs/settingsGroup"}}]},settingsGroup:{description:"Group of related settings definitions",type:"object",properties:{label:{description:"localizeKey that displays in the settings dialog as the group name",$ref:"#/$defs/localizeKey"},description:{description:"localizeKey that displays in the settings dialog to describe the group",$ref:"#/$defs/localizeKey"},properties:{$ref:"#/$defs/settingProperties"}},required:["label","properties"]},settingProperties:{description:"Object whose keys are setting IDs and whose values are settings objects",type:"object",patternProperties:{"^[\\w-]+\\.[\\w-]+$":{$ref:"#/$defs/setting"}},additionalProperties:!1},setting:{description:"A description of an extension's setting entry",anyOf:[{$ref:"#/$defs/extensionControlledSetting"}]},extensionControlledSetting:{description:"Setting definition that is validated by the extension.",allOf:[{$ref:"#/$defs/settingBase"},{$ref:"#/$defs/modifierExtensionControlled"}]},settingBase:{description:"Base information needed to describe a setting entry",allOf:[{$ref:"#/$defs/stateBase"},{type:"object",properties:{label:{description:"localizeKey that displays in the settings dialog as the setting name",$ref:"#/$defs/localizeKey"},description:{description:"localizeKey that displays in the settings dialog to describe the setting",$ref:"#/$defs/localizeKey"}},required:["label"]}]},projectStateContribution:{description:"The data an extension provides to inform Platform.Bible of the project state it provides",$ref:"#/$defs/userStateProperties"},userStateContribution:{description:"The data an extension provides to inform Platform.Bible of the user state it provides",$ref:"#/$defs/userStateProperties"},userStateProperties:{description:"Object whose keys are state IDs and whose values are state objects",type:"object",patternProperties:{"^[\\w\\-]+\\.[\\w\\-]+$":{$ref:"#/$defs/userState"}},additionalProperties:!1},userState:{description:"A description of an extension's user state entry",anyOf:[{$ref:"#/$defs/extensionControlledState"}]},extensionControlledState:{description:"State definition that is validated by the extension.",allOf:[{$ref:"#/$defs/stateBase"},{$ref:"#/$defs/modifierExtensionControlled"}]},modifierExtensionControlled:{description:'Modifies state/setting type to be extension-controlled. "Extension-controlled" means the extension provides the component and the validator for the state/setting, so the state/setting is controlled by the extension.',not:{anyOf:[{type:"object",required:["platformType"]},{type:"object",required:["type"]}]}},stateBase:{description:"Base information needed to describe a state entry",type:"object",properties:{default:{description:"default value for the state/setting",type:"any"},derivesFrom:{description:"a state/setting ID whose value to set to this state/setting's starting value the first time this state/setting is loaded",$ref:"#/$defs/id"}},required:["default"]},localizeKey:{description:"Identifier for a string that will be localized based on the user's UI language",type:"string",pattern:"^%[\\w\\-\\.]+%$",tsType:"LocalizeKey"},id:{description:"",type:"string",pattern:"^[\\w\\-]+\\.[\\w\\-]+$",tsType:"Id"}};function H(t){t&&Object.values(t).forEach(e=>{if(e.type){if("tsType"in e&&delete e.tsType,e.type==="any"){delete e.type;return}e.type==="object"&&H(e.properties)}})}H(U);const Je={$schema:"https://json-schema.org/draft/2020-12/schema",title:"Project Settings Contribution",description:"The data an extension provides to inform Platform.Bible of the project settings it provides",anyOf:[{$ref:"#/$defs/projectSettingsGroup"},{type:"array",items:{$ref:"#/$defs/projectSettingsGroup"}}],$defs:U};Object.freeze(Je);const Ge={$schema:"https://json-schema.org/draft/2020-12/schema",title:"Settings Contribution",description:"The data an extension provides to inform Platform.Bible of the settings it provides",anyOf:[{$ref:"#/$defs/settingsGroup"},{type:"array",items:{$ref:"#/$defs/settingsGroup"}}],$defs:U};Object.freeze(Ge);const Fe={languageStrings:{description:"Map whose keys are localized string keys and whose values provide information about how to localize strings for the localized string key",type:"object",patternProperties:{"^%[\\w\\-\\.]+%$":{$ref:"#/$defs/localizedStringValue"}},additionalProperties:!1},localizedStringValue:{description:"Localized string value associated with this key",type:"string"},stringsMetadata:{description:"Map whose keys are localized string keys and whose values provide additional non-locale-specific information about the localized string key",type:"object",patternProperties:{"^%[\\w\\-\\.]+%$":{$ref:"#/$defs/stringMetadata"}},additionalProperties:!1},stringMetadata:{description:"Additional non-locale-specific information about a localized string key",type:"object",properties:{fallbackKey:{description:"Localized string key from which to get this value if one does not exist in the specified language. If a new key/value pair needs to be made to replace an existing one, this could help smooth over the transition if the meanings are close enough",$ref:"#/$defs/localizeKey"},notes:{description:"Additional information provided by developers in English to help the translator to know how to translate this localized string accurately",type:"string"}}},localizeKey:{description:"Identifier for a string that will be localized based on the user's UI language",type:"string",pattern:"^%[\\w\\-\\.]+%$",tsType:"LocalizeKey"}};H(Fe);const Ke={$schema:"https://json-schema.org/draft/2020-12/schema",title:"Localized String Data Contribution",description:"The data an extension provides to inform Platform.Bible of the localized strings it provides.",type:"object",properties:{metadata:{$ref:"#/$defs/stringsMetadata"},localizedStrings:{type:"object",additionalProperties:{$ref:"#/$defs/languageStrings"}}},$defs:Fe};Object.freeze(Ke);const Xe={title:"Platform.Bible menus",type:"object",properties:{mainMenu:{description:"Top level menu for the application",$ref:"#/$defs/multiColumnMenu"},defaultWebViewTopMenu:{description:"Default top menu for web views that don't specify their own",$ref:"#/$defs/multiColumnMenu"},defaultWebViewContextMenu:{description:"Default context menu for web views that don't specify their own",$ref:"#/$defs/singleColumnMenu"},webViewMenus:{description:"Menus that apply per web view in the application",type:"object",patternProperties:{"^[\\w\\-]+\\.[\\w\\-]+$":{$ref:"#/$defs/menusForOneWebView"}},additionalProperties:!1}},required:["mainMenu","defaultWebViewTopMenu","defaultWebViewContextMenu","webViewMenus"],additionalProperties:!1,$defs:{localizeKey:{description:"Identifier for a string that will be localized in a menu based on the user's UI language",type:"string",pattern:"^%[\\w\\-\\.]+%$"},referencedItem:{description:"Name of some UI element (i.e., tab, column, group, menu item) or some PAPI object (i.e., command)",type:"string",pattern:"^[\\w\\-]+\\.[\\w\\-]+$"},columnsWithHeaders:{description:"Group of columns that can be combined with other columns to form a multi-column menu",type:"object",patternProperties:{"^[\\w\\-]+\\.[\\w\\-]+$":{description:"Single column with a header string",type:"object",properties:{label:{description:"Header text for this this column in the UI",$ref:"#/$defs/localizeKey"},localizeNotes:{description:"Additional information provided by developers to help people who perform localization",type:"string"},order:{description:"Relative order of this column compared to other columns (sorted ascending)",type:"number"},isExtensible:{description:"Defines whether contributions are allowed to add menu groups to this column",type:"boolean"}},required:["label","order"],additionalProperties:!1}},properties:{isExtensible:{description:"Defines whether contributions are allowed to add columns to this multi-column menu",type:"boolean"}}},menuGroups:{description:"Group of menu items that can be combined with other groups to form a single menu/submenu. Groups are separated using a line within the menu/submenu.",type:"object",patternProperties:{"^[\\w\\-]+\\.[\\w\\-]+$":{description:"Single group that contains menu items",type:"object",oneOf:[{properties:{column:{description:"Column where this group belongs, not required for single column menus",$ref:"#/$defs/referencedItem"},order:{description:"Relative order of this group compared to other groups in the same column or submenu (sorted ascending)",type:"number"},isExtensible:{description:"Defines whether contributions are allowed to add menu items to this menu group",type:"boolean"}},required:["order"],additionalProperties:!1},{properties:{menuItem:{description:"Menu item that anchors the submenu where this group belongs",$ref:"#/$defs/referencedItem"},order:{description:"Relative order of this group compared to other groups in the same column or submenu (sorted ascending)",type:"number"},isExtensible:{description:"Defines whether contributions are allowed to add menu items to this menu group",type:"boolean"}},required:["menuItem","order"],additionalProperties:!1}]}},additionalProperties:!1},menuItem:{description:"Single item in a menu that can be clicked on to take an action or can be the parent of a submenu",type:"object",oneOf:[{properties:{id:{description:"ID for this menu item that holds a submenu",$ref:"#/$defs/referencedItem"}},required:["id"]},{properties:{command:{description:"Name of the PAPI command to run when this menu item is selected.",$ref:"#/$defs/referencedItem"},iconPathBefore:{description:"Path to the icon to display before the menu text",type:"string"},iconPathAfter:{description:"Path to the icon to display after the menu text",type:"string"}},required:["command"]}],properties:{label:{description:"Key that represents the text of this menu item to display",$ref:"#/$defs/localizeKey"},tooltip:{description:"Key that represents the text to display if a mouse pointer hovers over the menu item",$ref:"#/$defs/localizeKey"},searchTerms:{description:"Key that represents additional words the platform should reference when users are searching for menu items",$ref:"#/$defs/localizeKey"},localizeNotes:{description:"Additional information provided by developers to help people who perform localization",type:"string"},group:{description:"Group to which this menu item belongs",$ref:"#/$defs/referencedItem"},order:{description:"Relative order of this menu item compared to other menu items in the same group (sorted ascending)",type:"number"}},required:["label","group","order"],unevaluatedProperties:!1},groupsAndItems:{description:"Core schema for a column",type:"object",properties:{groups:{description:"Groups that belong in this menu",$ref:"#/$defs/menuGroups"},items:{description:"List of menu items that belong in this menu",type:"array",items:{$ref:"#/$defs/menuItem"},uniqueItems:!0}},required:["groups","items"]},singleColumnMenu:{description:"Menu that contains a column without a header",type:"object",allOf:[{$ref:"#/$defs/groupsAndItems"}],unevaluatedProperties:!1},multiColumnMenu:{description:"Menu that can contain multiple columns with headers",type:"object",allOf:[{$ref:"#/$defs/groupsAndItems"},{properties:{columns:{description:"Columns that belong in this menu",$ref:"#/$defs/columnsWithHeaders"}},required:["columns"]}],unevaluatedProperties:!1},menusForOneWebView:{description:"Set of menus that are associated with a single tab",type:"object",properties:{includeDefaults:{description:"Indicates whether the platform default menus should be included for this webview",type:"boolean"},topMenu:{description:"Menu that opens when you click on the top left corner of a tab",$ref:"#/$defs/multiColumnMenu"},contextMenu:{description:"Menu that opens when you right click on the main body/area of a tab",$ref:"#/$defs/singleColumnMenu"}},additionalProperties:!1}}};Object.freeze(Xe);exports.AsyncVariable=tt;exports.Collator=rt;exports.DateTimeFormat=he;exports.DocumentCombiner=ge;exports.FIRST_SCR_BOOK_NUM=De;exports.FIRST_SCR_CHAPTER_NUM=Re;exports.FIRST_SCR_VERSE_NUM=Be;exports.LAST_SCR_BOOK_NUM=Me;exports.Mutex=ye;exports.MutexMap=pt;exports.NonValidatingDocumentCombiner=dt;exports.NumberFormat=mt;exports.PlatformEventEmitter=pe;exports.UnsubscriberAsyncList=gt;exports.aggregateUnsubscriberAsyncs=tr;exports.aggregateUnsubscribers=er;exports.at=qt;exports.charAt=$;exports.codePointAt=zt;exports.createSyncProxyForAsyncObject=ft;exports.debounce=nt;exports.deepClone=A;exports.deepEqual=qe;exports.deserialize=_e;exports.endsWith=Ce;exports.escapeStringRegexp=Ht;exports.formatReplacementString=Jt;exports.getAllObjectFunctionNames=ct;exports.getChaptersForBook=xe;exports.getCurrentLocale=Ar;exports.getErrorMessage=ut;exports.getLocalizedIdFromBookNumber=Yt;exports.groupBy=it;exports.htmlEncode=Ir;exports.includes=Ie;exports.indexOf=P;exports.isLocalizeKey=Ut;exports.isSerializable=Cr;exports.isString=de;exports.isSubset=ze;exports.lastIndexOf=Ae;exports.localizedStringsDocumentSchema=Ke;exports.menuDocumentSchema=Xe;exports.newGuid=st;exports.normalize=Gt;exports.offsetBook=Wt;exports.offsetChapter=Zt;exports.offsetVerse=Qt;exports.ordinalCompare=Ft;exports.padEnd=Kt;exports.padStart=Xt;exports.projectSettingsDocumentSchema=Je;exports.serialize=J;exports.settingsDocumentSchema=Ge;exports.slice=Lt;exports.split=_;exports.startsWith=L;exports.stringLength=g;exports.substring=w;exports.toArray=Pe;exports.wait=me;exports.waitForDuration=lt; //# sourceMappingURL=index.cjs.map diff --git a/lib/platform-bible-utils/dist/index.cjs.map b/lib/platform-bible-utils/dist/index.cjs.map index 38a0a6371a..91ef74d1a9 100644 --- a/lib/platform-bible-utils/dist/index.cjs.map +++ b/lib/platform-bible-utils/dist/index.cjs.map @@ -1 +1 @@ -{"version":3,"file":"index.cjs","sources":["../src/async-variable.ts","../src/intl-collator.ts","../src/intl-date-time-format.ts","../src/platform-event-emitter.model.ts","../src/util.ts","../src/document-combiner.ts","../src/mutex.ts","../src/mutex-map.ts","../src/non-validating-document-combiner.ts","../src/intl-number-format.ts","../src/unsubscriber-async-list.ts","../../../node_modules/@sillsdev/scripture/dist/index.es.js","../../../node_modules/char-regex/index.js","../../../node_modules/stringz/dist/index.js","../src/string-util.ts","../src/scripture-util.ts","../src/unsubscriber.ts","../../../node_modules/fast-equals/dist/esm/index.mjs","../src/equality-checking.ts","../src/subset-checking.ts","../src/serialization.ts","../src/intl-util.ts","../src/settings.model.ts","../src/localized-strings.model.ts","../src/menus.model.ts"],"sourcesContent":["/** This class provides a convenient way for one task to wait on a variable that another task sets. */\nexport default class AsyncVariable {\n private readonly variableName: string;\n private readonly promiseToValue: Promise;\n private resolver: ((value: T) => void) | undefined;\n private rejecter: ((reason: string | undefined) => void) | undefined;\n\n /**\n * Creates an instance of the class\n *\n * @param variableName Name to use when logging about this variable\n * @param rejectIfNotSettledWithinMS Milliseconds to wait before verifying if the promise was\n * settled (resolved or rejected); will reject if it has not settled by that time. Use -1 if you\n * do not want a timeout at all.\n */\n constructor(variableName: string, rejectIfNotSettledWithinMS: number = 10000) {\n this.variableName = variableName;\n this.promiseToValue = new Promise((resolve, reject) => {\n this.resolver = resolve;\n this.rejecter = reject;\n });\n if (rejectIfNotSettledWithinMS > 0) {\n setTimeout(() => {\n if (this.rejecter) {\n this.rejecter(`Timeout reached when waiting for ${this.variableName} to settle`);\n this.complete();\n }\n }, rejectIfNotSettledWithinMS);\n }\n Object.seal(this);\n }\n\n /**\n * Get this variable's promise to a value. This always returns the same promise even after the\n * value has been resolved or rejected.\n *\n * @returns The promise for the value to be set\n */\n get promise(): Promise {\n return this.promiseToValue;\n }\n\n /**\n * A simple way to see if this variable's promise was resolved or rejected already\n *\n * @returns Whether the variable was already resolved or rejected\n */\n get hasSettled(): boolean {\n return Object.isFrozen(this);\n }\n\n /**\n * Resolve this variable's promise to the given value\n *\n * @param value This variable's promise will resolve to this value\n * @param throwIfAlreadySettled Determines whether to throw if the variable was already resolved\n * or rejected\n */\n resolveToValue(value: T, throwIfAlreadySettled: boolean = false): void {\n if (this.resolver) {\n console.debug(`${this.variableName} is being resolved now`);\n this.resolver(value);\n this.complete();\n } else {\n if (throwIfAlreadySettled) throw Error(`${this.variableName} was already settled`);\n console.debug(`Ignoring subsequent resolution of ${this.variableName}`);\n }\n }\n\n /**\n * Reject this variable's promise for the value with the given reason\n *\n * @param reason This variable's promise will be rejected with this reason\n * @param throwIfAlreadySettled Determines whether to throw if the variable was already resolved\n * or rejected\n */\n rejectWithReason(reason: string, throwIfAlreadySettled: boolean = false): void {\n if (this.rejecter) {\n console.debug(`${this.variableName} is being rejected now`);\n this.rejecter(reason);\n this.complete();\n } else {\n if (throwIfAlreadySettled) throw Error(`${this.variableName} was already settled`);\n console.debug(`Ignoring subsequent rejection of ${this.variableName}`);\n }\n }\n\n /** Prevent any further updates to this variable */\n private complete(): void {\n this.resolver = undefined;\n this.rejecter = undefined;\n Object.freeze(this);\n }\n}\n","/** Enables language-sensitive string comparison. Wraps Intl.Collator */\nexport default class Collator {\n private collator: Intl.Collator;\n\n constructor(locales?: string | string[], options?: Intl.CollatorOptions) {\n this.collator = new Intl.Collator(locales, options);\n }\n\n /**\n * Compares two strings according to the sort order of this Collator object\n *\n * @param string1 String to compare\n * @param string2 String to compare\n * @returns A number indicating how string1 and string2 compare to each other according to the\n * sort order of this Collator object. Negative value if string1 comes before string2. Positive\n * value if string1 comes after string2. 0 if they are considered equal.\n */\n compare(string1: string, string2: string): number {\n return this.collator.compare(string1, string2);\n }\n\n /**\n * Returns a new object with properties reflecting the locale and collation options computed\n * during initialization of this collator object.\n *\n * @returns ResolvedCollatorOptions object\n */\n resolvedOptions(): Intl.ResolvedCollatorOptions {\n return this.collator.resolvedOptions();\n }\n}\n","/** Enables language-sensitive data and time formatting. Wraps Intl.DateTimeFormat */\nexport default class DateTimeFormat {\n private dateTimeFormatter: Intl.DateTimeFormat;\n\n constructor(locales?: string | string[], options?: Intl.DateTimeFormatOptions) {\n this.dateTimeFormatter = new Intl.DateTimeFormat(locales, options);\n }\n\n /**\n * Formats a date according to the locale and formatting option for this DateTimeFormat object\n *\n * @param date The date to format\n * @returns String representing the given date formatted according to the locale and formatting\n * options of this DateTimeFormat object\n */\n format(date: Date): string {\n return this.dateTimeFormatter.format(date);\n }\n\n /**\n * Formats a date range in the most concise way based on the locales and options provided when\n * instantiating this DateTimeFormat object\n *\n * @param startDate Date object representing start of the date range\n * @param endDate Date object representing the end of the date range\n * @returns String representing the given date range formatted according to the locale and\n * formatting options of this DateTimeFormat object\n */\n formatRange(startDate: Date, endDate: Date): string {\n return this.dateTimeFormatter.formatRange(startDate, endDate);\n }\n\n /**\n * Returns an array of locale-specific tokens representing each part of the formatted date range\n * produced by this DateTimeFormat object\n *\n * @param startDate Date object representing start of the date range\n * @param endDate Date object representing the end of the date range\n * @returns Array of DateTimeRangeFormatPart objects\n */\n formatRangeToParts(startDate: Date, endDate: Date): Intl.DateTimeRangeFormatPart[] {\n return this.dateTimeFormatter.formatRangeToParts(startDate, endDate);\n }\n\n /**\n * Allows locale-aware formatting of strings produced by this DateTimeFormat object\n *\n * @param date The date to format\n * @returns Array of DateTimeFormatPart objects\n */\n formatToParts(date: Date): Intl.DateTimeFormatPart[] {\n return this.dateTimeFormatter.formatToParts(date);\n }\n\n /**\n * Returns a new object with properties reflecting the locale and date and time formatting options\n * computed during initialization of this DateTimeFormat object\n *\n * @returns ResolvedDateTimeFormatOptions object\n */\n resolvedOptions(): Intl.ResolvedDateTimeFormatOptions {\n return this.dateTimeFormatter.resolvedOptions();\n }\n}\n","/** Interfaces, classes, and functions related to events and event emitters */\n\nimport { Dispose } from './disposal.model';\nimport { PlatformEvent, PlatformEventHandler } from './platform-event';\n\n/**\n * Event manager - accepts subscriptions to an event and runs the subscription callbacks when the\n * event is emitted Use eventEmitter.event(callback) to subscribe to the event. Use\n * eventEmitter.emit(event) to run the subscriptions. Generally, this EventEmitter should be\n * private, and its event should be public. That way, the emitter is not publicized, but anyone can\n * subscribe to the event.\n */\nexport default class PlatformEventEmitter implements Dispose {\n /**\n * Subscribes a function to run when this event is emitted.\n *\n * @param callback Function to run with the event when it is emitted\n * @returns Unsubscriber function to run to stop calling the passed-in function when the event is\n * emitted\n * @alias event\n */\n subscribe = this.event;\n\n /** All callback functions that will run when this event is emitted. Lazy loaded */\n private subscriptions?: PlatformEventHandler[];\n /** Event for listeners to subscribe to. Lazy loaded */\n private lazyEvent?: PlatformEvent;\n /** Whether this emitter has been disposed */\n private isDisposed = false;\n\n /**\n * Event for listeners to subscribe to. Subscribes a function to run when this event is emitted.\n * Use like `const unsubscriber = event(callback)`\n *\n * @param callback Function to run with the event when it is emitted\n * @returns Unsubscriber function to run to stop calling the passed-in function when the event is\n * emitted\n */\n get event(): PlatformEvent {\n this.assertNotDisposed();\n\n if (!this.lazyEvent) {\n this.lazyEvent = (callback) => {\n if (!callback || typeof callback !== 'function')\n throw new Error(`Event handler callback must be a function!`);\n\n // Initialize this.subscriptions if it does not exist\n if (!this.subscriptions) this.subscriptions = [];\n\n this.subscriptions.push(callback);\n\n return () => {\n if (!this.subscriptions) return false; // Did not find any subscribed callbacks\n\n const callbackIndex = this.subscriptions.indexOf(callback);\n\n if (callbackIndex < 0) return false; // Did not find this callback in the subscriptions\n\n // Remove the callback\n this.subscriptions.splice(callbackIndex, 1);\n\n return true;\n };\n };\n }\n return this.lazyEvent;\n }\n\n /** Disposes of this event, preparing it to release from memory */\n dispose = () => {\n return this.disposeFn();\n };\n\n /**\n * Runs the subscriptions for the event\n *\n * @param event Event data to provide to subscribed callbacks\n */\n emit = (event: T) => {\n // Do not do anything other than emitFn here. This emit is just binding `this` to emitFn\n this.emitFn(event);\n };\n\n /**\n * Function that runs the subscriptions for the event. Added here so children can override emit\n * and still call the base functionality. See NetworkEventEmitter.emit for example\n */\n protected emitFn(event: T) {\n this.assertNotDisposed();\n\n this.subscriptions?.forEach((callback) => callback(event));\n }\n\n /** Check to make sure this emitter is not disposed. Throw if it is */\n protected assertNotDisposed() {\n if (this.isDisposed) throw new Error('Emitter is disposed');\n }\n\n /**\n * Disposes of this event, preparing it to release from memory. Added here so children can\n * override emit and still call the base functionality.\n */\n protected disposeFn() {\n this.assertNotDisposed();\n\n this.isDisposed = true;\n this.subscriptions = undefined;\n this.lazyEvent = undefined;\n return Promise.resolve(true);\n }\n}\n","/** Collection of functions, objects, and types that are used as helpers in other services. */\n\n// Thanks to blubberdiblub at https://stackoverflow.com/a/68141099/217579\nexport function newGuid(): string {\n return '00-0-4-1-000'.replace(/[^-]/g, (s) =>\n // @ts-expect-error ts(2363) this works fine\n // eslint-disable-next-line no-bitwise\n (((Math.random() + ~~s) * 0x10000) >> s).toString(16).padStart(4, '0'),\n );\n}\n\n// thanks to DRAX at https://stackoverflow.com/a/9436948\n/**\n * Determine whether the object is a string\n *\n * @param o Object to determine if it is a string\n * @returns True if the object is a string; false otherwise\n */\nexport function isString(o: unknown): o is string {\n return typeof o === 'string' || o instanceof String;\n}\n\n/**\n * If deepClone isn't used when copying properties between objects, you may be left with dangling\n * references between the source and target of property copying operations.\n *\n * @param obj Object to clone\n * @returns Duplicate copy of `obj` without any references back to the original one\n */\nexport function deepClone(obj: T): T {\n // Assert the return type matches what is expected\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n return JSON.parse(JSON.stringify(obj)) as T;\n}\n\n/**\n * Get a function that reduces calls to the function passed in\n *\n * @param fn The function to debounce\n * @param delay How much delay in milliseconds after the most recent call to the debounced function\n * to call the function\n * @returns Function that, when called, only calls the function passed in at maximum every delay ms\n */\n// We don't know the parameter types since this function can be anything\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function debounce void>(fn: T, delay = 300): T {\n if (isString(fn)) throw new Error('Tried to debounce a string! Could be XSS');\n let timeout: ReturnType;\n // Ensure the right return type.\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n return ((...args) => {\n clearTimeout(timeout);\n timeout = setTimeout(() => fn(...args), delay);\n }) as T;\n}\n\n/**\n * Groups each item in the array of items into a map according to the keySelector\n *\n * @param items Array of items to group by\n * @param keySelector Function to run on each item to get the key for the group to which it belongs\n * @param valueSelector Function to run on each item to get the value it should have in the group\n * (like map function). If not provided, uses the item itself\n * @returns Map of keys to groups of values corresponding to each item\n */\nexport function groupBy(items: T[], keySelector: (item: T) => K): Map>;\nexport function groupBy(\n items: T[],\n keySelector: (item: T) => K,\n valueSelector: (item: T, key: K) => V,\n): Map>;\nexport function groupBy(\n items: T[],\n keySelector: (item: T) => K,\n valueSelector?: (item: T, key: K) => V,\n): Map> {\n const map = new Map>();\n items.forEach((item) => {\n const key = keySelector(item);\n const group = map.get(key);\n const value = valueSelector ? valueSelector(item, key) : item;\n if (group) group.push(value);\n else map.set(key, [value]);\n });\n return map;\n}\n\n// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript\ntype ErrorWithMessage = {\n message: string;\n};\n\n// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript\nfunction isErrorWithMessage(error: unknown): error is ErrorWithMessage {\n return (\n typeof error === 'object' &&\n // We're potentially dealing with objects we didn't create, so they might contain `null`\n // eslint-disable-next-line no-null/no-null\n error !== null &&\n 'message' in error &&\n // Type assert `error` to check it's `message`.\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n typeof (error as Record).message === 'string'\n );\n}\n\n// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript\n/**\n * Function to get an error from the object (useful for getting an error in a catch block)\n *\n * @param error Error object whose message to get\n * @returns Message of the error - if object has message, returns message. Otherwise tries to\n * stringify\n */\nfunction toErrorWithMessage(maybeError: unknown): ErrorWithMessage {\n if (isErrorWithMessage(maybeError)) return maybeError;\n\n try {\n return new Error(JSON.stringify(maybeError));\n } catch {\n // fallback in case there's an error stringifying the maybeError\n // like with circular references for example.\n return new Error(String(maybeError));\n }\n}\n\n// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript\n/**\n * Function to get an error message from the object (useful for getting error message in a catch\n * block)\n *\n * @example `try {...} catch (e) { logger.info(getErrorMessage(e)) }`\n *\n * @param error Error object whose message to get\n * @returns Message of the error - if object has message, returns message. Otherwise tries to\n * stringify\n */\nexport function getErrorMessage(error: unknown) {\n return toErrorWithMessage(error).message;\n}\n\n/** Asynchronously waits for the specified number of milliseconds. (wraps setTimeout in a promise) */\nexport function wait(ms: number) {\n // eslint-disable-next-line no-promise-executor-return\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Runs the specified function and will timeout if it takes longer than the specified wait time\n *\n * @param fn The function to run\n * @param maxWaitTimeInMS The maximum amount of time to wait for the function to resolve\n * @returns Promise that resolves to the resolved value of the function or undefined if it ran\n * longer than the specified wait time\n */\nexport function waitForDuration(fn: () => Promise, maxWaitTimeInMS: number) {\n const timeout = wait(maxWaitTimeInMS).then(() => undefined);\n return Promise.any([timeout, fn()]);\n}\n\n/**\n * Get all functions on an object and its prototype chain (so we don't miss any class methods or any\n * object methods). Note that the functions on the final item in the prototype chain (i.e., Object)\n * are skipped to avoid including functions like `__defineGetter__`, `__defineSetter__`, `toString`,\n * etc.\n *\n * @param obj Object whose functions to get\n * @param objId Optional ID of the object to use for debug logging\n * @returns Array of all function names on an object\n */\n// Note: lodash has something that MIGHT do the same thing as this. Investigate for https://github.com/paranext/paranext-core/issues/134\nexport function getAllObjectFunctionNames(\n obj: { [property: string]: unknown },\n objId: string = 'obj',\n): Set {\n const objectFunctionNames = new Set();\n\n // Get all function properties directly defined on the object\n Object.getOwnPropertyNames(obj).forEach((property) => {\n try {\n if (typeof obj[property] === 'function') objectFunctionNames.add(property);\n } catch (error) {\n console.debug(`Skipping ${property} on ${objId} due to error: ${error}`);\n }\n });\n\n // Walk up the prototype chain and get additional function properties, skipping the functions\n // provided by the final (Object) prototype\n let objectPrototype = Object.getPrototypeOf(obj);\n while (objectPrototype && Object.getPrototypeOf(objectPrototype)) {\n Object.getOwnPropertyNames(objectPrototype).forEach((property) => {\n try {\n if (typeof obj[property] === 'function') objectFunctionNames.add(property);\n } catch (error) {\n console.debug(`Skipping ${property} on ${objId}'s prototype due to error: ${error}`);\n }\n });\n objectPrototype = Object.getPrototypeOf(objectPrototype);\n }\n\n return objectFunctionNames;\n}\n\n/**\n * Creates a synchronous proxy for an asynchronous object. The proxy allows calling methods on an\n * object that is asynchronously fetched using a provided asynchronous function.\n *\n * @param getObject - A function that returns a promise resolving to the object whose asynchronous\n * methods to call.\n * @param objectToProxy - An optional object that is the object that is proxied. If a property is\n * accessed that does exist on this object, it will be returned. If a property is accessed that\n * does not exist on this object, it will be considered to be an asynchronous method called on the\n * object returned from getObject.\n * @returns A synchronous proxy for the asynchronous object.\n */\nexport function createSyncProxyForAsyncObject(\n getObject: (args?: unknown[]) => Promise,\n objectToProxy: Partial = {},\n): T {\n // objectToProxy will have only the synchronously accessed properties of T on it, and this proxy\n // makes the async methods that do not exist yet available synchronously so we have all of T\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n return new Proxy(objectToProxy as T, {\n get(target, prop) {\n // We don't have any type information for T, so we assume methodName exists on it and will let JavaScript throw if it doesn't exist\n // @ts-expect-error 7053\n if (prop in target) return target[prop];\n return async (...args: unknown[]) => {\n // 7053: We don't have any type information for T, so we assume methodName exists on it and will let JavaScript throw if it doesn't exist\n // 2556: The args here are the parameters for the method specified\n // @ts-expect-error 7053 2556\n return (await getObject())[prop](...args);\n };\n },\n });\n}\n\n/** Within type T, recursively change all properties to be optional */\nexport type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial } : T;\n\n/** Within type T, recursively change properties that were of type A to be of type B */\nexport type ReplaceType = T extends A\n ? B\n : T extends object\n ? { [K in keyof T]: ReplaceType }\n : T;\n\n// Thanks to jcalz at https://stackoverflow.com/a/50375286\n/**\n * Converts a union type to an intersection type (`|` to `&`).\n *\n * Note: this utility type is for use on object types. It may fail on other types.\n *\n * @example\n *\n * ```typescript\n * type TypeOne = { one: string };\n * type TypeTwo = { two: number };\n * type TypeThree = { three: string };\n *\n * type TypeNums = { one: TypeOne; two: TypeTwo; three: TypeThree };\n * const numNames = ['one', 'two'] as const;\n * type TypeNumNames = typeof numNames;\n *\n * // Same as `TypeOne | TypeTwo`\n * // `{ one: string } | { two: number }`\n * type TypeOneTwoUnion = TypeNums[TypeNumNames[number]];\n *\n * // Same as `TypeOne & TypeTwo`\n * // `{ one: string; two: number }`\n * type TypeOneTwoIntersection = UnionToIntersection;\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type UnionToIntersection = (U extends any ? (x: U) => void : never) extends (\n x: infer I,\n) => void\n ? I\n : never;\n","import PlatformEventEmitter from './platform-event-emitter.model';\nimport { deepClone } from './util';\n\ntype JsonObjectLike = { [key: string]: unknown };\ntype JsonArrayLike = unknown[];\n\nexport type JsonDocumentLike = JsonObjectLike | JsonArrayLike;\n\n/**\n * Options for DocumentCombiner objects\n *\n * - `copyDocuments`: If true, this instance will perform a deep copy of all provided documents before\n * composing the output. If false, then changes made to provided documents after they are\n * contributed will be reflected in the next time output is composed.\n * - `ignoreDuplicateProperties`: If true, then duplicate properties are skipped if they are seen in\n * contributed documents. If false, then throw when duplicate properties are seen in contributed\n * documents.\n */\nexport type DocumentCombinerOptions = {\n copyDocuments: boolean;\n ignoreDuplicateProperties: boolean;\n};\n\n/**\n * Base class for any code that wants to compose JSON documents (primarily in the form of JS objects\n * or arrays) together into a single output document.\n */\nexport default class DocumentCombiner {\n protected baseDocument: JsonDocumentLike;\n protected readonly contributions = new Map();\n protected latestOutput: JsonDocumentLike | undefined;\n protected readonly options: DocumentCombinerOptions;\n private readonly onDidRebuildEmitter = new PlatformEventEmitter();\n /** Event that emits to announce that the document has been rebuilt and the output has been updated */\n // Need `onDidRebuildEmitter` to be instantiated before this line\n // eslint-disable-next-line @typescript-eslint/member-ordering\n readonly onDidRebuild = this.onDidRebuildEmitter.subscribe;\n\n /**\n * Create a DocumentCombiner instance\n *\n * @param baseDocument This is the first document that will be used when composing the output\n * @param options Options used by this object when combining documents\n */\n protected constructor(baseDocument: JsonDocumentLike, options: DocumentCombinerOptions) {\n // Setting baseDocument redundantly because TS doesn't understand that updateBaseDocument does it\n this.baseDocument = baseDocument;\n this.options = options;\n this.updateBaseDocument(baseDocument);\n }\n\n /**\n * Update the starting document for composition process\n *\n * @param baseDocument Base JSON document/JS object that all other documents are added to\n * @returns Recalculated output document given the new starting state and existing other documents\n */\n updateBaseDocument(baseDocument: JsonDocumentLike): JsonDocumentLike | undefined {\n this.validateBaseDocument(baseDocument);\n this.baseDocument = this.options.copyDocuments ? deepClone(baseDocument) : baseDocument;\n this.baseDocument = this.transformBaseDocumentAfterValidation(this.baseDocument);\n return this.rebuild();\n }\n\n /**\n * Add or update one of the contribution documents for the composition process\n *\n * Note: the order in which contribution documents are added can be considered to be indeterminate\n * as it is currently ordered by however `Map.forEach` provides the contributions. The order\n * matters when merging two arrays into one. Also, when `options.ignoreDuplicateProperties` is\n * `true`, the order also matters when adding the same property to an object that is already\n * provided previously. Please let us know if you have trouble because of indeterminate\n * contribution ordering.\n *\n * @param documentName Name of the contributed document to combine\n * @param document Content of the contributed document to combine\n * @returns Recalculated output document given the new or updated contribution and existing other\n * documents\n */\n addOrUpdateContribution(\n documentName: string,\n document: JsonDocumentLike,\n ): JsonDocumentLike | undefined {\n this.validateContribution(documentName, document);\n const previousDocumentVersion = this.contributions.get(documentName);\n let documentToSet = this.options.copyDocuments && !!document ? deepClone(document) : document;\n documentToSet = this.transformContributionAfterValidation(documentName, documentToSet);\n this.contributions.set(documentName, documentToSet);\n try {\n return this.rebuild();\n } catch (error) {\n // If the output isn't valid after adding/updating the contribution, put it back how it was\n if (previousDocumentVersion) this.contributions.set(documentName, previousDocumentVersion);\n else this.contributions.delete(documentName);\n throw new Error(`Error when setting the document named ${documentName}: ${error}`);\n }\n }\n\n /**\n * Delete one of the contribution documents for the composition process\n *\n * @param documentName Name of the contributed document to delete\n * @returns Recalculated output document given the remaining other documents\n */\n deleteContribution(documentName: string): JsonDocumentLike | undefined {\n const document = this.contributions.get(documentName);\n if (!document) throw new Error(`${documentName} does not exist`);\n this.contributions.delete(documentName);\n try {\n return this.rebuild();\n } catch (error) {\n // If the output isn't valid after deleting the contribution, put it back and rethrow\n this.contributions.set(documentName, document);\n throw new Error(`Error when deleting the document named ${documentName}: ${error}`);\n }\n }\n\n /**\n * Delete all present contribution documents for the composition process and return to the base\n * document\n *\n * @returns Recalculated output document consisting only of the base document\n */\n deleteAllContributions(): JsonDocumentLike | undefined {\n if (this.contributions.size <= 0) return this.latestOutput;\n\n // Save out all contributions\n const contributions = [...this.contributions.entries()];\n\n // Delete all contributions\n contributions.forEach(([contributionName]) => this.contributions.delete(contributionName));\n\n // Rebuild with no contributions\n try {\n return this.rebuild();\n } catch (error) {\n // If the output isn't valid after deleting all contributions, put them back and rethrow\n contributions.forEach(([contributionName, document]) =>\n this.contributions.set(contributionName, document),\n );\n throw new Error(`Error when deleting all contributions: ${error}`);\n }\n }\n\n /**\n * Run the document composition process given the starting document and all contributions. Throws\n * if the output document fails to validate properly.\n *\n * @returns Recalculated output document given the starting and contributed documents\n */\n rebuild(): JsonDocumentLike | undefined {\n // The starting document is the output if there are no other contributions\n if (this.contributions.size === 0) {\n let potentialOutput = deepClone(this.baseDocument);\n potentialOutput = this.transformFinalOutputBeforeValidation(potentialOutput);\n this.validateOutput(potentialOutput);\n this.latestOutput = potentialOutput;\n this.onDidRebuildEmitter.emit(undefined);\n return this.latestOutput;\n }\n\n // Compose the output by validating each document one at a time to pinpoint errors better\n let outputIteration = this.baseDocument;\n this.contributions.forEach((contribution: JsonDocumentLike) => {\n outputIteration = mergeObjects(\n outputIteration,\n contribution,\n this.options.ignoreDuplicateProperties,\n );\n this.validateOutput(outputIteration);\n });\n outputIteration = this.transformFinalOutputBeforeValidation(outputIteration);\n this.validateOutput(outputIteration);\n this.latestOutput = outputIteration;\n this.onDidRebuildEmitter.emit(undefined);\n return this.latestOutput;\n }\n\n /**\n * Transform the starting document that is given to the combiner. This transformation occurs after\n * validating the base document and before combining any contributions.\n *\n * WARNING: If you do not create the combiner with option `copyDocuments: true` or clone inside\n * this method, this method will directly modify the `baseDocument` passed in.\n *\n * @param baseDocument Initial input document. Already validated via `validateBaseDocument`\n * @returns Transformed base document\n */\n // We just don't need `this` here. This is basically a no-op function that is available to child\n // classes to override\n // eslint-disable-next-line class-methods-use-this\n protected transformBaseDocumentAfterValidation(baseDocument: JsonDocumentLike): JsonDocumentLike {\n return baseDocument;\n }\n\n /**\n * Transform the contributed document associated with `documentName`. This transformation occurs\n * after validating the contributed document and before combining with other documents.\n *\n * WARNING: If you do not create the combiner with option `copyDocuments: true` or clone inside\n * this method, this method will directly modify the contributed `document` passed in.\n *\n * @param documentName Name of the contributed document to combine\n * @param document Content of the contributed document to combine. Already validated via\n * `validateContribution`\n * @returns Transformed contributed document\n */\n // We just don't need `this` here. This is basically a no-op function that is available to child\n // classes to override\n // eslint-disable-next-line class-methods-use-this\n protected transformContributionAfterValidation(\n // @ts-expect-error this parameter is unused but may be used in child classes\n documentName: string,\n document: JsonDocumentLike,\n ): JsonDocumentLike {\n return document;\n }\n\n /**\n * Throw an error if the provided document is not a valid starting document.\n *\n * @param baseDocument Base JSON document/JS object that all other documents are added to\n */\n // no-op intended to be overridden by child classes. Can't be static\n // @ts-expect-error ts(6133) parameter doesn't need to be used but still needs the right name\n // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars\n protected validateBaseDocument(baseDocument: JsonDocumentLike): void {}\n\n /**\n * Throw an error if the provided document is not a valid contribution document.\n *\n * @param documentName Name of the contributed document to combine\n * @param document Content of the contributed document to combine\n */\n // no-op intended to be overridden by child classes. Can't be static\n // @ts-expect-error ts(6133) parameter doesn't need to be used but still needs the right name\n // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars\n protected validateContribution(documentName: string, document: JsonDocumentLike): void {}\n\n /**\n * Throw an error if the provided output is not valid.\n *\n * @param output Output document that could potentially be returned to callers\n */\n // no-op intended to be overridden by child classes. Can't be static\n // @ts-expect-error ts(6133) parameter doesn't need to be used but still needs the right name\n // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars\n protected validateOutput(output: JsonDocumentLike): void {}\n\n /**\n * Transform the document that is the composition of the base document and all contribution\n * documents. This is the last step that will be run prior to validation via `validateOutput`\n * before `this.latestOutput` is updated to the new output.\n *\n * @param finalOutput Final output document that could potentially be returned to callers. \"Final\"\n * means no further contribution documents will be merged.\n */\n // no-op intended to be overridden by child classes. Can't be static\n // eslint-disable-next-line class-methods-use-this\n protected transformFinalOutputBeforeValidation(finalOutput: JsonDocumentLike): JsonDocumentLike {\n return finalOutput;\n }\n}\n\n// #region Helper functions\n\n/**\n * Determines if the input values are objects but not arrays\n *\n * @param values Objects to check\n * @returns True if all the values are objects but not arrays\n */\nfunction areNonArrayObjects(...values: unknown[]): boolean {\n let allMatch = true;\n values.forEach((value: unknown) => {\n if (!value || typeof value !== 'object' || Array.isArray(value)) allMatch = false;\n });\n return allMatch;\n}\n\n/**\n * Determines if the input values are arrays\n *\n * @param value Objects to check\n * @returns True if the values are arrays\n */\nfunction areArrayObjects(...values: unknown[]): boolean {\n let allMatch = true;\n values.forEach((value: unknown) => {\n if (!value || typeof value !== 'object' || !Array.isArray(value)) allMatch = false;\n });\n return allMatch;\n}\n\n/**\n * Deep clone and recursively merge the properties of one object (copyFrom) into another\n * (startingPoint). Throws if copyFrom would overwrite values already existing in startingPoint.\n *\n * Does not modify the objects passed in.\n *\n * @param startingPoint Object that is the starting point for the return value\n * @param copyFrom Object whose values are copied into the return value\n * @param ignoreDuplicateProperties Whether to ignore object properties that are present in\n * `copyFrom` that are already present in `startingPoint`. If `false`, throws when an object\n * property in `copyFrom` is already present in `startingPoint`\n * @returns Object that is the combination of the two documents\n */\nfunction mergeObjects(\n startingPoint: JsonDocumentLike,\n copyFrom: JsonDocumentLike,\n ignoreDuplicateProperties: boolean,\n): JsonDocumentLike {\n const retVal = deepClone(startingPoint);\n\n if (!copyFrom) return retVal;\n\n return mergeObjectsInternal(retVal, deepClone(copyFrom), ignoreDuplicateProperties);\n}\n\n/**\n * Recursively merge the properties of one object (copyFrom) into another (startingPoint). Throws if\n * copyFrom would overwrite values already existing in startingPoint.\n *\n * WARNING: Modifies the argument objects in some way. Recommended to use `mergeObjects`\n *\n * @param startingPoint Object that is the starting point for the return value\n * @param copyFrom Object whose values are copied into the return value\n * @param ignoreDuplicateProperties Whether to ignore object properties that are present in\n * `copyFrom` that are already present in `startingPoint`. If `false`, throws when an object\n * property in `copyFrom` is already present in `startingPoint`\n * @returns Object that is the combination of the two documents\n */\nfunction mergeObjectsInternal(\n startingPoint: JsonDocumentLike,\n copyFrom: JsonDocumentLike,\n ignoreDuplicateProperties: boolean,\n): JsonDocumentLike {\n if (!copyFrom) return startingPoint;\n\n if (areNonArrayObjects(startingPoint, copyFrom)) {\n // Merge properties since they are both objects\n\n // We know these are objects from the `if` check\n /* eslint-disable no-type-assertion/no-type-assertion */\n const startingPointObj = startingPoint as JsonObjectLike;\n const copyFromObj = copyFrom as JsonObjectLike;\n /* eslint-enable no-type-assertion/no-type-assertion */\n Object.keys(copyFromObj).forEach((key: string | number) => {\n if (Object.hasOwn(startingPointObj, key)) {\n if (areNonArrayObjects(startingPointObj[key], copyFromObj[key])) {\n startingPointObj[key] = mergeObjectsInternal(\n // We know these are objects from the `if` check\n /* eslint-disable no-type-assertion/no-type-assertion */\n startingPointObj[key] as JsonObjectLike,\n copyFromObj[key] as JsonObjectLike,\n ignoreDuplicateProperties,\n /* eslint-enable no-type-assertion/no-type-assertion */\n );\n } else if (areArrayObjects(startingPointObj[key], copyFromObj[key])) {\n // Concat the arrays since they are both arrays\n\n // We know these are arrays from the `else if` check\n /* eslint-disable no-type-assertion/no-type-assertion */\n startingPointObj[key] = (startingPointObj[key] as JsonArrayLike).concat(\n copyFromObj[key] as JsonArrayLike,\n );\n /* eslint-enable no-type-assertion/no-type-assertion */\n } else if (!ignoreDuplicateProperties)\n throw new Error(`Cannot merge objects: key \"${key}\" already exists in the target object`);\n // Note that the first non-object non-array value that gets placed in a property stays.\n // New values do not override existing ones\n } else {\n startingPointObj[key] = copyFromObj[key];\n }\n });\n } else if (areArrayObjects(startingPoint, copyFrom)) {\n // Concat the arrays since they are both arrays\n\n // Push the contents of copyFrom into startingPoint since it is a const and was already deep cloned\n // We know these are objects from the `else if` check\n /* eslint-disable no-type-assertion/no-type-assertion */\n (startingPoint as JsonArrayLike).push(...(copyFrom as JsonArrayLike));\n /* eslint-enable no-type-assertion/no-type-assertion */\n }\n\n // Note that nothing happens if `startingPoint` is not an object or an array or if `startingPoint`\n // and `copyFrom` are not both object or both arrays. Should we throw? Should we push `copyFrom`'s\n // values into the array? Other? Maybe one day we can add some options to decide what to do in\n // this situation, but YAGNI for now\n\n return startingPoint;\n}\n\n// #endregion\n","import { Mutex as AsyncMutex } from 'async-mutex';\n\n// Extending Mutex from async-mutex so we can add JSDoc\n\n/**\n * Class that allows calling asynchronous functions multiple times at once while only running one at\n * a time.\n *\n * @example\n *\n * ```typescript\n * const mutex = new Mutex();\n *\n * mutex.runExclusive(async () => {\n * // Do some asynchronous stuff\n * console.log('These run one-at-a-time');\n * });\n *\n * mutex.runExclusive(async () => {\n * // Do some asynchronous stuff\n * console.log('These run one-at-a-time');\n * });\n * ```\n *\n * See [`async-mutex`](https://www.npmjs.com/package/async-mutex) for more information.\n */\nclass Mutex extends AsyncMutex {}\n\nexport default Mutex;\n","import Mutex from './mutex';\n\n/** Map of {@link Mutex}es that automatically (lazily) generates a new {@link Mutex} for any new key */\nclass MutexMap {\n private mutexesByID = new Map();\n\n get(mutexID: string): Mutex {\n let retVal = this.mutexesByID.get(mutexID);\n if (retVal) return retVal;\n\n retVal = new Mutex();\n this.mutexesByID.set(mutexID, retVal);\n return retVal;\n }\n}\n\nexport default MutexMap;\n","import DocumentCombiner, { DocumentCombinerOptions, JsonDocumentLike } from './document-combiner';\n\nexport default class NonValidatingDocumentCombiner extends DocumentCombiner {\n // Making the protected base constructor public\n // eslint-disable-next-line @typescript-eslint/no-useless-constructor\n constructor(baseDocument: JsonDocumentLike, options: DocumentCombinerOptions) {\n super(baseDocument, options);\n }\n\n get output(): JsonDocumentLike | undefined {\n return this.latestOutput;\n }\n}\n","/** Enables language-sensitive number formatting. Wraps Intl.NumberFormat */\nexport default class NumberFormat {\n private numberFormatter: Intl.NumberFormat;\n\n constructor(locales?: string | string[], options?: Intl.NumberFormatOptions) {\n this.numberFormatter = new Intl.NumberFormat(locales, options);\n }\n\n /**\n * Formats a number according to the locale and formatting options of this NumberFormat object\n *\n * @param value Number or BigInt to format\n * @returns String representing the given number formatted according to the locale and formatting\n * options of this NumberFormat object\n */\n format(value: number | bigint): string {\n return this.numberFormatter.format(value);\n }\n\n /**\n * Formats a range of numbers according to the locale and formatting options of this NumberFormat\n * object\n *\n * @param startRange Number or bigint representing the start of the range\n * @param endRange Number or bigint representing the end of the range\n * @returns String representing the given range of numbers formatted according to the locale and\n * formatting options of this NumberFormat object\n */\n formatRange(startRange: number | bigint, endRange: number | bigint): string {\n return this.numberFormatter.formatRange(startRange, endRange);\n }\n\n /**\n * Returns an array of objects containing the locale-specific tokens from which it is possible to\n * build custom strings while preserving the locale-specific parts.\n *\n * @param startRange Number or bigint representing start of the range\n * @param endRange Number or bigint representing end of the range\n * @returns Array of NumberRangeFormatPart objects containing the formatted range of numbers in\n * parts\n */\n formatRangeToParts(\n startRange: number | bigint,\n endRange: number | bigint,\n ): Intl.NumberRangeFormatPart[] {\n return this.numberFormatter.formatRangeToParts(startRange, endRange);\n }\n\n /**\n * Allows locale-aware formatting of strings produced by this NumberFormat object\n *\n * @param value Number or bigint to format\n * @returns Array of NumberFormatPart objects containing the formatted number in parts\n */\n formatToParts(value: number | bigint): Intl.NumberFormatPart[] {\n return this.numberFormatter.formatToParts(value);\n }\n\n /**\n * Returns a new object with properties reflecting the locale and number formatting options\n * computed during initialization of this NumberFormat object\n *\n * @returns ResolvedNumberFormatOptions object\n */\n resolvedOptions(): Intl.ResolvedNumberFormatOptions {\n return this.numberFormatter.resolvedOptions();\n }\n}\n","import { Dispose } from './disposal.model';\nimport { Unsubscriber, UnsubscriberAsync } from './unsubscriber';\n\n/** Simple collection for UnsubscriberAsync objects that also provides an easy way to run them. */\nexport default class UnsubscriberAsyncList {\n readonly unsubscribers = new Set();\n\n constructor(private name = 'Anonymous') {}\n\n /**\n * Add unsubscribers to the list. Note that duplicates are not added twice.\n *\n * @param unsubscribers - Objects that were returned from a registration process.\n */\n add(...unsubscribers: (UnsubscriberAsync | Unsubscriber | Dispose)[]) {\n unsubscribers.forEach((unsubscriber) => {\n if ('dispose' in unsubscriber) this.unsubscribers.add(unsubscriber.dispose);\n else this.unsubscribers.add(unsubscriber);\n });\n }\n\n /**\n * Run all unsubscribers added to this list and then clear the list.\n *\n * @returns `true` if all unsubscribers succeeded, `false` otherwise.\n */\n async runAllUnsubscribers(): Promise {\n const unsubs = [...this.unsubscribers].map((unsubscriber) => unsubscriber());\n const results = await Promise.all(unsubs);\n this.unsubscribers.clear();\n return results.every((unsubscriberSucceeded, index) => {\n if (!unsubscriberSucceeded)\n console.error(`UnsubscriberAsyncList ${this.name}: Unsubscriber at index ${index} failed!`);\n\n return unsubscriberSucceeded;\n });\n }\n}\n","var P = Object.defineProperty;\nvar R = (t, e, s) => e in t ? P(t, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : t[e] = s;\nvar n = (t, e, s) => (R(t, typeof e != \"symbol\" ? e + \"\" : e, s), s);\nclass z {\n constructor() {\n n(this, \"books\");\n n(this, \"firstSelectedBookNum\");\n n(this, \"lastSelectedBookNum\");\n n(this, \"count\");\n n(this, \"selectedBookNumbers\");\n n(this, \"selectedBookIds\");\n }\n}\nconst m = [\n \"GEN\",\n \"EXO\",\n \"LEV\",\n \"NUM\",\n \"DEU\",\n \"JOS\",\n \"JDG\",\n \"RUT\",\n \"1SA\",\n \"2SA\",\n // 10\n \"1KI\",\n \"2KI\",\n \"1CH\",\n \"2CH\",\n \"EZR\",\n \"NEH\",\n \"EST\",\n \"JOB\",\n \"PSA\",\n \"PRO\",\n // 20\n \"ECC\",\n \"SNG\",\n \"ISA\",\n \"JER\",\n \"LAM\",\n \"EZK\",\n \"DAN\",\n \"HOS\",\n \"JOL\",\n \"AMO\",\n // 30\n \"OBA\",\n \"JON\",\n \"MIC\",\n \"NAM\",\n \"HAB\",\n \"ZEP\",\n \"HAG\",\n \"ZEC\",\n \"MAL\",\n \"MAT\",\n // 40\n \"MRK\",\n \"LUK\",\n \"JHN\",\n \"ACT\",\n \"ROM\",\n \"1CO\",\n \"2CO\",\n \"GAL\",\n \"EPH\",\n \"PHP\",\n // 50\n \"COL\",\n \"1TH\",\n \"2TH\",\n \"1TI\",\n \"2TI\",\n \"TIT\",\n \"PHM\",\n \"HEB\",\n \"JAS\",\n \"1PE\",\n // 60\n \"2PE\",\n \"1JN\",\n \"2JN\",\n \"3JN\",\n \"JUD\",\n \"REV\",\n \"TOB\",\n \"JDT\",\n \"ESG\",\n \"WIS\",\n // 70\n \"SIR\",\n \"BAR\",\n \"LJE\",\n \"S3Y\",\n \"SUS\",\n \"BEL\",\n \"1MA\",\n \"2MA\",\n \"3MA\",\n \"4MA\",\n // 80\n \"1ES\",\n \"2ES\",\n \"MAN\",\n \"PS2\",\n \"ODA\",\n \"PSS\",\n \"JSA\",\n // actual variant text for JOS, now in LXA text\n \"JDB\",\n // actual variant text for JDG, now in LXA text\n \"TBS\",\n // actual variant text for TOB, now in LXA text\n \"SST\",\n // actual variant text for SUS, now in LXA text // 90\n \"DNT\",\n // actual variant text for DAN, now in LXA text\n \"BLT\",\n // actual variant text for BEL, now in LXA text\n \"XXA\",\n \"XXB\",\n \"XXC\",\n \"XXD\",\n \"XXE\",\n \"XXF\",\n \"XXG\",\n \"FRT\",\n // 100\n \"BAK\",\n \"OTH\",\n \"3ES\",\n // Used previously but really should be 2ES\n \"EZA\",\n // Used to be called 4ES, but not actually in any known project\n \"5EZ\",\n // Used to be called 5ES, but not actually in any known project\n \"6EZ\",\n // Used to be called 6ES, but not actually in any known project\n \"INT\",\n \"CNC\",\n \"GLO\",\n \"TDX\",\n // 110\n \"NDX\",\n \"DAG\",\n \"PS3\",\n \"2BA\",\n \"LBA\",\n \"JUB\",\n \"ENO\",\n \"1MQ\",\n \"2MQ\",\n \"3MQ\",\n // 120\n \"REP\",\n \"4BA\",\n \"LAO\"\n], v = [\n \"XXA\",\n \"XXB\",\n \"XXC\",\n \"XXD\",\n \"XXE\",\n \"XXF\",\n \"XXG\",\n \"FRT\",\n \"BAK\",\n \"OTH\",\n \"INT\",\n \"CNC\",\n \"GLO\",\n \"TDX\",\n \"NDX\"\n], X = [\n \"Genesis\",\n \"Exodus\",\n \"Leviticus\",\n \"Numbers\",\n \"Deuteronomy\",\n \"Joshua\",\n \"Judges\",\n \"Ruth\",\n \"1 Samuel\",\n \"2 Samuel\",\n \"1 Kings\",\n \"2 Kings\",\n \"1 Chronicles\",\n \"2 Chronicles\",\n \"Ezra\",\n \"Nehemiah\",\n \"Esther (Hebrew)\",\n \"Job\",\n \"Psalms\",\n \"Proverbs\",\n \"Ecclesiastes\",\n \"Song of Songs\",\n \"Isaiah\",\n \"Jeremiah\",\n \"Lamentations\",\n \"Ezekiel\",\n \"Daniel (Hebrew)\",\n \"Hosea\",\n \"Joel\",\n \"Amos\",\n \"Obadiah\",\n \"Jonah\",\n \"Micah\",\n \"Nahum\",\n \"Habakkuk\",\n \"Zephaniah\",\n \"Haggai\",\n \"Zechariah\",\n \"Malachi\",\n \"Matthew\",\n \"Mark\",\n \"Luke\",\n \"John\",\n \"Acts\",\n \"Romans\",\n \"1 Corinthians\",\n \"2 Corinthians\",\n \"Galatians\",\n \"Ephesians\",\n \"Philippians\",\n \"Colossians\",\n \"1 Thessalonians\",\n \"2 Thessalonians\",\n \"1 Timothy\",\n \"2 Timothy\",\n \"Titus\",\n \"Philemon\",\n \"Hebrews\",\n \"James\",\n \"1 Peter\",\n \"2 Peter\",\n \"1 John\",\n \"2 John\",\n \"3 John\",\n \"Jude\",\n \"Revelation\",\n \"Tobit\",\n \"Judith\",\n \"Esther Greek\",\n \"Wisdom of Solomon\",\n \"Sirach (Ecclesiasticus)\",\n \"Baruch\",\n \"Letter of Jeremiah\",\n \"Song of 3 Young Men\",\n \"Susanna\",\n \"Bel and the Dragon\",\n \"1 Maccabees\",\n \"2 Maccabees\",\n \"3 Maccabees\",\n \"4 Maccabees\",\n \"1 Esdras (Greek)\",\n \"2 Esdras (Latin)\",\n \"Prayer of Manasseh\",\n \"Psalm 151\",\n \"Odes\",\n \"Psalms of Solomon\",\n // WARNING, if you change the spelling of the *obsolete* tag be sure to update\n // IsObsolete routine\n \"Joshua A. *obsolete*\",\n \"Judges B. *obsolete*\",\n \"Tobit S. *obsolete*\",\n \"Susanna Th. *obsolete*\",\n \"Daniel Th. *obsolete*\",\n \"Bel Th. *obsolete*\",\n \"Extra A\",\n \"Extra B\",\n \"Extra C\",\n \"Extra D\",\n \"Extra E\",\n \"Extra F\",\n \"Extra G\",\n \"Front Matter\",\n \"Back Matter\",\n \"Other Matter\",\n \"3 Ezra *obsolete*\",\n \"Apocalypse of Ezra\",\n \"5 Ezra (Latin Prologue)\",\n \"6 Ezra (Latin Epilogue)\",\n \"Introduction\",\n \"Concordance \",\n \"Glossary \",\n \"Topical Index\",\n \"Names Index\",\n \"Daniel Greek\",\n \"Psalms 152-155\",\n \"2 Baruch (Apocalypse)\",\n \"Letter of Baruch\",\n \"Jubilees\",\n \"Enoch\",\n \"1 Meqabyan\",\n \"2 Meqabyan\",\n \"3 Meqabyan\",\n \"Reproof (Proverbs 25-31)\",\n \"4 Baruch (Rest of Baruch)\",\n \"Laodiceans\"\n], C = K();\nfunction N(t, e = !0) {\n return e && (t = t.toUpperCase()), t in C ? C[t] : 0;\n}\nfunction B(t) {\n return N(t) > 0;\n}\nfunction x(t) {\n const e = typeof t == \"string\" ? N(t) : t;\n return e >= 40 && e <= 66;\n}\nfunction T(t) {\n return (typeof t == \"string\" ? N(t) : t) <= 39;\n}\nfunction O(t) {\n return t <= 66;\n}\nfunction V(t) {\n const e = typeof t == \"string\" ? N(t) : t;\n return I(e) && !O(e);\n}\nfunction* L() {\n for (let t = 1; t <= m.length; t++)\n yield t;\n}\nconst G = 1, S = m.length;\nfunction H() {\n return [\"XXA\", \"XXB\", \"XXC\", \"XXD\", \"XXE\", \"XXF\", \"XXG\"];\n}\nfunction k(t, e = \"***\") {\n const s = t - 1;\n return s < 0 || s >= m.length ? e : m[s];\n}\nfunction A(t) {\n return t <= 0 || t > S ? \"******\" : X[t - 1];\n}\nfunction y(t) {\n return A(N(t));\n}\nfunction I(t) {\n const e = typeof t == \"number\" ? k(t) : t;\n return B(e) && !v.includes(e);\n}\nfunction q(t) {\n const e = typeof t == \"number\" ? k(t) : t;\n return B(e) && v.includes(e);\n}\nfunction U(t) {\n return X[t - 1].includes(\"*obsolete*\");\n}\nfunction K() {\n const t = {};\n for (let e = 0; e < m.length; e++)\n t[m[e]] = e + 1;\n return t;\n}\nconst f = {\n allBookIds: m,\n nonCanonicalIds: v,\n bookIdToNumber: N,\n isBookIdValid: B,\n isBookNT: x,\n isBookOT: T,\n isBookOTNT: O,\n isBookDC: V,\n allBookNumbers: L,\n firstBook: G,\n lastBook: S,\n extraBooks: H,\n bookNumberToId: k,\n bookNumberToEnglishName: A,\n bookIdToEnglishName: y,\n isCanonical: I,\n isExtraMaterial: q,\n isObsolete: U\n};\nvar l = /* @__PURE__ */ ((t) => (t[t.Unknown = 0] = \"Unknown\", t[t.Original = 1] = \"Original\", t[t.Septuagint = 2] = \"Septuagint\", t[t.Vulgate = 3] = \"Vulgate\", t[t.English = 4] = \"English\", t[t.RussianProtestant = 5] = \"RussianProtestant\", t[t.RussianOrthodox = 6] = \"RussianOrthodox\", t))(l || {});\nconst u = class u {\n // private versInfo: Versification;\n constructor(e) {\n n(this, \"name\");\n n(this, \"fullPath\");\n n(this, \"isPresent\");\n n(this, \"hasVerseSegments\");\n n(this, \"isCustomized\");\n n(this, \"baseVersification\");\n n(this, \"scriptureBooks\");\n n(this, \"_type\");\n if (e != null)\n typeof e == \"string\" ? this.name = e : this._type = e;\n else\n throw new Error(\"Argument null\");\n }\n get type() {\n return this._type;\n }\n equals(e) {\n return !e.type || !this.type ? !1 : e.type === this.type;\n }\n};\nn(u, \"Original\", new u(l.Original)), n(u, \"Septuagint\", new u(l.Septuagint)), n(u, \"Vulgate\", new u(l.Vulgate)), n(u, \"English\", new u(l.English)), n(u, \"RussianProtestant\", new u(l.RussianProtestant)), n(u, \"RussianOrthodox\", new u(l.RussianOrthodox));\nlet c = u;\nfunction E(t, e) {\n const s = e[0];\n for (let r = 1; r < e.length; r++)\n t = t.split(e[r]).join(s);\n return t.split(s);\n}\nvar D = /* @__PURE__ */ ((t) => (t[t.Valid = 0] = \"Valid\", t[t.UnknownVersification = 1] = \"UnknownVersification\", t[t.OutOfRange = 2] = \"OutOfRange\", t[t.VerseOutOfOrder = 3] = \"VerseOutOfOrder\", t[t.VerseRepeated = 4] = \"VerseRepeated\", t))(D || {});\nconst i = class i {\n constructor(e, s, r, o) {\n /** Not yet implemented. */\n n(this, \"firstChapter\");\n /** Not yet implemented. */\n n(this, \"lastChapter\");\n /** Not yet implemented. */\n n(this, \"lastVerse\");\n /** Not yet implemented. */\n n(this, \"hasSegmentsDefined\");\n /** Not yet implemented. */\n n(this, \"text\");\n /** Not yet implemented. */\n n(this, \"BBBCCCVVVS\");\n /** Not yet implemented. */\n n(this, \"longHashCode\");\n /** The versification of the reference. */\n n(this, \"versification\");\n n(this, \"rtlMark\", \"‏\");\n n(this, \"_bookNum\", 0);\n n(this, \"_chapterNum\", 0);\n n(this, \"_verseNum\", 0);\n n(this, \"_verse\");\n if (r == null && o == null)\n if (e != null && typeof e == \"string\") {\n const a = e, h = s != null && s instanceof c ? s : void 0;\n this.setEmpty(h), this.parse(a);\n } else if (e != null && typeof e == \"number\") {\n const a = s != null && s instanceof c ? s : void 0;\n this.setEmpty(a), this._verseNum = e % i.chapterDigitShifter, this._chapterNum = Math.floor(\n e % i.bookDigitShifter / i.chapterDigitShifter\n ), this._bookNum = Math.floor(e / i.bookDigitShifter);\n } else if (s == null)\n if (e != null && e instanceof i) {\n const a = e;\n this._bookNum = a.bookNum, this._chapterNum = a.chapterNum, this._verseNum = a.verseNum, this._verse = a.verse, this.versification = a.versification;\n } else {\n if (e == null)\n return;\n const a = e instanceof c ? e : i.defaultVersification;\n this.setEmpty(a);\n }\n else\n throw new Error(\"VerseRef constructor not supported.\");\n else if (e != null && s != null && r != null)\n if (typeof e == \"string\" && typeof s == \"string\" && typeof r == \"string\")\n this.setEmpty(o), this.updateInternal(e, s, r);\n else if (typeof e == \"number\" && typeof s == \"number\" && typeof r == \"number\")\n this._bookNum = e, this._chapterNum = s, this._verseNum = r, this.versification = o ?? i.defaultVersification;\n else\n throw new Error(\"VerseRef constructor not supported.\");\n else\n throw new Error(\"VerseRef constructor not supported.\");\n }\n /**\n * @deprecated Will be removed in v2. Replace `VerseRef.parse('...')` with `new VerseRef('...')`\n * or refactor to use `VerseRef.tryParse('...')` which has a different return type.\n */\n static parse(e, s = i.defaultVersification) {\n const r = new i(s);\n return r.parse(e), r;\n }\n /**\n * Determines if the verse string is in a valid format (does not consider versification).\n */\n static isVerseParseable(e) {\n return e.length > 0 && \"0123456789\".includes(e[0]) && !e.endsWith(this.verseRangeSeparator) && !e.endsWith(this.verseSequenceIndicator);\n }\n /**\n * Tries to parse the specified string into a verse reference.\n * @param str - The string to attempt to parse.\n * @returns success: `true` if the specified string was successfully parsed, `false` otherwise.\n * @returns verseRef: The result of the parse if successful, or empty VerseRef if it failed\n */\n static tryParse(e) {\n let s;\n try {\n return s = i.parse(e), { success: !0, verseRef: s };\n } catch (r) {\n if (r instanceof d)\n return s = new i(), { success: !1, verseRef: s };\n throw r;\n }\n }\n /**\n * Gets the reference as a comparable integer where the book, chapter, and verse each occupy 3\n * digits.\n * @param bookNum - Book number (this is 1-based, not an index).\n * @param chapterNum - Chapter number.\n * @param verseNum - Verse number.\n * @returns The reference as a comparable integer where the book, chapter, and verse each occupy 3\n * digits.\n */\n static getBBBCCCVVV(e, s, r) {\n return e % i.bcvMaxValue * i.bookDigitShifter + (s >= 0 ? s % i.bcvMaxValue * i.chapterDigitShifter : 0) + (r >= 0 ? r % i.bcvMaxValue : 0);\n }\n /**\n * Parses a verse string and gets the leading numeric portion as a number.\n * @param verseStr - verse string to parse\n * @returns true if the entire string could be parsed as a single, simple verse number (1-999);\n * false if the verse string represented a verse bridge, contained segment letters, or was invalid\n */\n static tryGetVerseNum(e) {\n let s;\n if (!e)\n return s = -1, { success: !0, vNum: s };\n s = 0;\n let r;\n for (let o = 0; o < e.length; o++) {\n if (r = e[o], r < \"0\" || r > \"9\")\n return o === 0 && (s = -1), { success: !1, vNum: s };\n if (s = s * 10 + +r - +\"0\", s > i.bcvMaxValue)\n return s = -1, { success: !1, vNum: s };\n }\n return { success: !0, vNum: s };\n }\n /**\n * Checks to see if a VerseRef hasn't been set - all values are the default.\n */\n get isDefault() {\n return this.bookNum === 0 && this.chapterNum === 0 && this.verseNum === 0 && this.versification == null;\n }\n /**\n * Gets whether the verse contains multiple verses.\n */\n get hasMultiple() {\n return this._verse != null && (this._verse.includes(i.verseRangeSeparator) || this._verse.includes(i.verseSequenceIndicator));\n }\n /**\n * Gets or sets the book of the reference. Book is the 3-letter abbreviation in capital letters,\n * e.g. `'MAT'`.\n */\n get book() {\n return f.bookNumberToId(this.bookNum, \"\");\n }\n set book(e) {\n this.bookNum = f.bookIdToNumber(e);\n }\n /**\n * Gets or sets the chapter of the reference,. e.g. `'3'`.\n */\n get chapter() {\n return this.isDefault || this._chapterNum < 0 ? \"\" : this._chapterNum.toString();\n }\n set chapter(e) {\n const s = +e;\n this._chapterNum = Number.isInteger(s) ? s : -1;\n }\n /**\n * Gets or sets the verse of the reference, including range, segments, and sequences, e.g. `'4'`,\n * or `'4b-5a, 7'`.\n */\n get verse() {\n return this._verse != null ? this._verse : this.isDefault || this._verseNum < 0 ? \"\" : this._verseNum.toString();\n }\n set verse(e) {\n const { success: s, vNum: r } = i.tryGetVerseNum(e);\n this._verse = s ? void 0 : e.replace(this.rtlMark, \"\"), this._verseNum = r, !(this._verseNum >= 0) && ({ vNum: this._verseNum } = i.tryGetVerseNum(this._verse));\n }\n /**\n * Get or set Book based on book number, e.g. `42`.\n */\n get bookNum() {\n return this._bookNum;\n }\n set bookNum(e) {\n if (e <= 0 || e > f.lastBook)\n throw new d(\n \"BookNum must be greater than zero and less than or equal to last book\"\n );\n this._bookNum = e;\n }\n /**\n * Gets or sets the chapter number, e.g. `3`. `-1` if not valid.\n */\n get chapterNum() {\n return this._chapterNum;\n }\n set chapterNum(e) {\n this.chapterNum = e;\n }\n /**\n * Gets or sets verse start number, e.g. `4`. `-1` if not valid.\n */\n get verseNum() {\n return this._verseNum;\n }\n set verseNum(e) {\n this._verseNum = e;\n }\n /**\n * String representing the versification (should ONLY be used for serialization/deserialization).\n *\n * @remarks This is for backwards compatibility when ScrVers was an enumeration.\n */\n get versificationStr() {\n var e;\n return (e = this.versification) == null ? void 0 : e.name;\n }\n set versificationStr(e) {\n this.versification = this.versification != null ? new c(e) : void 0;\n }\n /**\n * Determines if the reference is valid.\n */\n get valid() {\n return this.validStatus === 0;\n }\n /**\n * Get the valid status for this reference.\n */\n get validStatus() {\n return this.validateVerse(i.verseRangeSeparators, i.verseSequenceIndicators);\n }\n /**\n * Gets the reference as a comparable integer where the book,\n * chapter, and verse each occupy three digits and the verse is 0.\n */\n get BBBCCC() {\n return i.getBBBCCCVVV(this._bookNum, this._chapterNum, 0);\n }\n /**\n * Gets the reference as a comparable integer where the book,\n * chapter, and verse each occupy three digits. If verse is not null\n * (i.e., this reference represents a complex reference with verse\n * segments or bridge) this cannot be used for an exact comparison.\n */\n get BBBCCCVVV() {\n return i.getBBBCCCVVV(this._bookNum, this._chapterNum, this._verseNum);\n }\n /**\n * Gets whether the verse is defined as an excluded verse in the versification.\n * Does not handle verse ranges.\n */\n // eslint-disable-next-line @typescript-eslint/class-literal-property-style\n get isExcluded() {\n return !1;\n }\n /**\n * Parses the reference in the specified string.\n * Optionally versification can follow reference as in GEN 3:11/4\n * Throw an exception if\n * - invalid book name\n * - chapter number is missing or not a number\n * - verse number is missing or does not start with a number\n * - versification is invalid\n * @param verseStr - string to parse e.g. 'MAT 3:11'\n */\n parse(e) {\n if (e = e.replace(this.rtlMark, \"\"), e.includes(\"/\")) {\n const a = e.split(\"/\");\n if (e = a[0], a.length > 1)\n try {\n const h = +a[1].trim();\n this.versification = new c(l[h]);\n } catch {\n throw new d(\"Invalid reference : \" + e);\n }\n }\n const s = e.trim().split(\" \");\n if (s.length !== 2)\n throw new d(\"Invalid reference : \" + e);\n const r = s[1].split(\":\"), o = +r[0];\n if (r.length !== 2 || f.bookIdToNumber(s[0]) === 0 || !Number.isInteger(o) || o < 0 || !i.isVerseParseable(r[1]))\n throw new d(\"Invalid reference : \" + e);\n this.updateInternal(s[0], r[0], r[1]);\n }\n /**\n * Simplifies this verse ref so that it has no bridging of verses or\n * verse segments like `'1a'`.\n */\n simplify() {\n this._verse = void 0;\n }\n /**\n * Makes a clone of the reference.\n *\n * @returns The cloned VerseRef.\n */\n clone() {\n return new i(this);\n }\n toString() {\n const e = this.book;\n return e === \"\" ? \"\" : `${e} ${this.chapter}:${this.verse}`;\n }\n /**\n * Compares this `VerseRef` with supplied one.\n * @param verseRef - object to compare this one to.\n * @returns `true` if this `VerseRef` is equal to the supplied on, `false` otherwise.\n */\n equals(e) {\n return e instanceof i ? e._bookNum === this._bookNum && e._chapterNum === this._chapterNum && e._verseNum === this._verseNum && e.verse === this.verse && e.versification != null && this.versification != null && e.versification.equals(this.versification) : !1;\n }\n /**\n * Enumerate all individual verses contained in a VerseRef.\n * Verse ranges are indicated by \"-\" and consecutive verses by \",\"s.\n * Examples:\n * GEN 1:2 returns GEN 1:2\n * GEN 1:1a-3b,5 returns GEN 1:1a, GEN 1:2, GEN 1:3b, GEN 1:5\n * GEN 1:2a-2c returns //! ??????\n *\n * @param specifiedVersesOnly - if set to true return only verses that are\n * explicitly specified only, not verses within a range. Defaults to `false`.\n * @param verseRangeSeparators - Verse range separators.\n * Defaults to `VerseRef.verseRangeSeparators`.\n * @param verseSequenceSeparators - Verse sequence separators.\n * Defaults to `VerseRef.verseSequenceIndicators`.\n * @returns An array of all single verse references in this VerseRef.\n */\n allVerses(e = !1, s = i.verseRangeSeparators, r = i.verseSequenceIndicators) {\n if (this._verse == null || this.chapterNum <= 0)\n return [this.clone()];\n const o = [], a = E(this._verse, r);\n for (const h of a.map((g) => E(g, s))) {\n const g = this.clone();\n g.verse = h[0];\n const w = g.verseNum;\n if (o.push(g), h.length > 1) {\n const p = this.clone();\n if (p.verse = h[1], !e)\n for (let b = w + 1; b < p.verseNum; b++) {\n const J = new i(\n this._bookNum,\n this._chapterNum,\n b,\n this.versification\n );\n this.isExcluded || o.push(J);\n }\n o.push(p);\n }\n }\n return o;\n }\n /**\n * Validates a verse number using the supplied separators rather than the defaults.\n */\n validateVerse(e, s) {\n if (!this.verse)\n return this.internalValid;\n let r = 0;\n for (const o of this.allVerses(!0, e, s)) {\n const a = o.internalValid;\n if (a !== 0)\n return a;\n const h = o.BBBCCCVVV;\n if (r > h)\n return 3;\n if (r === h)\n return 4;\n r = h;\n }\n return 0;\n }\n /**\n * Gets whether a single verse reference is valid.\n */\n get internalValid() {\n return this.versification == null ? 1 : this._bookNum <= 0 || this._bookNum > f.lastBook ? 2 : (f.isCanonical(this._bookNum), 0);\n }\n setEmpty(e = i.defaultVersification) {\n this._bookNum = 0, this._chapterNum = -1, this._verse = void 0, this.versification = e;\n }\n updateInternal(e, s, r) {\n this.bookNum = f.bookIdToNumber(e), this.chapter = s, this.verse = r;\n }\n};\nn(i, \"defaultVersification\", c.English), n(i, \"verseRangeSeparator\", \"-\"), n(i, \"verseSequenceIndicator\", \",\"), n(i, \"verseRangeSeparators\", [i.verseRangeSeparator]), n(i, \"verseSequenceIndicators\", [i.verseSequenceIndicator]), n(i, \"chapterDigitShifter\", 1e3), n(i, \"bookDigitShifter\", i.chapterDigitShifter * i.chapterDigitShifter), n(i, \"bcvMaxValue\", i.chapterDigitShifter - 1), /**\n * The valid status of the VerseRef.\n */\nn(i, \"ValidStatusType\", D);\nlet M = i;\nclass d extends Error {\n}\nexport {\n z as BookSet,\n f as Canon,\n c as ScrVers,\n l as ScrVersType,\n M as VerseRef,\n d as VerseRefException\n};\n//# sourceMappingURL=index.es.js.map\n","\"use strict\"\r\n\r\n// Based on: https://github.com/lodash/lodash/blob/6018350ac10d5ce6a5b7db625140b82aeab804df/.internal/unicodeSize.js\r\n\r\nmodule.exports = () => {\r\n\t// Used to compose unicode character classes.\r\n\tconst astralRange = \"\\\\ud800-\\\\udfff\"\r\n\tconst comboMarksRange = \"\\\\u0300-\\\\u036f\"\r\n\tconst comboHalfMarksRange = \"\\\\ufe20-\\\\ufe2f\"\r\n\tconst comboSymbolsRange = \"\\\\u20d0-\\\\u20ff\"\r\n\tconst comboMarksExtendedRange = \"\\\\u1ab0-\\\\u1aff\"\r\n\tconst comboMarksSupplementRange = \"\\\\u1dc0-\\\\u1dff\"\r\n\tconst comboRange = comboMarksRange + comboHalfMarksRange + comboSymbolsRange + comboMarksExtendedRange + comboMarksSupplementRange\r\n\tconst varRange = \"\\\\ufe0e\\\\ufe0f\"\r\n\tconst familyRange = \"\\\\uD83D\\\\uDC69\\\\uD83C\\\\uDFFB\\\\u200D\\\\uD83C\\\\uDF93\"\r\n\r\n\t// Used to compose unicode capture groups.\r\n\tconst astral = `[${astralRange}]`\r\n\tconst combo = `[${comboRange}]`\r\n\tconst fitz = \"\\\\ud83c[\\\\udffb-\\\\udfff]\"\r\n\tconst modifier = `(?:${combo}|${fitz})`\r\n\tconst nonAstral = `[^${astralRange}]`\r\n\tconst regional = \"(?:\\\\uD83C[\\\\uDDE6-\\\\uDDFF]){2}\"\r\n\tconst surrogatePair = \"[\\\\ud800-\\\\udbff][\\\\udc00-\\\\udfff]\"\r\n\tconst zwj = \"\\\\u200d\"\r\n\tconst blackFlag = \"(?:\\\\ud83c\\\\udff4\\\\udb40\\\\udc67\\\\udb40\\\\udc62\\\\udb40(?:\\\\udc65|\\\\udc73|\\\\udc77)\\\\udb40(?:\\\\udc6e|\\\\udc63|\\\\udc6c)\\\\udb40(?:\\\\udc67|\\\\udc74|\\\\udc73)\\\\udb40\\\\udc7f)\"\r\n\tconst family = `[${familyRange}]`\r\n\r\n\t// Used to compose unicode regexes.\r\n\tconst optModifier = `${modifier}?`\r\n\tconst optVar = `[${varRange}]?`\r\n\tconst optJoin = `(?:${zwj}(?:${[nonAstral, regional, surrogatePair].join(\"|\")})${optVar + optModifier})*`\r\n\tconst seq = optVar + optModifier + optJoin\r\n\tconst nonAstralCombo = `${nonAstral}${combo}?`\r\n\tconst symbol = `(?:${[nonAstralCombo, combo, regional, surrogatePair, astral, family].join(\"|\")})`\r\n\r\n\t// Used to match [String symbols](https://mathiasbynens.be/notes/javascript-unicode).\r\n\treturn new RegExp(`${blackFlag}|${fitz}(?=${fitz})|${symbol + seq}`, \"g\")\r\n}\r\n","\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\n// @ts-ignore\nvar char_regex_1 = __importDefault(require(\"char-regex\"));\n/**\n * Converts a string to an array of string chars\n * @param {string} str The string to turn into array\n * @returns {string[]}\n */\nfunction toArray(str) {\n if (typeof str !== 'string') {\n throw new Error('A string is expected as input');\n }\n return str.match(char_regex_1.default()) || [];\n}\nexports.toArray = toArray;\n/**\n * Returns the length of a string\n *\n * @export\n * @param {string} str\n * @returns {number}\n */\nfunction length(str) {\n // Check for input\n if (typeof str !== 'string') {\n throw new Error('Input must be a string');\n }\n var match = str.match(char_regex_1.default());\n return match === null ? 0 : match.length;\n}\nexports.length = length;\n/**\n * Returns a substring by providing start and end position\n *\n * @export\n * @param {string} str\n * @param {number} [begin=0] Starting position\n * @param {number} end End position\n * @returns {string}\n */\nfunction substring(str, begin, end) {\n if (begin === void 0) { begin = 0; }\n // Check for input\n if (typeof str !== 'string') {\n throw new Error('Input must be a string');\n }\n // Even though negative numbers work here, theyre not in the spec\n if (typeof begin !== 'number' || begin < 0) {\n begin = 0;\n }\n if (typeof end === 'number' && end < 0) {\n end = 0;\n }\n var match = str.match(char_regex_1.default());\n if (!match)\n return '';\n return match.slice(begin, end).join('');\n}\nexports.substring = substring;\n/**\n * Returns a substring by providing start position and length\n *\n * @export\n * @param {string} str\n * @param {number} [begin=0] Starting position\n * @param {number} len Desired length\n * @returns {string}\n */\nfunction substr(str, begin, len) {\n if (begin === void 0) { begin = 0; }\n // Check for input\n if (typeof str !== 'string') {\n throw new Error('Input must be a string');\n }\n var strLength = length(str);\n // Fix type\n if (typeof begin !== 'number') {\n begin = parseInt(begin, 10);\n }\n // Return zero-length string if got oversize number.\n if (begin >= strLength) {\n return '';\n }\n // Calculating postive version of negative value.\n if (begin < 0) {\n begin += strLength;\n }\n var end;\n if (typeof len === 'undefined') {\n end = strLength;\n }\n else {\n // Fix type\n if (typeof len !== 'number') {\n len = parseInt(len, 10);\n }\n end = len >= 0 ? len + begin : begin;\n }\n var match = str.match(char_regex_1.default());\n if (!match)\n return '';\n return match.slice(begin, end).join('');\n}\nexports.substr = substr;\n/**\n * Enforces a string to be a certain length by\n * adding or removing characters\n *\n * @export\n * @param {string} str\n * @param {number} [limit=16] Limit\n * @param {string} [padString='#'] The Pad String\n * @param {string} [padPosition='right'] The Pad Position\n * @returns {string}\n */\nfunction limit(str, limit, padString, padPosition) {\n if (limit === void 0) { limit = 16; }\n if (padString === void 0) { padString = '#'; }\n if (padPosition === void 0) { padPosition = 'right'; }\n // Input should be a string, limit should be a number\n if (typeof str !== 'string' || typeof limit !== 'number') {\n throw new Error('Invalid arguments specified');\n }\n // Pad position should be either left or right\n if (['left', 'right'].indexOf(padPosition) === -1) {\n throw new Error('Pad position should be either left or right');\n }\n // Pad string can be anything, we convert it to string\n if (typeof padString !== 'string') {\n padString = String(padString);\n }\n // Calculate string length considering astral code points\n var strLength = length(str);\n if (strLength > limit) {\n return substring(str, 0, limit);\n }\n else if (strLength < limit) {\n var padRepeats = padString.repeat(limit - strLength);\n return padPosition === 'left' ? padRepeats + str : str + padRepeats;\n }\n return str;\n}\nexports.limit = limit;\n/**\n * Returns the index of the first occurrence of a given string\n *\n * @export\n * @param {string} str\n * @param {string} [searchStr] the string to search\n * @param {number} [pos] starting position\n * @returns {number}\n */\nfunction indexOf(str, searchStr, pos) {\n if (pos === void 0) { pos = 0; }\n if (typeof str !== 'string') {\n throw new Error('Input must be a string');\n }\n if (str === '') {\n if (searchStr === '') {\n return 0;\n }\n return -1;\n }\n // fix type\n pos = Number(pos);\n pos = isNaN(pos) ? 0 : pos;\n searchStr = String(searchStr);\n var strArr = toArray(str);\n if (pos >= strArr.length) {\n if (searchStr === '') {\n return strArr.length;\n }\n return -1;\n }\n if (searchStr === '') {\n return pos;\n }\n var searchArr = toArray(searchStr);\n var finded = false;\n var index;\n for (index = pos; index < strArr.length; index += 1) {\n var searchIndex = 0;\n while (searchIndex < searchArr.length &&\n searchArr[searchIndex] === strArr[index + searchIndex]) {\n searchIndex += 1;\n }\n if (searchIndex === searchArr.length &&\n searchArr[searchIndex - 1] === strArr[index + searchIndex - 1]) {\n finded = true;\n break;\n }\n }\n return finded ? index : -1;\n}\nexports.indexOf = indexOf;\n","import { LocalizeKey } from 'menus.model';\nimport {\n indexOf as stringzIndexOf,\n substring as stringzSubstring,\n length as stringzLength,\n toArray as stringzToArray,\n limit as stringzLimit,\n substr as stringzSubstr,\n} from 'stringz';\n\n/**\n * This function mirrors the `at` function from the JavaScript Standard String object. It handles\n * Unicode code points instead of UTF-16 character codes.\n *\n * Finds the Unicode code point at the given index.\n *\n * @param string String to index\n * @param index Position of the character to be returned in range of -length(string) to\n * length(string)\n * @returns New string consisting of the Unicode code point located at the specified offset,\n * undefined if index is out of bounds\n */\nexport function at(string: string, index: number): string | undefined {\n if (index > stringLength(string) || index < -stringLength(string)) return undefined;\n return substr(string, index, 1);\n}\n\n/**\n * This function mirrors the `charAt` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns a new string consisting of the single unicode code point at the given index.\n *\n * @param string String to index\n * @param index Position of the string character to be returned, in the range of 0 to\n * length(string)-1\n * @returns New string consisting of the Unicode code point located at the specified offset, empty\n * string if index is out of bounds\n */\nexport function charAt(string: string, index: number): string {\n if (index < 0 || index > stringLength(string) - 1) return '';\n return substr(string, index, 1);\n}\n\n/**\n * This function mirrors the `codePointAt` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns a non-negative integer that is the Unicode code point value of the character starting at\n * the given index.\n *\n * @param string String to index\n * @param index Position of the string character to be returned, in the range of 0 to\n * length(string)-1\n * @returns Non-negative integer representing the code point value of the character at the given\n * index, or undefined if there is no element at that position\n */\nexport function codePointAt(string: string, index: number): number | undefined {\n if (index < 0 || index > stringLength(string) - 1) return undefined;\n return substr(string, index, 1).codePointAt(0);\n}\n\n/**\n * This function mirrors the `endsWith` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Determines whether a string ends with the characters of this string.\n *\n * @param string String to search through\n * @param searchString Characters to search for at the end of the string\n * @param endPosition End position where searchString is expected to be found. Default is\n * `length(string)`\n * @returns True if it ends with searchString, false if it does not\n */\nexport function endsWith(\n string: string,\n searchString: string,\n endPosition: number = stringLength(string),\n): boolean {\n const lastIndexOfSearchString = lastIndexOf(string, searchString);\n if (lastIndexOfSearchString === -1) return false;\n if (lastIndexOfSearchString + stringLength(searchString) !== endPosition) return false;\n return true;\n}\n\n/**\n * Get the index of the closest closing curly brace in a string.\n *\n * Note: when escaped, gets the index of the curly brace, not the backslash before it.\n *\n * @param str String to search\n * @param index Index at which to start searching. Inclusive of this index\n * @param escaped Whether to search for an escaped or an unescaped closing curly brace\n * @returns Index of closest closing curly brace or -1 if not found\n */\nfunction indexOfClosestClosingCurlyBrace(str: string, index: number, escaped: boolean) {\n if (index < 0) return -1;\n if (escaped) {\n if (charAt(str, index) === '}' && charAt(str, index - 1) === '\\\\') return index;\n const closeCurlyBraceIndex = indexOf(str, '\\\\}', index);\n return closeCurlyBraceIndex >= 0 ? closeCurlyBraceIndex + 1 : closeCurlyBraceIndex;\n }\n\n let i = index;\n const strLength = stringLength(str);\n while (i < strLength) {\n i = indexOf(str, '}', i);\n\n if (i === -1 || charAt(str, i - 1) !== '\\\\') break;\n\n // Didn't find an un-escaped close brace, so keep looking\n i += 1;\n }\n\n return i >= strLength ? -1 : i;\n}\n\n/**\n * Formats a string, replacing {localization key} with the localization (or multiple localizations\n * if there are multiple in the string). Will also remove \\ before curly braces if curly braces are\n * escaped with a backslash in order to preserve the curly braces. E.g. 'Hi, this is {name}! I like\n * `\\{curly braces\\}`! would become Hi, this is Jim! I like {curly braces}!\n *\n * If the key in unescaped braces is not found, just return the key without the braces. Empty\n * unescaped curly braces will just return a string without the braces e.g. ('I am {Nemo}', {\n * 'name': 'Jim'}) would return 'I am Nemo'.\n *\n * @param str String to format\n * @returns Formatted string\n */\nexport function formatReplacementString(str: string, replacers: { [key: string]: string }): string {\n let updatedStr = str;\n\n let i = 0;\n while (i < stringLength(updatedStr)) {\n switch (charAt(updatedStr, i)) {\n case '{':\n if (charAt(updatedStr, i - 1) !== '\\\\') {\n // This character is an un-escaped open curly brace. Try to match and replace\n const closeCurlyBraceIndex = indexOfClosestClosingCurlyBrace(updatedStr, i, false);\n if (closeCurlyBraceIndex >= 0) {\n // We have matching open and close indices. Try to replace the contents\n const replacerKey = substring(updatedStr, i + 1, closeCurlyBraceIndex);\n // Replace with the replacer string or just remove the curly braces\n const replacerString = replacerKey in replacers ? replacers[replacerKey] : replacerKey;\n\n updatedStr = `${substring(updatedStr, 0, i)}${replacerString}${substring(updatedStr, closeCurlyBraceIndex + 1)}`;\n // Put our index at the closing brace adjusted for the new string length minus two\n // because we are removing the curly braces\n // Ex: \"stuff {and} things\"\n // Replacer for and: n'\n // closeCurlyBraceIndex is 10\n // \"stuff n' things\"\n // i = 10 + 2 - 3 - 2 = 7\n i = closeCurlyBraceIndex + stringLength(replacerString) - stringLength(replacerKey) - 2;\n } else {\n // This is an unexpected un-escaped open curly brace with no matching closing curly\n // brace. Just ignore, I guess\n }\n } else {\n // This character is an escaped open curly brace. Remove the escape\n updatedStr = `${substring(updatedStr, 0, i - 1)}${substring(updatedStr, i)}`;\n // Adjust our index because we removed the escape\n i -= 1;\n }\n break;\n case '}':\n if (charAt(updatedStr, i - 1) !== '\\\\') {\n // This character is an un-escaped closing curly brace with no matching open curly\n // brace. Just ignore, I guess\n } else {\n // This character is an escaped closing curly brace. Remove the escape\n updatedStr = `${substring(updatedStr, 0, i - 1)}${substring(updatedStr, i)}`;\n // Adjust our index because we removed the escape\n i -= 1;\n }\n break;\n default:\n // No need to do anything with other characters at this point\n break;\n }\n\n i += 1;\n }\n\n return updatedStr;\n}\n/**\n * This function mirrors the `includes` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Performs a case-sensitive search to determine if searchString is found in string.\n *\n * @param string String to search through\n * @param searchString String to search for\n * @param position Position within the string to start searching for searchString. Default is `0`\n * @returns True if search string is found, false if it is not\n */\nexport function includes(string: string, searchString: string, position: number = 0): boolean {\n const partialString = substring(string, position);\n const indexOfSearchString = indexOf(partialString, searchString);\n if (indexOfSearchString === -1) return false;\n return true;\n}\n\n/**\n * This function mirrors the `indexOf` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns the index of the first occurrence of a given string.\n *\n * @param string String to search through\n * @param searchString The string to search for\n * @param position Start of searching. Default is `0`\n * @returns Index of the first occurrence of a given string\n */\nexport function indexOf(\n string: string,\n searchString: string,\n position: number | undefined = 0,\n): number {\n return stringzIndexOf(string, searchString, position);\n}\n\n/**\n * This function mirrors the `lastIndexOf` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Searches this string and returns the index of the last occurrence of the specified substring.\n *\n * @param string String to search through\n * @param searchString Substring to search for\n * @param position The index at which to begin searching. If omitted, the search begins at the end\n * of the string. Default is `undefined`\n * @returns Index of the last occurrence of searchString found, or -1 if not found.\n */\nexport function lastIndexOf(string: string, searchString: string, position?: number): number {\n let validatedPosition = position === undefined ? stringLength(string) : position;\n\n if (validatedPosition < 0) {\n validatedPosition = 0;\n } else if (validatedPosition >= stringLength(string)) {\n validatedPosition = stringLength(string) - 1;\n }\n\n for (let index = validatedPosition; index >= 0; index--) {\n if (substr(string, index, stringLength(searchString)) === searchString) {\n return index;\n }\n }\n\n return -1;\n}\n\n/**\n * This function mirrors the `length` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes. Since `length` appears to be a\n * reserved keyword, the function was renamed to `stringLength`\n *\n * Returns the length of a string.\n *\n * @param string String to return the length for\n * @returns Number that is length of the starting string\n */\nexport function stringLength(string: string): number {\n return stringzLength(string);\n}\n\n/**\n * This function mirrors the `normalize` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns the Unicode Normalization Form of this string.\n *\n * @param string The starting string\n * @param form Form specifying the Unicode Normalization Form. Default is `'NFC'`\n * @returns A string containing the Unicode Normalization Form of the given string.\n */\nexport function normalize(string: string, form: 'NFC' | 'NFD' | 'NFKC' | 'NFKD' | 'none'): string {\n const upperCaseForm = form.toUpperCase();\n if (upperCaseForm === 'NONE') {\n return string;\n }\n return string.normalize(upperCaseForm);\n}\n\n/**\n * Compares two strings using an ordinal comparison approach based on the specified collation\n * options. This function uses the built-in `localeCompare` method with the 'en' locale and the\n * provided collation options to compare the strings.\n *\n * @param string1 The first string to compare.\n * @param string2 The second string to compare.\n * @param options Optional. The collation options used for comparison.\n * @returns A number indicating the result of the comparison: - Negative value if string1 precedes\n * string2 in sorting order. - Zero if string1 and string2 are equivalent in sorting order. -\n * Positive value if string1 follows string2 in sorting order.\n */\nexport function ordinalCompare(\n string1: string,\n string2: string,\n options?: Intl.CollatorOptions,\n): number {\n return string1.localeCompare(string2, 'en', options);\n}\n\n/**\n * This function mirrors the `padEnd` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Pads this string with another string (multiple times, if needed) until the resulting string\n * reaches the given length. The padding is applied from the end of this string.\n *\n * @param string String to add padding too\n * @param targetLength The length of the resulting string once the starting string has been padded.\n * If value is less than or equal to length(string), then string is returned as is.\n * @param padString The string to pad the current string with. If padString is too long to stay\n * within targetLength, it will be truncated. Default is `\" \"`\n * @returns String with appropriate padding at the end\n */\n// Note: Limit with padString only works when length(padString) = 1, will be fixed with https://github.com/sallar/stringz/pull/59\nexport function padEnd(string: string, targetLength: number, padString: string = ' '): string {\n if (targetLength <= stringLength(string)) return string;\n return stringzLimit(string, targetLength, padString, 'right');\n}\n\n/**\n * This function mirrors the `padStart` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Pads this string with another string (multiple times, if needed) until the resulting string\n * reaches the given length. The padding is applied from the start of this string.\n *\n * @param string String to add padding too\n * @param targetLength The length of the resulting string once the starting string has been padded.\n * If value is less than or equal to length(string), then string is returned as is.\n * @param padString The string to pad the current string with. If padString is too long to stay\n * within the targetLength, it will be truncated from the end. Default is `\" \"`\n * @returns String with of specified targetLength with padString applied from the start\n */\n// Note: Limit with padString only works when length(padString) = 1, will be fixed with https://github.com/sallar/stringz/pull/59\nexport function padStart(string: string, targetLength: number, padString: string = ' '): string {\n if (targetLength <= stringLength(string)) return string;\n return stringzLimit(string, targetLength, padString, 'left');\n}\n\n// This is a helper function that performs a correction on the slice index to make sure it\n// cannot go out of bounds\nfunction correctSliceIndex(length: number, index: number) {\n if (index > length) return length;\n if (index < -length) return 0;\n if (index < 0) return index + length;\n return index;\n}\n\n/**\n * This function mirrors the `slice` function from the JavaScript Standard String object. It handles\n * Unicode code points instead of UTF-16 character codes.\n *\n * Extracts a section of this string and returns it as a new string, without modifying the original\n * string.\n *\n * @param string The starting string\n * @param indexStart The index of the first character to include in the returned substring.\n * @param indexEnd The index of the first character to exclude from the returned substring.\n * @returns A new string containing the extracted section of the string.\n */\nexport function slice(string: string, indexStart: number, indexEnd?: number): string {\n const length: number = stringLength(string);\n if (\n indexStart > length ||\n (indexEnd &&\n ((indexStart > indexEnd &&\n !(indexStart >= 0 && indexStart < length && indexEnd < 0 && indexEnd > -length)) ||\n indexEnd < -length))\n )\n return '';\n\n const newStart = correctSliceIndex(length, indexStart);\n const newEnd = indexEnd ? correctSliceIndex(length, indexEnd) : undefined;\n\n return substring(string, newStart, newEnd);\n}\n\n/**\n * This function mirrors the `split` function from the JavaScript Standard String object. It handles\n * Unicode code points instead of UTF-16 character codes.\n *\n * Takes a pattern and divides the string into an ordered list of substrings by searching for the\n * pattern, puts these substrings into an array, and returns the array.\n *\n * @param string The string to split\n * @param separator The pattern describing where each split should occur\n * @param splitLimit Limit on the number of substrings to be included in the array. Splits the\n * string at each occurrence of specified separator, but stops when limit entries have been placed\n * in the array.\n * @returns An array of strings, split at each point where separator occurs in the starting string.\n * Returns undefined if separator is not found in string.\n */\nexport function split(string: string, separator: string | RegExp, splitLimit?: number): string[] {\n const result: string[] = [];\n\n if (splitLimit !== undefined && splitLimit <= 0) {\n return [string];\n }\n\n if (separator === '') return toArray(string).slice(0, splitLimit);\n\n let regexSeparator = separator;\n if (\n typeof separator === 'string' ||\n (separator instanceof RegExp && !includes(separator.flags, 'g'))\n ) {\n regexSeparator = new RegExp(separator, 'g');\n }\n\n const matches: RegExpMatchArray | null = string.match(regexSeparator);\n\n let currentIndex = 0;\n\n if (!matches) return [string];\n\n for (let index = 0; index < (splitLimit ? splitLimit - 1 : matches.length); index++) {\n const matchIndex = indexOf(string, matches[index], currentIndex);\n const matchLength = stringLength(matches[index]);\n\n result.push(substring(string, currentIndex, matchIndex));\n currentIndex = matchIndex + matchLength;\n\n if (splitLimit !== undefined && result.length === splitLimit) {\n break;\n }\n }\n\n result.push(substring(string, currentIndex));\n\n return result;\n}\n\n/**\n * This function mirrors the `startsWith` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Determines whether the string begins with the characters of a specified string, returning true or\n * false as appropriate.\n *\n * @param string String to search through\n * @param searchString The characters to be searched for at the start of this string.\n * @param position The start position at which searchString is expected to be found (the index of\n * searchString's first character). Default is `0`\n * @returns True if the given characters are found at the beginning of the string, including when\n * searchString is an empty string; otherwise, false.\n */\nexport function startsWith(string: string, searchString: string, position: number = 0): boolean {\n const indexOfSearchString = indexOf(string, searchString, position);\n if (indexOfSearchString !== position) return false;\n return true;\n}\n\n/**\n * This function mirrors the `substr` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns a substring by providing start and length. This function is not exported because it is\n * considered deprecated, however it is still useful as a local helper function.\n *\n * @param string String to be divided\n * @param begin Start position. Default is `Start of string`\n * @param len Length of result. Default is `String length minus start parameter`. Default is `String\n * length minus start parameter`\n * @returns Substring from starting string\n */\nfunction substr(\n string: string,\n begin: number = 0,\n len: number = stringLength(string) - begin,\n): string {\n return stringzSubstr(string, begin, len);\n}\n\n/**\n * This function mirrors the `substring` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns a substring by providing start and end position.\n *\n * @param string String to be divided\n * @param begin Start position\n * @param end End position. Default is `End of string`\n * @returns Substring from starting string\n */\nexport function substring(\n string: string,\n begin: number,\n end: number = stringLength(string),\n): string {\n return stringzSubstring(string, begin, end);\n}\n\n/**\n * This function mirrors the `toArray` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Converts a string to an array of string characters.\n *\n * @param string String to convert to array\n * @returns An array of characters from the starting string\n */\nexport function toArray(string: string): string[] {\n return stringzToArray(string);\n}\n\n/** Determine whether the string is a `LocalizeKey` meant to be localized in Platform.Bible. */\nexport function isLocalizeKey(str: string): str is LocalizeKey {\n return startsWith(str, '%') && endsWith(str, '%');\n}\n\n/** This is an internal-only export for testing purposes and should not be used in development */\nexport const testingStringUtils = {\n indexOfClosestClosingCurlyBrace,\n};\n","import { Canon } from '@sillsdev/scripture';\nimport { BookInfo, ScriptureReference } from './scripture.model';\nimport { split, startsWith } from './string-util';\n\nconst scrBookData: BookInfo[] = [\n { shortName: 'ERR', fullNames: ['ERROR'], chapters: -1 },\n { shortName: 'GEN', fullNames: ['Genesis'], chapters: 50 },\n { shortName: 'EXO', fullNames: ['Exodus'], chapters: 40 },\n { shortName: 'LEV', fullNames: ['Leviticus'], chapters: 27 },\n { shortName: 'NUM', fullNames: ['Numbers'], chapters: 36 },\n { shortName: 'DEU', fullNames: ['Deuteronomy'], chapters: 34 },\n { shortName: 'JOS', fullNames: ['Joshua'], chapters: 24 },\n { shortName: 'JDG', fullNames: ['Judges'], chapters: 21 },\n { shortName: 'RUT', fullNames: ['Ruth'], chapters: 4 },\n { shortName: '1SA', fullNames: ['1 Samuel'], chapters: 31 },\n { shortName: '2SA', fullNames: ['2 Samuel'], chapters: 24 },\n { shortName: '1KI', fullNames: ['1 Kings'], chapters: 22 },\n { shortName: '2KI', fullNames: ['2 Kings'], chapters: 25 },\n { shortName: '1CH', fullNames: ['1 Chronicles'], chapters: 29 },\n { shortName: '2CH', fullNames: ['2 Chronicles'], chapters: 36 },\n { shortName: 'EZR', fullNames: ['Ezra'], chapters: 10 },\n { shortName: 'NEH', fullNames: ['Nehemiah'], chapters: 13 },\n { shortName: 'EST', fullNames: ['Esther'], chapters: 10 },\n { shortName: 'JOB', fullNames: ['Job'], chapters: 42 },\n { shortName: 'PSA', fullNames: ['Psalm', 'Psalms'], chapters: 150 },\n { shortName: 'PRO', fullNames: ['Proverbs'], chapters: 31 },\n { shortName: 'ECC', fullNames: ['Ecclesiastes'], chapters: 12 },\n { shortName: 'SNG', fullNames: ['Song of Solomon', 'Song of Songs'], chapters: 8 },\n { shortName: 'ISA', fullNames: ['Isaiah'], chapters: 66 },\n { shortName: 'JER', fullNames: ['Jeremiah'], chapters: 52 },\n { shortName: 'LAM', fullNames: ['Lamentations'], chapters: 5 },\n { shortName: 'EZK', fullNames: ['Ezekiel'], chapters: 48 },\n { shortName: 'DAN', fullNames: ['Daniel'], chapters: 12 },\n { shortName: 'HOS', fullNames: ['Hosea'], chapters: 14 },\n { shortName: 'JOL', fullNames: ['Joel'], chapters: 3 },\n { shortName: 'AMO', fullNames: ['Amos'], chapters: 9 },\n { shortName: 'OBA', fullNames: ['Obadiah'], chapters: 1 },\n { shortName: 'JON', fullNames: ['Jonah'], chapters: 4 },\n { shortName: 'MIC', fullNames: ['Micah'], chapters: 7 },\n { shortName: 'NAM', fullNames: ['Nahum'], chapters: 3 },\n { shortName: 'HAB', fullNames: ['Habakkuk'], chapters: 3 },\n { shortName: 'ZEP', fullNames: ['Zephaniah'], chapters: 3 },\n { shortName: 'HAG', fullNames: ['Haggai'], chapters: 2 },\n { shortName: 'ZEC', fullNames: ['Zechariah'], chapters: 14 },\n { shortName: 'MAL', fullNames: ['Malachi'], chapters: 4 },\n { shortName: 'MAT', fullNames: ['Matthew'], chapters: 28 },\n { shortName: 'MRK', fullNames: ['Mark'], chapters: 16 },\n { shortName: 'LUK', fullNames: ['Luke'], chapters: 24 },\n { shortName: 'JHN', fullNames: ['John'], chapters: 21 },\n { shortName: 'ACT', fullNames: ['Acts'], chapters: 28 },\n { shortName: 'ROM', fullNames: ['Romans'], chapters: 16 },\n { shortName: '1CO', fullNames: ['1 Corinthians'], chapters: 16 },\n { shortName: '2CO', fullNames: ['2 Corinthians'], chapters: 13 },\n { shortName: 'GAL', fullNames: ['Galatians'], chapters: 6 },\n { shortName: 'EPH', fullNames: ['Ephesians'], chapters: 6 },\n { shortName: 'PHP', fullNames: ['Philippians'], chapters: 4 },\n { shortName: 'COL', fullNames: ['Colossians'], chapters: 4 },\n { shortName: '1TH', fullNames: ['1 Thessalonians'], chapters: 5 },\n { shortName: '2TH', fullNames: ['2 Thessalonians'], chapters: 3 },\n { shortName: '1TI', fullNames: ['1 Timothy'], chapters: 6 },\n { shortName: '2TI', fullNames: ['2 Timothy'], chapters: 4 },\n { shortName: 'TIT', fullNames: ['Titus'], chapters: 3 },\n { shortName: 'PHM', fullNames: ['Philemon'], chapters: 1 },\n { shortName: 'HEB', fullNames: ['Hebrews'], chapters: 13 },\n { shortName: 'JAS', fullNames: ['James'], chapters: 5 },\n { shortName: '1PE', fullNames: ['1 Peter'], chapters: 5 },\n { shortName: '2PE', fullNames: ['2 Peter'], chapters: 3 },\n { shortName: '1JN', fullNames: ['1 John'], chapters: 5 },\n { shortName: '2JN', fullNames: ['2 John'], chapters: 1 },\n { shortName: '3JN', fullNames: ['3 John'], chapters: 1 },\n { shortName: 'JUD', fullNames: ['Jude'], chapters: 1 },\n { shortName: 'REV', fullNames: ['Revelation'], chapters: 22 },\n];\n\nexport const FIRST_SCR_BOOK_NUM = 1;\nexport const LAST_SCR_BOOK_NUM = scrBookData.length - 1;\nexport const FIRST_SCR_CHAPTER_NUM = 1;\nexport const FIRST_SCR_VERSE_NUM = 1;\n\nexport const getChaptersForBook = (bookNum: number): number => {\n return scrBookData[bookNum]?.chapters ?? -1;\n};\n\nexport const offsetBook = (scrRef: ScriptureReference, offset: number): ScriptureReference => ({\n bookNum: Math.max(FIRST_SCR_BOOK_NUM, Math.min(scrRef.bookNum + offset, LAST_SCR_BOOK_NUM)),\n chapterNum: 1,\n verseNum: 1,\n});\n\nexport const offsetChapter = (scrRef: ScriptureReference, offset: number): ScriptureReference => ({\n ...scrRef,\n chapterNum: Math.min(\n Math.max(FIRST_SCR_CHAPTER_NUM, scrRef.chapterNum + offset),\n getChaptersForBook(scrRef.bookNum),\n ),\n verseNum: 1,\n});\n\nexport const offsetVerse = (scrRef: ScriptureReference, offset: number): ScriptureReference => ({\n ...scrRef,\n verseNum: Math.max(FIRST_SCR_VERSE_NUM, scrRef.verseNum + offset),\n});\n\n/**\n * https://github.com/ubsicap/Paratext/blob/master/ParatextData/SILScriptureExtensions.cs#L72\n *\n * Convert book number to a localized Id (a short description of the book). This should be used\n * whenever a book ID (short code) is shown to the user. It is primarily needed for people who do\n * not read Roman script well and need to have books identified in a alternate script (e.g. Chinese\n * or Russian)\n *\n * @param bookNumber\n * @param localizationLanguage In BCP 47 format\n * @param getLocalizedString Function that provides the localized versions of the book ids and names\n * asynchronously.\n * @returns\n */\nexport async function getLocalizedIdFromBookNumber(\n bookNumber: number,\n localizationLanguage: string,\n getLocalizedString: (item: {\n localizeKey: string;\n languagesToSearch?: string[];\n }) => Promise,\n) {\n const id = Canon.bookNumberToId(bookNumber);\n\n if (!startsWith(Intl.getCanonicalLocales(localizationLanguage)[0], 'zh'))\n return getLocalizedString({\n localizeKey: `LocalizedId.${id}`,\n languagesToSearch: [localizationLanguage],\n });\n\n // For Chinese the normal book name is already fairly short.\n const bookName = await getLocalizedString({\n localizeKey: `Book.${id}`,\n languagesToSearch: [localizationLanguage],\n });\n const parts = split(bookName, '-');\n // some entries had a second name inside ideographic parenthesis\n const parts2 = split(parts[0], '\\xff08');\n const retVal = parts2[0].trim();\n return retVal;\n}\n","/** Function to run to dispose of something. Returns true if successfully unsubscribed */\nexport type Unsubscriber = () => boolean;\n\n/**\n * Returns an Unsubscriber function that combines all the unsubscribers passed in.\n *\n * @param unsubscribers All unsubscribers to aggregate into one unsubscriber\n * @returns Function that unsubscribes from all passed in unsubscribers when run\n */\nexport const aggregateUnsubscribers = (unsubscribers: Unsubscriber[]): Unsubscriber => {\n return (...args) => {\n // Run the unsubscriber for each handler\n const unsubs = unsubscribers.map((unsubscriber) => unsubscriber(...args));\n\n // If all the unsubscribers resolve to truthiness, we succeed\n return unsubs.every((success) => success);\n };\n};\n\n/**\n * Function to run to dispose of something that runs asynchronously. The promise resolves to true if\n * successfully unsubscribed\n */\nexport type UnsubscriberAsync = () => Promise;\n\n/**\n * Returns an UnsubscriberAsync function that combines all the unsubscribers passed in.\n *\n * @param unsubscribers - All unsubscribers to aggregate into one unsubscriber.\n * @returns Function that unsubscribes from all passed in unsubscribers when run\n */\nexport const aggregateUnsubscriberAsyncs = (\n unsubscribers: (UnsubscriberAsync | Unsubscriber)[],\n): UnsubscriberAsync => {\n return async (...args) => {\n // Run the unsubscriber for each handler\n const unsubPromises = unsubscribers.map(async (unsubscriber) => unsubscriber(...args));\n\n // If all the unsubscribers resolve to truthiness, we succeed\n return (await Promise.all(unsubPromises)).every((success) => success);\n };\n};\n","var getOwnPropertyNames = Object.getOwnPropertyNames, getOwnPropertySymbols = Object.getOwnPropertySymbols;\nvar hasOwnProperty = Object.prototype.hasOwnProperty;\n/**\n * Combine two comparators into a single comparators.\n */\nfunction combineComparators(comparatorA, comparatorB) {\n return function isEqual(a, b, state) {\n return comparatorA(a, b, state) && comparatorB(a, b, state);\n };\n}\n/**\n * Wrap the provided `areItemsEqual` method to manage the circular state, allowing\n * for circular references to be safely included in the comparison without creating\n * stack overflows.\n */\nfunction createIsCircular(areItemsEqual) {\n return function isCircular(a, b, state) {\n if (!a || !b || typeof a !== 'object' || typeof b !== 'object') {\n return areItemsEqual(a, b, state);\n }\n var cache = state.cache;\n var cachedA = cache.get(a);\n var cachedB = cache.get(b);\n if (cachedA && cachedB) {\n return cachedA === b && cachedB === a;\n }\n cache.set(a, b);\n cache.set(b, a);\n var result = areItemsEqual(a, b, state);\n cache.delete(a);\n cache.delete(b);\n return result;\n };\n}\n/**\n * Get the properties to strictly examine, which include both own properties that are\n * not enumerable and symbol properties.\n */\nfunction getStrictProperties(object) {\n return getOwnPropertyNames(object).concat(getOwnPropertySymbols(object));\n}\n/**\n * Whether the object contains the property passed as an own property.\n */\nvar hasOwn = Object.hasOwn ||\n (function (object, property) {\n return hasOwnProperty.call(object, property);\n });\n/**\n * Whether the values passed are strictly equal or both NaN.\n */\nfunction sameValueZeroEqual(a, b) {\n return a || b ? a === b : a === b || (a !== a && b !== b);\n}\n\nvar OWNER = '_owner';\nvar getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor, keys = Object.keys;\n/**\n * Whether the arrays are equal in value.\n */\nfunction areArraysEqual(a, b, state) {\n var index = a.length;\n if (b.length !== index) {\n return false;\n }\n while (index-- > 0) {\n if (!state.equals(a[index], b[index], index, index, a, b, state)) {\n return false;\n }\n }\n return true;\n}\n/**\n * Whether the dates passed are equal in value.\n */\nfunction areDatesEqual(a, b) {\n return sameValueZeroEqual(a.getTime(), b.getTime());\n}\n/**\n * Whether the `Map`s are equal in value.\n */\nfunction areMapsEqual(a, b, state) {\n if (a.size !== b.size) {\n return false;\n }\n var matchedIndices = {};\n var aIterable = a.entries();\n var index = 0;\n var aResult;\n var bResult;\n while ((aResult = aIterable.next())) {\n if (aResult.done) {\n break;\n }\n var bIterable = b.entries();\n var hasMatch = false;\n var matchIndex = 0;\n while ((bResult = bIterable.next())) {\n if (bResult.done) {\n break;\n }\n var _a = aResult.value, aKey = _a[0], aValue = _a[1];\n var _b = bResult.value, bKey = _b[0], bValue = _b[1];\n if (!hasMatch &&\n !matchedIndices[matchIndex] &&\n (hasMatch =\n state.equals(aKey, bKey, index, matchIndex, a, b, state) &&\n state.equals(aValue, bValue, aKey, bKey, a, b, state))) {\n matchedIndices[matchIndex] = true;\n }\n matchIndex++;\n }\n if (!hasMatch) {\n return false;\n }\n index++;\n }\n return true;\n}\n/**\n * Whether the objects are equal in value.\n */\nfunction areObjectsEqual(a, b, state) {\n var properties = keys(a);\n var index = properties.length;\n if (keys(b).length !== index) {\n return false;\n }\n var property;\n // Decrementing `while` showed faster results than either incrementing or\n // decrementing `for` loop and than an incrementing `while` loop. Declarative\n // methods like `some` / `every` were not used to avoid incurring the garbage\n // cost of anonymous callbacks.\n while (index-- > 0) {\n property = properties[index];\n if (property === OWNER &&\n (a.$$typeof || b.$$typeof) &&\n a.$$typeof !== b.$$typeof) {\n return false;\n }\n if (!hasOwn(b, property) ||\n !state.equals(a[property], b[property], property, property, a, b, state)) {\n return false;\n }\n }\n return true;\n}\n/**\n * Whether the objects are equal in value with strict property checking.\n */\nfunction areObjectsEqualStrict(a, b, state) {\n var properties = getStrictProperties(a);\n var index = properties.length;\n if (getStrictProperties(b).length !== index) {\n return false;\n }\n var property;\n var descriptorA;\n var descriptorB;\n // Decrementing `while` showed faster results than either incrementing or\n // decrementing `for` loop and than an incrementing `while` loop. Declarative\n // methods like `some` / `every` were not used to avoid incurring the garbage\n // cost of anonymous callbacks.\n while (index-- > 0) {\n property = properties[index];\n if (property === OWNER &&\n (a.$$typeof || b.$$typeof) &&\n a.$$typeof !== b.$$typeof) {\n return false;\n }\n if (!hasOwn(b, property)) {\n return false;\n }\n if (!state.equals(a[property], b[property], property, property, a, b, state)) {\n return false;\n }\n descriptorA = getOwnPropertyDescriptor(a, property);\n descriptorB = getOwnPropertyDescriptor(b, property);\n if ((descriptorA || descriptorB) &&\n (!descriptorA ||\n !descriptorB ||\n descriptorA.configurable !== descriptorB.configurable ||\n descriptorA.enumerable !== descriptorB.enumerable ||\n descriptorA.writable !== descriptorB.writable)) {\n return false;\n }\n }\n return true;\n}\n/**\n * Whether the primitive wrappers passed are equal in value.\n */\nfunction arePrimitiveWrappersEqual(a, b) {\n return sameValueZeroEqual(a.valueOf(), b.valueOf());\n}\n/**\n * Whether the regexps passed are equal in value.\n */\nfunction areRegExpsEqual(a, b) {\n return a.source === b.source && a.flags === b.flags;\n}\n/**\n * Whether the `Set`s are equal in value.\n */\nfunction areSetsEqual(a, b, state) {\n if (a.size !== b.size) {\n return false;\n }\n var matchedIndices = {};\n var aIterable = a.values();\n var aResult;\n var bResult;\n while ((aResult = aIterable.next())) {\n if (aResult.done) {\n break;\n }\n var bIterable = b.values();\n var hasMatch = false;\n var matchIndex = 0;\n while ((bResult = bIterable.next())) {\n if (bResult.done) {\n break;\n }\n if (!hasMatch &&\n !matchedIndices[matchIndex] &&\n (hasMatch = state.equals(aResult.value, bResult.value, aResult.value, bResult.value, a, b, state))) {\n matchedIndices[matchIndex] = true;\n }\n matchIndex++;\n }\n if (!hasMatch) {\n return false;\n }\n }\n return true;\n}\n/**\n * Whether the TypedArray instances are equal in value.\n */\nfunction areTypedArraysEqual(a, b) {\n var index = a.length;\n if (b.length !== index) {\n return false;\n }\n while (index-- > 0) {\n if (a[index] !== b[index]) {\n return false;\n }\n }\n return true;\n}\n\nvar ARGUMENTS_TAG = '[object Arguments]';\nvar BOOLEAN_TAG = '[object Boolean]';\nvar DATE_TAG = '[object Date]';\nvar MAP_TAG = '[object Map]';\nvar NUMBER_TAG = '[object Number]';\nvar OBJECT_TAG = '[object Object]';\nvar REG_EXP_TAG = '[object RegExp]';\nvar SET_TAG = '[object Set]';\nvar STRING_TAG = '[object String]';\nvar isArray = Array.isArray;\nvar isTypedArray = typeof ArrayBuffer === 'function' && ArrayBuffer.isView\n ? ArrayBuffer.isView\n : null;\nvar assign = Object.assign;\nvar getTag = Object.prototype.toString.call.bind(Object.prototype.toString);\n/**\n * Create a comparator method based on the type-specific equality comparators passed.\n */\nfunction createEqualityComparator(_a) {\n var areArraysEqual = _a.areArraysEqual, areDatesEqual = _a.areDatesEqual, areMapsEqual = _a.areMapsEqual, areObjectsEqual = _a.areObjectsEqual, arePrimitiveWrappersEqual = _a.arePrimitiveWrappersEqual, areRegExpsEqual = _a.areRegExpsEqual, areSetsEqual = _a.areSetsEqual, areTypedArraysEqual = _a.areTypedArraysEqual;\n /**\n * compare the value of the two objects and return true if they are equivalent in values\n */\n return function comparator(a, b, state) {\n // If the items are strictly equal, no need to do a value comparison.\n if (a === b) {\n return true;\n }\n // If the items are not non-nullish objects, then the only possibility\n // of them being equal but not strictly is if they are both `NaN`. Since\n // `NaN` is uniquely not equal to itself, we can use self-comparison of\n // both objects, which is faster than `isNaN()`.\n if (a == null ||\n b == null ||\n typeof a !== 'object' ||\n typeof b !== 'object') {\n return a !== a && b !== b;\n }\n var constructor = a.constructor;\n // Checks are listed in order of commonality of use-case:\n // 1. Common complex object types (plain object, array)\n // 2. Common data values (date, regexp)\n // 3. Less-common complex object types (map, set)\n // 4. Less-common data values (promise, primitive wrappers)\n // Inherently this is both subjective and assumptive, however\n // when reviewing comparable libraries in the wild this order\n // appears to be generally consistent.\n // Constructors should match, otherwise there is potential for false positives\n // between class and subclass or custom object and POJO.\n if (constructor !== b.constructor) {\n return false;\n }\n // `isPlainObject` only checks against the object's own realm. Cross-realm\n // comparisons are rare, and will be handled in the ultimate fallback, so\n // we can avoid capturing the string tag.\n if (constructor === Object) {\n return areObjectsEqual(a, b, state);\n }\n // `isArray()` works on subclasses and is cross-realm, so we can avoid capturing\n // the string tag or doing an `instanceof` check.\n if (isArray(a)) {\n return areArraysEqual(a, b, state);\n }\n // `isTypedArray()` works on all possible TypedArray classes, so we can avoid\n // capturing the string tag or comparing against all possible constructors.\n if (isTypedArray != null && isTypedArray(a)) {\n return areTypedArraysEqual(a, b, state);\n }\n // Try to fast-path equality checks for other complex object types in the\n // same realm to avoid capturing the string tag. Strict equality is used\n // instead of `instanceof` because it is more performant for the common\n // use-case. If someone is subclassing a native class, it will be handled\n // with the string tag comparison.\n if (constructor === Date) {\n return areDatesEqual(a, b, state);\n }\n if (constructor === RegExp) {\n return areRegExpsEqual(a, b, state);\n }\n if (constructor === Map) {\n return areMapsEqual(a, b, state);\n }\n if (constructor === Set) {\n return areSetsEqual(a, b, state);\n }\n // Since this is a custom object, capture the string tag to determing its type.\n // This is reasonably performant in modern environments like v8 and SpiderMonkey.\n var tag = getTag(a);\n if (tag === DATE_TAG) {\n return areDatesEqual(a, b, state);\n }\n if (tag === REG_EXP_TAG) {\n return areRegExpsEqual(a, b, state);\n }\n if (tag === MAP_TAG) {\n return areMapsEqual(a, b, state);\n }\n if (tag === SET_TAG) {\n return areSetsEqual(a, b, state);\n }\n if (tag === OBJECT_TAG) {\n // The exception for value comparison is custom `Promise`-like class instances. These should\n // be treated the same as standard `Promise` objects, which means strict equality, and if\n // it reaches this point then that strict equality comparison has already failed.\n return (typeof a.then !== 'function' &&\n typeof b.then !== 'function' &&\n areObjectsEqual(a, b, state));\n }\n // If an arguments tag, it should be treated as a standard object.\n if (tag === ARGUMENTS_TAG) {\n return areObjectsEqual(a, b, state);\n }\n // As the penultimate fallback, check if the values passed are primitive wrappers. This\n // is very rare in modern JS, which is why it is deprioritized compared to all other object\n // types.\n if (tag === BOOLEAN_TAG || tag === NUMBER_TAG || tag === STRING_TAG) {\n return arePrimitiveWrappersEqual(a, b, state);\n }\n // If not matching any tags that require a specific type of comparison, then we hard-code false because\n // the only thing remaining is strict equality, which has already been compared. This is for a few reasons:\n // - Certain types that cannot be introspected (e.g., `WeakMap`). For these types, this is the only\n // comparison that can be made.\n // - For types that can be introspected, but rarely have requirements to be compared\n // (`ArrayBuffer`, `DataView`, etc.), the cost is avoided to prioritize the common\n // use-cases (may be included in a future release, if requested enough).\n // - For types that can be introspected but do not have an objective definition of what\n // equality is (`Error`, etc.), the subjective decision is to be conservative and strictly compare.\n // In all cases, these decisions should be reevaluated based on changes to the language and\n // common development practices.\n return false;\n };\n}\n/**\n * Create the configuration object used for building comparators.\n */\nfunction createEqualityComparatorConfig(_a) {\n var circular = _a.circular, createCustomConfig = _a.createCustomConfig, strict = _a.strict;\n var config = {\n areArraysEqual: strict\n ? areObjectsEqualStrict\n : areArraysEqual,\n areDatesEqual: areDatesEqual,\n areMapsEqual: strict\n ? combineComparators(areMapsEqual, areObjectsEqualStrict)\n : areMapsEqual,\n areObjectsEqual: strict\n ? areObjectsEqualStrict\n : areObjectsEqual,\n arePrimitiveWrappersEqual: arePrimitiveWrappersEqual,\n areRegExpsEqual: areRegExpsEqual,\n areSetsEqual: strict\n ? combineComparators(areSetsEqual, areObjectsEqualStrict)\n : areSetsEqual,\n areTypedArraysEqual: strict\n ? areObjectsEqualStrict\n : areTypedArraysEqual,\n };\n if (createCustomConfig) {\n config = assign({}, config, createCustomConfig(config));\n }\n if (circular) {\n var areArraysEqual$1 = createIsCircular(config.areArraysEqual);\n var areMapsEqual$1 = createIsCircular(config.areMapsEqual);\n var areObjectsEqual$1 = createIsCircular(config.areObjectsEqual);\n var areSetsEqual$1 = createIsCircular(config.areSetsEqual);\n config = assign({}, config, {\n areArraysEqual: areArraysEqual$1,\n areMapsEqual: areMapsEqual$1,\n areObjectsEqual: areObjectsEqual$1,\n areSetsEqual: areSetsEqual$1,\n });\n }\n return config;\n}\n/**\n * Default equality comparator pass-through, used as the standard `isEqual` creator for\n * use inside the built comparator.\n */\nfunction createInternalEqualityComparator(compare) {\n return function (a, b, _indexOrKeyA, _indexOrKeyB, _parentA, _parentB, state) {\n return compare(a, b, state);\n };\n}\n/**\n * Create the `isEqual` function used by the consuming application.\n */\nfunction createIsEqual(_a) {\n var circular = _a.circular, comparator = _a.comparator, createState = _a.createState, equals = _a.equals, strict = _a.strict;\n if (createState) {\n return function isEqual(a, b) {\n var _a = createState(), _b = _a.cache, cache = _b === void 0 ? circular ? new WeakMap() : undefined : _b, meta = _a.meta;\n return comparator(a, b, {\n cache: cache,\n equals: equals,\n meta: meta,\n strict: strict,\n });\n };\n }\n if (circular) {\n return function isEqual(a, b) {\n return comparator(a, b, {\n cache: new WeakMap(),\n equals: equals,\n meta: undefined,\n strict: strict,\n });\n };\n }\n var state = {\n cache: undefined,\n equals: equals,\n meta: undefined,\n strict: strict,\n };\n return function isEqual(a, b) {\n return comparator(a, b, state);\n };\n}\n\n/**\n * Whether the items passed are deeply-equal in value.\n */\nvar deepEqual = createCustomEqual();\n/**\n * Whether the items passed are deeply-equal in value based on strict comparison.\n */\nvar strictDeepEqual = createCustomEqual({ strict: true });\n/**\n * Whether the items passed are deeply-equal in value, including circular references.\n */\nvar circularDeepEqual = createCustomEqual({ circular: true });\n/**\n * Whether the items passed are deeply-equal in value, including circular references,\n * based on strict comparison.\n */\nvar strictCircularDeepEqual = createCustomEqual({\n circular: true,\n strict: true,\n});\n/**\n * Whether the items passed are shallowly-equal in value.\n */\nvar shallowEqual = createCustomEqual({\n createInternalComparator: function () { return sameValueZeroEqual; },\n});\n/**\n * Whether the items passed are shallowly-equal in value based on strict comparison\n */\nvar strictShallowEqual = createCustomEqual({\n strict: true,\n createInternalComparator: function () { return sameValueZeroEqual; },\n});\n/**\n * Whether the items passed are shallowly-equal in value, including circular references.\n */\nvar circularShallowEqual = createCustomEqual({\n circular: true,\n createInternalComparator: function () { return sameValueZeroEqual; },\n});\n/**\n * Whether the items passed are shallowly-equal in value, including circular references,\n * based on strict comparison.\n */\nvar strictCircularShallowEqual = createCustomEqual({\n circular: true,\n createInternalComparator: function () { return sameValueZeroEqual; },\n strict: true,\n});\n/**\n * Create a custom equality comparison method.\n *\n * This can be done to create very targeted comparisons in extreme hot-path scenarios\n * where the standard methods are not performant enough, but can also be used to provide\n * support for legacy environments that do not support expected features like\n * `RegExp.prototype.flags` out of the box.\n */\nfunction createCustomEqual(options) {\n if (options === void 0) { options = {}; }\n var _a = options.circular, circular = _a === void 0 ? false : _a, createCustomInternalComparator = options.createInternalComparator, createState = options.createState, _b = options.strict, strict = _b === void 0 ? false : _b;\n var config = createEqualityComparatorConfig(options);\n var comparator = createEqualityComparator(config);\n var equals = createCustomInternalComparator\n ? createCustomInternalComparator(comparator)\n : createInternalEqualityComparator(comparator);\n return createIsEqual({ circular: circular, comparator: comparator, createState: createState, equals: equals, strict: strict });\n}\n\nexport { circularDeepEqual, circularShallowEqual, createCustomEqual, deepEqual, sameValueZeroEqual, shallowEqual, strictCircularDeepEqual, strictCircularShallowEqual, strictDeepEqual, strictShallowEqual };\n//# sourceMappingURL=index.mjs.map\n","// There is a circular version https://www.npmjs.com/package/fast-equals#circulardeepequal that I\n// think allows comparing React refs (which have circular references in particular places that this\n// library would ignore). Maybe we can change to that version sometime if needed.\nimport { deepEqual as isEqualDeep } from 'fast-equals';\n\n/**\n * Check that two objects are deeply equal, comparing members of each object and such\n *\n * @param a The first object to compare\n * @param b The second object to compare\n *\n * WARNING: Objects like arrays from different iframes have different constructor function\n * references even if they do the same thing, so this deep equality comparison fails objects that\n * look the same but have different constructors because different constructors could produce\n * false positives in [a few specific\n * situations](https://github.com/planttheidea/fast-equals/blob/a41afc0a240ad5a472e47b53791e9be017f52281/src/comparator.ts#L96).\n * This means that two objects like arrays from different iframes that look the same will fail\n * this check. Please use some other means to check deep equality in those situations.\n *\n * Note: This deep equality check considers `undefined` values on keys of objects NOT to be equal to\n * not specifying the key at all. For example, `{ stuff: 3, things: undefined }` and `{ stuff: 3\n * }` are not considered equal in this case\n *\n * - For more information and examples, see [this\n * CodeSandbox](https://codesandbox.io/s/deepequallibrarycomparison-4g4kk4?file=/src/index.mjs).\n *\n * @returns True if a and b are deeply equal; false otherwise\n */\nexport default function deepEqual(a: unknown, b: unknown) {\n return isEqualDeep(a, b);\n}\n","import deepEqual from './equality-checking';\n\n/**\n * Check if one object is a subset of the other object. \"Subset\" means that all properties of one\n * object are present in the other object, and if they are present that all values of those\n * properties are deeply equal. Sub-objects are also checked to be subsets of the corresponding\n * sub-object in the other object.\n *\n * @example ObjB is a subset of objA given these objects:\n *\n * ```ts\n * objA = { name: 'Alice', age: 30, address: { city: 'Seattle', state: 'Washington' } };\n * objB = { name: 'Alice', address: { city: 'Seattle' } };\n * ```\n *\n * It is important to note that only arrays of primitives (i.e., booleans, numbers, strings) are\n * supported. In particular, objects in arrays will not be checked for deep equality. Also, presence\n * in an array is all this checks, not the number of times that an item appears in an array. `[1,\n * 1]` is a subset of `[1]`.\n *\n * @param objectWithAllProperties Object to be checked if it is a superset of\n * `objectWithPartialProperties`\n * @param objectWithPartialProperties Object to be checked if it is a subset of\n * `objectWithAllProperties`\n * @returns True if `objectWithAllProperties` contains all the properties of\n * `objectWithPartialProperties` and all values of those properties are deeply equal\n */\nexport default function isSubset(\n objectWithAllProperties: unknown,\n objectWithPartialProperties: unknown,\n): boolean {\n if (typeof objectWithAllProperties !== typeof objectWithPartialProperties) return false;\n\n // For this function we're saying that all falsy things of the same type are equal to each other\n if (!objectWithAllProperties && !objectWithPartialProperties) return true;\n\n if (Array.isArray(objectWithAllProperties)) {\n // We know these are arrays from the line above\n /* eslint-disable no-type-assertion/no-type-assertion */\n const partialArray = objectWithPartialProperties as Array;\n const allArray = objectWithAllProperties as Array;\n /* eslint-enable no-type-assertion/no-type-assertion */\n\n if (partialArray.length === 0) return true;\n\n // This only works with arrays of primitives.\n // If someone cares about checking arrays of objects this needs updating.\n return partialArray.every((item) => allArray.includes(item));\n }\n\n if (typeof objectWithAllProperties !== 'object')\n return deepEqual(objectWithAllProperties, objectWithPartialProperties);\n\n // We know these are objects that potentially have properties because of the earlier checks\n /* eslint-disable no-type-assertion/no-type-assertion */\n const partialObj = objectWithPartialProperties as Record;\n const allObj = objectWithAllProperties as Record;\n /* eslint-enable no-type-assertion/no-type-assertion */\n\n let retVal = true;\n Object.keys(partialObj).forEach((key) => {\n if (!retVal) return;\n if (!Object.hasOwn(allObj, key)) retVal = false;\n else if (!isSubset(allObj[key], partialObj[key])) retVal = false;\n });\n return retVal;\n}\n","/**\n * Converts a JavaScript value to a JSON string, changing `undefined` properties in the JavaScript\n * object to `null` properties in the JSON string.\n *\n * WARNING: `null` values will become `undefined` values after passing through {@link serialize} then\n * {@link deserialize}. For example, `{ a: 1, b: undefined, c: null }` will become `{ a: 1, b:\n * undefined, c: undefined }`. If you are passing around user data that needs to retain `null`\n * values, you should wrap them yourself in a string before using this function. Alternatively, you\n * can write your own replacer that will preserve `null` in a way that you can recover later.\n *\n * @param value A JavaScript value, usually an object or array, to be converted.\n * @param replacer A function that transforms the results. Note that all `undefined` values returned\n * by the replacer will be further transformed into `null` in the JSON string.\n * @param space Adds indentation, white space, and line break characters to the return-value JSON\n * text to make it easier to read. See the `space` parameter of `JSON.stringify` for more\n * details.\n */\nexport function serialize(\n value: unknown,\n replacer?: (this: unknown, key: string, value: unknown) => unknown,\n space?: string | number,\n): string {\n const undefinedReplacer = (replacerKey: string, replacerValue: unknown) => {\n let newValue = replacerValue;\n if (replacer) newValue = replacer(replacerKey, newValue);\n // All `undefined` values become `null` on the way from JS objects into JSON strings\n // eslint-disable-next-line no-null/no-null\n if (newValue === undefined) newValue = null;\n return newValue;\n };\n return JSON.stringify(value, undefinedReplacer, space);\n}\n\n/**\n * Converts a JSON string into a value, converting all `null` properties from JSON into `undefined`\n * in the returned JavaScript value/object.\n *\n * WARNING: `null` values will become `undefined` values after passing through {@link serialize} then\n * {@link deserialize}. For example, `{ a: 1, b: undefined, c: null }` will become `{ a: 1, b:\n * undefined, c: undefined }`. If you are passing around user data that needs to retain `null`\n * values, you should wrap them yourself in a string before using this function. Alternatively, you\n * can write your own replacer that will preserve `null` in a way that you can recover later.\n *\n * @param value A valid JSON string.\n * @param reviver A function that transforms the results. This function is called for each member of\n * the object. If a member contains nested objects, the nested objects are transformed before the\n * parent object is. Note that `null` values are converted into `undefined` values after the\n * reviver has run.\n */\nexport function deserialize(\n value: string,\n reviver?: (this: unknown, key: string, value: unknown) => unknown,\n // Need to use `any` instead of `unknown` here to match the signature of JSON.parse\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): any {\n // Helper function to replace `null` with `undefined` on a per property basis. This can't be done\n // with our own reviver because `JSON.parse` removes `undefined` properties from the return value.\n function replaceNull(obj: Record): Record {\n Object.keys(obj).forEach((key: string | number) => {\n // We only want to replace `null`, not other falsy values\n // eslint-disable-next-line no-null/no-null\n if (obj[key] === null) obj[key] = undefined;\n // If the property is an object, recursively call the helper function on it\n else if (typeof obj[key] === 'object')\n // Since the object came from a string, we know the keys will not be symbols\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n obj[key] = replaceNull(obj[key] as Record);\n });\n return obj;\n }\n\n const parsedObject = JSON.parse(value, reviver);\n // Explicitly convert the value 'null' that isn't stored as a property on an object to 'undefined'\n // eslint-disable-next-line no-null/no-null\n if (parsedObject === null) return undefined;\n if (typeof parsedObject === 'object') return replaceNull(parsedObject);\n return parsedObject;\n}\n\n/**\n * Check to see if the value is serializable without losing information\n *\n * @param value Value to test\n * @returns True if serializable; false otherwise\n *\n * Note: the values `undefined` and `null` are serializable (on their own or in an array), but\n * `null` values get transformed into `undefined` when serializing/deserializing.\n *\n * WARNING: This is inefficient right now as it stringifies, parses, stringifies, and === the value.\n * Please only use this if you need to\n *\n * DISCLAIMER: this does not successfully detect that values are not serializable in some cases:\n *\n * - Losses of removed properties like functions and `Map`s\n * - Class instances (not deserializable into class instances without special code)\n *\n * We intend to improve this in the future if it becomes important to do so. See [`JSON.stringify`\n * documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description)\n * for more information.\n */\nexport function isSerializable(value: unknown): boolean {\n try {\n const serializedValue = serialize(value);\n return serializedValue === serialize(deserialize(serializedValue));\n } catch (e) {\n return false;\n }\n}\n\n/**\n * HTML Encodes the provided string. Thanks to ChatGPT\n *\n * @param str String to HTML encode\n * @returns HTML-encoded string\n */\nexport const htmlEncode = (str: string): string =>\n str\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/\\//g, '/');\n","import DateTimeFormat from './intl-date-time-format';\n\n/**\n * Retrieves the current locale of the user's environment.\n *\n * @returns A string representing the current locale. If the locale cannot be determined, the\n * function returns an empty string.\n */\nexport default function getCurrentLocale(): string {\n // Use navigator when available\n if (typeof navigator !== 'undefined' && navigator.languages) {\n return navigator.languages[0];\n }\n // For Node.js\n return new DateTimeFormat().resolvedOptions().locale;\n}\n","//----------------------------------------------------------------------------------------------\n// NOTE: If you change any of the types, make sure the JSON schema at the end of this file gets\n// changed so they align.\n//----------------------------------------------------------------------------------------------\n\nimport { LocalizeKey, ReferencedItem } from 'menus.model';\n\n/** The data an extension provides to inform Platform.Bible of the settings it provides */\nexport type SettingsContribution = SettingsGroup | SettingsGroup[];\n/** A description of an extension's setting entry */\nexport type Setting = ExtensionControlledSetting;\n/** Setting definition that is validated by the extension. */\nexport type ExtensionControlledSetting = SettingBase & ModifierExtensionControlled;\n/** Base information needed to describe a setting entry */\nexport type SettingBase = StateBase & {\n [k: string]: unknown;\n /** LocalizeKey that displays in the settings dialog as the setting name */\n label: LocalizeKey;\n /** LocalizeKey that displays in the settings dialog to describe the setting */\n description?: LocalizeKey;\n};\n/** The data an extension provides to inform Platform.Bible of the project settings it provides */\nexport type ProjectSettingsContribution = ProjectSettingsGroup | ProjectSettingsGroup[];\n/** A description of an extension's setting entry */\nexport type ProjectSetting = ExtensionControlledProjectSetting;\n/** Setting definition that is validated by the extension. */\nexport type ExtensionControlledProjectSetting = ProjectSettingBase & ModifierExtensionControlled;\n/** Base information needed to describe a project setting entry */\nexport type ProjectSettingBase = SettingBase & ModifierProject;\n/** A description of an extension's user state entry */\nexport type UserState = ExtensionControlledState;\n/** State definition that is validated by the extension. */\nexport type ExtensionControlledState = StateBase & ModifierExtensionControlled;\n/** Group of related settings definitions */\nexport interface SettingsGroup {\n [k: string]: unknown;\n /** LocalizeKey that displays in the settings dialog as the group name */\n label: LocalizeKey;\n /** LocalizeKey that displays in the settings dialog to describe the group */\n description?: LocalizeKey;\n properties: SettingProperties;\n}\n/** Object whose keys are setting IDs and whose values are settings objects */\nexport interface SettingProperties {\n [k: ReferencedItem]: Setting;\n}\n/** Base information needed to describe a state entry */\nexport interface StateBase {\n [k: string]: unknown;\n /** Default value for the state/setting */\n default: unknown;\n /**\n * A state/setting ID whose value to set to this state/setting's starting value the first time\n * this state/setting is loaded\n */\n derivesFrom?: ReferencedItem;\n}\n/**\n * Modifies state/setting type to be extension-controlled. \"Extension-controlled\" means the\n * extension provides the component and the validator for the state/setting, so the state/setting is\n * controlled by the extension.\n */\nexport interface ModifierExtensionControlled {\n [k: string]: unknown;\n platformType?: undefined;\n type?: undefined;\n}\n/** Group of related settings definitions */\nexport interface ProjectSettingsGroup {\n [k: string]: unknown;\n /** LocalizeKey that displays in the project settings dialog as the group name */\n label: LocalizeKey;\n /** LocalizeKey that displays in the project settings dialog to describe the group */\n description?: LocalizeKey;\n properties: ProjectSettingProperties;\n}\n/** Object whose keys are setting IDs and whose values are settings objects */\nexport interface ProjectSettingProperties {\n [k: ReferencedItem]: ProjectSetting;\n}\n\n// Note: we removed the index signature on ModifierProject to avoid having it on\n// `ProjectMetadataFilterOptions`. Unfortunately adding \"additionalProperties\": false on the json\n// schema messes up validation. Please remove the index signature again in the future if you\n// regenerate types\nexport interface ModifierProject {\n /**\n * String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s\n * (using the\n * [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)\n * function) to determine if they should be included.\n *\n * If this is one string, it will be matched against `projectInterface`s. If this is an array,\n * each entry is handled based on its type (at least one entry must match for this filter\n * condition to pass):\n *\n * - If the entry is a string, it will be matched against each `projectInterface`. If any match, the\n * project will pass this filter condition\n * - If the entry is an array of strings, each will be matched against each `projectInterface`. If\n * every string matches against at least one `projectInterface`, the project will pass this\n * filter condition\n *\n * In other words, each entry in the first-level array is `OR`'ed together. Each entry in\n * second-level arrays (arrays within the first-level array) are `AND`'ed together.\n *\n * Defaults to all {@link ProjectInterfaces}, so all projects that do not match\n * `excludeProjectInterfaces` will be included\n *\n * @example\n *\n * ```typescript\n * includeProjectInterfaces: ['one', ['two', 'three']];\n * ```\n *\n * This filter condition will succeed on projects whose `projectInterface`s fulfill at least one\n * of the following conditions (At least one entry in the array must match):\n *\n * - Include `one`\n * - Include both `two` and `three`.\n */\n includeProjectInterfaces?: undefined | string | (string | string[])[];\n /**\n * String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s\n * (using the\n * [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)\n * function) to determine if they should absolutely not be included even if they match with\n * `includeProjectInterfaces`.\n *\n * If this is one string, it will be matched against `projectInterface`s. If this is an array,\n * each entry is handled based on its type (at least one entry must match for this filter\n * condition to exclude the project):\n *\n * - If the entry is a string, it will be matched against each `projectInterface`. If any match, the\n * project will pass this filter condition and exclude the project\n * - If the entry is an array of strings, each will be matched against each `projectInterface`. If\n * every string matches against at least one `projectInterface`, the project will pass this\n * filter condition and exclude the project\n *\n * In other words, each entry in the first-level array is `OR`'ed together. Each entry in\n * second-level arrays (arrays within the first-level array) are `AND`'ed together.\n *\n * Defaults to no {@link ProjectInterfaces}, so all projects that match `includeProjectInterfaces`\n * will be included\n *\n * @example\n *\n * ```typescript\n * excludeProjectInterfaces: ['one', ['two', 'three']];\n * ```\n *\n * This filter condition will succeed and exclude projects whose `projectInterface`s fulfill at\n * least one of the following conditions (At least one entry in the array must match):\n *\n * - Include `one`\n * - Include both `two` and `three`.\n */\n excludeProjectInterfaces?: undefined | string | (string | string[])[];\n /**\n * String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory\n * Ids that provided each project's metadata (using the\n * [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)\n * function) to determine if the projects should be included.\n *\n * Defaults to all Project Data Provider Factory Ids, so all projects that do not match\n * `excludePdpFactoryIds` will be included\n */\n includePdpFactoryIds?: undefined | string | string[];\n /**\n * String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory\n * Ids that provided each project's metadata (using the\n * [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)\n * function) to determine if the projects should absolutely not be included even if they match\n * with `includeProjectInterfaces`.\n *\n * Defaults to none, so all projects that match `includePdpFactoryIds` will be included\n */\n excludePdpFactoryIds?: undefined | string | string[];\n}\n\n/** The data an extension provides to inform Platform.Bible of the user state it provides */\nexport interface UserStateContribution {\n [k: ReferencedItem]: UserState;\n}\n/** The data an extension provides to inform Platform.Bible of the project state it provides */\nexport interface ProjectStateContribution {\n [k: ReferencedItem]: UserState;\n}\n\n//----------------------------------------------------------------------------------------------\n// NOTE: If you change the schema below, make sure the TS types above get changed so they align.\n//----------------------------------------------------------------------------------------------\nconst settingsDefs = {\n projectSettingsContribution: {\n description:\n 'The data an extension provides to inform Platform.Bible of the project settings it provides',\n anyOf: [\n {\n $ref: '#/$defs/projectSettingsGroup',\n },\n {\n type: 'array',\n items: {\n $ref: '#/$defs/projectSettingsGroup',\n },\n },\n ],\n },\n projectSettingsGroup: {\n description: 'Group of related settings definitions',\n type: 'object',\n properties: {\n label: {\n description: 'localizeKey that displays in the project settings dialog as the group name',\n $ref: '#/$defs/localizeKey',\n },\n description: {\n description:\n 'localizeKey that displays in the project settings dialog to describe the group',\n $ref: '#/$defs/localizeKey',\n },\n properties: {\n $ref: '#/$defs/projectSettingProperties',\n },\n },\n required: ['label', 'properties'],\n },\n projectSettingProperties: {\n description: 'Object whose keys are setting IDs and whose values are settings objects',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n $ref: '#/$defs/projectSetting',\n },\n },\n additionalProperties: false,\n },\n projectSetting: {\n description: \"A description of an extension's setting entry\",\n anyOf: [\n {\n $ref: '#/$defs/extensionControlledProjectSetting',\n },\n ],\n },\n extensionControlledProjectSetting: {\n description: 'Setting definition that is validated by the extension.',\n allOf: [\n {\n $ref: '#/$defs/projectSettingBase',\n },\n {\n $ref: '#/$defs/modifierExtensionControlled',\n },\n ],\n },\n projectSettingBase: {\n description: 'Base information needed to describe a project setting entry',\n allOf: [\n {\n $ref: '#/$defs/settingBase',\n },\n {\n $ref: '#/$defs/modifierProject',\n },\n ],\n },\n modifierProject: {\n description: 'Modifies setting type to be project setting',\n type: 'object',\n properties: {\n includeProjectInterfaces: {\n description:\n \"String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if they should be included.\\n\\nIf this is one string, it will be matched against `projectInterface`s. If this is an array, each entry is handled based on its type (at least one entry must match for this filter condition to pass):\\n\\n- If the entry is a string, it will be matched against each `projectInterface`. If any match, the project will pass this filter condition\\n- If the entry is an array of strings, each will be matched against each `projectInterface`. If every string matches against at least one `projectInterface`, the project will pass this filter condition\\n\\nIn other words, each entry in the first-level array is `OR`'ed together. Each entry in second-level arrays (arrays within the first-level array) are `AND`'ed together.\\n\\nDefaults to all {@link ProjectInterfaces}, so all projects that do not match `excludeProjectInterfaces` will be included\\n\\n@example\\n\\n```typescript\\nincludeProjectInterfaces: ['one', ['two', 'three']];\\n```\\n\\nThis filter condition will succeed on projects whose `projectInterface`s fulfill at least one of the following conditions (At least one entry in the array must match):\\n\\n- Include `one`\\n- Include both `two` and `three`.\",\n anyOf: [\n {\n type: 'null',\n },\n {\n type: 'string',\n },\n {\n type: 'array',\n items: {\n anyOf: [\n {\n type: 'string',\n },\n {\n type: 'array',\n items: { type: 'string' },\n },\n ],\n },\n },\n ],\n },\n excludeProjectInterfaces: {\n description:\n \"String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if they should absolutely not be included even if they match with `includeProjectInterfaces`.\\n\\nIf this is one string, it will be matched against `projectInterface`s. If this is an array, each entry is handled based on its type (at least one entry must match for this filter condition to exclude the project):\\n\\n- If the entry is a string, it will be matched against each `projectInterface`. If any match, the project will pass this filter condition and exclude the project\\n- If the entry is an array of strings, each will be matched against each `projectInterface`. If every string matches against at least one `projectInterface`, the project will pass this filter condition and exclude the project\\n\\nIn other words, each entry in the first-level array is `OR`'ed together. Each entry in second-level arrays (arrays within the first-level array) are `AND`'ed together.\\n\\nDefaults to no {@link ProjectInterfaces}, so all projects that match `includeProjectInterfaces` will be included\\n\\n@example\\n\\n```typescript\\nexcludeProjectInterfaces: ['one', ['two', 'three']];\\n```\\n\\nThis filter condition will succeed and exclude projects whose `projectInterface`s fulfill at least one of the following conditions (At least one entry in the array must match):\\n\\n- Include `one`\\n- Include both `two` and `three`.\",\n anyOf: [\n {\n type: 'null',\n },\n {\n type: 'string',\n },\n {\n type: 'array',\n items: {\n anyOf: [\n {\n type: 'string',\n },\n {\n type: 'array',\n items: { type: 'string' },\n },\n ],\n },\n },\n ],\n },\n includePdpFactoryIds: {\n description:\n \"String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory Ids that provided each project's metadata (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if the projects should be included.\\n\\nDefaults to all Project Data Provider Factory Ids, so all projects that do not match `excludePdpFactoryIds` will be included\",\n anyOf: [\n {\n type: 'null',\n },\n {\n type: 'string',\n },\n {\n type: 'array',\n items: { type: 'string' },\n },\n ],\n },\n excludePdpFactoryIds: {\n description:\n \"String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory Ids that provided each project's metadata (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if the projects should absolutely not be included even if they match with `includeProjectInterfaces`.\\n\\nDefaults to none, so all projects that match `includePdpFactoryIds` will be included\",\n anyOf: [\n {\n type: 'null',\n },\n {\n type: 'string',\n },\n {\n type: 'array',\n items: { type: 'string' },\n },\n ],\n },\n },\n },\n settingsContribution: {\n description:\n 'The data an extension provides to inform Platform.Bible of the settings it provides',\n anyOf: [\n {\n $ref: '#/$defs/settingsGroup',\n },\n {\n type: 'array',\n items: {\n $ref: '#/$defs/settingsGroup',\n },\n },\n ],\n },\n settingsGroup: {\n description: 'Group of related settings definitions',\n type: 'object',\n properties: {\n label: {\n description: 'localizeKey that displays in the settings dialog as the group name',\n $ref: '#/$defs/localizeKey',\n },\n description: {\n description: 'localizeKey that displays in the settings dialog to describe the group',\n $ref: '#/$defs/localizeKey',\n },\n properties: {\n $ref: '#/$defs/settingProperties',\n },\n },\n required: ['label', 'properties'],\n },\n settingProperties: {\n description: 'Object whose keys are setting IDs and whose values are settings objects',\n type: 'object',\n patternProperties: {\n '^[\\\\w-]+\\\\.[\\\\w-]+$': {\n $ref: '#/$defs/setting',\n },\n },\n additionalProperties: false,\n },\n setting: {\n description: \"A description of an extension's setting entry\",\n anyOf: [\n {\n $ref: '#/$defs/extensionControlledSetting',\n },\n ],\n },\n extensionControlledSetting: {\n description: 'Setting definition that is validated by the extension.',\n allOf: [\n {\n $ref: '#/$defs/settingBase',\n },\n {\n $ref: '#/$defs/modifierExtensionControlled',\n },\n ],\n },\n settingBase: {\n description: 'Base information needed to describe a setting entry',\n allOf: [\n {\n $ref: '#/$defs/stateBase',\n },\n {\n type: 'object',\n properties: {\n label: {\n description: 'localizeKey that displays in the settings dialog as the setting name',\n $ref: '#/$defs/localizeKey',\n },\n description: {\n description: 'localizeKey that displays in the settings dialog to describe the setting',\n $ref: '#/$defs/localizeKey',\n },\n },\n required: ['label'],\n },\n ],\n },\n projectStateContribution: {\n description:\n 'The data an extension provides to inform Platform.Bible of the project state it provides',\n $ref: '#/$defs/userStateProperties',\n },\n userStateContribution: {\n description:\n 'The data an extension provides to inform Platform.Bible of the user state it provides',\n $ref: '#/$defs/userStateProperties',\n },\n userStateProperties: {\n description: 'Object whose keys are state IDs and whose values are state objects',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n $ref: '#/$defs/userState',\n },\n },\n additionalProperties: false,\n },\n userState: {\n description: \"A description of an extension's user state entry\",\n anyOf: [\n {\n $ref: '#/$defs/extensionControlledState',\n },\n ],\n },\n extensionControlledState: {\n description: 'State definition that is validated by the extension.',\n allOf: [\n {\n $ref: '#/$defs/stateBase',\n },\n {\n $ref: '#/$defs/modifierExtensionControlled',\n },\n ],\n },\n modifierExtensionControlled: {\n description:\n 'Modifies state/setting type to be extension-controlled. \"Extension-controlled\" means the extension provides the component and the validator for the state/setting, so the state/setting is controlled by the extension.',\n not: {\n anyOf: [\n {\n type: 'object',\n required: ['platformType'],\n },\n {\n type: 'object',\n required: ['type'],\n },\n ],\n },\n },\n stateBase: {\n description: 'Base information needed to describe a state entry',\n type: 'object',\n properties: {\n default: {\n description: 'default value for the state/setting',\n type: 'any',\n },\n derivesFrom: {\n description:\n \"a state/setting ID whose value to set to this state/setting's starting value the first time this state/setting is loaded\",\n $ref: '#/$defs/id',\n },\n },\n required: ['default'],\n },\n localizeKey: {\n description: \"Identifier for a string that will be localized based on the user's UI language\",\n type: 'string',\n pattern: '^%[\\\\w\\\\-\\\\.]+%$',\n tsType: 'LocalizeKey',\n },\n id: {\n description: '',\n type: 'string',\n pattern: '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$',\n tsType: 'Id',\n },\n};\n\n/**\n * Json-schema-to-typescript has some added stuff that isn't actually compatible with JSON schema,\n * so we remove them here\n *\n * @param defs The `$defs` property of a JSON schema (will be modified in place)\n */\n// JSON schema types are weird, so we'll just be careful\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function removeJsonToTypeScriptTypesStuff(defs: any) {\n if (!defs) return;\n\n // JSON schema types are weird, so we'll just be careful\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Object.values(defs).forEach((def: any) => {\n if (!def.type) return;\n\n if ('tsType' in def) delete def.tsType;\n\n if (def.type === 'any') {\n delete def.type;\n return;\n }\n\n if (def.type === 'object') {\n removeJsonToTypeScriptTypesStuff(def.properties);\n }\n });\n}\n\nremoveJsonToTypeScriptTypesStuff(settingsDefs);\n\n/** JSON schema object that aligns with the ProjectSettingsContribution type */\nexport const projectSettingsDocumentSchema = {\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n title: 'Project Settings Contribution',\n description:\n 'The data an extension provides to inform Platform.Bible of the project settings it provides',\n anyOf: [\n {\n $ref: '#/$defs/projectSettingsGroup',\n },\n {\n type: 'array',\n items: {\n $ref: '#/$defs/projectSettingsGroup',\n },\n },\n ],\n\n $defs: settingsDefs,\n};\n\nObject.freeze(projectSettingsDocumentSchema);\n\n/** JSON schema object that aligns with the {@link SettingsContribution} type */\nexport const settingsDocumentSchema = {\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n title: 'Settings Contribution',\n description:\n 'The data an extension provides to inform Platform.Bible of the settings it provides',\n anyOf: [\n {\n $ref: '#/$defs/settingsGroup',\n },\n {\n type: 'array',\n items: {\n $ref: '#/$defs/settingsGroup',\n },\n },\n ],\n\n $defs: settingsDefs,\n};\n\nObject.freeze(settingsDocumentSchema);\n","//----------------------------------------------------------------------------------------------\n// NOTE: If you change any of the types, make sure the JSON schema at the end of this file gets\n// changed so they align.\n//----------------------------------------------------------------------------------------------\n\nimport { LocalizeKey } from 'menus.model';\nimport { removeJsonToTypeScriptTypesStuff } from './settings.model';\n\n/** Localized string value associated with this key */\nexport type LocalizedStringValue = string;\n\n/** The data an extension provides to inform Platform.Bible of the localized strings it provides. */\nexport interface LocalizedStringDataContribution {\n [k: string]: unknown;\n metadata?: StringsMetadata;\n localizedStrings?: {\n [k: string]: LanguageStrings;\n };\n}\n/**\n * Map whose keys are localized string keys and whose values provide additional non-locale-specific\n * information about the localized string key\n */\nexport interface StringsMetadata {\n [k: LocalizeKey]: StringMetadata;\n}\n/** Additional non-locale-specific information about a localized string key */\nexport interface StringMetadata {\n [k: string]: unknown;\n /**\n * Localized string key from which to get this value if one does not exist in the specified\n * language. If a new key/value pair needs to be made to replace an existing one, this could help\n * smooth over the transition if the meanings are close enough\n */\n fallbackKey?: LocalizeKey;\n /**\n * Additional information provided by developers in English to help the translator to know how to\n * translate this localized string accurately\n */\n notes?: string;\n}\n/**\n * Map whose keys are localized string keys and whose values provide information about how to\n * localize strings for the localized string key\n */\nexport interface LanguageStrings {\n [k: LocalizeKey]: LocalizedStringValue;\n}\n\n//----------------------------------------------------------------------------------------------\n// NOTE: If you change the schema below, make sure the TS types above get changed so they align.\n//----------------------------------------------------------------------------------------------\n\nconst localizedStringsDefs = {\n languageStrings: {\n description:\n 'Map whose keys are localized string keys and whose values provide information about how to localize strings for the localized string key',\n type: 'object',\n patternProperties: {\n '^%[\\\\w\\\\-\\\\.]+%$': {\n $ref: '#/$defs/localizedStringValue',\n },\n },\n additionalProperties: false,\n },\n localizedStringValue: {\n description: 'Localized string value associated with this key',\n type: 'string',\n },\n stringsMetadata: {\n description:\n 'Map whose keys are localized string keys and whose values provide additional non-locale-specific information about the localized string key',\n type: 'object',\n patternProperties: {\n '^%[\\\\w\\\\-\\\\.]+%$': {\n $ref: '#/$defs/stringMetadata',\n },\n },\n additionalProperties: false,\n },\n stringMetadata: {\n description: 'Additional non-locale-specific information about a localized string key',\n type: 'object',\n properties: {\n fallbackKey: {\n description:\n 'Localized string key from which to get this value if one does not exist in the specified language. If a new key/value pair needs to be made to replace an existing one, this could help smooth over the transition if the meanings are close enough',\n $ref: '#/$defs/localizeKey',\n },\n notes: {\n description:\n 'Additional information provided by developers in English to help the translator to know how to translate this localized string accurately',\n type: 'string',\n },\n },\n },\n localizeKey: {\n description: \"Identifier for a string that will be localized based on the user's UI language\",\n type: 'string',\n pattern: '^%[\\\\w\\\\-\\\\.]+%$',\n tsType: 'LocalizeKey',\n },\n};\n\nremoveJsonToTypeScriptTypesStuff(localizedStringsDefs);\n\n/** JSON schema object that aligns with the LocalizedStringDataContribution type */\nexport const localizedStringsDocumentSchema = {\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n title: 'Localized String Data Contribution',\n description:\n 'The data an extension provides to inform Platform.Bible of the localized strings it provides.',\n type: 'object',\n properties: {\n metadata: {\n $ref: '#/$defs/stringsMetadata',\n },\n localizedStrings: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/languageStrings',\n },\n },\n },\n $defs: localizedStringsDefs,\n};\n\nObject.freeze(localizedStringsDocumentSchema);\n","//----------------------------------------------------------------------------------------------\n// NOTE: If you change any of the types, make sure the JSON schema at the end of this file gets\n// changed so they align.\n//----------------------------------------------------------------------------------------------\n\nimport { ReplaceType } from './util';\n\n/** Identifier for a string that will be localized in a menu based on the user's UI language */\nexport type LocalizeKey = `%${string}%`;\n\n/** Name of some UI element (i.e., tab, column, group, menu item) or some PAPI object (i.e., command) */\nexport type ReferencedItem = `${string}.${string}`;\n\nexport type OrderedItem = {\n /** Relative order of this item compared to other items in the same parent/scope (sorted ascending) */\n order: number;\n};\n\nexport type OrderedExtensibleContainer = OrderedItem & {\n /** Determines whether other items can be added to this after it has been defined */\n isExtensible?: boolean;\n};\n\n/** Group of menu items that belongs in a column */\nexport type MenuGroupDetailsInColumn = OrderedExtensibleContainer & {\n /** ID of column in which this group resides */\n column: ReferencedItem;\n};\n\n/** Group of menu items that belongs in a submenu */\nexport type MenuGroupDetailsInSubMenu = OrderedExtensibleContainer & {\n /** ID of menu item hosting the submenu in which this group resides */\n menuItem: ReferencedItem;\n};\n\n/** Column that includes header text in a menu */\nexport type MenuColumnWithHeader = OrderedExtensibleContainer & {\n /** Key that represents the text of the header text of the column */\n label: LocalizeKey;\n};\n\nexport type MenuItemBase = OrderedItem & {\n /** Menu group to which this menu item belongs */\n group: ReferencedItem;\n /** Key that represents the text of this menu item to display */\n label: LocalizeKey;\n /** Key that represents words the platform should reference when users are searching for menu items */\n searchTerms?: LocalizeKey;\n /** Key that represents the text to display if a mouse pointer hovers over the menu item */\n tooltip?: LocalizeKey;\n /** Additional information provided by developers to help people who perform localization */\n localizeNotes: string;\n};\n\n/** Menu item that hosts a submenu */\nexport type MenuItemContainingSubmenu = MenuItemBase & {\n /** ID for this menu item that holds a submenu */\n id: ReferencedItem;\n};\n\n/** Menu item that runs a command */\nexport type MenuItemContainingCommand = MenuItemBase & {\n /** Name of the PAPI command to run when this menu item is selected. */\n command: ReferencedItem;\n /** Path to the icon to display after the menu text */\n iconPathAfter?: string;\n /** Path to the icon to display before the menu text */\n iconPathBefore?: string;\n};\n\n/**\n * Group of menu items that can be combined with other groups to form a single context menu/submenu.\n * Groups are separated using a line within the menu/submenu.\n */\nexport type GroupsInSingleColumnMenu = {\n /** Named menu group */\n [property: ReferencedItem]: OrderedExtensibleContainer | MenuGroupDetailsInSubMenu;\n};\n\n/**\n * Group of menu items that can be combined with other groups to form a single menu/submenu within a\n * multi-column menu. Groups are separated using a line within the menu/submenu.\n */\nexport type GroupsInMultiColumnMenu = {\n /** Named menu group */\n [property: ReferencedItem]: MenuGroupDetailsInColumn | MenuGroupDetailsInSubMenu;\n};\n\n/** Group of columns that can be combined with other columns to form a multi-column menu */\nexport type ColumnsWithHeaders = {\n /** Named column of a menu */\n [property: ReferencedItem]: MenuColumnWithHeader;\n /** Defines whether columns can be added to this multi-column menu */\n isExtensible?: boolean;\n};\n\n/** Menu that contains a column without a header */\nexport type SingleColumnMenu = {\n /** Groups that belong in this menu */\n groups: GroupsInSingleColumnMenu;\n /** List of menu items that belong in this menu */\n items: (MenuItemContainingCommand | MenuItemContainingSubmenu)[];\n};\n\n/** Menu that contains multiple columns with headers */\nexport type MultiColumnMenu = {\n /** Columns that belong in this menu */\n columns: ColumnsWithHeaders;\n /** Groups that belong in this menu */\n groups: GroupsInMultiColumnMenu;\n /** List of menu items that belong in this menu */\n items: (MenuItemContainingCommand | MenuItemContainingSubmenu)[];\n};\n\n/** Menus for one single web view */\nexport type WebViewMenu = {\n /** Indicates whether the platform default menus should be included for this webview */\n includeDefaults: boolean | undefined;\n /** Menu that opens when you click on the top left corner of a tab */\n topMenu: MultiColumnMenu | undefined;\n /** Menu that opens when you right click on the main body/area of a tab */\n contextMenu: SingleColumnMenu | undefined;\n};\n\n/** Menus for all web views */\nexport type WebViewMenus = {\n /** Named web view */\n [property: ReferencedItem]: WebViewMenu;\n};\n\n/** Platform.Bible menus before they are localized */\nexport type PlatformMenus = {\n /** Top level menu for the application */\n mainMenu: MultiColumnMenu;\n /** Menus that apply per web view in the application */\n webViewMenus: WebViewMenus;\n /** Default context menu for web views that don't specify their own */\n defaultWebViewContextMenu: SingleColumnMenu;\n /** Default top menu for web views that don't specify their own */\n defaultWebViewTopMenu: MultiColumnMenu;\n};\n\n/**\n * Type that converts any menu type before it is localized to what it is after it is localized. This\n * can be applied to any menu type as needed.\n */\nexport type Localized = ReplaceType, ReferencedItem, string>;\n\n//----------------------------------------------------------------------------------------------\n// NOTE: If you change the schema below, make sure the TS types above get changed so they align.\n//----------------------------------------------------------------------------------------------\n/** JSON schema object that aligns with the PlatformMenus type */\nexport const menuDocumentSchema = {\n title: 'Platform.Bible menus',\n type: 'object',\n properties: {\n mainMenu: {\n description: 'Top level menu for the application',\n $ref: '#/$defs/multiColumnMenu',\n },\n defaultWebViewTopMenu: {\n description: \"Default top menu for web views that don't specify their own\",\n $ref: '#/$defs/multiColumnMenu',\n },\n defaultWebViewContextMenu: {\n description: \"Default context menu for web views that don't specify their own\",\n $ref: '#/$defs/singleColumnMenu',\n },\n webViewMenus: {\n description: 'Menus that apply per web view in the application',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n $ref: '#/$defs/menusForOneWebView',\n },\n },\n additionalProperties: false,\n },\n },\n required: ['mainMenu', 'defaultWebViewTopMenu', 'defaultWebViewContextMenu', 'webViewMenus'],\n additionalProperties: false,\n $defs: {\n localizeKey: {\n description:\n \"Identifier for a string that will be localized in a menu based on the user's UI language\",\n type: 'string',\n pattern: '^%[\\\\w\\\\-\\\\.]+%$',\n },\n referencedItem: {\n description:\n 'Name of some UI element (i.e., tab, column, group, menu item) or some PAPI object (i.e., command)',\n type: 'string',\n pattern: '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$',\n },\n columnsWithHeaders: {\n description:\n 'Group of columns that can be combined with other columns to form a multi-column menu',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n description: 'Single column with a header string',\n type: 'object',\n properties: {\n label: {\n description: 'Header text for this this column in the UI',\n $ref: '#/$defs/localizeKey',\n },\n localizeNotes: {\n description:\n 'Additional information provided by developers to help people who perform localization',\n type: 'string',\n },\n order: {\n description:\n 'Relative order of this column compared to other columns (sorted ascending)',\n type: 'number',\n },\n isExtensible: {\n description:\n 'Defines whether contributions are allowed to add menu groups to this column',\n type: 'boolean',\n },\n },\n required: ['label', 'order'],\n additionalProperties: false,\n },\n },\n properties: {\n isExtensible: {\n description:\n 'Defines whether contributions are allowed to add columns to this multi-column menu',\n type: 'boolean',\n },\n },\n },\n menuGroups: {\n description:\n 'Group of menu items that can be combined with other groups to form a single menu/submenu. Groups are separated using a line within the menu/submenu.',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n description: 'Single group that contains menu items',\n type: 'object',\n oneOf: [\n {\n properties: {\n column: {\n description:\n 'Column where this group belongs, not required for single column menus',\n $ref: '#/$defs/referencedItem',\n },\n order: {\n description:\n 'Relative order of this group compared to other groups in the same column or submenu (sorted ascending)',\n type: 'number',\n },\n isExtensible: {\n description:\n 'Defines whether contributions are allowed to add menu items to this menu group',\n type: 'boolean',\n },\n },\n required: ['order'],\n additionalProperties: false,\n },\n {\n properties: {\n menuItem: {\n description: 'Menu item that anchors the submenu where this group belongs',\n $ref: '#/$defs/referencedItem',\n },\n order: {\n description:\n 'Relative order of this group compared to other groups in the same column or submenu (sorted ascending)',\n type: 'number',\n },\n isExtensible: {\n description:\n 'Defines whether contributions are allowed to add menu items to this menu group',\n type: 'boolean',\n },\n },\n required: ['menuItem', 'order'],\n additionalProperties: false,\n },\n ],\n },\n },\n additionalProperties: false,\n },\n menuItem: {\n description:\n 'Single item in a menu that can be clicked on to take an action or can be the parent of a submenu',\n type: 'object',\n oneOf: [\n {\n properties: {\n id: {\n description: 'ID for this menu item that holds a submenu',\n $ref: '#/$defs/referencedItem',\n },\n },\n required: ['id'],\n },\n {\n properties: {\n command: {\n description: 'Name of the PAPI command to run when this menu item is selected.',\n $ref: '#/$defs/referencedItem',\n },\n iconPathBefore: {\n description: 'Path to the icon to display before the menu text',\n type: 'string',\n },\n iconPathAfter: {\n description: 'Path to the icon to display after the menu text',\n type: 'string',\n },\n },\n required: ['command'],\n },\n ],\n properties: {\n label: {\n description: 'Key that represents the text of this menu item to display',\n $ref: '#/$defs/localizeKey',\n },\n tooltip: {\n description:\n 'Key that represents the text to display if a mouse pointer hovers over the menu item',\n $ref: '#/$defs/localizeKey',\n },\n searchTerms: {\n description:\n 'Key that represents additional words the platform should reference when users are searching for menu items',\n $ref: '#/$defs/localizeKey',\n },\n localizeNotes: {\n description:\n 'Additional information provided by developers to help people who perform localization',\n type: 'string',\n },\n group: {\n description: 'Group to which this menu item belongs',\n $ref: '#/$defs/referencedItem',\n },\n order: {\n description:\n 'Relative order of this menu item compared to other menu items in the same group (sorted ascending)',\n type: 'number',\n },\n },\n required: ['label', 'group', 'order'],\n unevaluatedProperties: false,\n },\n groupsAndItems: {\n description: 'Core schema for a column',\n type: 'object',\n properties: {\n groups: {\n description: 'Groups that belong in this menu',\n $ref: '#/$defs/menuGroups',\n },\n items: {\n description: 'List of menu items that belong in this menu',\n type: 'array',\n items: { $ref: '#/$defs/menuItem' },\n uniqueItems: true,\n },\n },\n required: ['groups', 'items'],\n },\n singleColumnMenu: {\n description: 'Menu that contains a column without a header',\n type: 'object',\n allOf: [{ $ref: '#/$defs/groupsAndItems' }],\n unevaluatedProperties: false,\n },\n multiColumnMenu: {\n description: 'Menu that can contain multiple columns with headers',\n type: 'object',\n allOf: [\n { $ref: '#/$defs/groupsAndItems' },\n {\n properties: {\n columns: {\n description: 'Columns that belong in this menu',\n $ref: '#/$defs/columnsWithHeaders',\n },\n },\n required: ['columns'],\n },\n ],\n unevaluatedProperties: false,\n },\n menusForOneWebView: {\n description: 'Set of menus that are associated with a single tab',\n type: 'object',\n properties: {\n includeDefaults: {\n description:\n 'Indicates whether the platform default menus should be included for this webview',\n type: 'boolean',\n },\n topMenu: {\n description: 'Menu that opens when you click on the top left corner of a tab',\n $ref: '#/$defs/multiColumnMenu',\n },\n contextMenu: {\n description: 'Menu that opens when you right click on the main body/area of a tab',\n $ref: '#/$defs/singleColumnMenu',\n },\n },\n additionalProperties: false,\n },\n },\n};\n\nObject.freeze(menuDocumentSchema);\n"],"names":["AsyncVariable","variableName","rejectIfNotSettledWithinMS","__publicField","resolve","reject","value","throwIfAlreadySettled","reason","Collator","locales","options","string1","string2","DateTimeFormat","date","startDate","endDate","PlatformEventEmitter","event","callback","callbackIndex","_a","newGuid","s","isString","o","deepClone","obj","debounce","fn","delay","timeout","args","groupBy","items","keySelector","valueSelector","map","item","key","group","isErrorWithMessage","error","toErrorWithMessage","maybeError","getErrorMessage","wait","ms","waitForDuration","maxWaitTimeInMS","getAllObjectFunctionNames","objId","objectFunctionNames","property","objectPrototype","createSyncProxyForAsyncObject","getObject","objectToProxy","target","prop","DocumentCombiner","baseDocument","documentName","document","previousDocumentVersion","documentToSet","contributions","contributionName","potentialOutput","outputIteration","contribution","mergeObjects","output","finalOutput","areNonArrayObjects","values","allMatch","areArrayObjects","startingPoint","copyFrom","ignoreDuplicateProperties","retVal","mergeObjectsInternal","startingPointObj","copyFromObj","Mutex","AsyncMutex","MutexMap","mutexID","NonValidatingDocumentCombiner","NumberFormat","startRange","endRange","UnsubscriberAsyncList","name","unsubscribers","unsubscriber","unsubs","results","unsubscriberSucceeded","index","P","R","n","m","v","X","C","K","N","B","x","T","O","V","I","L","G","S","H","k","A","y","q","U","f","l","u","c","E","r","D","i","a","h","d","g","w","b","J","charRegex","astralRange","comboMarksRange","comboHalfMarksRange","comboSymbolsRange","comboMarksExtendedRange","comboMarksSupplementRange","comboRange","varRange","familyRange","astral","combo","fitz","modifier","nonAstral","regional","surrogatePair","zwj","blackFlag","family","optModifier","optVar","optJoin","seq","symbol","__importDefault","this","mod","dist","char_regex_1","require$$0","toArray","str","toArray_1","length","match","length_1","substring","begin","end","substring_1","substr","len","strLength","substr_1","limit","padString","padPosition","padRepeats","limit_1","indexOf","searchStr","pos","strArr","searchArr","finded","searchIndex","indexOf_1","at","string","stringLength","charAt","codePointAt","endsWith","searchString","endPosition","lastIndexOfSearchString","lastIndexOf","indexOfClosestClosingCurlyBrace","escaped","closeCurlyBraceIndex","formatReplacementString","replacers","updatedStr","replacerKey","replacerString","includes","position","partialString","stringzIndexOf","validatedPosition","stringzLength","normalize","form","upperCaseForm","ordinalCompare","padEnd","targetLength","stringzLimit","padStart","correctSliceIndex","slice","indexStart","indexEnd","newStart","newEnd","split","separator","splitLimit","result","regexSeparator","matches","currentIndex","matchIndex","matchLength","startsWith","stringzSubstr","stringzSubstring","stringzToArray","isLocalizeKey","scrBookData","FIRST_SCR_BOOK_NUM","LAST_SCR_BOOK_NUM","FIRST_SCR_CHAPTER_NUM","FIRST_SCR_VERSE_NUM","getChaptersForBook","bookNum","offsetBook","scrRef","offset","offsetChapter","offsetVerse","getLocalizedIdFromBookNumber","bookNumber","localizationLanguage","getLocalizedString","id","Canon","bookName","parts","aggregateUnsubscribers","success","aggregateUnsubscriberAsyncs","unsubPromises","getOwnPropertyNames","getOwnPropertySymbols","hasOwnProperty","combineComparators","comparatorA","comparatorB","state","createIsCircular","areItemsEqual","cache","cachedA","cachedB","getStrictProperties","object","hasOwn","sameValueZeroEqual","OWNER","getOwnPropertyDescriptor","keys","areArraysEqual","areDatesEqual","areMapsEqual","matchedIndices","aIterable","aResult","bResult","bIterable","hasMatch","aKey","aValue","_b","bKey","bValue","areObjectsEqual","properties","areObjectsEqualStrict","descriptorA","descriptorB","arePrimitiveWrappersEqual","areRegExpsEqual","areSetsEqual","areTypedArraysEqual","ARGUMENTS_TAG","BOOLEAN_TAG","DATE_TAG","MAP_TAG","NUMBER_TAG","OBJECT_TAG","REG_EXP_TAG","SET_TAG","STRING_TAG","isArray","isTypedArray","assign","getTag","createEqualityComparator","constructor","tag","createEqualityComparatorConfig","circular","createCustomConfig","strict","config","areArraysEqual$1","areMapsEqual$1","areObjectsEqual$1","areSetsEqual$1","createInternalEqualityComparator","compare","_indexOrKeyA","_indexOrKeyB","_parentA","_parentB","createIsEqual","comparator","createState","equals","meta","deepEqual","createCustomEqual","createCustomInternalComparator","isEqualDeep","isSubset","objectWithAllProperties","objectWithPartialProperties","partialArray","allArray","partialObj","allObj","serialize","replacer","space","replacerValue","newValue","deserialize","reviver","replaceNull","parsedObject","isSerializable","serializedValue","htmlEncode","getCurrentLocale","settingsDefs","removeJsonToTypeScriptTypesStuff","defs","def","projectSettingsDocumentSchema","settingsDocumentSchema","localizedStringsDefs","localizedStringsDocumentSchema","menuDocumentSchema"],"mappings":"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E,KAAA,cAAc,IAAIA,CAAY,CAAA,CACzC,CACH,CAOA,MAAM,qBAAwC,CACtC,MAAAC,EAAS,CAAC,GAAG,KAAK,aAAa,EAAE,IAAKD,GAAiBA,EAAA,CAAc,EACrEE,EAAU,MAAM,QAAQ,IAAID,CAAM,EACxC,YAAK,cAAc,QACZC,EAAQ,MAAM,CAACC,EAAuBC,KACtCD,GACH,QAAQ,MAAM,yBAAyB,KAAK,IAAI,2BAA2BC,CAAK,UAAU,EAErFD,EACR,CACH,CACF,CCrCA,IAAIE,GAAI,OAAO,eACXC,GAAI,CAAC,EAAG,EAAG/E,IAAM,KAAK,EAAI8E,GAAE,EAAG,EAAG,CAAE,WAAY,GAAI,aAAc,GAAI,SAAU,GAAI,MAAO9E,CAAC,CAAE,EAAI,EAAE,CAAC,EAAIA,EACzGgF,EAAI,CAAC,EAAG,EAAGhF,KAAO+E,GAAE,EAAG,OAAO,GAAK,SAAW,EAAI,GAAK,EAAG/E,CAAC,EAAGA,GAWlE,MAAMiF,EAAI,CACR,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MAEA,MAEA,MAEA,MAEA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MAEA,MAEA,MAEA,MAEA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,KACF,EAAGC,EAAI,CACL,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,KACF,EAAGC,GAAI,CACL,UACA,SACA,YACA,UACA,cACA,SACA,SACA,OACA,WACA,WACA,UACA,UACA,eACA,eACA,OACA,WACA,kBACA,MACA,SACA,WACA,eACA,gBACA,SACA,WACA,eACA,UACA,kBACA,QACA,OACA,OACA,UACA,QACA,QACA,QACA,WACA,YACA,SACA,YACA,UACA,UACA,OACA,OACA,OACA,OACA,SACA,gBACA,gBACA,YACA,YACA,cACA,aACA,kBACA,kBACA,YACA,YACA,QACA,WACA,UACA,QACA,UACA,UACA,SACA,SACA,SACA,OACA,aACA,QACA,SACA,eACA,oBACA,0BACA,SACA,qBACA,sBACA,UACA,qBACA,cACA,cACA,cACA,cACA,mBACA,mBACA,qBACA,YACA,OACA,oBAGA,uBACA,uBACA,sBACA,yBACA,wBACA,qBACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,eACA,cACA,eACA,oBACA,qBACA,0BACA,0BACA,eACA,eACA,YACA,gBACA,cACA,eACA,iBACA,wBACA,mBACA,WACA,QACA,aACA,aACA,aACA,2BACA,4BACA,YACF,EAAGC,GAAIC,KACP,SAASC,EAAE,EAAG,EAAI,GAAI,CACpB,OAAO,IAAM,EAAI,EAAE,YAAa,GAAG,KAAKF,GAAIA,GAAE,CAAC,EAAI,CACrD,CACA,SAASG,EAAE,EAAG,CACZ,OAAOD,EAAE,CAAC,EAAI,CAChB,CACA,SAASE,GAAE,EAAG,CACZ,MAAM,EAAI,OAAO,GAAK,SAAWF,EAAE,CAAC,EAAI,EACxC,OAAO,GAAK,IAAM,GAAK,EACzB,CACA,SAASG,GAAE,EAAG,CACZ,OAAQ,OAAO,GAAK,SAAWH,EAAE,CAAC,EAAI,IAAM,EAC9C,CACA,SAASI,GAAE,EAAG,CACZ,OAAO,GAAK,EACd,CACA,SAASC,GAAE,EAAG,CACZ,MAAM,EAAI,OAAO,GAAK,SAAWL,EAAE,CAAC,EAAI,EACxC,OAAOM,GAAE,CAAC,GAAK,CAACF,GAAE,CA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b,MAAO,CACL,CACE,KAAM,qBACR,EACA,CACE,KAAM,yBACR,CACF,CACF,EACA,gBAAiB,CACf,YAAa,8CACb,KAAM,SACN,WAAY,CACV,yBAA0B,CACxB,YACE,q4CACF,MAAO,CACL,CACE,KAAM,MACR,EACA,CACE,KAAM,QACR,EACA,CACE,KAAM,QACN,MAAO,CACL,MAAO,CACL,CACE,KAAM,QACR,EACA,CACE,KAAM,QACN,MAAO,CAAE,KAAM,QAAS,CAC1B,CACF,CACF,CACF,CACF,CACF,EACA,yBAA0B,CACxB,YACE,ugDACF,MAAO,CACL,CACE,KAAM,MACR,EACA,CACE,KAAM,QACR,EACA,CACE,KAAM,QACN,MAAO,CACL,MAAO,CACL,CACE,KAAM,QACR,EACA,CACE,KAAM,QACN,MAAO,CAAE,KAAM,QAAS,CAC1B,CACF,CACF,CACF,CACF,CACF,EACA,qBAAsB,CACpB,YACE,ybACF,MAAO,CACL,CACE,KAAM,MACR,EACA,CACE,KAAM,QACR,EACA,CACE,KAAM,QACN,MAAO,CAAE,KAAM,QAAS,CAC1B,CACF,CACF,EACA,qBAAsB,CACpB,YACE,mdACF,MAAO,CACL,CACE,KAAM,MACR,EACA,CACE,KAAM,QACR,EACA,CACE,KAAM,QACN,MAAO,CAAE,KAAM,QAAS,CAC1B,CACF,CACF,CACF,CACF,EACA,qBAAsB,CACpB,YACE,sFACF,MAAO,CACL,CACE,KAAM,uBACR,EACA,CACE,KAAM,QACN,MAAO,CACL,KAAM,uBACR,CACF,CACF,CACF,EACA,cAAe,CACb,YAAa,wCACb,KAAM,SACN,WAAY,CACV,MAAO,CACL,YAAa,qEACb,KAAM,qBACR,EACA,YAAa,CACX,YAAa,yEACb,KAAM,qBACR,EACA,WAAY,CACV,KAAM,2BACR,CACF,EACA,SAAU,CAAC,QAAS,YAAY,CAClC,EACA,kBAAmB,CACjB,YAAa,0EACb,KAAM,SACN,kBAAmB,CACjB,sBAAuB,CACrB,KAAM,iBACR,CACF,EACA,qBAAsB,EACxB,EACA,QAAS,CACP,YAAa,gDACb,MAAO,CACL,CACE,KAAM,oCACR,CACF,CACF,EACA,2BAA4B,CAC1B,YAAa,yDACb,MAAO,CACL,CACE,KAAM,qBACR,EACA,CACE,KAAM,qCACR,CACF,CACF,EACA,YAAa,CACX,YAAa,sDACb,MAAO,CACL,CACE,KAAM,mBACR,EACA,CACE,KAAM,SACN,WAAY,CACV,MAAO,CACL,YAAa,uEACb,KAAM,qBACR,EACA,YAAa,CACX,YAAa,2EACb,KAAM,qBACR,CACF,EACA,SAAU,CAAC,OAAO,CACpB,CACF,CACF,EACA,yBAA0B,CACxB,YACE,2FACF,KAAM,6BACR,EACA,sBAAuB,CACrB,YACE,wFACF,KAAM,6BACR,EACA,oBAAqB,CACnB,YAAa,qEACb,KAAM,SACN,kBAAmB,CACjB,0BAA2B,CACzB,KAAM,mBACR,CACF,EACA,qBAAsB,EACxB,EACA,UAAW,CACT,YAAa,mDACb,MAAO,CACL,CACE,KAAM,kCACR,CACF,CACF,EACA,yBAA0B,CACxB,YAAa,uDACb,MAAO,CACL,CACE,KAAM,mBACR,EACA,CACE,KAAM,qCACR,CACF,CACF,EACA,4BAA6B,CAC3B,YACE,0NACF,IAAK,CACH,MAAO,CACL,CACE,KAAM,SACN,SAAU,CAAC,cAAc,CAC3B,EACA,CACE,KAAM,SACN,SAAU,CAAC,MAAM,CACnB,CACF,CACF,CACF,EACA,UAAW,CACT,YAAa,oDACb,KAAM,SACN,WAAY,CACV,QAAS,CACP,YAAa,sCACb,KAAM,KACR,EACA,YAAa,CACX,YACE,2HACF,KAAM,YACR,CACF,EACA,SAAU,CAAC,SAAS,CACtB,EACA,YAAa,CACX,YAAa,iFACb,KAAM,SACN,QAAS,mBACT,OAAQ,aACV,EACA,GAAI,CACF,YAAa,GACb,KAAM,SACN,QAAS,0BACT,OAAQ,IACV,CACF,EAUO,SAASC,EAAiCC,EAAW,CACrDA,GAIL,OAAO,OAAOA,CAAI,EAAE,QAASC,GAAa,CACxC,GAAKA,EAAI,KAIL,IAFA,WAAYA,GAAK,OAAOA,EAAI,OAE5BA,EAAI,OAAS,MAAO,CACtB,OAAOA,EAAI,KACX,MACF,CAEIA,EAAI,OAAS,UACfF,EAAiCE,EAAI,UAAU,EACjD,CACD,CACH,CAEAF,EAAiCD,CAAY,EAGtC,MAAMI,GAAgC,CAC3C,QAAS,+CACT,MAAO,gCACP,YACE,8FACF,MAAO,CACL,CACE,KAAM,8BACR,EACA,CACE,KAAM,QACN,MAAO,CACL,KAAM,8BACR,CACF,CACF,EAEA,MAAOJ,CACT,EAEA,OAAO,OAAOI,EAA6B,EAGpC,MAAMC,GAAyB,CACpC,QAAS,+CACT,MAAO,wBACP,YACE,sFACF,MAAO,CACL,CACE,KAAM,uBACR,EACA,CACE,KAAM,QACN,MAAO,CACL,KAAM,uBACR,CACF,CACF,EAEA,MAAOL,CACT,EAEA,OAAO,OAAOK,EAAsB,ECniBpC,MAAMC,GAAuB,CAC3B,gBAAiB,CACf,YACE,2IACF,KAAM,SACN,kBAAmB,CACjB,mBAAoB,CAClB,KAAM,8BACR,CACF,EACA,qBAAsB,EACxB,EACA,qBAAsB,CACpB,YAAa,kDACb,KAAM,QACR,EACA,gBAAiB,CACf,YACE,8IACF,KAAM,SACN,kBAAmB,CACjB,mBAAoB,CAClB,KAAM,wBACR,CACF,EACA,qBAAsB,EACxB,EACA,eAAgB,CACd,YAAa,0EACb,KAAM,SACN,WAAY,CACV,YAAa,CACX,YACE,sPACF,KAAM,qBACR,EACA,MAAO,CACL,YACE,4IACF,KAAM,QACR,CACF,CACF,EACA,YAAa,CACX,YAAa,iFACb,KAAM,SACN,QAAS,mBACT,OAAQ,aACV,CACF,EAEAL,EAAiCK,EAAoB,EAG9C,MAAMC,GAAiC,CAC5C,QAAS,+CACT,MAAO,qCACP,YACE,gGACF,KAAM,SACN,WAAY,CACV,SAAU,CACR,KAAM,yBACR,EACA,iBAAkB,CAChB,KAAM,SACN,qBAAsB,CACpB,KAAM,yBACR,CACF,CACF,EACA,MAAOD,EACT,EAEA,OAAO,OAAOC,EAA8B,ECyBrC,MAAMC,GAAqB,CAChC,MAAO,uBACP,KAAM,SACN,WAAY,CACV,SAAU,CACR,YAAa,qCACb,KAAM,yBACR,EACA,sBAAuB,CACrB,YAAa,8DACb,KAAM,yBACR,EACA,0BAA2B,CACzB,YAAa,kEACb,KAAM,0BACR,EACA,aAAc,CACZ,YAAa,mDACb,KAAM,SACN,kBAAmB,CACjB,0BAA2B,CACzB,KAAM,4BACR,CACF,EACA,qBAAsB,EACxB,CACF,EACA,SAAU,CAAC,WAAY,wBAAyB,4BAA6B,cAAc,EAC3F,qBAAsB,GACtB,MAAO,CACL,YAAa,CACX,YACE,2FACF,KAAM,SACN,QAAS,kBACX,EACA,eAAgB,CACd,YACE,oGACF,KAAM,SACN,QAAS,yBACX,EACA,mBAAoB,CAClB,YACE,uFACF,KAAM,SACN,kBAAmB,CACjB,0BAA2B,CACzB,YAAa,qCACb,KAAM,SACN,WAAY,CACV,MAAO,CACL,YAAa,6CACb,KAAM,qBACR,EACA,cAAe,CACb,YACE,wFACF,KAAM,QACR,EACA,MAAO,CACL,YACE,6EACF,KAAM,QACR,EACA,aAAc,CACZ,YACE,8EACF,KAAM,SACR,CACF,EACA,SAAU,CAAC,QAAS,OAAO,EAC3B,qBAAsB,EACxB,CACF,EACA,WAAY,CACV,aAAc,CACZ,YACE,qFACF,KAAM,SACR,CACF,CACF,EACA,WAAY,CACV,YACE,uJACF,KAAM,SACN,kBAAmB,CACjB,0BAA2B,CACzB,YAAa,wCACb,KAAM,SACN,MAAO,CACL,CACE,WAAY,CACV,OAAQ,CACN,YACE,wEACF,KAAM,wBACR,EACA,MAAO,CACL,YACE,yGACF,KAAM,QACR,EACA,aAAc,CACZ,YACE,iFACF,KAAM,SACR,CACF,EACA,SAAU,CAAC,OAAO,EAClB,qBAAsB,EACxB,EACA,CACE,WAAY,CACV,SAAU,CACR,YAAa,8DACb,KAAM,wBACR,EACA,MAAO,CACL,YACE,yGACF,KAAM,QACR,EACA,aAAc,CACZ,YACE,iFACF,KAAM,SACR,CACF,EACA,SAAU,CAAC,WAAY,OAAO,EAC9B,qBAAsB,EACxB,CACF,CACF,CACF,EACA,qBAAsB,EACxB,EACA,SAAU,CACR,YACE,mGACF,KAAM,SACN,MAAO,CACL,CACE,WAAY,CACV,GAAI,CACF,YAAa,6CACb,KAAM,wBACR,CACF,EACA,SAAU,CAAC,IAAI,CACjB,EACA,CACE,WAAY,CACV,QAAS,CACP,YAAa,mEACb,KAAM,wBACR,EACA,eAAgB,CACd,YAAa,mDACb,KAAM,QACR,EACA,cAAe,CACb,YAAa,kDACb,KAAM,QACR,CACF,EACA,SAAU,CAAC,SAAS,CACtB,CACF,EACA,WAAY,CACV,MAAO,CACL,YAAa,4DACb,KAAM,qBACR,EACA,QAAS,CACP,YACE,uFACF,KAAM,qBACR,EACA,YAAa,CACX,YACE,6GACF,KAAM,qBACR,EACA,cAAe,CACb,YACE,wFACF,KAAM,QACR,EACA,MAAO,CACL,YAAa,wCACb,KAAM,wBACR,EACA,MAAO,CACL,YACE,qGACF,KAAM,QACR,CACF,EACA,SAAU,CAAC,QAAS,QAAS,OAAO,EACpC,sBAAuB,EACzB,EACA,eAAgB,CACd,YAAa,2BACb,KAAM,SACN,WAAY,CACV,OAAQ,CACN,YAAa,kCACb,KAAM,oBACR,EACA,MAAO,CACL,YAAa,8CACb,KAAM,QACN,MAAO,CAAE,KAAM,kBAAmB,EAClC,YAAa,EACf,CACF,EACA,SAAU,CAAC,SAAU,OAAO,CAC9B,EACA,iBAAkB,CAChB,YAAa,+CACb,KAAM,SACN,MAAO,CAAC,CAAE,KAAM,yBAA0B,EAC1C,sBAAuB,EACzB,EACA,gBAAiB,CACf,YAAa,sDACb,KAAM,SACN,MAAO,CACL,CAAE,KAAM,wBAAyB,EACjC,CACE,WAAY,CACV,QAAS,CACP,YAAa,mCACb,KAAM,4BACR,CACF,EACA,SAAU,CAAC,SAAS,CACtB,CACF,EACA,sBAAuB,EACzB,EACA,mBAAoB,CAClB,YAAa,qDACb,KAAM,SACN,WAAY,CACV,gBAAiB,CACf,YACE,mFACF,KAAM,SACR,EACA,QAAS,CACP,YAAa,iEACb,KAAM,yBACR,EACA,YAAa,CACX,YAAa,sEACb,KAAM,0BACR,CACF,EACA,qBAAsB,EACxB,CACF,CACF,EAEA,OAAO,OAAOA,EAAkB","x_google_ignoreList":[11,12,13,17]} \ No newline at end of file +{"version":3,"file":"index.cjs","sources":["../src/async-variable.ts","../src/intl-collator.ts","../src/intl-date-time-format.ts","../src/platform-event-emitter.model.ts","../src/util.ts","../src/document-combiner.ts","../src/mutex.ts","../src/mutex-map.ts","../src/non-validating-document-combiner.ts","../src/intl-number-format.ts","../src/unsubscriber-async-list.ts","../../../node_modules/@sillsdev/scripture/dist/index.es.js","../../../node_modules/char-regex/index.js","../../../node_modules/stringz/dist/index.js","../src/string-util.ts","../src/scripture-util.ts","../src/unsubscriber.ts","../../../node_modules/fast-equals/dist/esm/index.mjs","../src/equality-checking.ts","../src/subset-checking.ts","../src/serialization.ts","../src/intl-util.ts","../src/settings.model.ts","../src/localized-strings.model.ts","../src/menus.model.ts"],"sourcesContent":["/** This class provides a convenient way for one task to wait on a variable that another task sets. */\nexport default class AsyncVariable {\n private readonly variableName: string;\n private readonly promiseToValue: Promise;\n private resolver: ((value: T) => void) | undefined;\n private rejecter: ((reason: string | undefined) => void) | undefined;\n\n /**\n * Creates an instance of the class\n *\n * @param variableName Name to use when logging about this variable\n * @param rejectIfNotSettledWithinMS Milliseconds to wait before verifying if the promise was\n * settled (resolved or rejected); will reject if it has not settled by that time. Use -1 if you\n * do not want a timeout at all.\n */\n constructor(variableName: string, rejectIfNotSettledWithinMS: number = 10000) {\n this.variableName = variableName;\n this.promiseToValue = new Promise((resolve, reject) => {\n this.resolver = resolve;\n this.rejecter = reject;\n });\n if (rejectIfNotSettledWithinMS > 0) {\n setTimeout(() => {\n if (this.rejecter) {\n this.rejecter(`Timeout reached when waiting for ${this.variableName} to settle`);\n this.complete();\n }\n }, rejectIfNotSettledWithinMS);\n }\n Object.seal(this);\n }\n\n /**\n * Get this variable's promise to a value. This always returns the same promise even after the\n * value has been resolved or rejected.\n *\n * @returns The promise for the value to be set\n */\n get promise(): Promise {\n return this.promiseToValue;\n }\n\n /**\n * A simple way to see if this variable's promise was resolved or rejected already\n *\n * @returns Whether the variable was already resolved or rejected\n */\n get hasSettled(): boolean {\n return Object.isFrozen(this);\n }\n\n /**\n * Resolve this variable's promise to the given value\n *\n * @param value This variable's promise will resolve to this value\n * @param throwIfAlreadySettled Determines whether to throw if the variable was already resolved\n * or rejected\n */\n resolveToValue(value: T, throwIfAlreadySettled: boolean = false): void {\n if (this.resolver) {\n console.debug(`${this.variableName} is being resolved now`);\n this.resolver(value);\n this.complete();\n } else {\n if (throwIfAlreadySettled) throw Error(`${this.variableName} was already settled`);\n console.debug(`Ignoring subsequent resolution of ${this.variableName}`);\n }\n }\n\n /**\n * Reject this variable's promise for the value with the given reason\n *\n * @param reason This variable's promise will be rejected with this reason\n * @param throwIfAlreadySettled Determines whether to throw if the variable was already resolved\n * or rejected\n */\n rejectWithReason(reason: string, throwIfAlreadySettled: boolean = false): void {\n if (this.rejecter) {\n console.debug(`${this.variableName} is being rejected now`);\n this.rejecter(reason);\n this.complete();\n } else {\n if (throwIfAlreadySettled) throw Error(`${this.variableName} was already settled`);\n console.debug(`Ignoring subsequent rejection of ${this.variableName}`);\n }\n }\n\n /** Prevent any further updates to this variable */\n private complete(): void {\n this.resolver = undefined;\n this.rejecter = undefined;\n Object.freeze(this);\n }\n}\n","/** Enables language-sensitive string comparison. Wraps Intl.Collator */\nexport default class Collator {\n private collator: Intl.Collator;\n\n constructor(locales?: string | string[], options?: Intl.CollatorOptions) {\n this.collator = new Intl.Collator(locales, options);\n }\n\n /**\n * Compares two strings according to the sort order of this Collator object\n *\n * @param string1 String to compare\n * @param string2 String to compare\n * @returns A number indicating how string1 and string2 compare to each other according to the\n * sort order of this Collator object. Negative value if string1 comes before string2. Positive\n * value if string1 comes after string2. 0 if they are considered equal.\n */\n compare(string1: string, string2: string): number {\n return this.collator.compare(string1, string2);\n }\n\n /**\n * Returns a new object with properties reflecting the locale and collation options computed\n * during initialization of this collator object.\n *\n * @returns ResolvedCollatorOptions object\n */\n resolvedOptions(): Intl.ResolvedCollatorOptions {\n return this.collator.resolvedOptions();\n }\n}\n","/** Enables language-sensitive data and time formatting. Wraps Intl.DateTimeFormat */\nexport default class DateTimeFormat {\n private dateTimeFormatter: Intl.DateTimeFormat;\n\n constructor(locales?: string | string[], options?: Intl.DateTimeFormatOptions) {\n this.dateTimeFormatter = new Intl.DateTimeFormat(locales, options);\n }\n\n /**\n * Formats a date according to the locale and formatting option for this DateTimeFormat object\n *\n * @param date The date to format\n * @returns String representing the given date formatted according to the locale and formatting\n * options of this DateTimeFormat object\n */\n format(date: Date): string {\n return this.dateTimeFormatter.format(date);\n }\n\n /**\n * Formats a date range in the most concise way based on the locales and options provided when\n * instantiating this DateTimeFormat object\n *\n * @param startDate Date object representing start of the date range\n * @param endDate Date object representing the end of the date range\n * @returns String representing the given date range formatted according to the locale and\n * formatting options of this DateTimeFormat object\n */\n formatRange(startDate: Date, endDate: Date): string {\n return this.dateTimeFormatter.formatRange(startDate, endDate);\n }\n\n /**\n * Returns an array of locale-specific tokens representing each part of the formatted date range\n * produced by this DateTimeFormat object\n *\n * @param startDate Date object representing start of the date range\n * @param endDate Date object representing the end of the date range\n * @returns Array of DateTimeRangeFormatPart objects\n */\n formatRangeToParts(startDate: Date, endDate: Date): Intl.DateTimeRangeFormatPart[] {\n return this.dateTimeFormatter.formatRangeToParts(startDate, endDate);\n }\n\n /**\n * Allows locale-aware formatting of strings produced by this DateTimeFormat object\n *\n * @param date The date to format\n * @returns Array of DateTimeFormatPart objects\n */\n formatToParts(date: Date): Intl.DateTimeFormatPart[] {\n return this.dateTimeFormatter.formatToParts(date);\n }\n\n /**\n * Returns a new object with properties reflecting the locale and date and time formatting options\n * computed during initialization of this DateTimeFormat object\n *\n * @returns ResolvedDateTimeFormatOptions object\n */\n resolvedOptions(): Intl.ResolvedDateTimeFormatOptions {\n return this.dateTimeFormatter.resolvedOptions();\n }\n}\n","/** Interfaces, classes, and functions related to events and event emitters */\n\nimport { Dispose } from './disposal.model';\nimport { PlatformEvent, PlatformEventHandler } from './platform-event';\n\n/**\n * Event manager - accepts subscriptions to an event and runs the subscription callbacks when the\n * event is emitted Use eventEmitter.event(callback) to subscribe to the event. Use\n * eventEmitter.emit(event) to run the subscriptions. Generally, this EventEmitter should be\n * private, and its event should be public. That way, the emitter is not publicized, but anyone can\n * subscribe to the event.\n */\nexport default class PlatformEventEmitter implements Dispose {\n /**\n * Subscribes a function to run when this event is emitted.\n *\n * @param callback Function to run with the event when it is emitted\n * @returns Unsubscriber function to run to stop calling the passed-in function when the event is\n * emitted\n * @alias event\n */\n subscribe = this.event;\n\n /** All callback functions that will run when this event is emitted. Lazy loaded */\n private subscriptions?: PlatformEventHandler[];\n /** Event for listeners to subscribe to. Lazy loaded */\n private lazyEvent?: PlatformEvent;\n /** Whether this emitter has been disposed */\n private isDisposed = false;\n\n /**\n * Event for listeners to subscribe to. Subscribes a function to run when this event is emitted.\n * Use like `const unsubscriber = event(callback)`\n *\n * @param callback Function to run with the event when it is emitted\n * @returns Unsubscriber function to run to stop calling the passed-in function when the event is\n * emitted\n */\n get event(): PlatformEvent {\n this.assertNotDisposed();\n\n if (!this.lazyEvent) {\n this.lazyEvent = (callback) => {\n if (!callback || typeof callback !== 'function')\n throw new Error(`Event handler callback must be a function!`);\n\n // Initialize this.subscriptions if it does not exist\n if (!this.subscriptions) this.subscriptions = [];\n\n this.subscriptions.push(callback);\n\n return () => {\n if (!this.subscriptions) return false; // Did not find any subscribed callbacks\n\n const callbackIndex = this.subscriptions.indexOf(callback);\n\n if (callbackIndex < 0) return false; // Did not find this callback in the subscriptions\n\n // Remove the callback\n this.subscriptions.splice(callbackIndex, 1);\n\n return true;\n };\n };\n }\n return this.lazyEvent;\n }\n\n /** Disposes of this event, preparing it to release from memory */\n dispose = () => {\n return this.disposeFn();\n };\n\n /**\n * Runs the subscriptions for the event\n *\n * @param event Event data to provide to subscribed callbacks\n */\n emit = (event: T) => {\n // Do not do anything other than emitFn here. This emit is just binding `this` to emitFn\n this.emitFn(event);\n };\n\n /**\n * Function that runs the subscriptions for the event. Added here so children can override emit\n * and still call the base functionality. See NetworkEventEmitter.emit for example\n */\n protected emitFn(event: T) {\n this.assertNotDisposed();\n\n this.subscriptions?.forEach((callback) => callback(event));\n }\n\n /** Check to make sure this emitter is not disposed. Throw if it is */\n protected assertNotDisposed() {\n if (this.isDisposed) throw new Error('Emitter is disposed');\n }\n\n /**\n * Disposes of this event, preparing it to release from memory. Added here so children can\n * override emit and still call the base functionality.\n */\n protected disposeFn() {\n this.assertNotDisposed();\n\n this.isDisposed = true;\n this.subscriptions = undefined;\n this.lazyEvent = undefined;\n return Promise.resolve(true);\n }\n}\n","/** Collection of functions, objects, and types that are used as helpers in other services. */\n\n// Thanks to blubberdiblub at https://stackoverflow.com/a/68141099/217579\nexport function newGuid(): string {\n return '00-0-4-1-000'.replace(/[^-]/g, (s) =>\n // @ts-expect-error ts(2363) this works fine\n // eslint-disable-next-line no-bitwise\n (((Math.random() + ~~s) * 0x10000) >> s).toString(16).padStart(4, '0'),\n );\n}\n\n// thanks to DRAX at https://stackoverflow.com/a/9436948\n/**\n * Determine whether the object is a string\n *\n * @param o Object to determine if it is a string\n * @returns True if the object is a string; false otherwise\n */\nexport function isString(o: unknown): o is string {\n return typeof o === 'string' || o instanceof String;\n}\n\n/**\n * If deepClone isn't used when copying properties between objects, you may be left with dangling\n * references between the source and target of property copying operations.\n *\n * @param obj Object to clone\n * @returns Duplicate copy of `obj` without any references back to the original one\n */\nexport function deepClone(obj: T): T {\n // Assert the return type matches what is expected\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n return JSON.parse(JSON.stringify(obj)) as T;\n}\n\n/**\n * Get a function that reduces calls to the function passed in\n *\n * @param fn The function to debounce\n * @param delay How much delay in milliseconds after the most recent call to the debounced function\n * to call the function\n * @returns Function that, when called, only calls the function passed in at maximum every delay ms\n */\n// We don't know the parameter types since this function can be anything\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function debounce void>(fn: T, delay = 300): T {\n if (isString(fn)) throw new Error('Tried to debounce a string! Could be XSS');\n let timeout: ReturnType;\n // Ensure the right return type.\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n return ((...args) => {\n clearTimeout(timeout);\n timeout = setTimeout(() => fn(...args), delay);\n }) as T;\n}\n\n/**\n * Groups each item in the array of items into a map according to the keySelector\n *\n * @param items Array of items to group by\n * @param keySelector Function to run on each item to get the key for the group to which it belongs\n * @param valueSelector Function to run on each item to get the value it should have in the group\n * (like map function). If not provided, uses the item itself\n * @returns Map of keys to groups of values corresponding to each item\n */\nexport function groupBy(items: T[], keySelector: (item: T) => K): Map>;\nexport function groupBy(\n items: T[],\n keySelector: (item: T) => K,\n valueSelector: (item: T, key: K) => V,\n): Map>;\nexport function groupBy(\n items: T[],\n keySelector: (item: T) => K,\n valueSelector?: (item: T, key: K) => V,\n): Map> {\n const map = new Map>();\n items.forEach((item) => {\n const key = keySelector(item);\n const group = map.get(key);\n const value = valueSelector ? valueSelector(item, key) : item;\n if (group) group.push(value);\n else map.set(key, [value]);\n });\n return map;\n}\n\n// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript\ntype ErrorWithMessage = {\n message: string;\n};\n\n// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript\nfunction isErrorWithMessage(error: unknown): error is ErrorWithMessage {\n return (\n typeof error === 'object' &&\n // We're potentially dealing with objects we didn't create, so they might contain `null`\n // eslint-disable-next-line no-null/no-null\n error !== null &&\n 'message' in error &&\n // Type assert `error` to check it's `message`.\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n typeof (error as Record).message === 'string'\n );\n}\n\n// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript\n/**\n * Function to get an error from the object (useful for getting an error in a catch block)\n *\n * @param error Error object whose message to get\n * @returns Message of the error - if object has message, returns message. Otherwise tries to\n * stringify\n */\nfunction toErrorWithMessage(maybeError: unknown): ErrorWithMessage {\n if (isErrorWithMessage(maybeError)) return maybeError;\n\n try {\n return new Error(JSON.stringify(maybeError));\n } catch {\n // fallback in case there's an error stringifying the maybeError\n // like with circular references for example.\n return new Error(String(maybeError));\n }\n}\n\n// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript\n/**\n * Function to get an error message from the object (useful for getting error message in a catch\n * block)\n *\n * @example `try {...} catch (e) { logger.info(getErrorMessage(e)) }`\n *\n * @param error Error object whose message to get\n * @returns Message of the error - if object has message, returns message. Otherwise tries to\n * stringify\n */\nexport function getErrorMessage(error: unknown) {\n return toErrorWithMessage(error).message;\n}\n\n/** Asynchronously waits for the specified number of milliseconds. (wraps setTimeout in a promise) */\nexport function wait(ms: number) {\n // eslint-disable-next-line no-promise-executor-return\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Runs the specified function and will timeout if it takes longer than the specified wait time\n *\n * @param fn The function to run\n * @param maxWaitTimeInMS The maximum amount of time to wait for the function to resolve\n * @returns Promise that resolves to the resolved value of the function or undefined if it ran\n * longer than the specified wait time\n */\nexport function waitForDuration(fn: () => Promise, maxWaitTimeInMS: number) {\n const timeout = wait(maxWaitTimeInMS).then(() => undefined);\n return Promise.any([timeout, fn()]);\n}\n\n/**\n * Get all functions on an object and its prototype chain (so we don't miss any class methods or any\n * object methods). Note that the functions on the final item in the prototype chain (i.e., Object)\n * are skipped to avoid including functions like `__defineGetter__`, `__defineSetter__`, `toString`,\n * etc.\n *\n * @param obj Object whose functions to get\n * @param objId Optional ID of the object to use for debug logging\n * @returns Array of all function names on an object\n */\n// Note: lodash has something that MIGHT do the same thing as this. Investigate for https://github.com/paranext/paranext-core/issues/134\nexport function getAllObjectFunctionNames(\n obj: { [property: string]: unknown },\n objId: string = 'obj',\n): Set {\n const objectFunctionNames = new Set();\n\n // Get all function properties directly defined on the object\n Object.getOwnPropertyNames(obj).forEach((property) => {\n try {\n if (typeof obj[property] === 'function') objectFunctionNames.add(property);\n } catch (error) {\n console.debug(`Skipping ${property} on ${objId} due to error: ${error}`);\n }\n });\n\n // Walk up the prototype chain and get additional function properties, skipping the functions\n // provided by the final (Object) prototype\n let objectPrototype = Object.getPrototypeOf(obj);\n while (objectPrototype && Object.getPrototypeOf(objectPrototype)) {\n Object.getOwnPropertyNames(objectPrototype).forEach((property) => {\n try {\n if (typeof obj[property] === 'function') objectFunctionNames.add(property);\n } catch (error) {\n console.debug(`Skipping ${property} on ${objId}'s prototype due to error: ${error}`);\n }\n });\n objectPrototype = Object.getPrototypeOf(objectPrototype);\n }\n\n return objectFunctionNames;\n}\n\n/**\n * Creates a synchronous proxy for an asynchronous object. The proxy allows calling methods on an\n * object that is asynchronously fetched using a provided asynchronous function.\n *\n * @param getObject - A function that returns a promise resolving to the object whose asynchronous\n * methods to call.\n * @param objectToProxy - An optional object that is the object that is proxied. If a property is\n * accessed that does exist on this object, it will be returned. If a property is accessed that\n * does not exist on this object, it will be considered to be an asynchronous method called on the\n * object returned from getObject.\n * @returns A synchronous proxy for the asynchronous object.\n */\nexport function createSyncProxyForAsyncObject(\n getObject: (args?: unknown[]) => Promise,\n objectToProxy: Partial = {},\n): T {\n // objectToProxy will have only the synchronously accessed properties of T on it, and this proxy\n // makes the async methods that do not exist yet available synchronously so we have all of T\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n return new Proxy(objectToProxy as T, {\n get(target, prop) {\n // We don't have any type information for T, so we assume methodName exists on it and will let JavaScript throw if it doesn't exist\n // @ts-expect-error 7053\n if (prop in target) return target[prop];\n return async (...args: unknown[]) => {\n // 7053: We don't have any type information for T, so we assume methodName exists on it and will let JavaScript throw if it doesn't exist\n // 2556: The args here are the parameters for the method specified\n // @ts-expect-error 7053 2556\n return (await getObject())[prop](...args);\n };\n },\n });\n}\n\n/** Within type T, recursively change all properties to be optional */\nexport type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial } : T;\n\n/** Within type T, recursively change properties that were of type A to be of type B */\nexport type ReplaceType = T extends A\n ? B\n : T extends object\n ? { [K in keyof T]: ReplaceType }\n : T;\n\n// Thanks to jcalz at https://stackoverflow.com/a/50375286\n/**\n * Converts a union type to an intersection type (`|` to `&`).\n *\n * Note: this utility type is for use on object types. It may fail on other types.\n *\n * @example\n *\n * ```typescript\n * type TypeOne = { one: string };\n * type TypeTwo = { two: number };\n * type TypeThree = { three: string };\n *\n * type TypeNums = { one: TypeOne; two: TypeTwo; three: TypeThree };\n * const numNames = ['one', 'two'] as const;\n * type TypeNumNames = typeof numNames;\n *\n * // Same as `TypeOne | TypeTwo`\n * // `{ one: string } | { two: number }`\n * type TypeOneTwoUnion = TypeNums[TypeNumNames[number]];\n *\n * // Same as `TypeOne & TypeTwo`\n * // `{ one: string; two: number }`\n * type TypeOneTwoIntersection = UnionToIntersection;\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type UnionToIntersection = (U extends any ? (x: U) => void : never) extends (\n x: infer I,\n) => void\n ? I\n : never;\n","import PlatformEventEmitter from './platform-event-emitter.model';\nimport { deepClone } from './util';\n\ntype JsonObjectLike = { [key: string]: unknown };\ntype JsonArrayLike = unknown[];\n\nexport type JsonDocumentLike = JsonObjectLike | JsonArrayLike;\n\n/**\n * Options for DocumentCombiner objects\n *\n * - `copyDocuments`: If true, this instance will perform a deep copy of all provided documents before\n * composing the output. If false, then changes made to provided documents after they are\n * contributed will be reflected in the next time output is composed.\n * - `ignoreDuplicateProperties`: If true, then duplicate properties are skipped if they are seen in\n * contributed documents. If false, then throw when duplicate properties are seen in contributed\n * documents.\n */\nexport type DocumentCombinerOptions = {\n copyDocuments: boolean;\n ignoreDuplicateProperties: boolean;\n};\n\n/**\n * Base class for any code that wants to compose JSON documents (primarily in the form of JS objects\n * or arrays) together into a single output document.\n */\nexport default class DocumentCombiner {\n protected baseDocument: JsonDocumentLike;\n protected readonly contributions = new Map();\n protected latestOutput: JsonDocumentLike | undefined;\n protected readonly options: DocumentCombinerOptions;\n private readonly onDidRebuildEmitter = new PlatformEventEmitter();\n /** Event that emits to announce that the document has been rebuilt and the output has been updated */\n // Need `onDidRebuildEmitter` to be instantiated before this line\n // eslint-disable-next-line @typescript-eslint/member-ordering\n readonly onDidRebuild = this.onDidRebuildEmitter.subscribe;\n\n /**\n * Create a DocumentCombiner instance\n *\n * @param baseDocument This is the first document that will be used when composing the output\n * @param options Options used by this object when combining documents\n */\n protected constructor(baseDocument: JsonDocumentLike, options: DocumentCombinerOptions) {\n // Setting baseDocument redundantly because TS doesn't understand that updateBaseDocument does it\n this.baseDocument = baseDocument;\n this.options = options;\n this.updateBaseDocument(baseDocument);\n }\n\n /**\n * Update the starting document for composition process\n *\n * @param baseDocument Base JSON document/JS object that all other documents are added to\n * @returns Recalculated output document given the new starting state and existing other documents\n */\n updateBaseDocument(baseDocument: JsonDocumentLike): JsonDocumentLike | undefined {\n this.validateBaseDocument(baseDocument);\n this.baseDocument = this.options.copyDocuments ? deepClone(baseDocument) : baseDocument;\n this.baseDocument = this.transformBaseDocumentAfterValidation(this.baseDocument);\n return this.rebuild();\n }\n\n /**\n * Add or update one of the contribution documents for the composition process\n *\n * Note: the order in which contribution documents are added can be considered to be indeterminate\n * as it is currently ordered by however `Map.forEach` provides the contributions. The order\n * matters when merging two arrays into one. Also, when `options.ignoreDuplicateProperties` is\n * `true`, the order also matters when adding the same property to an object that is already\n * provided previously. Please let us know if you have trouble because of indeterminate\n * contribution ordering.\n *\n * @param documentName Name of the contributed document to combine\n * @param document Content of the contributed document to combine\n * @returns Recalculated output document given the new or updated contribution and existing other\n * documents\n */\n addOrUpdateContribution(\n documentName: string,\n document: JsonDocumentLike,\n ): JsonDocumentLike | undefined {\n this.validateContribution(documentName, document);\n const previousDocumentVersion = this.contributions.get(documentName);\n let documentToSet = this.options.copyDocuments && !!document ? deepClone(document) : document;\n documentToSet = this.transformContributionAfterValidation(documentName, documentToSet);\n this.contributions.set(documentName, documentToSet);\n try {\n return this.rebuild();\n } catch (error) {\n // If the output isn't valid after adding/updating the contribution, put it back how it was\n if (previousDocumentVersion) this.contributions.set(documentName, previousDocumentVersion);\n else this.contributions.delete(documentName);\n throw new Error(`Error when setting the document named ${documentName}: ${error}`);\n }\n }\n\n /**\n * Delete one of the contribution documents for the composition process\n *\n * @param documentName Name of the contributed document to delete\n * @returns Recalculated output document given the remaining other documents\n */\n deleteContribution(documentName: string): JsonDocumentLike | undefined {\n const document = this.contributions.get(documentName);\n if (!document) throw new Error(`${documentName} does not exist`);\n this.contributions.delete(documentName);\n try {\n return this.rebuild();\n } catch (error) {\n // If the output isn't valid after deleting the contribution, put it back and rethrow\n this.contributions.set(documentName, document);\n throw new Error(`Error when deleting the document named ${documentName}: ${error}`);\n }\n }\n\n /**\n * Delete all present contribution documents for the composition process and return to the base\n * document\n *\n * @returns Recalculated output document consisting only of the base document\n */\n deleteAllContributions(): JsonDocumentLike | undefined {\n if (this.contributions.size <= 0) return this.latestOutput;\n\n // Save out all contributions\n const contributions = [...this.contributions.entries()];\n\n // Delete all contributions\n contributions.forEach(([contributionName]) => this.contributions.delete(contributionName));\n\n // Rebuild with no contributions\n try {\n return this.rebuild();\n } catch (error) {\n // If the output isn't valid after deleting all contributions, put them back and rethrow\n contributions.forEach(([contributionName, document]) =>\n this.contributions.set(contributionName, document),\n );\n throw new Error(`Error when deleting all contributions: ${error}`);\n }\n }\n\n /**\n * Run the document composition process given the starting document and all contributions. Throws\n * if the output document fails to validate properly.\n *\n * @returns Recalculated output document given the starting and contributed documents\n */\n rebuild(): JsonDocumentLike | undefined {\n // The starting document is the output if there are no other contributions\n if (this.contributions.size === 0) {\n let potentialOutput = deepClone(this.baseDocument);\n potentialOutput = this.transformFinalOutputBeforeValidation(potentialOutput);\n this.validateOutput(potentialOutput);\n this.latestOutput = potentialOutput;\n this.onDidRebuildEmitter.emit(undefined);\n return this.latestOutput;\n }\n\n // Compose the output by validating each document one at a time to pinpoint errors better\n let outputIteration = this.baseDocument;\n this.contributions.forEach((contribution: JsonDocumentLike) => {\n outputIteration = mergeObjects(\n outputIteration,\n contribution,\n this.options.ignoreDuplicateProperties,\n );\n this.validateOutput(outputIteration);\n });\n outputIteration = this.transformFinalOutputBeforeValidation(outputIteration);\n this.validateOutput(outputIteration);\n this.latestOutput = outputIteration;\n this.onDidRebuildEmitter.emit(undefined);\n return this.latestOutput;\n }\n\n /**\n * Transform the starting document that is given to the combiner. This transformation occurs after\n * validating the base document and before combining any contributions.\n *\n * WARNING: If you do not create the combiner with option `copyDocuments: true` or clone inside\n * this method, this method will directly modify the `baseDocument` passed in.\n *\n * @param baseDocument Initial input document. Already validated via `validateBaseDocument`\n * @returns Transformed base document\n */\n // We just don't need `this` here. This is basically a no-op function that is available to child\n // classes to override\n // eslint-disable-next-line class-methods-use-this\n protected transformBaseDocumentAfterValidation(baseDocument: JsonDocumentLike): JsonDocumentLike {\n return baseDocument;\n }\n\n /**\n * Transform the contributed document associated with `documentName`. This transformation occurs\n * after validating the contributed document and before combining with other documents.\n *\n * WARNING: If you do not create the combiner with option `copyDocuments: true` or clone inside\n * this method, this method will directly modify the contributed `document` passed in.\n *\n * @param documentName Name of the contributed document to combine\n * @param document Content of the contributed document to combine. Already validated via\n * `validateContribution`\n * @returns Transformed contributed document\n */\n // We just don't need `this` here. This is basically a no-op function that is available to child\n // classes to override\n // eslint-disable-next-line class-methods-use-this\n protected transformContributionAfterValidation(\n // @ts-expect-error this parameter is unused but may be used in child classes\n documentName: string,\n document: JsonDocumentLike,\n ): JsonDocumentLike {\n return document;\n }\n\n /**\n * Throw an error if the provided document is not a valid starting document.\n *\n * @param baseDocument Base JSON document/JS object that all other documents are added to\n */\n // no-op intended to be overridden by child classes. Can't be static\n // @ts-expect-error ts(6133) parameter doesn't need to be used but still needs the right name\n // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars\n protected validateBaseDocument(baseDocument: JsonDocumentLike): void {}\n\n /**\n * Throw an error if the provided document is not a valid contribution document.\n *\n * @param documentName Name of the contributed document to combine\n * @param document Content of the contributed document to combine\n */\n // no-op intended to be overridden by child classes. Can't be static\n // @ts-expect-error ts(6133) parameter doesn't need to be used but still needs the right name\n // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars\n protected validateContribution(documentName: string, document: JsonDocumentLike): void {}\n\n /**\n * Throw an error if the provided output is not valid.\n *\n * @param output Output document that could potentially be returned to callers\n */\n // no-op intended to be overridden by child classes. Can't be static\n // @ts-expect-error ts(6133) parameter doesn't need to be used but still needs the right name\n // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars\n protected validateOutput(output: JsonDocumentLike): void {}\n\n /**\n * Transform the document that is the composition of the base document and all contribution\n * documents. This is the last step that will be run prior to validation via `validateOutput`\n * before `this.latestOutput` is updated to the new output.\n *\n * @param finalOutput Final output document that could potentially be returned to callers. \"Final\"\n * means no further contribution documents will be merged.\n */\n // no-op intended to be overridden by child classes. Can't be static\n // eslint-disable-next-line class-methods-use-this\n protected transformFinalOutputBeforeValidation(finalOutput: JsonDocumentLike): JsonDocumentLike {\n return finalOutput;\n }\n}\n\n// #region Helper functions\n\n/**\n * Determines if the input values are objects but not arrays\n *\n * @param values Objects to check\n * @returns True if all the values are objects but not arrays\n */\nfunction areNonArrayObjects(...values: unknown[]): boolean {\n let allMatch = true;\n values.forEach((value: unknown) => {\n if (!value || typeof value !== 'object' || Array.isArray(value)) allMatch = false;\n });\n return allMatch;\n}\n\n/**\n * Determines if the input values are arrays\n *\n * @param value Objects to check\n * @returns True if the values are arrays\n */\nfunction areArrayObjects(...values: unknown[]): boolean {\n let allMatch = true;\n values.forEach((value: unknown) => {\n if (!value || typeof value !== 'object' || !Array.isArray(value)) allMatch = false;\n });\n return allMatch;\n}\n\n/**\n * Deep clone and recursively merge the properties of one object (copyFrom) into another\n * (startingPoint). Throws if copyFrom would overwrite values already existing in startingPoint.\n *\n * Does not modify the objects passed in.\n *\n * @param startingPoint Object that is the starting point for the return value\n * @param copyFrom Object whose values are copied into the return value\n * @param ignoreDuplicateProperties Whether to ignore object properties that are present in\n * `copyFrom` that are already present in `startingPoint`. If `false`, throws when an object\n * property in `copyFrom` is already present in `startingPoint`\n * @returns Object that is the combination of the two documents\n */\nfunction mergeObjects(\n startingPoint: JsonDocumentLike,\n copyFrom: JsonDocumentLike,\n ignoreDuplicateProperties: boolean,\n): JsonDocumentLike {\n const retVal = deepClone(startingPoint);\n\n if (!copyFrom) return retVal;\n\n return mergeObjectsInternal(retVal, deepClone(copyFrom), ignoreDuplicateProperties);\n}\n\n/**\n * Recursively merge the properties of one object (copyFrom) into another (startingPoint). Throws if\n * copyFrom would overwrite values already existing in startingPoint.\n *\n * WARNING: Modifies the argument objects in some way. Recommended to use `mergeObjects`\n *\n * @param startingPoint Object that is the starting point for the return value\n * @param copyFrom Object whose values are copied into the return value\n * @param ignoreDuplicateProperties Whether to ignore object properties that are present in\n * `copyFrom` that are already present in `startingPoint`. If `false`, throws when an object\n * property in `copyFrom` is already present in `startingPoint`\n * @returns Object that is the combination of the two documents\n */\nfunction mergeObjectsInternal(\n startingPoint: JsonDocumentLike,\n copyFrom: JsonDocumentLike,\n ignoreDuplicateProperties: boolean,\n): JsonDocumentLike {\n if (!copyFrom) return startingPoint;\n\n if (areNonArrayObjects(startingPoint, copyFrom)) {\n // Merge properties since they are both objects\n\n // We know these are objects from the `if` check\n /* eslint-disable no-type-assertion/no-type-assertion */\n const startingPointObj = startingPoint as JsonObjectLike;\n const copyFromObj = copyFrom as JsonObjectLike;\n /* eslint-enable no-type-assertion/no-type-assertion */\n Object.keys(copyFromObj).forEach((key: string | number) => {\n if (Object.hasOwn(startingPointObj, key)) {\n if (areNonArrayObjects(startingPointObj[key], copyFromObj[key])) {\n startingPointObj[key] = mergeObjectsInternal(\n // We know these are objects from the `if` check\n /* eslint-disable no-type-assertion/no-type-assertion */\n startingPointObj[key] as JsonObjectLike,\n copyFromObj[key] as JsonObjectLike,\n ignoreDuplicateProperties,\n /* eslint-enable no-type-assertion/no-type-assertion */\n );\n } else if (areArrayObjects(startingPointObj[key], copyFromObj[key])) {\n // Concat the arrays since they are both arrays\n\n // We know these are arrays from the `else if` check\n /* eslint-disable no-type-assertion/no-type-assertion */\n startingPointObj[key] = (startingPointObj[key] as JsonArrayLike).concat(\n copyFromObj[key] as JsonArrayLike,\n );\n /* eslint-enable no-type-assertion/no-type-assertion */\n } else if (!ignoreDuplicateProperties)\n throw new Error(`Cannot merge objects: key \"${key}\" already exists in the target object`);\n // Note that the first non-object non-array value that gets placed in a property stays.\n // New values do not override existing ones\n } else {\n startingPointObj[key] = copyFromObj[key];\n }\n });\n } else if (areArrayObjects(startingPoint, copyFrom)) {\n // Concat the arrays since they are both arrays\n\n // Push the contents of copyFrom into startingPoint since it is a const and was already deep cloned\n // We know these are objects from the `else if` check\n /* eslint-disable no-type-assertion/no-type-assertion */\n (startingPoint as JsonArrayLike).push(...(copyFrom as JsonArrayLike));\n /* eslint-enable no-type-assertion/no-type-assertion */\n }\n\n // Note that nothing happens if `startingPoint` is not an object or an array or if `startingPoint`\n // and `copyFrom` are not both object or both arrays. Should we throw? Should we push `copyFrom`'s\n // values into the array? Other? Maybe one day we can add some options to decide what to do in\n // this situation, but YAGNI for now\n\n return startingPoint;\n}\n\n// #endregion\n","import { Mutex as AsyncMutex } from 'async-mutex';\n\n// Extending Mutex from async-mutex so we can add JSDoc\n\n/**\n * Class that allows calling asynchronous functions multiple times at once while only running one at\n * a time.\n *\n * @example\n *\n * ```typescript\n * const mutex = new Mutex();\n *\n * mutex.runExclusive(async () => {\n * // Do some asynchronous stuff\n * console.log('These run one-at-a-time');\n * });\n *\n * mutex.runExclusive(async () => {\n * // Do some asynchronous stuff\n * console.log('These run one-at-a-time');\n * });\n * ```\n *\n * See [`async-mutex`](https://www.npmjs.com/package/async-mutex) for more information.\n */\nclass Mutex extends AsyncMutex {}\n\nexport default Mutex;\n","import Mutex from './mutex';\n\n/** Map of {@link Mutex}es that automatically (lazily) generates a new {@link Mutex} for any new key */\nclass MutexMap {\n private mutexesByID = new Map();\n\n get(mutexID: string): Mutex {\n let retVal = this.mutexesByID.get(mutexID);\n if (retVal) return retVal;\n\n retVal = new Mutex();\n this.mutexesByID.set(mutexID, retVal);\n return retVal;\n }\n}\n\nexport default MutexMap;\n","import DocumentCombiner, { DocumentCombinerOptions, JsonDocumentLike } from './document-combiner';\n\nexport default class NonValidatingDocumentCombiner extends DocumentCombiner {\n // Making the protected base constructor public\n // eslint-disable-next-line @typescript-eslint/no-useless-constructor\n constructor(baseDocument: JsonDocumentLike, options: DocumentCombinerOptions) {\n super(baseDocument, options);\n }\n\n get output(): JsonDocumentLike | undefined {\n return this.latestOutput;\n }\n}\n","/** Enables language-sensitive number formatting. Wraps Intl.NumberFormat */\nexport default class NumberFormat {\n private numberFormatter: Intl.NumberFormat;\n\n constructor(locales?: string | string[], options?: Intl.NumberFormatOptions) {\n this.numberFormatter = new Intl.NumberFormat(locales, options);\n }\n\n /**\n * Formats a number according to the locale and formatting options of this NumberFormat object\n *\n * @param value Number or BigInt to format\n * @returns String representing the given number formatted according to the locale and formatting\n * options of this NumberFormat object\n */\n format(value: number | bigint): string {\n return this.numberFormatter.format(value);\n }\n\n /**\n * Formats a range of numbers according to the locale and formatting options of this NumberFormat\n * object\n *\n * @param startRange Number or bigint representing the start of the range\n * @param endRange Number or bigint representing the end of the range\n * @returns String representing the given range of numbers formatted according to the locale and\n * formatting options of this NumberFormat object\n */\n formatRange(startRange: number | bigint, endRange: number | bigint): string {\n return this.numberFormatter.formatRange(startRange, endRange);\n }\n\n /**\n * Returns an array of objects containing the locale-specific tokens from which it is possible to\n * build custom strings while preserving the locale-specific parts.\n *\n * @param startRange Number or bigint representing start of the range\n * @param endRange Number or bigint representing end of the range\n * @returns Array of NumberRangeFormatPart objects containing the formatted range of numbers in\n * parts\n */\n formatRangeToParts(\n startRange: number | bigint,\n endRange: number | bigint,\n ): Intl.NumberRangeFormatPart[] {\n return this.numberFormatter.formatRangeToParts(startRange, endRange);\n }\n\n /**\n * Allows locale-aware formatting of strings produced by this NumberFormat object\n *\n * @param value Number or bigint to format\n * @returns Array of NumberFormatPart objects containing the formatted number in parts\n */\n formatToParts(value: number | bigint): Intl.NumberFormatPart[] {\n return this.numberFormatter.formatToParts(value);\n }\n\n /**\n * Returns a new object with properties reflecting the locale and number formatting options\n * computed during initialization of this NumberFormat object\n *\n * @returns ResolvedNumberFormatOptions object\n */\n resolvedOptions(): Intl.ResolvedNumberFormatOptions {\n return this.numberFormatter.resolvedOptions();\n }\n}\n","import { Dispose } from './disposal.model';\nimport { Unsubscriber, UnsubscriberAsync } from './unsubscriber';\n\n/** Simple collection for UnsubscriberAsync objects that also provides an easy way to run them. */\nexport default class UnsubscriberAsyncList {\n readonly unsubscribers = new Set();\n\n constructor(private name = 'Anonymous') {}\n\n /**\n * Add unsubscribers to the list. Note that duplicates are not added twice.\n *\n * @param unsubscribers - Objects that were returned from a registration process.\n */\n add(...unsubscribers: (UnsubscriberAsync | Unsubscriber | Dispose)[]) {\n unsubscribers.forEach((unsubscriber) => {\n if ('dispose' in unsubscriber) this.unsubscribers.add(unsubscriber.dispose);\n else this.unsubscribers.add(unsubscriber);\n });\n }\n\n /**\n * Run all unsubscribers added to this list and then clear the list.\n *\n * @returns `true` if all unsubscribers succeeded, `false` otherwise.\n */\n async runAllUnsubscribers(): Promise {\n const unsubs = [...this.unsubscribers].map((unsubscriber) => unsubscriber());\n const results = await Promise.all(unsubs);\n this.unsubscribers.clear();\n return results.every((unsubscriberSucceeded, index) => {\n if (!unsubscriberSucceeded)\n console.error(`UnsubscriberAsyncList ${this.name}: Unsubscriber at index ${index} failed!`);\n\n return unsubscriberSucceeded;\n });\n }\n}\n","var P = Object.defineProperty;\nvar R = (t, e, s) => e in t ? P(t, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : t[e] = s;\nvar n = (t, e, s) => (R(t, typeof e != \"symbol\" ? e + \"\" : e, s), s);\nclass z {\n constructor() {\n n(this, \"books\");\n n(this, \"firstSelectedBookNum\");\n n(this, \"lastSelectedBookNum\");\n n(this, \"count\");\n n(this, \"selectedBookNumbers\");\n n(this, \"selectedBookIds\");\n }\n}\nconst m = [\n \"GEN\",\n \"EXO\",\n \"LEV\",\n \"NUM\",\n \"DEU\",\n \"JOS\",\n \"JDG\",\n \"RUT\",\n \"1SA\",\n \"2SA\",\n // 10\n \"1KI\",\n \"2KI\",\n \"1CH\",\n \"2CH\",\n \"EZR\",\n \"NEH\",\n \"EST\",\n \"JOB\",\n \"PSA\",\n \"PRO\",\n // 20\n \"ECC\",\n \"SNG\",\n \"ISA\",\n \"JER\",\n \"LAM\",\n \"EZK\",\n \"DAN\",\n \"HOS\",\n \"JOL\",\n \"AMO\",\n // 30\n \"OBA\",\n \"JON\",\n \"MIC\",\n \"NAM\",\n \"HAB\",\n \"ZEP\",\n \"HAG\",\n \"ZEC\",\n \"MAL\",\n \"MAT\",\n // 40\n \"MRK\",\n \"LUK\",\n \"JHN\",\n \"ACT\",\n \"ROM\",\n \"1CO\",\n \"2CO\",\n \"GAL\",\n \"EPH\",\n \"PHP\",\n // 50\n \"COL\",\n \"1TH\",\n \"2TH\",\n \"1TI\",\n \"2TI\",\n \"TIT\",\n \"PHM\",\n \"HEB\",\n \"JAS\",\n \"1PE\",\n // 60\n \"2PE\",\n \"1JN\",\n \"2JN\",\n \"3JN\",\n \"JUD\",\n \"REV\",\n \"TOB\",\n \"JDT\",\n \"ESG\",\n \"WIS\",\n // 70\n \"SIR\",\n \"BAR\",\n \"LJE\",\n \"S3Y\",\n \"SUS\",\n \"BEL\",\n \"1MA\",\n \"2MA\",\n \"3MA\",\n \"4MA\",\n // 80\n \"1ES\",\n \"2ES\",\n \"MAN\",\n \"PS2\",\n \"ODA\",\n \"PSS\",\n \"JSA\",\n // actual variant text for JOS, now in LXA text\n \"JDB\",\n // actual variant text for JDG, now in LXA text\n \"TBS\",\n // actual variant text for TOB, now in LXA text\n \"SST\",\n // actual variant text for SUS, now in LXA text // 90\n \"DNT\",\n // actual variant text for DAN, now in LXA text\n \"BLT\",\n // actual variant text for BEL, now in LXA text\n \"XXA\",\n \"XXB\",\n \"XXC\",\n \"XXD\",\n \"XXE\",\n \"XXF\",\n \"XXG\",\n \"FRT\",\n // 100\n \"BAK\",\n \"OTH\",\n \"3ES\",\n // Used previously but really should be 2ES\n \"EZA\",\n // Used to be called 4ES, but not actually in any known project\n \"5EZ\",\n // Used to be called 5ES, but not actually in any known project\n \"6EZ\",\n // Used to be called 6ES, but not actually in any known project\n \"INT\",\n \"CNC\",\n \"GLO\",\n \"TDX\",\n // 110\n \"NDX\",\n \"DAG\",\n \"PS3\",\n \"2BA\",\n \"LBA\",\n \"JUB\",\n \"ENO\",\n \"1MQ\",\n \"2MQ\",\n \"3MQ\",\n // 120\n \"REP\",\n \"4BA\",\n \"LAO\"\n], v = [\n \"XXA\",\n \"XXB\",\n \"XXC\",\n \"XXD\",\n \"XXE\",\n \"XXF\",\n \"XXG\",\n \"FRT\",\n \"BAK\",\n \"OTH\",\n \"INT\",\n \"CNC\",\n \"GLO\",\n \"TDX\",\n \"NDX\"\n], X = [\n \"Genesis\",\n \"Exodus\",\n \"Leviticus\",\n \"Numbers\",\n \"Deuteronomy\",\n \"Joshua\",\n \"Judges\",\n \"Ruth\",\n \"1 Samuel\",\n \"2 Samuel\",\n \"1 Kings\",\n \"2 Kings\",\n \"1 Chronicles\",\n \"2 Chronicles\",\n \"Ezra\",\n \"Nehemiah\",\n \"Esther (Hebrew)\",\n \"Job\",\n \"Psalms\",\n \"Proverbs\",\n \"Ecclesiastes\",\n \"Song of Songs\",\n \"Isaiah\",\n \"Jeremiah\",\n \"Lamentations\",\n \"Ezekiel\",\n \"Daniel (Hebrew)\",\n \"Hosea\",\n \"Joel\",\n \"Amos\",\n \"Obadiah\",\n \"Jonah\",\n \"Micah\",\n \"Nahum\",\n \"Habakkuk\",\n \"Zephaniah\",\n \"Haggai\",\n \"Zechariah\",\n \"Malachi\",\n \"Matthew\",\n \"Mark\",\n \"Luke\",\n \"John\",\n \"Acts\",\n \"Romans\",\n \"1 Corinthians\",\n \"2 Corinthians\",\n \"Galatians\",\n \"Ephesians\",\n \"Philippians\",\n \"Colossians\",\n \"1 Thessalonians\",\n \"2 Thessalonians\",\n \"1 Timothy\",\n \"2 Timothy\",\n \"Titus\",\n \"Philemon\",\n \"Hebrews\",\n \"James\",\n \"1 Peter\",\n \"2 Peter\",\n \"1 John\",\n \"2 John\",\n \"3 John\",\n \"Jude\",\n \"Revelation\",\n \"Tobit\",\n \"Judith\",\n \"Esther Greek\",\n \"Wisdom of Solomon\",\n \"Sirach (Ecclesiasticus)\",\n \"Baruch\",\n \"Letter of Jeremiah\",\n \"Song of 3 Young Men\",\n \"Susanna\",\n \"Bel and the Dragon\",\n \"1 Maccabees\",\n \"2 Maccabees\",\n \"3 Maccabees\",\n \"4 Maccabees\",\n \"1 Esdras (Greek)\",\n \"2 Esdras (Latin)\",\n \"Prayer of Manasseh\",\n \"Psalm 151\",\n \"Odes\",\n \"Psalms of Solomon\",\n // WARNING, if you change the spelling of the *obsolete* tag be sure to update\n // IsObsolete routine\n \"Joshua A. *obsolete*\",\n \"Judges B. *obsolete*\",\n \"Tobit S. *obsolete*\",\n \"Susanna Th. *obsolete*\",\n \"Daniel Th. *obsolete*\",\n \"Bel Th. *obsolete*\",\n \"Extra A\",\n \"Extra B\",\n \"Extra C\",\n \"Extra D\",\n \"Extra E\",\n \"Extra F\",\n \"Extra G\",\n \"Front Matter\",\n \"Back Matter\",\n \"Other Matter\",\n \"3 Ezra *obsolete*\",\n \"Apocalypse of Ezra\",\n \"5 Ezra (Latin Prologue)\",\n \"6 Ezra (Latin Epilogue)\",\n \"Introduction\",\n \"Concordance \",\n \"Glossary \",\n \"Topical Index\",\n \"Names Index\",\n \"Daniel Greek\",\n \"Psalms 152-155\",\n \"2 Baruch (Apocalypse)\",\n \"Letter of Baruch\",\n \"Jubilees\",\n \"Enoch\",\n \"1 Meqabyan\",\n \"2 Meqabyan\",\n \"3 Meqabyan\",\n \"Reproof (Proverbs 25-31)\",\n \"4 Baruch (Rest of Baruch)\",\n \"Laodiceans\"\n], C = K();\nfunction N(t, e = !0) {\n return e && (t = t.toUpperCase()), t in C ? C[t] : 0;\n}\nfunction B(t) {\n return N(t) > 0;\n}\nfunction x(t) {\n const e = typeof t == \"string\" ? N(t) : t;\n return e >= 40 && e <= 66;\n}\nfunction T(t) {\n return (typeof t == \"string\" ? N(t) : t) <= 39;\n}\nfunction O(t) {\n return t <= 66;\n}\nfunction V(t) {\n const e = typeof t == \"string\" ? N(t) : t;\n return I(e) && !O(e);\n}\nfunction* L() {\n for (let t = 1; t <= m.length; t++)\n yield t;\n}\nconst G = 1, S = m.length;\nfunction H() {\n return [\"XXA\", \"XXB\", \"XXC\", \"XXD\", \"XXE\", \"XXF\", \"XXG\"];\n}\nfunction k(t, e = \"***\") {\n const s = t - 1;\n return s < 0 || s >= m.length ? e : m[s];\n}\nfunction A(t) {\n return t <= 0 || t > S ? \"******\" : X[t - 1];\n}\nfunction y(t) {\n return A(N(t));\n}\nfunction I(t) {\n const e = typeof t == \"number\" ? k(t) : t;\n return B(e) && !v.includes(e);\n}\nfunction q(t) {\n const e = typeof t == \"number\" ? k(t) : t;\n return B(e) && v.includes(e);\n}\nfunction U(t) {\n return X[t - 1].includes(\"*obsolete*\");\n}\nfunction K() {\n const t = {};\n for (let e = 0; e < m.length; e++)\n t[m[e]] = e + 1;\n return t;\n}\nconst f = {\n allBookIds: m,\n nonCanonicalIds: v,\n bookIdToNumber: N,\n isBookIdValid: B,\n isBookNT: x,\n isBookOT: T,\n isBookOTNT: O,\n isBookDC: V,\n allBookNumbers: L,\n firstBook: G,\n lastBook: S,\n extraBooks: H,\n bookNumberToId: k,\n bookNumberToEnglishName: A,\n bookIdToEnglishName: y,\n isCanonical: I,\n isExtraMaterial: q,\n isObsolete: U\n};\nvar l = /* @__PURE__ */ ((t) => (t[t.Unknown = 0] = \"Unknown\", t[t.Original = 1] = \"Original\", t[t.Septuagint = 2] = \"Septuagint\", t[t.Vulgate = 3] = \"Vulgate\", t[t.English = 4] = \"English\", t[t.RussianProtestant = 5] = \"RussianProtestant\", t[t.RussianOrthodox = 6] = \"RussianOrthodox\", t))(l || {});\nconst u = class u {\n // private versInfo: Versification;\n constructor(e) {\n n(this, \"name\");\n n(this, \"fullPath\");\n n(this, \"isPresent\");\n n(this, \"hasVerseSegments\");\n n(this, \"isCustomized\");\n n(this, \"baseVersification\");\n n(this, \"scriptureBooks\");\n n(this, \"_type\");\n if (e != null)\n typeof e == \"string\" ? this.name = e : this._type = e;\n else\n throw new Error(\"Argument null\");\n }\n get type() {\n return this._type;\n }\n equals(e) {\n return !e.type || !this.type ? !1 : e.type === this.type;\n }\n};\nn(u, \"Original\", new u(l.Original)), n(u, \"Septuagint\", new u(l.Septuagint)), n(u, \"Vulgate\", new u(l.Vulgate)), n(u, \"English\", new u(l.English)), n(u, \"RussianProtestant\", new u(l.RussianProtestant)), n(u, \"RussianOrthodox\", new u(l.RussianOrthodox));\nlet c = u;\nfunction E(t, e) {\n const s = e[0];\n for (let r = 1; r < e.length; r++)\n t = t.split(e[r]).join(s);\n return t.split(s);\n}\nvar D = /* @__PURE__ */ ((t) => (t[t.Valid = 0] = \"Valid\", t[t.UnknownVersification = 1] = \"UnknownVersification\", t[t.OutOfRange = 2] = \"OutOfRange\", t[t.VerseOutOfOrder = 3] = \"VerseOutOfOrder\", t[t.VerseRepeated = 4] = \"VerseRepeated\", t))(D || {});\nconst i = class i {\n constructor(e, s, r, o) {\n /** Not yet implemented. */\n n(this, \"firstChapter\");\n /** Not yet implemented. */\n n(this, \"lastChapter\");\n /** Not yet implemented. */\n n(this, \"lastVerse\");\n /** Not yet implemented. */\n n(this, \"hasSegmentsDefined\");\n /** Not yet implemented. */\n n(this, \"text\");\n /** Not yet implemented. */\n n(this, \"BBBCCCVVVS\");\n /** Not yet implemented. */\n n(this, \"longHashCode\");\n /** The versification of the reference. */\n n(this, \"versification\");\n n(this, \"rtlMark\", \"‏\");\n n(this, \"_bookNum\", 0);\n n(this, \"_chapterNum\", 0);\n n(this, \"_verseNum\", 0);\n n(this, \"_verse\");\n if (r == null && o == null)\n if (e != null && typeof e == \"string\") {\n const a = e, h = s != null && s instanceof c ? s : void 0;\n this.setEmpty(h), this.parse(a);\n } else if (e != null && typeof e == \"number\") {\n const a = s != null && s instanceof c ? s : void 0;\n this.setEmpty(a), this._verseNum = e % i.chapterDigitShifter, this._chapterNum = Math.floor(\n e % i.bookDigitShifter / i.chapterDigitShifter\n ), this._bookNum = Math.floor(e / i.bookDigitShifter);\n } else if (s == null)\n if (e != null && e instanceof i) {\n const a = e;\n this._bookNum = a.bookNum, this._chapterNum = a.chapterNum, this._verseNum = a.verseNum, this._verse = a.verse, this.versification = a.versification;\n } else {\n if (e == null)\n return;\n const a = e instanceof c ? e : i.defaultVersification;\n this.setEmpty(a);\n }\n else\n throw new Error(\"VerseRef constructor not supported.\");\n else if (e != null && s != null && r != null)\n if (typeof e == \"string\" && typeof s == \"string\" && typeof r == \"string\")\n this.setEmpty(o), this.updateInternal(e, s, r);\n else if (typeof e == \"number\" && typeof s == \"number\" && typeof r == \"number\")\n this._bookNum = e, this._chapterNum = s, this._verseNum = r, this.versification = o ?? i.defaultVersification;\n else\n throw new Error(\"VerseRef constructor not supported.\");\n else\n throw new Error(\"VerseRef constructor not supported.\");\n }\n /**\n * @deprecated Will be removed in v2. Replace `VerseRef.parse('...')` with `new VerseRef('...')`\n * or refactor to use `VerseRef.tryParse('...')` which has a different return type.\n */\n static parse(e, s = i.defaultVersification) {\n const r = new i(s);\n return r.parse(e), r;\n }\n /**\n * Determines if the verse string is in a valid format (does not consider versification).\n */\n static isVerseParseable(e) {\n return e.length > 0 && \"0123456789\".includes(e[0]) && !e.endsWith(this.verseRangeSeparator) && !e.endsWith(this.verseSequenceIndicator);\n }\n /**\n * Tries to parse the specified string into a verse reference.\n * @param str - The string to attempt to parse.\n * @returns success: `true` if the specified string was successfully parsed, `false` otherwise.\n * @returns verseRef: The result of the parse if successful, or empty VerseRef if it failed\n */\n static tryParse(e) {\n let s;\n try {\n return s = i.parse(e), { success: !0, verseRef: s };\n } catch (r) {\n if (r instanceof d)\n return s = new i(), { success: !1, verseRef: s };\n throw r;\n }\n }\n /**\n * Gets the reference as a comparable integer where the book, chapter, and verse each occupy 3\n * digits.\n * @param bookNum - Book number (this is 1-based, not an index).\n * @param chapterNum - Chapter number.\n * @param verseNum - Verse number.\n * @returns The reference as a comparable integer where the book, chapter, and verse each occupy 3\n * digits.\n */\n static getBBBCCCVVV(e, s, r) {\n return e % i.bcvMaxValue * i.bookDigitShifter + (s >= 0 ? s % i.bcvMaxValue * i.chapterDigitShifter : 0) + (r >= 0 ? r % i.bcvMaxValue : 0);\n }\n /**\n * Parses a verse string and gets the leading numeric portion as a number.\n * @param verseStr - verse string to parse\n * @returns true if the entire string could be parsed as a single, simple verse number (1-999);\n * false if the verse string represented a verse bridge, contained segment letters, or was invalid\n */\n static tryGetVerseNum(e) {\n let s;\n if (!e)\n return s = -1, { success: !0, vNum: s };\n s = 0;\n let r;\n for (let o = 0; o < e.length; o++) {\n if (r = e[o], r < \"0\" || r > \"9\")\n return o === 0 && (s = -1), { success: !1, vNum: s };\n if (s = s * 10 + +r - +\"0\", s > i.bcvMaxValue)\n return s = -1, { success: !1, vNum: s };\n }\n return { success: !0, vNum: s };\n }\n /**\n * Checks to see if a VerseRef hasn't been set - all values are the default.\n */\n get isDefault() {\n return this.bookNum === 0 && this.chapterNum === 0 && this.verseNum === 0 && this.versification == null;\n }\n /**\n * Gets whether the verse contains multiple verses.\n */\n get hasMultiple() {\n return this._verse != null && (this._verse.includes(i.verseRangeSeparator) || this._verse.includes(i.verseSequenceIndicator));\n }\n /**\n * Gets or sets the book of the reference. Book is the 3-letter abbreviation in capital letters,\n * e.g. `'MAT'`.\n */\n get book() {\n return f.bookNumberToId(this.bookNum, \"\");\n }\n set book(e) {\n this.bookNum = f.bookIdToNumber(e);\n }\n /**\n * Gets or sets the chapter of the reference,. e.g. `'3'`.\n */\n get chapter() {\n return this.isDefault || this._chapterNum < 0 ? \"\" : this._chapterNum.toString();\n }\n set chapter(e) {\n const s = +e;\n this._chapterNum = Number.isInteger(s) ? s : -1;\n }\n /**\n * Gets or sets the verse of the reference, including range, segments, and sequences, e.g. `'4'`,\n * or `'4b-5a, 7'`.\n */\n get verse() {\n return this._verse != null ? this._verse : this.isDefault || this._verseNum < 0 ? \"\" : this._verseNum.toString();\n }\n set verse(e) {\n const { success: s, vNum: r } = i.tryGetVerseNum(e);\n this._verse = s ? void 0 : e.replace(this.rtlMark, \"\"), this._verseNum = r, !(this._verseNum >= 0) && ({ vNum: this._verseNum } = i.tryGetVerseNum(this._verse));\n }\n /**\n * Get or set Book based on book number, e.g. `42`.\n */\n get bookNum() {\n return this._bookNum;\n }\n set bookNum(e) {\n if (e <= 0 || e > f.lastBook)\n throw new d(\n \"BookNum must be greater than zero and less than or equal to last book\"\n );\n this._bookNum = e;\n }\n /**\n * Gets or sets the chapter number, e.g. `3`. `-1` if not valid.\n */\n get chapterNum() {\n return this._chapterNum;\n }\n set chapterNum(e) {\n this.chapterNum = e;\n }\n /**\n * Gets or sets verse start number, e.g. `4`. `-1` if not valid.\n */\n get verseNum() {\n return this._verseNum;\n }\n set verseNum(e) {\n this._verseNum = e;\n }\n /**\n * String representing the versification (should ONLY be used for serialization/deserialization).\n *\n * @remarks This is for backwards compatibility when ScrVers was an enumeration.\n */\n get versificationStr() {\n var e;\n return (e = this.versification) == null ? void 0 : e.name;\n }\n set versificationStr(e) {\n this.versification = this.versification != null ? new c(e) : void 0;\n }\n /**\n * Determines if the reference is valid.\n */\n get valid() {\n return this.validStatus === 0;\n }\n /**\n * Get the valid status for this reference.\n */\n get validStatus() {\n return this.validateVerse(i.verseRangeSeparators, i.verseSequenceIndicators);\n }\n /**\n * Gets the reference as a comparable integer where the book,\n * chapter, and verse each occupy three digits and the verse is 0.\n */\n get BBBCCC() {\n return i.getBBBCCCVVV(this._bookNum, this._chapterNum, 0);\n }\n /**\n * Gets the reference as a comparable integer where the book,\n * chapter, and verse each occupy three digits. If verse is not null\n * (i.e., this reference represents a complex reference with verse\n * segments or bridge) this cannot be used for an exact comparison.\n */\n get BBBCCCVVV() {\n return i.getBBBCCCVVV(this._bookNum, this._chapterNum, this._verseNum);\n }\n /**\n * Gets whether the verse is defined as an excluded verse in the versification.\n * Does not handle verse ranges.\n */\n // eslint-disable-next-line @typescript-eslint/class-literal-property-style\n get isExcluded() {\n return !1;\n }\n /**\n * Parses the reference in the specified string.\n * Optionally versification can follow reference as in GEN 3:11/4\n * Throw an exception if\n * - invalid book name\n * - chapter number is missing or not a number\n * - verse number is missing or does not start with a number\n * - versification is invalid\n * @param verseStr - string to parse e.g. 'MAT 3:11'\n */\n parse(e) {\n if (e = e.replace(this.rtlMark, \"\"), e.includes(\"/\")) {\n const a = e.split(\"/\");\n if (e = a[0], a.length > 1)\n try {\n const h = +a[1].trim();\n this.versification = new c(l[h]);\n } catch {\n throw new d(\"Invalid reference : \" + e);\n }\n }\n const s = e.trim().split(\" \");\n if (s.length !== 2)\n throw new d(\"Invalid reference : \" + e);\n const r = s[1].split(\":\"), o = +r[0];\n if (r.length !== 2 || f.bookIdToNumber(s[0]) === 0 || !Number.isInteger(o) || o < 0 || !i.isVerseParseable(r[1]))\n throw new d(\"Invalid reference : \" + e);\n this.updateInternal(s[0], r[0], r[1]);\n }\n /**\n * Simplifies this verse ref so that it has no bridging of verses or\n * verse segments like `'1a'`.\n */\n simplify() {\n this._verse = void 0;\n }\n /**\n * Makes a clone of the reference.\n *\n * @returns The cloned VerseRef.\n */\n clone() {\n return new i(this);\n }\n toString() {\n const e = this.book;\n return e === \"\" ? \"\" : `${e} ${this.chapter}:${this.verse}`;\n }\n /**\n * Compares this `VerseRef` with supplied one.\n * @param verseRef - object to compare this one to.\n * @returns `true` if this `VerseRef` is equal to the supplied on, `false` otherwise.\n */\n equals(e) {\n return e instanceof i ? e._bookNum === this._bookNum && e._chapterNum === this._chapterNum && e._verseNum === this._verseNum && e.verse === this.verse && e.versification != null && this.versification != null && e.versification.equals(this.versification) : !1;\n }\n /**\n * Enumerate all individual verses contained in a VerseRef.\n * Verse ranges are indicated by \"-\" and consecutive verses by \",\"s.\n * Examples:\n * GEN 1:2 returns GEN 1:2\n * GEN 1:1a-3b,5 returns GEN 1:1a, GEN 1:2, GEN 1:3b, GEN 1:5\n * GEN 1:2a-2c returns //! ??????\n *\n * @param specifiedVersesOnly - if set to true return only verses that are\n * explicitly specified only, not verses within a range. Defaults to `false`.\n * @param verseRangeSeparators - Verse range separators.\n * Defaults to `VerseRef.verseRangeSeparators`.\n * @param verseSequenceSeparators - Verse sequence separators.\n * Defaults to `VerseRef.verseSequenceIndicators`.\n * @returns An array of all single verse references in this VerseRef.\n */\n allVerses(e = !1, s = i.verseRangeSeparators, r = i.verseSequenceIndicators) {\n if (this._verse == null || this.chapterNum <= 0)\n return [this.clone()];\n const o = [], a = E(this._verse, r);\n for (const h of a.map((g) => E(g, s))) {\n const g = this.clone();\n g.verse = h[0];\n const w = g.verseNum;\n if (o.push(g), h.length > 1) {\n const p = this.clone();\n if (p.verse = h[1], !e)\n for (let b = w + 1; b < p.verseNum; b++) {\n const J = new i(\n this._bookNum,\n this._chapterNum,\n b,\n this.versification\n );\n this.isExcluded || o.push(J);\n }\n o.push(p);\n }\n }\n return o;\n }\n /**\n * Validates a verse number using the supplied separators rather than the defaults.\n */\n validateVerse(e, s) {\n if (!this.verse)\n return this.internalValid;\n let r = 0;\n for (const o of this.allVerses(!0, e, s)) {\n const a = o.internalValid;\n if (a !== 0)\n return a;\n const h = o.BBBCCCVVV;\n if (r > h)\n return 3;\n if (r === h)\n return 4;\n r = h;\n }\n return 0;\n }\n /**\n * Gets whether a single verse reference is valid.\n */\n get internalValid() {\n return this.versification == null ? 1 : this._bookNum <= 0 || this._bookNum > f.lastBook ? 2 : (f.isCanonical(this._bookNum), 0);\n }\n setEmpty(e = i.defaultVersification) {\n this._bookNum = 0, this._chapterNum = -1, this._verse = void 0, this.versification = e;\n }\n updateInternal(e, s, r) {\n this.bookNum = f.bookIdToNumber(e), this.chapter = s, this.verse = r;\n }\n};\nn(i, \"defaultVersification\", c.English), n(i, \"verseRangeSeparator\", \"-\"), n(i, \"verseSequenceIndicator\", \",\"), n(i, \"verseRangeSeparators\", [i.verseRangeSeparator]), n(i, \"verseSequenceIndicators\", [i.verseSequenceIndicator]), n(i, \"chapterDigitShifter\", 1e3), n(i, \"bookDigitShifter\", i.chapterDigitShifter * i.chapterDigitShifter), n(i, \"bcvMaxValue\", i.chapterDigitShifter - 1), /**\n * The valid status of the VerseRef.\n */\nn(i, \"ValidStatusType\", D);\nlet M = i;\nclass d extends Error {\n}\nexport {\n z as BookSet,\n f as Canon,\n c as ScrVers,\n l as ScrVersType,\n M as VerseRef,\n d as VerseRefException\n};\n//# sourceMappingURL=index.es.js.map\n","\"use strict\"\r\n\r\n// Based on: https://github.com/lodash/lodash/blob/6018350ac10d5ce6a5b7db625140b82aeab804df/.internal/unicodeSize.js\r\n\r\nmodule.exports = () => {\r\n\t// Used to compose unicode character classes.\r\n\tconst astralRange = \"\\\\ud800-\\\\udfff\"\r\n\tconst comboMarksRange = \"\\\\u0300-\\\\u036f\"\r\n\tconst comboHalfMarksRange = \"\\\\ufe20-\\\\ufe2f\"\r\n\tconst comboSymbolsRange = \"\\\\u20d0-\\\\u20ff\"\r\n\tconst comboMarksExtendedRange = \"\\\\u1ab0-\\\\u1aff\"\r\n\tconst comboMarksSupplementRange = \"\\\\u1dc0-\\\\u1dff\"\r\n\tconst comboRange = comboMarksRange + comboHalfMarksRange + comboSymbolsRange + comboMarksExtendedRange + comboMarksSupplementRange\r\n\tconst varRange = \"\\\\ufe0e\\\\ufe0f\"\r\n\tconst familyRange = \"\\\\uD83D\\\\uDC69\\\\uD83C\\\\uDFFB\\\\u200D\\\\uD83C\\\\uDF93\"\r\n\r\n\t// Used to compose unicode capture groups.\r\n\tconst astral = `[${astralRange}]`\r\n\tconst combo = `[${comboRange}]`\r\n\tconst fitz = \"\\\\ud83c[\\\\udffb-\\\\udfff]\"\r\n\tconst modifier = `(?:${combo}|${fitz})`\r\n\tconst nonAstral = `[^${astralRange}]`\r\n\tconst regional = \"(?:\\\\uD83C[\\\\uDDE6-\\\\uDDFF]){2}\"\r\n\tconst surrogatePair = \"[\\\\ud800-\\\\udbff][\\\\udc00-\\\\udfff]\"\r\n\tconst zwj = \"\\\\u200d\"\r\n\tconst blackFlag = \"(?:\\\\ud83c\\\\udff4\\\\udb40\\\\udc67\\\\udb40\\\\udc62\\\\udb40(?:\\\\udc65|\\\\udc73|\\\\udc77)\\\\udb40(?:\\\\udc6e|\\\\udc63|\\\\udc6c)\\\\udb40(?:\\\\udc67|\\\\udc74|\\\\udc73)\\\\udb40\\\\udc7f)\"\r\n\tconst family = `[${familyRange}]`\r\n\r\n\t// Used to compose unicode regexes.\r\n\tconst optModifier = `${modifier}?`\r\n\tconst optVar = `[${varRange}]?`\r\n\tconst optJoin = `(?:${zwj}(?:${[nonAstral, regional, surrogatePair].join(\"|\")})${optVar + optModifier})*`\r\n\tconst seq = optVar + optModifier + optJoin\r\n\tconst nonAstralCombo = `${nonAstral}${combo}?`\r\n\tconst symbol = `(?:${[nonAstralCombo, combo, regional, surrogatePair, astral, family].join(\"|\")})`\r\n\r\n\t// Used to match [String symbols](https://mathiasbynens.be/notes/javascript-unicode).\r\n\treturn new RegExp(`${blackFlag}|${fitz}(?=${fitz})|${symbol + seq}`, \"g\")\r\n}\r\n","\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\n// @ts-ignore\nvar char_regex_1 = __importDefault(require(\"char-regex\"));\n/**\n * Converts a string to an array of string chars\n * @param {string} str The string to turn into array\n * @returns {string[]}\n */\nfunction toArray(str) {\n if (typeof str !== 'string') {\n throw new Error('A string is expected as input');\n }\n return str.match(char_regex_1.default()) || [];\n}\nexports.toArray = toArray;\n/**\n * Returns the length of a string\n *\n * @export\n * @param {string} str\n * @returns {number}\n */\nfunction length(str) {\n // Check for input\n if (typeof str !== 'string') {\n throw new Error('Input must be a string');\n }\n var match = str.match(char_regex_1.default());\n return match === null ? 0 : match.length;\n}\nexports.length = length;\n/**\n * Returns a substring by providing start and end position\n *\n * @export\n * @param {string} str\n * @param {number} [begin=0] Starting position\n * @param {number} end End position\n * @returns {string}\n */\nfunction substring(str, begin, end) {\n if (begin === void 0) { begin = 0; }\n // Check for input\n if (typeof str !== 'string') {\n throw new Error('Input must be a string');\n }\n // Even though negative numbers work here, theyre not in the spec\n if (typeof begin !== 'number' || begin < 0) {\n begin = 0;\n }\n if (typeof end === 'number' && end < 0) {\n end = 0;\n }\n var match = str.match(char_regex_1.default());\n if (!match)\n return '';\n return match.slice(begin, end).join('');\n}\nexports.substring = substring;\n/**\n * Returns a substring by providing start position and length\n *\n * @export\n * @param {string} str\n * @param {number} [begin=0] Starting position\n * @param {number} len Desired length\n * @returns {string}\n */\nfunction substr(str, begin, len) {\n if (begin === void 0) { begin = 0; }\n // Check for input\n if (typeof str !== 'string') {\n throw new Error('Input must be a string');\n }\n var strLength = length(str);\n // Fix type\n if (typeof begin !== 'number') {\n begin = parseInt(begin, 10);\n }\n // Return zero-length string if got oversize number.\n if (begin >= strLength) {\n return '';\n }\n // Calculating postive version of negative value.\n if (begin < 0) {\n begin += strLength;\n }\n var end;\n if (typeof len === 'undefined') {\n end = strLength;\n }\n else {\n // Fix type\n if (typeof len !== 'number') {\n len = parseInt(len, 10);\n }\n end = len >= 0 ? len + begin : begin;\n }\n var match = str.match(char_regex_1.default());\n if (!match)\n return '';\n return match.slice(begin, end).join('');\n}\nexports.substr = substr;\n/**\n * Enforces a string to be a certain length by\n * adding or removing characters\n *\n * @export\n * @param {string} str\n * @param {number} [limit=16] Limit\n * @param {string} [padString='#'] The Pad String\n * @param {string} [padPosition='right'] The Pad Position\n * @returns {string}\n */\nfunction limit(str, limit, padString, padPosition) {\n if (limit === void 0) { limit = 16; }\n if (padString === void 0) { padString = '#'; }\n if (padPosition === void 0) { padPosition = 'right'; }\n // Input should be a string, limit should be a number\n if (typeof str !== 'string' || typeof limit !== 'number') {\n throw new Error('Invalid arguments specified');\n }\n // Pad position should be either left or right\n if (['left', 'right'].indexOf(padPosition) === -1) {\n throw new Error('Pad position should be either left or right');\n }\n // Pad string can be anything, we convert it to string\n if (typeof padString !== 'string') {\n padString = String(padString);\n }\n // Calculate string length considering astral code points\n var strLength = length(str);\n if (strLength > limit) {\n return substring(str, 0, limit);\n }\n else if (strLength < limit) {\n var padRepeats = padString.repeat(limit - strLength);\n return padPosition === 'left' ? padRepeats + str : str + padRepeats;\n }\n return str;\n}\nexports.limit = limit;\n/**\n * Returns the index of the first occurrence of a given string\n *\n * @export\n * @param {string} str\n * @param {string} [searchStr] the string to search\n * @param {number} [pos] starting position\n * @returns {number}\n */\nfunction indexOf(str, searchStr, pos) {\n if (pos === void 0) { pos = 0; }\n if (typeof str !== 'string') {\n throw new Error('Input must be a string');\n }\n if (str === '') {\n if (searchStr === '') {\n return 0;\n }\n return -1;\n }\n // fix type\n pos = Number(pos);\n pos = isNaN(pos) ? 0 : pos;\n searchStr = String(searchStr);\n var strArr = toArray(str);\n if (pos >= strArr.length) {\n if (searchStr === '') {\n return strArr.length;\n }\n return -1;\n }\n if (searchStr === '') {\n return pos;\n }\n var searchArr = toArray(searchStr);\n var finded = false;\n var index;\n for (index = pos; index < strArr.length; index += 1) {\n var searchIndex = 0;\n while (searchIndex < searchArr.length &&\n searchArr[searchIndex] === strArr[index + searchIndex]) {\n searchIndex += 1;\n }\n if (searchIndex === searchArr.length &&\n searchArr[searchIndex - 1] === strArr[index + searchIndex - 1]) {\n finded = true;\n break;\n }\n }\n return finded ? index : -1;\n}\nexports.indexOf = indexOf;\n","import { LocalizeKey } from 'menus.model';\nimport {\n indexOf as stringzIndexOf,\n substring as stringzSubstring,\n length as stringzLength,\n toArray as stringzToArray,\n limit as stringzLimit,\n substr as stringzSubstr,\n} from 'stringz';\n\n/**\n * This function mirrors the `at` function from the JavaScript Standard String object. It handles\n * Unicode code points instead of UTF-16 character codes.\n *\n * Finds the Unicode code point at the given index.\n *\n * @param string String to index\n * @param index Position of the character to be returned in range of -length(string) to\n * length(string)\n * @returns New string consisting of the Unicode code point located at the specified offset,\n * undefined if index is out of bounds\n */\nexport function at(string: string, index: number): string | undefined {\n if (index > stringLength(string) || index < -stringLength(string)) return undefined;\n return substr(string, index, 1);\n}\n\n/**\n * This function mirrors the `charAt` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns a new string consisting of the single unicode code point at the given index.\n *\n * @param string String to index\n * @param index Position of the string character to be returned, in the range of 0 to\n * length(string)-1\n * @returns New string consisting of the Unicode code point located at the specified offset, empty\n * string if index is out of bounds\n */\nexport function charAt(string: string, index: number): string {\n if (index < 0 || index > stringLength(string) - 1) return '';\n return substr(string, index, 1);\n}\n\n/**\n * This function mirrors the `codePointAt` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns a non-negative integer that is the Unicode code point value of the character starting at\n * the given index.\n *\n * @param string String to index\n * @param index Position of the string character to be returned, in the range of 0 to\n * length(string)-1\n * @returns Non-negative integer representing the code point value of the character at the given\n * index, or undefined if there is no element at that position\n */\nexport function codePointAt(string: string, index: number): number | undefined {\n if (index < 0 || index > stringLength(string) - 1) return undefined;\n return substr(string, index, 1).codePointAt(0);\n}\n\n/**\n * This function mirrors the `endsWith` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Determines whether a string ends with the characters of this string.\n *\n * @param string String to search through\n * @param searchString Characters to search for at the end of the string\n * @param endPosition End position where searchString is expected to be found. Default is\n * `length(string)`\n * @returns True if it ends with searchString, false if it does not\n */\nexport function endsWith(\n string: string,\n searchString: string,\n endPosition: number = stringLength(string),\n): boolean {\n const lastIndexOfSearchString = lastIndexOf(string, searchString);\n if (lastIndexOfSearchString === -1) return false;\n if (lastIndexOfSearchString + stringLength(searchString) !== endPosition) return false;\n return true;\n}\n\n/**\n * Get the index of the closest closing curly brace in a string.\n *\n * Note: when escaped, gets the index of the curly brace, not the backslash before it.\n *\n * @param str String to search\n * @param index Index at which to start searching. Inclusive of this index\n * @param escaped Whether to search for an escaped or an unescaped closing curly brace\n * @returns Index of closest closing curly brace or -1 if not found\n */\nfunction indexOfClosestClosingCurlyBrace(str: string, index: number, escaped: boolean) {\n if (index < 0) return -1;\n if (escaped) {\n if (charAt(str, index) === '}' && charAt(str, index - 1) === '\\\\') return index;\n const closeCurlyBraceIndex = indexOf(str, '\\\\}', index);\n return closeCurlyBraceIndex >= 0 ? closeCurlyBraceIndex + 1 : closeCurlyBraceIndex;\n }\n\n let i = index;\n const strLength = stringLength(str);\n while (i < strLength) {\n i = indexOf(str, '}', i);\n\n if (i === -1 || charAt(str, i - 1) !== '\\\\') break;\n\n // Didn't find an un-escaped close brace, so keep looking\n i += 1;\n }\n\n return i >= strLength ? -1 : i;\n}\n\n/**\n * Formats a string, replacing {localization key} with the localization (or multiple localizations\n * if there are multiple in the string). Will also remove \\ before curly braces if curly braces are\n * escaped with a backslash in order to preserve the curly braces. E.g. 'Hi, this is {name}! I like\n * `\\{curly braces\\}`! would become Hi, this is Jim! I like {curly braces}!\n *\n * If the key in unescaped braces is not found, just return the key without the braces. Empty\n * unescaped curly braces will just return a string without the braces e.g. ('I am {Nemo}', {\n * 'name': 'Jim'}) would return 'I am Nemo'.\n *\n * @param str String to format\n * @returns Formatted string\n */\nexport function formatReplacementString(str: string, replacers: { [key: string]: string }): string {\n let updatedStr = str;\n\n let i = 0;\n while (i < stringLength(updatedStr)) {\n switch (charAt(updatedStr, i)) {\n case '{':\n if (charAt(updatedStr, i - 1) !== '\\\\') {\n // This character is an un-escaped open curly brace. Try to match and replace\n const closeCurlyBraceIndex = indexOfClosestClosingCurlyBrace(updatedStr, i, false);\n if (closeCurlyBraceIndex >= 0) {\n // We have matching open and close indices. Try to replace the contents\n const replacerKey = substring(updatedStr, i + 1, closeCurlyBraceIndex);\n // Replace with the replacer string or just remove the curly braces\n const replacerString = replacerKey in replacers ? replacers[replacerKey] : replacerKey;\n\n updatedStr = `${substring(updatedStr, 0, i)}${replacerString}${substring(updatedStr, closeCurlyBraceIndex + 1)}`;\n // Put our index at the closing brace adjusted for the new string length minus two\n // because we are removing the curly braces\n // Ex: \"stuff {and} things\"\n // Replacer for and: n'\n // closeCurlyBraceIndex is 10\n // \"stuff n' things\"\n // i = 10 + 2 - 3 - 2 = 7\n i = closeCurlyBraceIndex + stringLength(replacerString) - stringLength(replacerKey) - 2;\n } else {\n // This is an unexpected un-escaped open curly brace with no matching closing curly\n // brace. Just ignore, I guess\n }\n } else {\n // This character is an escaped open curly brace. Remove the escape\n updatedStr = `${substring(updatedStr, 0, i - 1)}${substring(updatedStr, i)}`;\n // Adjust our index because we removed the escape\n i -= 1;\n }\n break;\n case '}':\n if (charAt(updatedStr, i - 1) !== '\\\\') {\n // This character is an un-escaped closing curly brace with no matching open curly\n // brace. Just ignore, I guess\n } else {\n // This character is an escaped closing curly brace. Remove the escape\n updatedStr = `${substring(updatedStr, 0, i - 1)}${substring(updatedStr, i)}`;\n // Adjust our index because we removed the escape\n i -= 1;\n }\n break;\n default:\n // No need to do anything with other characters at this point\n break;\n }\n\n i += 1;\n }\n\n return updatedStr;\n}\n/**\n * This function mirrors the `includes` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Performs a case-sensitive search to determine if searchString is found in string.\n *\n * @param string String to search through\n * @param searchString String to search for\n * @param position Position within the string to start searching for searchString. Default is `0`\n * @returns True if search string is found, false if it is not\n */\nexport function includes(string: string, searchString: string, position: number = 0): boolean {\n const partialString = substring(string, position);\n const indexOfSearchString = indexOf(partialString, searchString);\n if (indexOfSearchString === -1) return false;\n return true;\n}\n\n/**\n * This function mirrors the `indexOf` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns the index of the first occurrence of a given string.\n *\n * @param string String to search through\n * @param searchString The string to search for\n * @param position Start of searching. Default is `0`\n * @returns Index of the first occurrence of a given string\n */\nexport function indexOf(\n string: string,\n searchString: string,\n position: number | undefined = 0,\n): number {\n return stringzIndexOf(string, searchString, position);\n}\n\n/**\n * This function mirrors the `lastIndexOf` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Searches this string and returns the index of the last occurrence of the specified substring.\n *\n * @param string String to search through\n * @param searchString Substring to search for\n * @param position The index at which to begin searching. If omitted, the search begins at the end\n * of the string. Default is `undefined`\n * @returns Index of the last occurrence of searchString found, or -1 if not found.\n */\nexport function lastIndexOf(string: string, searchString: string, position?: number): number {\n let validatedPosition = position === undefined ? stringLength(string) : position;\n\n if (validatedPosition < 0) {\n validatedPosition = 0;\n } else if (validatedPosition >= stringLength(string)) {\n validatedPosition = stringLength(string) - 1;\n }\n\n for (let index = validatedPosition; index >= 0; index--) {\n if (substr(string, index, stringLength(searchString)) === searchString) {\n return index;\n }\n }\n\n return -1;\n}\n\n/**\n * This function mirrors the `length` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes. Since `length` appears to be a\n * reserved keyword, the function was renamed to `stringLength`\n *\n * Returns the length of a string.\n *\n * @param string String to return the length for\n * @returns Number that is length of the starting string\n */\nexport function stringLength(string: string): number {\n return stringzLength(string);\n}\n\n/**\n * This function mirrors the `normalize` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns the Unicode Normalization Form of this string.\n *\n * @param string The starting string\n * @param form Form specifying the Unicode Normalization Form. Default is `'NFC'`\n * @returns A string containing the Unicode Normalization Form of the given string.\n */\nexport function normalize(string: string, form: 'NFC' | 'NFD' | 'NFKC' | 'NFKD' | 'none'): string {\n const upperCaseForm = form.toUpperCase();\n if (upperCaseForm === 'NONE') {\n return string;\n }\n return string.normalize(upperCaseForm);\n}\n\n/**\n * Compares two strings using an ordinal comparison approach based on the specified collation\n * options. This function uses the built-in `localeCompare` method with the 'en' locale and the\n * provided collation options to compare the strings.\n *\n * @param string1 The first string to compare.\n * @param string2 The second string to compare.\n * @param options Optional. The collation options used for comparison.\n * @returns A number indicating the result of the comparison: - Negative value if string1 precedes\n * string2 in sorting order. - Zero if string1 and string2 are equivalent in sorting order. -\n * Positive value if string1 follows string2 in sorting order.\n */\nexport function ordinalCompare(\n string1: string,\n string2: string,\n options?: Intl.CollatorOptions,\n): number {\n return string1.localeCompare(string2, 'en', options);\n}\n\n/**\n * This function mirrors the `padEnd` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Pads this string with another string (multiple times, if needed) until the resulting string\n * reaches the given length. The padding is applied from the end of this string.\n *\n * @param string String to add padding too\n * @param targetLength The length of the resulting string once the starting string has been padded.\n * If value is less than or equal to length(string), then string is returned as is.\n * @param padString The string to pad the current string with. If padString is too long to stay\n * within targetLength, it will be truncated. Default is `\" \"`\n * @returns String with appropriate padding at the end\n */\n// Note: Limit with padString only works when length(padString) = 1, will be fixed with https://github.com/sallar/stringz/pull/59\nexport function padEnd(string: string, targetLength: number, padString: string = ' '): string {\n if (targetLength <= stringLength(string)) return string;\n return stringzLimit(string, targetLength, padString, 'right');\n}\n\n/**\n * This function mirrors the `padStart` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Pads this string with another string (multiple times, if needed) until the resulting string\n * reaches the given length. The padding is applied from the start of this string.\n *\n * @param string String to add padding too\n * @param targetLength The length of the resulting string once the starting string has been padded.\n * If value is less than or equal to length(string), then string is returned as is.\n * @param padString The string to pad the current string with. If padString is too long to stay\n * within the targetLength, it will be truncated from the end. Default is `\" \"`\n * @returns String with of specified targetLength with padString applied from the start\n */\n// Note: Limit with padString only works when length(padString) = 1, will be fixed with https://github.com/sallar/stringz/pull/59\nexport function padStart(string: string, targetLength: number, padString: string = ' '): string {\n if (targetLength <= stringLength(string)) return string;\n return stringzLimit(string, targetLength, padString, 'left');\n}\n\n// This is a helper function that performs a correction on the slice index to make sure it\n// cannot go out of bounds\nfunction correctSliceIndex(length: number, index: number) {\n if (index > length) return length;\n if (index < -length) return 0;\n if (index < 0) return index + length;\n return index;\n}\n\n/**\n * This function mirrors the `slice` function from the JavaScript Standard String object. It handles\n * Unicode code points instead of UTF-16 character codes.\n *\n * Extracts a section of this string and returns it as a new string, without modifying the original\n * string.\n *\n * @param string The starting string\n * @param indexStart The index of the first character to include in the returned substring.\n * @param indexEnd The index of the first character to exclude from the returned substring.\n * @returns A new string containing the extracted section of the string.\n */\nexport function slice(string: string, indexStart: number, indexEnd?: number): string {\n const length: number = stringLength(string);\n if (\n indexStart > length ||\n (indexEnd &&\n ((indexStart > indexEnd &&\n !(indexStart >= 0 && indexStart < length && indexEnd < 0 && indexEnd > -length)) ||\n indexEnd < -length))\n )\n return '';\n\n const newStart = correctSliceIndex(length, indexStart);\n const newEnd = indexEnd ? correctSliceIndex(length, indexEnd) : undefined;\n\n return substring(string, newStart, newEnd);\n}\n\n/**\n * This function mirrors the `split` function from the JavaScript Standard String object. It handles\n * Unicode code points instead of UTF-16 character codes.\n *\n * Takes a pattern and divides the string into an ordered list of substrings by searching for the\n * pattern, puts these substrings into an array, and returns the array.\n *\n * @param string The string to split\n * @param separator The pattern describing where each split should occur\n * @param splitLimit Limit on the number of substrings to be included in the array. Splits the\n * string at each occurrence of specified separator, but stops when limit entries have been placed\n * in the array.\n * @returns An array of strings, split at each point where separator occurs in the starting string.\n * Returns undefined if separator is not found in string.\n */\nexport function split(string: string, separator: string | RegExp, splitLimit?: number): string[] {\n const result: string[] = [];\n\n if (splitLimit !== undefined && splitLimit <= 0) {\n return [string];\n }\n\n if (separator === '') return toArray(string).slice(0, splitLimit);\n\n let regexSeparator = separator;\n if (\n typeof separator === 'string' ||\n (separator instanceof RegExp && !includes(separator.flags, 'g'))\n ) {\n regexSeparator = new RegExp(separator, 'g');\n }\n\n const matches: RegExpMatchArray | null = string.match(regexSeparator);\n\n let currentIndex = 0;\n\n if (!matches) return [string];\n\n for (let index = 0; index < (splitLimit ? splitLimit - 1 : matches.length); index++) {\n const matchIndex = indexOf(string, matches[index], currentIndex);\n const matchLength = stringLength(matches[index]);\n\n result.push(substring(string, currentIndex, matchIndex));\n currentIndex = matchIndex + matchLength;\n\n if (splitLimit !== undefined && result.length === splitLimit) {\n break;\n }\n }\n\n result.push(substring(string, currentIndex));\n\n return result;\n}\n\n/**\n * This function mirrors the `startsWith` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Determines whether the string begins with the characters of a specified string, returning true or\n * false as appropriate.\n *\n * @param string String to search through\n * @param searchString The characters to be searched for at the start of this string.\n * @param position The start position at which searchString is expected to be found (the index of\n * searchString's first character). Default is `0`\n * @returns True if the given characters are found at the beginning of the string, including when\n * searchString is an empty string; otherwise, false.\n */\nexport function startsWith(string: string, searchString: string, position: number = 0): boolean {\n const indexOfSearchString = indexOf(string, searchString, position);\n if (indexOfSearchString !== position) return false;\n return true;\n}\n\n/**\n * This function mirrors the `substr` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns a substring by providing start and length. This function is not exported because it is\n * considered deprecated, however it is still useful as a local helper function.\n *\n * @param string String to be divided\n * @param begin Start position. Default is `Start of string`\n * @param len Length of result. Default is `String length minus start parameter`. Default is `String\n * length minus start parameter`\n * @returns Substring from starting string\n */\nfunction substr(\n string: string,\n begin: number = 0,\n len: number = stringLength(string) - begin,\n): string {\n return stringzSubstr(string, begin, len);\n}\n\n/**\n * This function mirrors the `substring` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns a substring by providing start and end position.\n *\n * @param string String to be divided\n * @param begin Start position\n * @param end End position. Default is `End of string`\n * @returns Substring from starting string\n */\nexport function substring(\n string: string,\n begin: number,\n end: number = stringLength(string),\n): string {\n return stringzSubstring(string, begin, end);\n}\n\n/**\n * This function mirrors the `toArray` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Converts a string to an array of string characters.\n *\n * @param string String to convert to array\n * @returns An array of characters from the starting string\n */\nexport function toArray(string: string): string[] {\n return stringzToArray(string);\n}\n\n/** Determine whether the string is a `LocalizeKey` meant to be localized in Platform.Bible. */\nexport function isLocalizeKey(str: string): str is LocalizeKey {\n return startsWith(str, '%') && endsWith(str, '%');\n}\n\n/**\n * Escape RegExp special characters.\n *\n * You can also use this to escape a string that is inserted into the middle of a regex, for\n * example, into a character class.\n *\n * All credit to [`escape-string-regexp`](https://www.npmjs.com/package/escape-string-regexp) - this\n * function is simply copied directly from there to allow a common js export\n *\n * @example\n *\n * import escapeStringRegexp from 'platform-bible-utils';\n *\n * const escapedString = escapeStringRegexp('How much $ for a 🦄?');\n * //=> 'How much \\\\$ for a 🦄\\\\?'\n *\n * new RegExp(escapedString);\n */\nexport function escapeStringRegexp(string: string): string {\n if (typeof string !== 'string') {\n throw new TypeError('Expected a string');\n }\n\n // Escape characters with special meaning either inside or outside character sets.\n // Use a simple backslash escape when it’s always valid, and a `\\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar.\n return string.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&').replace(/-/g, '\\\\x2d');\n}\n\n/** This is an internal-only export for testing purposes and should not be used in development */\nexport const testingStringUtils = {\n indexOfClosestClosingCurlyBrace,\n};\n","import { Canon } from '@sillsdev/scripture';\nimport { BookInfo, ScriptureReference } from './scripture.model';\nimport { split, startsWith } from './string-util';\n\nconst scrBookData: BookInfo[] = [\n { shortName: 'ERR', fullNames: ['ERROR'], chapters: -1 },\n { shortName: 'GEN', fullNames: ['Genesis'], chapters: 50 },\n { shortName: 'EXO', fullNames: ['Exodus'], chapters: 40 },\n { shortName: 'LEV', fullNames: ['Leviticus'], chapters: 27 },\n { shortName: 'NUM', fullNames: ['Numbers'], chapters: 36 },\n { shortName: 'DEU', fullNames: ['Deuteronomy'], chapters: 34 },\n { shortName: 'JOS', fullNames: ['Joshua'], chapters: 24 },\n { shortName: 'JDG', fullNames: ['Judges'], chapters: 21 },\n { shortName: 'RUT', fullNames: ['Ruth'], chapters: 4 },\n { shortName: '1SA', fullNames: ['1 Samuel'], chapters: 31 },\n { shortName: '2SA', fullNames: ['2 Samuel'], chapters: 24 },\n { shortName: '1KI', fullNames: ['1 Kings'], chapters: 22 },\n { shortName: '2KI', fullNames: ['2 Kings'], chapters: 25 },\n { shortName: '1CH', fullNames: ['1 Chronicles'], chapters: 29 },\n { shortName: '2CH', fullNames: ['2 Chronicles'], chapters: 36 },\n { shortName: 'EZR', fullNames: ['Ezra'], chapters: 10 },\n { shortName: 'NEH', fullNames: ['Nehemiah'], chapters: 13 },\n { shortName: 'EST', fullNames: ['Esther'], chapters: 10 },\n { shortName: 'JOB', fullNames: ['Job'], chapters: 42 },\n { shortName: 'PSA', fullNames: ['Psalm', 'Psalms'], chapters: 150 },\n { shortName: 'PRO', fullNames: ['Proverbs'], chapters: 31 },\n { shortName: 'ECC', fullNames: ['Ecclesiastes'], chapters: 12 },\n { shortName: 'SNG', fullNames: ['Song of Solomon', 'Song of Songs'], chapters: 8 },\n { shortName: 'ISA', fullNames: ['Isaiah'], chapters: 66 },\n { shortName: 'JER', fullNames: ['Jeremiah'], chapters: 52 },\n { shortName: 'LAM', fullNames: ['Lamentations'], chapters: 5 },\n { shortName: 'EZK', fullNames: ['Ezekiel'], chapters: 48 },\n { shortName: 'DAN', fullNames: ['Daniel'], chapters: 12 },\n { shortName: 'HOS', fullNames: ['Hosea'], chapters: 14 },\n { shortName: 'JOL', fullNames: ['Joel'], chapters: 3 },\n { shortName: 'AMO', fullNames: ['Amos'], chapters: 9 },\n { shortName: 'OBA', fullNames: ['Obadiah'], chapters: 1 },\n { shortName: 'JON', fullNames: ['Jonah'], chapters: 4 },\n { shortName: 'MIC', fullNames: ['Micah'], chapters: 7 },\n { shortName: 'NAM', fullNames: ['Nahum'], chapters: 3 },\n { shortName: 'HAB', fullNames: ['Habakkuk'], chapters: 3 },\n { shortName: 'ZEP', fullNames: ['Zephaniah'], chapters: 3 },\n { shortName: 'HAG', fullNames: ['Haggai'], chapters: 2 },\n { shortName: 'ZEC', fullNames: ['Zechariah'], chapters: 14 },\n { shortName: 'MAL', fullNames: ['Malachi'], chapters: 4 },\n { shortName: 'MAT', fullNames: ['Matthew'], chapters: 28 },\n { shortName: 'MRK', fullNames: ['Mark'], chapters: 16 },\n { shortName: 'LUK', fullNames: ['Luke'], chapters: 24 },\n { shortName: 'JHN', fullNames: ['John'], chapters: 21 },\n { shortName: 'ACT', fullNames: ['Acts'], chapters: 28 },\n { shortName: 'ROM', fullNames: ['Romans'], chapters: 16 },\n { shortName: '1CO', fullNames: ['1 Corinthians'], chapters: 16 },\n { shortName: '2CO', fullNames: ['2 Corinthians'], chapters: 13 },\n { shortName: 'GAL', fullNames: ['Galatians'], chapters: 6 },\n { shortName: 'EPH', fullNames: ['Ephesians'], chapters: 6 },\n { shortName: 'PHP', fullNames: ['Philippians'], chapters: 4 },\n { shortName: 'COL', fullNames: ['Colossians'], chapters: 4 },\n { shortName: '1TH', fullNames: ['1 Thessalonians'], chapters: 5 },\n { shortName: '2TH', fullNames: ['2 Thessalonians'], chapters: 3 },\n { shortName: '1TI', fullNames: ['1 Timothy'], chapters: 6 },\n { shortName: '2TI', fullNames: ['2 Timothy'], chapters: 4 },\n { shortName: 'TIT', fullNames: ['Titus'], chapters: 3 },\n { shortName: 'PHM', fullNames: ['Philemon'], chapters: 1 },\n { shortName: 'HEB', fullNames: ['Hebrews'], chapters: 13 },\n { shortName: 'JAS', fullNames: ['James'], chapters: 5 },\n { shortName: '1PE', fullNames: ['1 Peter'], chapters: 5 },\n { shortName: '2PE', fullNames: ['2 Peter'], chapters: 3 },\n { shortName: '1JN', fullNames: ['1 John'], chapters: 5 },\n { shortName: '2JN', fullNames: ['2 John'], chapters: 1 },\n { shortName: '3JN', fullNames: ['3 John'], chapters: 1 },\n { shortName: 'JUD', fullNames: ['Jude'], chapters: 1 },\n { shortName: 'REV', fullNames: ['Revelation'], chapters: 22 },\n];\n\nexport const FIRST_SCR_BOOK_NUM = 1;\nexport const LAST_SCR_BOOK_NUM = scrBookData.length - 1;\nexport const FIRST_SCR_CHAPTER_NUM = 1;\nexport const FIRST_SCR_VERSE_NUM = 1;\n\nexport const getChaptersForBook = (bookNum: number): number => {\n return scrBookData[bookNum]?.chapters ?? -1;\n};\n\nexport const offsetBook = (scrRef: ScriptureReference, offset: number): ScriptureReference => ({\n bookNum: Math.max(FIRST_SCR_BOOK_NUM, Math.min(scrRef.bookNum + offset, LAST_SCR_BOOK_NUM)),\n chapterNum: 1,\n verseNum: 1,\n});\n\nexport const offsetChapter = (scrRef: ScriptureReference, offset: number): ScriptureReference => ({\n ...scrRef,\n chapterNum: Math.min(\n Math.max(FIRST_SCR_CHAPTER_NUM, scrRef.chapterNum + offset),\n getChaptersForBook(scrRef.bookNum),\n ),\n verseNum: 1,\n});\n\nexport const offsetVerse = (scrRef: ScriptureReference, offset: number): ScriptureReference => ({\n ...scrRef,\n verseNum: Math.max(FIRST_SCR_VERSE_NUM, scrRef.verseNum + offset),\n});\n\n/**\n * https://github.com/ubsicap/Paratext/blob/master/ParatextData/SILScriptureExtensions.cs#L72\n *\n * Convert book number to a localized Id (a short description of the book). This should be used\n * whenever a book ID (short code) is shown to the user. It is primarily needed for people who do\n * not read Roman script well and need to have books identified in a alternate script (e.g. Chinese\n * or Russian)\n *\n * @param bookNumber\n * @param localizationLanguage In BCP 47 format\n * @param getLocalizedString Function that provides the localized versions of the book ids and names\n * asynchronously.\n * @returns\n */\nexport async function getLocalizedIdFromBookNumber(\n bookNumber: number,\n localizationLanguage: string,\n getLocalizedString: (item: {\n localizeKey: string;\n languagesToSearch?: string[];\n }) => Promise,\n) {\n const id = Canon.bookNumberToId(bookNumber);\n\n if (!startsWith(Intl.getCanonicalLocales(localizationLanguage)[0], 'zh'))\n return getLocalizedString({\n localizeKey: `LocalizedId.${id}`,\n languagesToSearch: [localizationLanguage],\n });\n\n // For Chinese the normal book name is already fairly short.\n const bookName = await getLocalizedString({\n localizeKey: `Book.${id}`,\n languagesToSearch: [localizationLanguage],\n });\n const parts = split(bookName, '-');\n // some entries had a second name inside ideographic parenthesis\n const parts2 = split(parts[0], '\\xff08');\n const retVal = parts2[0].trim();\n return retVal;\n}\n","/** Function to run to dispose of something. Returns true if successfully unsubscribed */\nexport type Unsubscriber = () => boolean;\n\n/**\n * Returns an Unsubscriber function that combines all the unsubscribers passed in.\n *\n * @param unsubscribers All unsubscribers to aggregate into one unsubscriber\n * @returns Function that unsubscribes from all passed in unsubscribers when run\n */\nexport const aggregateUnsubscribers = (unsubscribers: Unsubscriber[]): Unsubscriber => {\n return (...args) => {\n // Run the unsubscriber for each handler\n const unsubs = unsubscribers.map((unsubscriber) => unsubscriber(...args));\n\n // If all the unsubscribers resolve to truthiness, we succeed\n return unsubs.every((success) => success);\n };\n};\n\n/**\n * Function to run to dispose of something that runs asynchronously. The promise resolves to true if\n * successfully unsubscribed\n */\nexport type UnsubscriberAsync = () => Promise;\n\n/**\n * Returns an UnsubscriberAsync function that combines all the unsubscribers passed in.\n *\n * @param unsubscribers - All unsubscribers to aggregate into one unsubscriber.\n * @returns Function that unsubscribes from all passed in unsubscribers when run\n */\nexport const aggregateUnsubscriberAsyncs = (\n unsubscribers: (UnsubscriberAsync | Unsubscriber)[],\n): UnsubscriberAsync => {\n return async (...args) => {\n // Run the unsubscriber for each handler\n const unsubPromises = unsubscribers.map(async (unsubscriber) => unsubscriber(...args));\n\n // If all the unsubscribers resolve to truthiness, we succeed\n return (await Promise.all(unsubPromises)).every((success) => success);\n };\n};\n","var getOwnPropertyNames = Object.getOwnPropertyNames, getOwnPropertySymbols = Object.getOwnPropertySymbols;\nvar hasOwnProperty = Object.prototype.hasOwnProperty;\n/**\n * Combine two comparators into a single comparators.\n */\nfunction combineComparators(comparatorA, comparatorB) {\n return function isEqual(a, b, state) {\n return comparatorA(a, b, state) && comparatorB(a, b, state);\n };\n}\n/**\n * Wrap the provided `areItemsEqual` method to manage the circular state, allowing\n * for circular references to be safely included in the comparison without creating\n * stack overflows.\n */\nfunction createIsCircular(areItemsEqual) {\n return function isCircular(a, b, state) {\n if (!a || !b || typeof a !== 'object' || typeof b !== 'object') {\n return areItemsEqual(a, b, state);\n }\n var cache = state.cache;\n var cachedA = cache.get(a);\n var cachedB = cache.get(b);\n if (cachedA && cachedB) {\n return cachedA === b && cachedB === a;\n }\n cache.set(a, b);\n cache.set(b, a);\n var result = areItemsEqual(a, b, state);\n cache.delete(a);\n cache.delete(b);\n return result;\n };\n}\n/**\n * Get the properties to strictly examine, which include both own properties that are\n * not enumerable and symbol properties.\n */\nfunction getStrictProperties(object) {\n return getOwnPropertyNames(object).concat(getOwnPropertySymbols(object));\n}\n/**\n * Whether the object contains the property passed as an own property.\n */\nvar hasOwn = Object.hasOwn ||\n (function (object, property) {\n return hasOwnProperty.call(object, property);\n });\n/**\n * Whether the values passed are strictly equal or both NaN.\n */\nfunction sameValueZeroEqual(a, b) {\n return a || b ? a === b : a === b || (a !== a && b !== b);\n}\n\nvar OWNER = '_owner';\nvar getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor, keys = Object.keys;\n/**\n * Whether the arrays are equal in value.\n */\nfunction areArraysEqual(a, b, state) {\n var index = a.length;\n if (b.length !== index) {\n return false;\n }\n while (index-- > 0) {\n if (!state.equals(a[index], b[index], index, index, a, b, state)) {\n return false;\n }\n }\n return true;\n}\n/**\n * Whether the dates passed are equal in value.\n */\nfunction areDatesEqual(a, b) {\n return sameValueZeroEqual(a.getTime(), b.getTime());\n}\n/**\n * Whether the `Map`s are equal in value.\n */\nfunction areMapsEqual(a, b, state) {\n if (a.size !== b.size) {\n return false;\n }\n var matchedIndices = {};\n var aIterable = a.entries();\n var index = 0;\n var aResult;\n var bResult;\n while ((aResult = aIterable.next())) {\n if (aResult.done) {\n break;\n }\n var bIterable = b.entries();\n var hasMatch = false;\n var matchIndex = 0;\n while ((bResult = bIterable.next())) {\n if (bResult.done) {\n break;\n }\n var _a = aResult.value, aKey = _a[0], aValue = _a[1];\n var _b = bResult.value, bKey = _b[0], bValue = _b[1];\n if (!hasMatch &&\n !matchedIndices[matchIndex] &&\n (hasMatch =\n state.equals(aKey, bKey, index, matchIndex, a, b, state) &&\n state.equals(aValue, bValue, aKey, bKey, a, b, state))) {\n matchedIndices[matchIndex] = true;\n }\n matchIndex++;\n }\n if (!hasMatch) {\n return false;\n }\n index++;\n }\n return true;\n}\n/**\n * Whether the objects are equal in value.\n */\nfunction areObjectsEqual(a, b, state) {\n var properties = keys(a);\n var index = properties.length;\n if (keys(b).length !== index) {\n return false;\n }\n var property;\n // Decrementing `while` showed faster results than either incrementing or\n // decrementing `for` loop and than an incrementing `while` loop. Declarative\n // methods like `some` / `every` were not used to avoid incurring the garbage\n // cost of anonymous callbacks.\n while (index-- > 0) {\n property = properties[index];\n if (property === OWNER &&\n (a.$$typeof || b.$$typeof) &&\n a.$$typeof !== b.$$typeof) {\n return false;\n }\n if (!hasOwn(b, property) ||\n !state.equals(a[property], b[property], property, property, a, b, state)) {\n return false;\n }\n }\n return true;\n}\n/**\n * Whether the objects are equal in value with strict property checking.\n */\nfunction areObjectsEqualStrict(a, b, state) {\n var properties = getStrictProperties(a);\n var index = properties.length;\n if (getStrictProperties(b).length !== index) {\n return false;\n }\n var property;\n var descriptorA;\n var descriptorB;\n // Decrementing `while` showed faster results than either incrementing or\n // decrementing `for` loop and than an incrementing `while` loop. Declarative\n // methods like `some` / `every` were not used to avoid incurring the garbage\n // cost of anonymous callbacks.\n while (index-- > 0) {\n property = properties[index];\n if (property === OWNER &&\n (a.$$typeof || b.$$typeof) &&\n a.$$typeof !== b.$$typeof) {\n return false;\n }\n if (!hasOwn(b, property)) {\n return false;\n }\n if (!state.equals(a[property], b[property], property, property, a, b, state)) {\n return false;\n }\n descriptorA = getOwnPropertyDescriptor(a, property);\n descriptorB = getOwnPropertyDescriptor(b, property);\n if ((descriptorA || descriptorB) &&\n (!descriptorA ||\n !descriptorB ||\n descriptorA.configurable !== descriptorB.configurable ||\n descriptorA.enumerable !== descriptorB.enumerable ||\n descriptorA.writable !== descriptorB.writable)) {\n return false;\n }\n }\n return true;\n}\n/**\n * Whether the primitive wrappers passed are equal in value.\n */\nfunction arePrimitiveWrappersEqual(a, b) {\n return sameValueZeroEqual(a.valueOf(), b.valueOf());\n}\n/**\n * Whether the regexps passed are equal in value.\n */\nfunction areRegExpsEqual(a, b) {\n return a.source === b.source && a.flags === b.flags;\n}\n/**\n * Whether the `Set`s are equal in value.\n */\nfunction areSetsEqual(a, b, state) {\n if (a.size !== b.size) {\n return false;\n }\n var matchedIndices = {};\n var aIterable = a.values();\n var aResult;\n var bResult;\n while ((aResult = aIterable.next())) {\n if (aResult.done) {\n break;\n }\n var bIterable = b.values();\n var hasMatch = false;\n var matchIndex = 0;\n while ((bResult = bIterable.next())) {\n if (bResult.done) {\n break;\n }\n if (!hasMatch &&\n !matchedIndices[matchIndex] &&\n (hasMatch = state.equals(aResult.value, bResult.value, aResult.value, bResult.value, a, b, state))) {\n matchedIndices[matchIndex] = true;\n }\n matchIndex++;\n }\n if (!hasMatch) {\n return false;\n }\n }\n return true;\n}\n/**\n * Whether the TypedArray instances are equal in value.\n */\nfunction areTypedArraysEqual(a, b) {\n var index = a.length;\n if (b.length !== index) {\n return false;\n }\n while (index-- > 0) {\n if (a[index] !== b[index]) {\n return false;\n }\n }\n return true;\n}\n\nvar ARGUMENTS_TAG = '[object Arguments]';\nvar BOOLEAN_TAG = '[object Boolean]';\nvar DATE_TAG = '[object Date]';\nvar MAP_TAG = '[object Map]';\nvar NUMBER_TAG = '[object Number]';\nvar OBJECT_TAG = '[object Object]';\nvar REG_EXP_TAG = '[object RegExp]';\nvar SET_TAG = '[object Set]';\nvar STRING_TAG = '[object String]';\nvar isArray = Array.isArray;\nvar isTypedArray = typeof ArrayBuffer === 'function' && ArrayBuffer.isView\n ? ArrayBuffer.isView\n : null;\nvar assign = Object.assign;\nvar getTag = Object.prototype.toString.call.bind(Object.prototype.toString);\n/**\n * Create a comparator method based on the type-specific equality comparators passed.\n */\nfunction createEqualityComparator(_a) {\n var areArraysEqual = _a.areArraysEqual, areDatesEqual = _a.areDatesEqual, areMapsEqual = _a.areMapsEqual, areObjectsEqual = _a.areObjectsEqual, arePrimitiveWrappersEqual = _a.arePrimitiveWrappersEqual, areRegExpsEqual = _a.areRegExpsEqual, areSetsEqual = _a.areSetsEqual, areTypedArraysEqual = _a.areTypedArraysEqual;\n /**\n * compare the value of the two objects and return true if they are equivalent in values\n */\n return function comparator(a, b, state) {\n // If the items are strictly equal, no need to do a value comparison.\n if (a === b) {\n return true;\n }\n // If the items are not non-nullish objects, then the only possibility\n // of them being equal but not strictly is if they are both `NaN`. Since\n // `NaN` is uniquely not equal to itself, we can use self-comparison of\n // both objects, which is faster than `isNaN()`.\n if (a == null ||\n b == null ||\n typeof a !== 'object' ||\n typeof b !== 'object') {\n return a !== a && b !== b;\n }\n var constructor = a.constructor;\n // Checks are listed in order of commonality of use-case:\n // 1. Common complex object types (plain object, array)\n // 2. Common data values (date, regexp)\n // 3. Less-common complex object types (map, set)\n // 4. Less-common data values (promise, primitive wrappers)\n // Inherently this is both subjective and assumptive, however\n // when reviewing comparable libraries in the wild this order\n // appears to be generally consistent.\n // Constructors should match, otherwise there is potential for false positives\n // between class and subclass or custom object and POJO.\n if (constructor !== b.constructor) {\n return false;\n }\n // `isPlainObject` only checks against the object's own realm. Cross-realm\n // comparisons are rare, and will be handled in the ultimate fallback, so\n // we can avoid capturing the string tag.\n if (constructor === Object) {\n return areObjectsEqual(a, b, state);\n }\n // `isArray()` works on subclasses and is cross-realm, so we can avoid capturing\n // the string tag or doing an `instanceof` check.\n if (isArray(a)) {\n return areArraysEqual(a, b, state);\n }\n // `isTypedArray()` works on all possible TypedArray classes, so we can avoid\n // capturing the string tag or comparing against all possible constructors.\n if (isTypedArray != null && isTypedArray(a)) {\n return areTypedArraysEqual(a, b, state);\n }\n // Try to fast-path equality checks for other complex object types in the\n // same realm to avoid capturing the string tag. Strict equality is used\n // instead of `instanceof` because it is more performant for the common\n // use-case. If someone is subclassing a native class, it will be handled\n // with the string tag comparison.\n if (constructor === Date) {\n return areDatesEqual(a, b, state);\n }\n if (constructor === RegExp) {\n return areRegExpsEqual(a, b, state);\n }\n if (constructor === Map) {\n return areMapsEqual(a, b, state);\n }\n if (constructor === Set) {\n return areSetsEqual(a, b, state);\n }\n // Since this is a custom object, capture the string tag to determing its type.\n // This is reasonably performant in modern environments like v8 and SpiderMonkey.\n var tag = getTag(a);\n if (tag === DATE_TAG) {\n return areDatesEqual(a, b, state);\n }\n if (tag === REG_EXP_TAG) {\n return areRegExpsEqual(a, b, state);\n }\n if (tag === MAP_TAG) {\n return areMapsEqual(a, b, state);\n }\n if (tag === SET_TAG) {\n return areSetsEqual(a, b, state);\n }\n if (tag === OBJECT_TAG) {\n // The exception for value comparison is custom `Promise`-like class instances. These should\n // be treated the same as standard `Promise` objects, which means strict equality, and if\n // it reaches this point then that strict equality comparison has already failed.\n return (typeof a.then !== 'function' &&\n typeof b.then !== 'function' &&\n areObjectsEqual(a, b, state));\n }\n // If an arguments tag, it should be treated as a standard object.\n if (tag === ARGUMENTS_TAG) {\n return areObjectsEqual(a, b, state);\n }\n // As the penultimate fallback, check if the values passed are primitive wrappers. This\n // is very rare in modern JS, which is why it is deprioritized compared to all other object\n // types.\n if (tag === BOOLEAN_TAG || tag === NUMBER_TAG || tag === STRING_TAG) {\n return arePrimitiveWrappersEqual(a, b, state);\n }\n // If not matching any tags that require a specific type of comparison, then we hard-code false because\n // the only thing remaining is strict equality, which has already been compared. This is for a few reasons:\n // - Certain types that cannot be introspected (e.g., `WeakMap`). For these types, this is the only\n // comparison that can be made.\n // - For types that can be introspected, but rarely have requirements to be compared\n // (`ArrayBuffer`, `DataView`, etc.), the cost is avoided to prioritize the common\n // use-cases (may be included in a future release, if requested enough).\n // - For types that can be introspected but do not have an objective definition of what\n // equality is (`Error`, etc.), the subjective decision is to be conservative and strictly compare.\n // In all cases, these decisions should be reevaluated based on changes to the language and\n // common development practices.\n return false;\n };\n}\n/**\n * Create the configuration object used for building comparators.\n */\nfunction createEqualityComparatorConfig(_a) {\n var circular = _a.circular, createCustomConfig = _a.createCustomConfig, strict = _a.strict;\n var config = {\n areArraysEqual: strict\n ? areObjectsEqualStrict\n : areArraysEqual,\n areDatesEqual: areDatesEqual,\n areMapsEqual: strict\n ? combineComparators(areMapsEqual, areObjectsEqualStrict)\n : areMapsEqual,\n areObjectsEqual: strict\n ? areObjectsEqualStrict\n : areObjectsEqual,\n arePrimitiveWrappersEqual: arePrimitiveWrappersEqual,\n areRegExpsEqual: areRegExpsEqual,\n areSetsEqual: strict\n ? combineComparators(areSetsEqual, areObjectsEqualStrict)\n : areSetsEqual,\n areTypedArraysEqual: strict\n ? areObjectsEqualStrict\n : areTypedArraysEqual,\n };\n if (createCustomConfig) {\n config = assign({}, config, createCustomConfig(config));\n }\n if (circular) {\n var areArraysEqual$1 = createIsCircular(config.areArraysEqual);\n var areMapsEqual$1 = createIsCircular(config.areMapsEqual);\n var areObjectsEqual$1 = createIsCircular(config.areObjectsEqual);\n var areSetsEqual$1 = createIsCircular(config.areSetsEqual);\n config = assign({}, config, {\n areArraysEqual: areArraysEqual$1,\n areMapsEqual: areMapsEqual$1,\n areObjectsEqual: areObjectsEqual$1,\n areSetsEqual: areSetsEqual$1,\n });\n }\n return config;\n}\n/**\n * Default equality comparator pass-through, used as the standard `isEqual` creator for\n * use inside the built comparator.\n */\nfunction createInternalEqualityComparator(compare) {\n return function (a, b, _indexOrKeyA, _indexOrKeyB, _parentA, _parentB, state) {\n return compare(a, b, state);\n };\n}\n/**\n * Create the `isEqual` function used by the consuming application.\n */\nfunction createIsEqual(_a) {\n var circular = _a.circular, comparator = _a.comparator, createState = _a.createState, equals = _a.equals, strict = _a.strict;\n if (createState) {\n return function isEqual(a, b) {\n var _a = createState(), _b = _a.cache, cache = _b === void 0 ? circular ? new WeakMap() : undefined : _b, meta = _a.meta;\n return comparator(a, b, {\n cache: cache,\n equals: equals,\n meta: meta,\n strict: strict,\n });\n };\n }\n if (circular) {\n return function isEqual(a, b) {\n return comparator(a, b, {\n cache: new WeakMap(),\n equals: equals,\n meta: undefined,\n strict: strict,\n });\n };\n }\n var state = {\n cache: undefined,\n equals: equals,\n meta: undefined,\n strict: strict,\n };\n return function isEqual(a, b) {\n return comparator(a, b, state);\n };\n}\n\n/**\n * Whether the items passed are deeply-equal in value.\n */\nvar deepEqual = createCustomEqual();\n/**\n * Whether the items passed are deeply-equal in value based on strict comparison.\n */\nvar strictDeepEqual = createCustomEqual({ strict: true });\n/**\n * Whether the items passed are deeply-equal in value, including circular references.\n */\nvar circularDeepEqual = createCustomEqual({ circular: true });\n/**\n * Whether the items passed are deeply-equal in value, including circular references,\n * based on strict comparison.\n */\nvar strictCircularDeepEqual = createCustomEqual({\n circular: true,\n strict: true,\n});\n/**\n * Whether the items passed are shallowly-equal in value.\n */\nvar shallowEqual = createCustomEqual({\n createInternalComparator: function () { return sameValueZeroEqual; },\n});\n/**\n * Whether the items passed are shallowly-equal in value based on strict comparison\n */\nvar strictShallowEqual = createCustomEqual({\n strict: true,\n createInternalComparator: function () { return sameValueZeroEqual; },\n});\n/**\n * Whether the items passed are shallowly-equal in value, including circular references.\n */\nvar circularShallowEqual = createCustomEqual({\n circular: true,\n createInternalComparator: function () { return sameValueZeroEqual; },\n});\n/**\n * Whether the items passed are shallowly-equal in value, including circular references,\n * based on strict comparison.\n */\nvar strictCircularShallowEqual = createCustomEqual({\n circular: true,\n createInternalComparator: function () { return sameValueZeroEqual; },\n strict: true,\n});\n/**\n * Create a custom equality comparison method.\n *\n * This can be done to create very targeted comparisons in extreme hot-path scenarios\n * where the standard methods are not performant enough, but can also be used to provide\n * support for legacy environments that do not support expected features like\n * `RegExp.prototype.flags` out of the box.\n */\nfunction createCustomEqual(options) {\n if (options === void 0) { options = {}; }\n var _a = options.circular, circular = _a === void 0 ? false : _a, createCustomInternalComparator = options.createInternalComparator, createState = options.createState, _b = options.strict, strict = _b === void 0 ? false : _b;\n var config = createEqualityComparatorConfig(options);\n var comparator = createEqualityComparator(config);\n var equals = createCustomInternalComparator\n ? createCustomInternalComparator(comparator)\n : createInternalEqualityComparator(comparator);\n return createIsEqual({ circular: circular, comparator: comparator, createState: createState, equals: equals, strict: strict });\n}\n\nexport { circularDeepEqual, circularShallowEqual, createCustomEqual, deepEqual, sameValueZeroEqual, shallowEqual, strictCircularDeepEqual, strictCircularShallowEqual, strictDeepEqual, strictShallowEqual };\n//# sourceMappingURL=index.mjs.map\n","// There is a circular version https://www.npmjs.com/package/fast-equals#circulardeepequal that I\n// think allows comparing React refs (which have circular references in particular places that this\n// library would ignore). Maybe we can change to that version sometime if needed.\nimport { deepEqual as isEqualDeep } from 'fast-equals';\n\n/**\n * Check that two objects are deeply equal, comparing members of each object and such\n *\n * @param a The first object to compare\n * @param b The second object to compare\n *\n * WARNING: Objects like arrays from different iframes have different constructor function\n * references even if they do the same thing, so this deep equality comparison fails objects that\n * look the same but have different constructors because different constructors could produce\n * false positives in [a few specific\n * situations](https://github.com/planttheidea/fast-equals/blob/a41afc0a240ad5a472e47b53791e9be017f52281/src/comparator.ts#L96).\n * This means that two objects like arrays from different iframes that look the same will fail\n * this check. Please use some other means to check deep equality in those situations.\n *\n * Note: This deep equality check considers `undefined` values on keys of objects NOT to be equal to\n * not specifying the key at all. For example, `{ stuff: 3, things: undefined }` and `{ stuff: 3\n * }` are not considered equal in this case\n *\n * - For more information and examples, see [this\n * CodeSandbox](https://codesandbox.io/s/deepequallibrarycomparison-4g4kk4?file=/src/index.mjs).\n *\n * @returns True if a and b are deeply equal; false otherwise\n */\nexport default function deepEqual(a: unknown, b: unknown) {\n return isEqualDeep(a, b);\n}\n","import deepEqual from './equality-checking';\n\n/**\n * Check if one object is a subset of the other object. \"Subset\" means that all properties of one\n * object are present in the other object, and if they are present that all values of those\n * properties are deeply equal. Sub-objects are also checked to be subsets of the corresponding\n * sub-object in the other object.\n *\n * @example ObjB is a subset of objA given these objects:\n *\n * ```ts\n * objA = { name: 'Alice', age: 30, address: { city: 'Seattle', state: 'Washington' } };\n * objB = { name: 'Alice', address: { city: 'Seattle' } };\n * ```\n *\n * It is important to note that only arrays of primitives (i.e., booleans, numbers, strings) are\n * supported. In particular, objects in arrays will not be checked for deep equality. Also, presence\n * in an array is all this checks, not the number of times that an item appears in an array. `[1,\n * 1]` is a subset of `[1]`.\n *\n * @param objectWithAllProperties Object to be checked if it is a superset of\n * `objectWithPartialProperties`\n * @param objectWithPartialProperties Object to be checked if it is a subset of\n * `objectWithAllProperties`\n * @returns True if `objectWithAllProperties` contains all the properties of\n * `objectWithPartialProperties` and all values of those properties are deeply equal\n */\nexport default function isSubset(\n objectWithAllProperties: unknown,\n objectWithPartialProperties: unknown,\n): boolean {\n if (typeof objectWithAllProperties !== typeof objectWithPartialProperties) return false;\n\n // For this function we're saying that all falsy things of the same type are equal to each other\n if (!objectWithAllProperties && !objectWithPartialProperties) return true;\n\n if (Array.isArray(objectWithAllProperties)) {\n // We know these are arrays from the line above\n /* eslint-disable no-type-assertion/no-type-assertion */\n const partialArray = objectWithPartialProperties as Array;\n const allArray = objectWithAllProperties as Array;\n /* eslint-enable no-type-assertion/no-type-assertion */\n\n if (partialArray.length === 0) return true;\n\n // This only works with arrays of primitives.\n // If someone cares about checking arrays of objects this needs updating.\n return partialArray.every((item) => allArray.includes(item));\n }\n\n if (typeof objectWithAllProperties !== 'object')\n return deepEqual(objectWithAllProperties, objectWithPartialProperties);\n\n // We know these are objects that potentially have properties because of the earlier checks\n /* eslint-disable no-type-assertion/no-type-assertion */\n const partialObj = objectWithPartialProperties as Record;\n const allObj = objectWithAllProperties as Record;\n /* eslint-enable no-type-assertion/no-type-assertion */\n\n let retVal = true;\n Object.keys(partialObj).forEach((key) => {\n if (!retVal) return;\n if (!Object.hasOwn(allObj, key)) retVal = false;\n else if (!isSubset(allObj[key], partialObj[key])) retVal = false;\n });\n return retVal;\n}\n","/**\n * Converts a JavaScript value to a JSON string, changing `undefined` properties in the JavaScript\n * object to `null` properties in the JSON string.\n *\n * WARNING: `null` values will become `undefined` values after passing through {@link serialize} then\n * {@link deserialize}. For example, `{ a: 1, b: undefined, c: null }` will become `{ a: 1, b:\n * undefined, c: undefined }`. If you are passing around user data that needs to retain `null`\n * values, you should wrap them yourself in a string before using this function. Alternatively, you\n * can write your own replacer that will preserve `null` in a way that you can recover later.\n *\n * @param value A JavaScript value, usually an object or array, to be converted.\n * @param replacer A function that transforms the results. Note that all `undefined` values returned\n * by the replacer will be further transformed into `null` in the JSON string.\n * @param space Adds indentation, white space, and line break characters to the return-value JSON\n * text to make it easier to read. See the `space` parameter of `JSON.stringify` for more\n * details.\n */\nexport function serialize(\n value: unknown,\n replacer?: (this: unknown, key: string, value: unknown) => unknown,\n space?: string | number,\n): string {\n const undefinedReplacer = (replacerKey: string, replacerValue: unknown) => {\n let newValue = replacerValue;\n if (replacer) newValue = replacer(replacerKey, newValue);\n // All `undefined` values become `null` on the way from JS objects into JSON strings\n // eslint-disable-next-line no-null/no-null\n if (newValue === undefined) newValue = null;\n return newValue;\n };\n return JSON.stringify(value, undefinedReplacer, space);\n}\n\n/**\n * Converts a JSON string into a value, converting all `null` properties from JSON into `undefined`\n * in the returned JavaScript value/object.\n *\n * WARNING: `null` values will become `undefined` values after passing through {@link serialize} then\n * {@link deserialize}. For example, `{ a: 1, b: undefined, c: null }` will become `{ a: 1, b:\n * undefined, c: undefined }`. If you are passing around user data that needs to retain `null`\n * values, you should wrap them yourself in a string before using this function. Alternatively, you\n * can write your own replacer that will preserve `null` in a way that you can recover later.\n *\n * @param value A valid JSON string.\n * @param reviver A function that transforms the results. This function is called for each member of\n * the object. If a member contains nested objects, the nested objects are transformed before the\n * parent object is. Note that `null` values are converted into `undefined` values after the\n * reviver has run.\n */\nexport function deserialize(\n value: string,\n reviver?: (this: unknown, key: string, value: unknown) => unknown,\n // Need to use `any` instead of `unknown` here to match the signature of JSON.parse\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): any {\n // Helper function to replace `null` with `undefined` on a per property basis. This can't be done\n // with our own reviver because `JSON.parse` removes `undefined` properties from the return value.\n function replaceNull(obj: Record): Record {\n Object.keys(obj).forEach((key: string | number) => {\n // We only want to replace `null`, not other falsy values\n // eslint-disable-next-line no-null/no-null\n if (obj[key] === null) obj[key] = undefined;\n // If the property is an object, recursively call the helper function on it\n else if (typeof obj[key] === 'object')\n // Since the object came from a string, we know the keys will not be symbols\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n obj[key] = replaceNull(obj[key] as Record);\n });\n return obj;\n }\n\n const parsedObject = JSON.parse(value, reviver);\n // Explicitly convert the value 'null' that isn't stored as a property on an object to 'undefined'\n // eslint-disable-next-line no-null/no-null\n if (parsedObject === null) return undefined;\n if (typeof parsedObject === 'object') return replaceNull(parsedObject);\n return parsedObject;\n}\n\n/**\n * Check to see if the value is serializable without losing information\n *\n * @param value Value to test\n * @returns True if serializable; false otherwise\n *\n * Note: the values `undefined` and `null` are serializable (on their own or in an array), but\n * `null` values get transformed into `undefined` when serializing/deserializing.\n *\n * WARNING: This is inefficient right now as it stringifies, parses, stringifies, and === the value.\n * Please only use this if you need to\n *\n * DISCLAIMER: this does not successfully detect that values are not serializable in some cases:\n *\n * - Losses of removed properties like functions and `Map`s\n * - Class instances (not deserializable into class instances without special code)\n *\n * We intend to improve this in the future if it becomes important to do so. See [`JSON.stringify`\n * documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description)\n * for more information.\n */\nexport function isSerializable(value: unknown): boolean {\n try {\n const serializedValue = serialize(value);\n return serializedValue === serialize(deserialize(serializedValue));\n } catch (e) {\n return false;\n }\n}\n\n/**\n * HTML Encodes the provided string. Thanks to ChatGPT\n *\n * @param str String to HTML encode\n * @returns HTML-encoded string\n */\nexport const htmlEncode = (str: string): string =>\n str\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/\\//g, '/');\n","import DateTimeFormat from './intl-date-time-format';\n\n/**\n * Retrieves the current locale of the user's environment.\n *\n * @returns A string representing the current locale. If the locale cannot be determined, the\n * function returns an empty string.\n */\nexport default function getCurrentLocale(): string {\n // Use navigator when available\n if (typeof navigator !== 'undefined' && navigator.languages) {\n return navigator.languages[0];\n }\n // For Node.js\n return new DateTimeFormat().resolvedOptions().locale;\n}\n","//----------------------------------------------------------------------------------------------\n// NOTE: If you change any of the types, make sure the JSON schema at the end of this file gets\n// changed so they align.\n//----------------------------------------------------------------------------------------------\n\nimport { LocalizeKey, ReferencedItem } from 'menus.model';\n\n/** The data an extension provides to inform Platform.Bible of the settings it provides */\nexport type SettingsContribution = SettingsGroup | SettingsGroup[];\n/** A description of an extension's setting entry */\nexport type Setting = ExtensionControlledSetting;\n/** Setting definition that is validated by the extension. */\nexport type ExtensionControlledSetting = SettingBase & ModifierExtensionControlled;\n/** Base information needed to describe a setting entry */\nexport type SettingBase = StateBase & {\n [k: string]: unknown;\n /** LocalizeKey that displays in the settings dialog as the setting name */\n label: LocalizeKey;\n /** LocalizeKey that displays in the settings dialog to describe the setting */\n description?: LocalizeKey;\n};\n/** The data an extension provides to inform Platform.Bible of the project settings it provides */\nexport type ProjectSettingsContribution = ProjectSettingsGroup | ProjectSettingsGroup[];\n/** A description of an extension's setting entry */\nexport type ProjectSetting = ExtensionControlledProjectSetting;\n/** Setting definition that is validated by the extension. */\nexport type ExtensionControlledProjectSetting = ProjectSettingBase & ModifierExtensionControlled;\n/** Base information needed to describe a project setting entry */\nexport type ProjectSettingBase = SettingBase & ModifierProject;\n/** A description of an extension's user state entry */\nexport type UserState = ExtensionControlledState;\n/** State definition that is validated by the extension. */\nexport type ExtensionControlledState = StateBase & ModifierExtensionControlled;\n/** Group of related settings definitions */\nexport interface SettingsGroup {\n [k: string]: unknown;\n /** LocalizeKey that displays in the settings dialog as the group name */\n label: LocalizeKey;\n /** LocalizeKey that displays in the settings dialog to describe the group */\n description?: LocalizeKey;\n properties: SettingProperties;\n}\n/** Object whose keys are setting IDs and whose values are settings objects */\nexport interface SettingProperties {\n [k: ReferencedItem]: Setting;\n}\n/** Base information needed to describe a state entry */\nexport interface StateBase {\n [k: string]: unknown;\n /** Default value for the state/setting */\n default: unknown;\n /**\n * A state/setting ID whose value to set to this state/setting's starting value the first time\n * this state/setting is loaded\n */\n derivesFrom?: ReferencedItem;\n}\n/**\n * Modifies state/setting type to be extension-controlled. \"Extension-controlled\" means the\n * extension provides the component and the validator for the state/setting, so the state/setting is\n * controlled by the extension.\n */\nexport interface ModifierExtensionControlled {\n [k: string]: unknown;\n platformType?: undefined;\n type?: undefined;\n}\n/** Group of related settings definitions */\nexport interface ProjectSettingsGroup {\n [k: string]: unknown;\n /** LocalizeKey that displays in the project settings dialog as the group name */\n label: LocalizeKey;\n /** LocalizeKey that displays in the project settings dialog to describe the group */\n description?: LocalizeKey;\n properties: ProjectSettingProperties;\n}\n/** Object whose keys are setting IDs and whose values are settings objects */\nexport interface ProjectSettingProperties {\n [k: ReferencedItem]: ProjectSetting;\n}\n\n// Note: we removed the index signature on ModifierProject to avoid having it on\n// `ProjectMetadataFilterOptions`. Unfortunately adding \"additionalProperties\": false on the json\n// schema messes up validation. Please remove the index signature again in the future if you\n// regenerate types\nexport interface ModifierProject {\n /**\n * String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s\n * (using the\n * [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)\n * function) to determine if they should be included.\n *\n * If this is one string, it will be matched against `projectInterface`s. If this is an array,\n * each entry is handled based on its type (at least one entry must match for this filter\n * condition to pass):\n *\n * - If the entry is a string, it will be matched against each `projectInterface`. If any match, the\n * project will pass this filter condition\n * - If the entry is an array of strings, each will be matched against each `projectInterface`. If\n * every string matches against at least one `projectInterface`, the project will pass this\n * filter condition\n *\n * In other words, each entry in the first-level array is `OR`'ed together. Each entry in\n * second-level arrays (arrays within the first-level array) are `AND`'ed together.\n *\n * Defaults to all {@link ProjectInterfaces}, so all projects that do not match\n * `excludeProjectInterfaces` will be included\n *\n * @example\n *\n * ```typescript\n * includeProjectInterfaces: ['one', ['two', 'three']];\n * ```\n *\n * This filter condition will succeed on projects whose `projectInterface`s fulfill at least one\n * of the following conditions (At least one entry in the array must match):\n *\n * - Include `one`\n * - Include both `two` and `three`.\n */\n includeProjectInterfaces?: undefined | string | (string | string[])[];\n /**\n * String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s\n * (using the\n * [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)\n * function) to determine if they should absolutely not be included even if they match with\n * `includeProjectInterfaces`.\n *\n * If this is one string, it will be matched against `projectInterface`s. If this is an array,\n * each entry is handled based on its type (at least one entry must match for this filter\n * condition to exclude the project):\n *\n * - If the entry is a string, it will be matched against each `projectInterface`. If any match, the\n * project will pass this filter condition and exclude the project\n * - If the entry is an array of strings, each will be matched against each `projectInterface`. If\n * every string matches against at least one `projectInterface`, the project will pass this\n * filter condition and exclude the project\n *\n * In other words, each entry in the first-level array is `OR`'ed together. Each entry in\n * second-level arrays (arrays within the first-level array) are `AND`'ed together.\n *\n * Defaults to no {@link ProjectInterfaces}, so all projects that match `includeProjectInterfaces`\n * will be included\n *\n * @example\n *\n * ```typescript\n * excludeProjectInterfaces: ['one', ['two', 'three']];\n * ```\n *\n * This filter condition will succeed and exclude projects whose `projectInterface`s fulfill at\n * least one of the following conditions (At least one entry in the array must match):\n *\n * - Include `one`\n * - Include both `two` and `three`.\n */\n excludeProjectInterfaces?: undefined | string | (string | string[])[];\n /**\n * String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory\n * Ids that provided each project's metadata (using the\n * [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)\n * function) to determine if the projects should be included.\n *\n * Defaults to all Project Data Provider Factory Ids, so all projects that do not match\n * `excludePdpFactoryIds` will be included\n */\n includePdpFactoryIds?: undefined | string | string[];\n /**\n * String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory\n * Ids that provided each project's metadata (using the\n * [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)\n * function) to determine if the projects should absolutely not be included even if they match\n * with `includeProjectInterfaces`.\n *\n * Defaults to none, so all projects that match `includePdpFactoryIds` will be included\n */\n excludePdpFactoryIds?: undefined | string | string[];\n}\n\n/** The data an extension provides to inform Platform.Bible of the user state it provides */\nexport interface UserStateContribution {\n [k: ReferencedItem]: UserState;\n}\n/** The data an extension provides to inform Platform.Bible of the project state it provides */\nexport interface ProjectStateContribution {\n [k: ReferencedItem]: UserState;\n}\n\n//----------------------------------------------------------------------------------------------\n// NOTE: If you change the schema below, make sure the TS types above get changed so they align.\n//----------------------------------------------------------------------------------------------\nconst settingsDefs = {\n projectSettingsContribution: {\n description:\n 'The data an extension provides to inform Platform.Bible of the project settings it provides',\n anyOf: [\n {\n $ref: '#/$defs/projectSettingsGroup',\n },\n {\n type: 'array',\n items: {\n $ref: '#/$defs/projectSettingsGroup',\n },\n },\n ],\n },\n projectSettingsGroup: {\n description: 'Group of related settings definitions',\n type: 'object',\n properties: {\n label: {\n description: 'localizeKey that displays in the project settings dialog as the group name',\n $ref: '#/$defs/localizeKey',\n },\n description: {\n description:\n 'localizeKey that displays in the project settings dialog to describe the group',\n $ref: '#/$defs/localizeKey',\n },\n properties: {\n $ref: '#/$defs/projectSettingProperties',\n },\n },\n required: ['label', 'properties'],\n },\n projectSettingProperties: {\n description: 'Object whose keys are setting IDs and whose values are settings objects',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n $ref: '#/$defs/projectSetting',\n },\n },\n additionalProperties: false,\n },\n projectSetting: {\n description: \"A description of an extension's setting entry\",\n anyOf: [\n {\n $ref: '#/$defs/extensionControlledProjectSetting',\n },\n ],\n },\n extensionControlledProjectSetting: {\n description: 'Setting definition that is validated by the extension.',\n allOf: [\n {\n $ref: '#/$defs/projectSettingBase',\n },\n {\n $ref: '#/$defs/modifierExtensionControlled',\n },\n ],\n },\n projectSettingBase: {\n description: 'Base information needed to describe a project setting entry',\n allOf: [\n {\n $ref: '#/$defs/settingBase',\n },\n {\n $ref: '#/$defs/modifierProject',\n },\n ],\n },\n modifierProject: {\n description: 'Modifies setting type to be project setting',\n type: 'object',\n properties: {\n includeProjectInterfaces: {\n description:\n \"String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if they should be included.\\n\\nIf this is one string, it will be matched against `projectInterface`s. If this is an array, each entry is handled based on its type (at least one entry must match for this filter condition to pass):\\n\\n- If the entry is a string, it will be matched against each `projectInterface`. If any match, the project will pass this filter condition\\n- If the entry is an array of strings, each will be matched against each `projectInterface`. If every string matches against at least one `projectInterface`, the project will pass this filter condition\\n\\nIn other words, each entry in the first-level array is `OR`'ed together. Each entry in second-level arrays (arrays within the first-level array) are `AND`'ed together.\\n\\nDefaults to all {@link ProjectInterfaces}, so all projects that do not match `excludeProjectInterfaces` will be included\\n\\n@example\\n\\n```typescript\\nincludeProjectInterfaces: ['one', ['two', 'three']];\\n```\\n\\nThis filter condition will succeed on projects whose `projectInterface`s fulfill at least one of the following conditions (At least one entry in the array must match):\\n\\n- Include `one`\\n- Include both `two` and `three`.\",\n anyOf: [\n {\n type: 'null',\n },\n {\n type: 'string',\n },\n {\n type: 'array',\n items: {\n anyOf: [\n {\n type: 'string',\n },\n {\n type: 'array',\n items: { type: 'string' },\n },\n ],\n },\n },\n ],\n },\n excludeProjectInterfaces: {\n description:\n \"String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if they should absolutely not be included even if they match with `includeProjectInterfaces`.\\n\\nIf this is one string, it will be matched against `projectInterface`s. If this is an array, each entry is handled based on its type (at least one entry must match for this filter condition to exclude the project):\\n\\n- If the entry is a string, it will be matched against each `projectInterface`. If any match, the project will pass this filter condition and exclude the project\\n- If the entry is an array of strings, each will be matched against each `projectInterface`. If every string matches against at least one `projectInterface`, the project will pass this filter condition and exclude the project\\n\\nIn other words, each entry in the first-level array is `OR`'ed together. Each entry in second-level arrays (arrays within the first-level array) are `AND`'ed together.\\n\\nDefaults to no {@link ProjectInterfaces}, so all projects that match `includeProjectInterfaces` will be included\\n\\n@example\\n\\n```typescript\\nexcludeProjectInterfaces: ['one', ['two', 'three']];\\n```\\n\\nThis filter condition will succeed and exclude projects whose `projectInterface`s fulfill at least one of the following conditions (At least one entry in the array must match):\\n\\n- Include `one`\\n- Include both `two` and `three`.\",\n anyOf: [\n {\n type: 'null',\n },\n {\n type: 'string',\n },\n {\n type: 'array',\n items: {\n anyOf: [\n {\n type: 'string',\n },\n {\n type: 'array',\n items: { type: 'string' },\n },\n ],\n },\n },\n ],\n },\n includePdpFactoryIds: {\n description:\n \"String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory Ids that provided each project's metadata (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if the projects should be included.\\n\\nDefaults to all Project Data Provider Factory Ids, so all projects that do not match `excludePdpFactoryIds` will be included\",\n anyOf: [\n {\n type: 'null',\n },\n {\n type: 'string',\n },\n {\n type: 'array',\n items: { type: 'string' },\n },\n ],\n },\n excludePdpFactoryIds: {\n description:\n \"String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory Ids that provided each project's metadata (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if the projects should absolutely not be included even if they match with `includeProjectInterfaces`.\\n\\nDefaults to none, so all projects that match `includePdpFactoryIds` will be included\",\n anyOf: [\n {\n type: 'null',\n },\n {\n type: 'string',\n },\n {\n type: 'array',\n items: { type: 'string' },\n },\n ],\n },\n },\n },\n settingsContribution: {\n description:\n 'The data an extension provides to inform Platform.Bible of the settings it provides',\n anyOf: [\n {\n $ref: '#/$defs/settingsGroup',\n },\n {\n type: 'array',\n items: {\n $ref: '#/$defs/settingsGroup',\n },\n },\n ],\n },\n settingsGroup: {\n description: 'Group of related settings definitions',\n type: 'object',\n properties: {\n label: {\n description: 'localizeKey that displays in the settings dialog as the group name',\n $ref: '#/$defs/localizeKey',\n },\n description: {\n description: 'localizeKey that displays in the settings dialog to describe the group',\n $ref: '#/$defs/localizeKey',\n },\n properties: {\n $ref: '#/$defs/settingProperties',\n },\n },\n required: ['label', 'properties'],\n },\n settingProperties: {\n description: 'Object whose keys are setting IDs and whose values are settings objects',\n type: 'object',\n patternProperties: {\n '^[\\\\w-]+\\\\.[\\\\w-]+$': {\n $ref: '#/$defs/setting',\n },\n },\n additionalProperties: false,\n },\n setting: {\n description: \"A description of an extension's setting entry\",\n anyOf: [\n {\n $ref: '#/$defs/extensionControlledSetting',\n },\n ],\n },\n extensionControlledSetting: {\n description: 'Setting definition that is validated by the extension.',\n allOf: [\n {\n $ref: '#/$defs/settingBase',\n },\n {\n $ref: '#/$defs/modifierExtensionControlled',\n },\n ],\n },\n settingBase: {\n description: 'Base information needed to describe a setting entry',\n allOf: [\n {\n $ref: '#/$defs/stateBase',\n },\n {\n type: 'object',\n properties: {\n label: {\n description: 'localizeKey that displays in the settings dialog as the setting name',\n $ref: '#/$defs/localizeKey',\n },\n description: {\n description: 'localizeKey that displays in the settings dialog to describe the setting',\n $ref: '#/$defs/localizeKey',\n },\n },\n required: ['label'],\n },\n ],\n },\n projectStateContribution: {\n description:\n 'The data an extension provides to inform Platform.Bible of the project state it provides',\n $ref: '#/$defs/userStateProperties',\n },\n userStateContribution: {\n description:\n 'The data an extension provides to inform Platform.Bible of the user state it provides',\n $ref: '#/$defs/userStateProperties',\n },\n userStateProperties: {\n description: 'Object whose keys are state IDs and whose values are state objects',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n $ref: '#/$defs/userState',\n },\n },\n additionalProperties: false,\n },\n userState: {\n description: \"A description of an extension's user state entry\",\n anyOf: [\n {\n $ref: '#/$defs/extensionControlledState',\n },\n ],\n },\n extensionControlledState: {\n description: 'State definition that is validated by the extension.',\n allOf: [\n {\n $ref: '#/$defs/stateBase',\n },\n {\n $ref: '#/$defs/modifierExtensionControlled',\n },\n ],\n },\n modifierExtensionControlled: {\n description:\n 'Modifies state/setting type to be extension-controlled. \"Extension-controlled\" means the extension provides the component and the validator for the state/setting, so the state/setting is controlled by the extension.',\n not: {\n anyOf: [\n {\n type: 'object',\n required: ['platformType'],\n },\n {\n type: 'object',\n required: ['type'],\n },\n ],\n },\n },\n stateBase: {\n description: 'Base information needed to describe a state entry',\n type: 'object',\n properties: {\n default: {\n description: 'default value for the state/setting',\n type: 'any',\n },\n derivesFrom: {\n description:\n \"a state/setting ID whose value to set to this state/setting's starting value the first time this state/setting is loaded\",\n $ref: '#/$defs/id',\n },\n },\n required: ['default'],\n },\n localizeKey: {\n description: \"Identifier for a string that will be localized based on the user's UI language\",\n type: 'string',\n pattern: '^%[\\\\w\\\\-\\\\.]+%$',\n tsType: 'LocalizeKey',\n },\n id: {\n description: '',\n type: 'string',\n pattern: '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$',\n tsType: 'Id',\n },\n};\n\n/**\n * Json-schema-to-typescript has some added stuff that isn't actually compatible with JSON schema,\n * so we remove them here\n *\n * @param defs The `$defs` property of a JSON schema (will be modified in place)\n */\n// JSON schema types are weird, so we'll just be careful\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function removeJsonToTypeScriptTypesStuff(defs: any) {\n if (!defs) return;\n\n // JSON schema types are weird, so we'll just be careful\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Object.values(defs).forEach((def: any) => {\n if (!def.type) return;\n\n if ('tsType' in def) delete def.tsType;\n\n if (def.type === 'any') {\n delete def.type;\n return;\n }\n\n if (def.type === 'object') {\n removeJsonToTypeScriptTypesStuff(def.properties);\n }\n });\n}\n\nremoveJsonToTypeScriptTypesStuff(settingsDefs);\n\n/** JSON schema object that aligns with the ProjectSettingsContribution type */\nexport const projectSettingsDocumentSchema = {\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n title: 'Project Settings Contribution',\n description:\n 'The data an extension provides to inform Platform.Bible of the project settings it provides',\n anyOf: [\n {\n $ref: '#/$defs/projectSettingsGroup',\n },\n {\n type: 'array',\n items: {\n $ref: '#/$defs/projectSettingsGroup',\n },\n },\n ],\n\n $defs: settingsDefs,\n};\n\nObject.freeze(projectSettingsDocumentSchema);\n\n/** JSON schema object that aligns with the {@link SettingsContribution} type */\nexport const settingsDocumentSchema = {\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n title: 'Settings Contribution',\n description:\n 'The data an extension provides to inform Platform.Bible of the settings it provides',\n anyOf: [\n {\n $ref: '#/$defs/settingsGroup',\n },\n {\n type: 'array',\n items: {\n $ref: '#/$defs/settingsGroup',\n },\n },\n ],\n\n $defs: settingsDefs,\n};\n\nObject.freeze(settingsDocumentSchema);\n","//----------------------------------------------------------------------------------------------\n// NOTE: If you change any of the types, make sure the JSON schema at the end of this file gets\n// changed so they align.\n//----------------------------------------------------------------------------------------------\n\nimport { LocalizeKey } from 'menus.model';\nimport { removeJsonToTypeScriptTypesStuff } from './settings.model';\n\n/** Localized string value associated with this key */\nexport type LocalizedStringValue = string;\n\n/** The data an extension provides to inform Platform.Bible of the localized strings it provides. */\nexport interface LocalizedStringDataContribution {\n [k: string]: unknown;\n metadata?: StringsMetadata;\n localizedStrings?: {\n [k: string]: LanguageStrings;\n };\n}\n/**\n * Map whose keys are localized string keys and whose values provide additional non-locale-specific\n * information about the localized string key\n */\nexport interface StringsMetadata {\n [k: LocalizeKey]: StringMetadata;\n}\n/** Additional non-locale-specific information about a localized string key */\nexport interface StringMetadata {\n [k: string]: unknown;\n /**\n * Localized string key from which to get this value if one does not exist in the specified\n * language. If a new key/value pair needs to be made to replace an existing one, this could help\n * smooth over the transition if the meanings are close enough\n */\n fallbackKey?: LocalizeKey;\n /**\n * Additional information provided by developers in English to help the translator to know how to\n * translate this localized string accurately\n */\n notes?: string;\n}\n/**\n * Map whose keys are localized string keys and whose values provide information about how to\n * localize strings for the localized string key\n */\nexport interface LanguageStrings {\n [k: LocalizeKey]: LocalizedStringValue;\n}\n\n//----------------------------------------------------------------------------------------------\n// NOTE: If you change the schema below, make sure the TS types above get changed so they align.\n//----------------------------------------------------------------------------------------------\n\nconst localizedStringsDefs = {\n languageStrings: {\n description:\n 'Map whose keys are localized string keys and whose values provide information about how to localize strings for the localized string key',\n type: 'object',\n patternProperties: {\n '^%[\\\\w\\\\-\\\\.]+%$': {\n $ref: '#/$defs/localizedStringValue',\n },\n },\n additionalProperties: false,\n },\n localizedStringValue: {\n description: 'Localized string value associated with this key',\n type: 'string',\n },\n stringsMetadata: {\n description:\n 'Map whose keys are localized string keys and whose values provide additional non-locale-specific information about the localized string key',\n type: 'object',\n patternProperties: {\n '^%[\\\\w\\\\-\\\\.]+%$': {\n $ref: '#/$defs/stringMetadata',\n },\n },\n additionalProperties: false,\n },\n stringMetadata: {\n description: 'Additional non-locale-specific information about a localized string key',\n type: 'object',\n properties: {\n fallbackKey: {\n description:\n 'Localized string key from which to get this value if one does not exist in the specified language. If a new key/value pair needs to be made to replace an existing one, this could help smooth over the transition if the meanings are close enough',\n $ref: '#/$defs/localizeKey',\n },\n notes: {\n description:\n 'Additional information provided by developers in English to help the translator to know how to translate this localized string accurately',\n type: 'string',\n },\n },\n },\n localizeKey: {\n description: \"Identifier for a string that will be localized based on the user's UI language\",\n type: 'string',\n pattern: '^%[\\\\w\\\\-\\\\.]+%$',\n tsType: 'LocalizeKey',\n },\n};\n\nremoveJsonToTypeScriptTypesStuff(localizedStringsDefs);\n\n/** JSON schema object that aligns with the LocalizedStringDataContribution type */\nexport const localizedStringsDocumentSchema = {\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n title: 'Localized String Data Contribution',\n description:\n 'The data an extension provides to inform Platform.Bible of the localized strings it provides.',\n type: 'object',\n properties: {\n metadata: {\n $ref: '#/$defs/stringsMetadata',\n },\n localizedStrings: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/languageStrings',\n },\n },\n },\n $defs: localizedStringsDefs,\n};\n\nObject.freeze(localizedStringsDocumentSchema);\n","//----------------------------------------------------------------------------------------------\n// NOTE: If you change any of the types, make sure the JSON schema at the end of this file gets\n// changed so they align.\n//----------------------------------------------------------------------------------------------\n\nimport { ReplaceType } from './util';\n\n/** Identifier for a string that will be localized in a menu based on the user's UI language */\nexport type LocalizeKey = `%${string}%`;\n\n/** Name of some UI element (i.e., tab, column, group, menu item) or some PAPI object (i.e., command) */\nexport type ReferencedItem = `${string}.${string}`;\n\nexport type OrderedItem = {\n /** Relative order of this item compared to other items in the same parent/scope (sorted ascending) */\n order: number;\n};\n\nexport type OrderedExtensibleContainer = OrderedItem & {\n /** Determines whether other items can be added to this after it has been defined */\n isExtensible?: boolean;\n};\n\n/** Group of menu items that belongs in a column */\nexport type MenuGroupDetailsInColumn = OrderedExtensibleContainer & {\n /** ID of column in which this group resides */\n column: ReferencedItem;\n};\n\n/** Group of menu items that belongs in a submenu */\nexport type MenuGroupDetailsInSubMenu = OrderedExtensibleContainer & {\n /** ID of menu item hosting the submenu in which this group resides */\n menuItem: ReferencedItem;\n};\n\n/** Column that includes header text in a menu */\nexport type MenuColumnWithHeader = OrderedExtensibleContainer & {\n /** Key that represents the text of the header text of the column */\n label: LocalizeKey;\n};\n\nexport type MenuItemBase = OrderedItem & {\n /** Menu group to which this menu item belongs */\n group: ReferencedItem;\n /** Key that represents the text of this menu item to display */\n label: LocalizeKey;\n /** Key that represents words the platform should reference when users are searching for menu items */\n searchTerms?: LocalizeKey;\n /** Key that represents the text to display if a mouse pointer hovers over the menu item */\n tooltip?: LocalizeKey;\n /** Additional information provided by developers to help people who perform localization */\n localizeNotes: string;\n};\n\n/** Menu item that hosts a submenu */\nexport type MenuItemContainingSubmenu = MenuItemBase & {\n /** ID for this menu item that holds a submenu */\n id: ReferencedItem;\n};\n\n/** Menu item that runs a command */\nexport type MenuItemContainingCommand = MenuItemBase & {\n /** Name of the PAPI command to run when this menu item is selected. */\n command: ReferencedItem;\n /** Path to the icon to display after the menu text */\n iconPathAfter?: string;\n /** Path to the icon to display before the menu text */\n iconPathBefore?: string;\n};\n\n/**\n * Group of menu items that can be combined with other groups to form a single context menu/submenu.\n * Groups are separated using a line within the menu/submenu.\n */\nexport type GroupsInSingleColumnMenu = {\n /** Named menu group */\n [property: ReferencedItem]: OrderedExtensibleContainer | MenuGroupDetailsInSubMenu;\n};\n\n/**\n * Group of menu items that can be combined with other groups to form a single menu/submenu within a\n * multi-column menu. Groups are separated using a line within the menu/submenu.\n */\nexport type GroupsInMultiColumnMenu = {\n /** Named menu group */\n [property: ReferencedItem]: MenuGroupDetailsInColumn | MenuGroupDetailsInSubMenu;\n};\n\n/** Group of columns that can be combined with other columns to form a multi-column menu */\nexport type ColumnsWithHeaders = {\n /** Named column of a menu */\n [property: ReferencedItem]: MenuColumnWithHeader;\n /** Defines whether columns can be added to this multi-column menu */\n isExtensible?: boolean;\n};\n\n/** Menu that contains a column without a header */\nexport type SingleColumnMenu = {\n /** Groups that belong in this menu */\n groups: GroupsInSingleColumnMenu;\n /** List of menu items that belong in this menu */\n items: (MenuItemContainingCommand | MenuItemContainingSubmenu)[];\n};\n\n/** Menu that contains multiple columns with headers */\nexport type MultiColumnMenu = {\n /** Columns that belong in this menu */\n columns: ColumnsWithHeaders;\n /** Groups that belong in this menu */\n groups: GroupsInMultiColumnMenu;\n /** List of menu items that belong in this menu */\n items: (MenuItemContainingCommand | MenuItemContainingSubmenu)[];\n};\n\n/** Menus for one single web view */\nexport type WebViewMenu = {\n /** Indicates whether the platform default menus should be included for this webview */\n includeDefaults: boolean | undefined;\n /** Menu that opens when you click on the top left corner of a tab */\n topMenu: MultiColumnMenu | undefined;\n /** Menu that opens when you right click on the main body/area of a tab */\n contextMenu: SingleColumnMenu | undefined;\n};\n\n/** Menus for all web views */\nexport type WebViewMenus = {\n /** Named web view */\n [property: ReferencedItem]: WebViewMenu;\n};\n\n/** Platform.Bible menus before they are localized */\nexport type PlatformMenus = {\n /** Top level menu for the application */\n mainMenu: MultiColumnMenu;\n /** Menus that apply per web view in the application */\n webViewMenus: WebViewMenus;\n /** Default context menu for web views that don't specify their own */\n defaultWebViewContextMenu: SingleColumnMenu;\n /** Default top menu for web views that don't specify their own */\n defaultWebViewTopMenu: MultiColumnMenu;\n};\n\n/**\n * Type that converts any menu type before it is localized to what it is after it is localized. This\n * can be applied to any menu type as needed.\n */\nexport type Localized = ReplaceType, ReferencedItem, string>;\n\n//----------------------------------------------------------------------------------------------\n// NOTE: If you change the schema below, make sure the TS types above get changed so they align.\n//----------------------------------------------------------------------------------------------\n/** JSON schema object that aligns with the PlatformMenus type */\nexport const menuDocumentSchema = {\n title: 'Platform.Bible menus',\n type: 'object',\n properties: {\n mainMenu: {\n description: 'Top level menu for the application',\n $ref: '#/$defs/multiColumnMenu',\n },\n defaultWebViewTopMenu: {\n description: \"Default top menu for web views that don't specify their own\",\n $ref: '#/$defs/multiColumnMenu',\n },\n defaultWebViewContextMenu: {\n description: \"Default context menu for web views that don't specify their own\",\n $ref: '#/$defs/singleColumnMenu',\n },\n webViewMenus: {\n description: 'Menus that apply per web view in the application',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n $ref: '#/$defs/menusForOneWebView',\n },\n },\n additionalProperties: false,\n },\n },\n required: ['mainMenu', 'defaultWebViewTopMenu', 'defaultWebViewContextMenu', 'webViewMenus'],\n additionalProperties: false,\n $defs: {\n localizeKey: {\n description:\n \"Identifier for a string that will be localized in a menu based on the user's UI language\",\n type: 'string',\n pattern: '^%[\\\\w\\\\-\\\\.]+%$',\n },\n referencedItem: {\n description:\n 'Name of some UI element (i.e., tab, column, group, menu item) or some PAPI object (i.e., command)',\n type: 'string',\n pattern: '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$',\n },\n columnsWithHeaders: {\n description:\n 'Group of columns that can be combined with other columns to form a multi-column menu',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n description: 'Single column with a header string',\n type: 'object',\n properties: {\n label: {\n description: 'Header text for this this column in the UI',\n $ref: '#/$defs/localizeKey',\n },\n localizeNotes: {\n description:\n 'Additional information provided by developers to help people who perform localization',\n type: 'string',\n },\n order: {\n description:\n 'Relative order of this column compared to other columns (sorted ascending)',\n type: 'number',\n },\n isExtensible: {\n description:\n 'Defines whether contributions are allowed to add menu groups to this column',\n type: 'boolean',\n },\n },\n required: ['label', 'order'],\n additionalProperties: false,\n },\n },\n properties: {\n isExtensible: {\n description:\n 'Defines whether contributions are allowed to add columns to this multi-column menu',\n type: 'boolean',\n },\n },\n },\n menuGroups: {\n description:\n 'Group of menu items that can be combined with other groups to form a single menu/submenu. Groups are separated using a line within the menu/submenu.',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n description: 'Single group that contains menu items',\n type: 'object',\n oneOf: [\n {\n properties: {\n column: {\n description:\n 'Column where this group belongs, not required for single column menus',\n $ref: '#/$defs/referencedItem',\n },\n order: {\n description:\n 'Relative order of this group compared to other groups in the same column or submenu (sorted ascending)',\n type: 'number',\n },\n isExtensible: {\n description:\n 'Defines whether contributions are allowed to add menu items to this menu group',\n type: 'boolean',\n },\n },\n required: ['order'],\n additionalProperties: false,\n },\n {\n properties: {\n menuItem: {\n description: 'Menu item that anchors the submenu where this group belongs',\n $ref: '#/$defs/referencedItem',\n },\n order: {\n description:\n 'Relative order of this group compared to other groups in the same column or submenu (sorted ascending)',\n type: 'number',\n },\n isExtensible: {\n description:\n 'Defines whether contributions are allowed to add menu items to this menu group',\n type: 'boolean',\n },\n },\n required: ['menuItem', 'order'],\n additionalProperties: false,\n },\n ],\n },\n },\n additionalProperties: false,\n },\n menuItem: {\n description:\n 'Single item in a menu that can be clicked on to take an action or can be the parent of a submenu',\n type: 'object',\n oneOf: [\n {\n properties: {\n id: {\n description: 'ID for this menu item that holds a submenu',\n $ref: '#/$defs/referencedItem',\n },\n },\n required: ['id'],\n },\n {\n properties: {\n command: {\n description: 'Name of the PAPI command to run when this menu item is selected.',\n $ref: '#/$defs/referencedItem',\n },\n iconPathBefore: {\n description: 'Path to the icon to display before the menu text',\n type: 'string',\n },\n iconPathAfter: {\n description: 'Path to the icon to display after the menu text',\n type: 'string',\n },\n },\n required: ['command'],\n },\n ],\n properties: {\n label: {\n description: 'Key that represents the text of this menu item to display',\n $ref: '#/$defs/localizeKey',\n },\n tooltip: {\n description:\n 'Key that represents the text to display if a mouse pointer hovers over the menu item',\n $ref: '#/$defs/localizeKey',\n },\n searchTerms: {\n description:\n 'Key that represents additional words the platform should reference when users are searching for menu items',\n $ref: '#/$defs/localizeKey',\n },\n localizeNotes: {\n description:\n 'Additional information provided by developers to help people who perform localization',\n type: 'string',\n },\n group: {\n description: 'Group to which this menu item belongs',\n $ref: '#/$defs/referencedItem',\n },\n order: {\n description:\n 'Relative order of this menu item compared to other menu items in the same group (sorted ascending)',\n type: 'number',\n },\n },\n required: ['label', 'group', 'order'],\n unevaluatedProperties: false,\n },\n groupsAndItems: {\n description: 'Core schema for a column',\n type: 'object',\n properties: {\n groups: {\n description: 'Groups that belong in this menu',\n $ref: '#/$defs/menuGroups',\n },\n items: {\n description: 'List of menu items that belong in this menu',\n type: 'array',\n items: { $ref: '#/$defs/menuItem' },\n uniqueItems: true,\n },\n },\n required: ['groups', 'items'],\n },\n singleColumnMenu: {\n description: 'Menu that contains a column without a header',\n type: 'object',\n allOf: [{ $ref: '#/$defs/groupsAndItems' }],\n unevaluatedProperties: false,\n },\n multiColumnMenu: {\n description: 'Menu that can contain multiple columns with headers',\n type: 'object',\n allOf: [\n { $ref: '#/$defs/groupsAndItems' },\n {\n properties: {\n columns: {\n description: 'Columns that belong in this menu',\n $ref: '#/$defs/columnsWithHeaders',\n },\n },\n required: ['columns'],\n },\n ],\n unevaluatedProperties: false,\n },\n menusForOneWebView: {\n description: 'Set of menus that are associated with a single tab',\n type: 'object',\n properties: {\n includeDefaults: {\n description:\n 'Indicates whether the platform default menus should be included for this webview',\n type: 'boolean',\n },\n topMenu: {\n description: 'Menu that opens when you click on the top left corner of a tab',\n $ref: '#/$defs/multiColumnMenu',\n },\n contextMenu: {\n description: 'Menu that opens when you right click on the main body/area of a tab',\n $ref: '#/$defs/singleColumnMenu',\n },\n },\n additionalProperties: false,\n },\n },\n};\n\nObject.freeze(menuDocumentSchema);\n"],"names":["AsyncVariable","variableName","rejectIfNotSettledWithinMS","__publicField","resolve","reject","value","throwIfAlreadySettled","reason","Collator","locales","options","string1","string2","DateTimeFormat","date","startDate","endDate","PlatformEventEmitter","event","callback","callbackIndex","_a","newGuid","s","isString","o","deepClone","obj","debounce","fn","delay","timeout","args","groupBy","items","keySelector","valueSelector","map","item","key","group","isErrorWithMessage","error","toErrorWithMessage","maybeError","getErrorMessage","wait","ms","waitForDuration","maxWaitTimeInMS","getAllObjectFunctionNames","objId","objectFunctionNames","property","objectPrototype","createSyncProxyForAsyncObject","getObject","objectToProxy","target","prop","DocumentCombiner","baseDocument","documentName","document","previousDocumentVersion","documentToSet","contributions","contributionName","potentialOutput","outputIteration","contribution","mergeObjects","output","finalOutput","areNonArrayObjects","values","allMatch","areArrayObjects","startingPoint","copyFrom","ignoreDuplicateProperties","retVal","mergeObjectsInternal","startingPointObj","copyFromObj","Mutex","AsyncMutex","MutexMap","mutexID","NonValidatingDocumentCombiner","NumberFormat","startRange","endRange","UnsubscriberAsyncList","name","unsubscribers","unsubscriber","unsubs","results","unsubscriberSucceeded","index","P","R","n","m","v","X","C","K","N","B","x","T","O","V","I","L","G","S","H","k","A","y","q","U","f","l","u","c","E","r","D","i","a","h","d","g","w","b","J","charRegex","astralRange","comboMarksRange","comboHalfMarksRange","comboSymbolsRange","comboMarksExtendedRange","comboMarksSupplementRange","comboRange","varRange","familyRange","astral","combo","fitz","modifier","nonAstral","regional","surrogatePair","zwj","blackFlag","family","optModifier","optVar","optJoin","seq","symbol","__importDefault","this","mod","dist","char_regex_1","require$$0","toArray","str","toArray_1","length","match","length_1","substring","begin","end","substring_1","substr","len","strLength","substr_1","limit","padString","padPosition","padRepeats","limit_1","indexOf","searchStr","pos","strArr","searchArr","finded","searchIndex","indexOf_1","at","string","stringLength","charAt","codePointAt","endsWith","searchString","endPosition","lastIndexOfSearchString","lastIndexOf","indexOfClosestClosingCurlyBrace","escaped","closeCurlyBraceIndex","formatReplacementString","replacers","updatedStr","replacerKey","replacerString","includes","position","partialString","stringzIndexOf","validatedPosition","stringzLength","normalize","form","upperCaseForm","ordinalCompare","padEnd","targetLength","stringzLimit","padStart","correctSliceIndex","slice","indexStart","indexEnd","newStart","newEnd","split","separator","splitLimit","result","regexSeparator","matches","currentIndex","matchIndex","matchLength","startsWith","stringzSubstr","stringzSubstring","stringzToArray","isLocalizeKey","escapeStringRegexp","scrBookData","FIRST_SCR_BOOK_NUM","LAST_SCR_BOOK_NUM","FIRST_SCR_CHAPTER_NUM","FIRST_SCR_VERSE_NUM","getChaptersForBook","bookNum","offsetBook","scrRef","offset","offsetChapter","offsetVerse","getLocalizedIdFromBookNumber","bookNumber","localizationLanguage","getLocalizedString","id","Canon","bookName","parts","aggregateUnsubscribers","success","aggregateUnsubscriberAsyncs","unsubPromises","getOwnPropertyNames","getOwnPropertySymbols","hasOwnProperty","combineComparators","comparatorA","comparatorB","state","createIsCircular","areItemsEqual","cache","cachedA","cachedB","getStrictProperties","object","hasOwn","sameValueZeroEqual","OWNER","getOwnPropertyDescriptor","keys","areArraysEqual","areDatesEqual","areMapsEqual","matchedIndices","aIterable","aResult","bResult","bIterable","hasMatch","aKey","aValue","_b","bKey","bValue","areObjectsEqual","properties","areObjectsEqualStrict","descriptorA","descriptorB","arePrimitiveWrappersEqual","areRegExpsEqual","areSetsEqual","areTypedArraysEqual","ARGUMENTS_TAG","BOOLEAN_TAG","DATE_TAG","MAP_TAG","NUMBER_TAG","OBJECT_TAG","REG_EXP_TAG","SET_TAG","STRING_TAG","isArray","isTypedArray","assign","getTag","createEqualityComparator","constructor","tag","createEqualityComparatorConfig","circular","createCustomConfig","strict","config","areArraysEqual$1","areMapsEqual$1","areObjectsEqual$1","areSetsEqual$1","createInternalEqualityComparator","compare","_indexOrKeyA","_indexOrKeyB","_parentA","_parentB","createIsEqual","comparator","createState","equals","meta","deepEqual","createCustomEqual","createCustomInternalComparator","isEqualDeep","isSubset","objectWithAllProperties","objectWithPartialProperties","partialArray","allArray","partialObj","allObj","serialize","replacer","space","replacerValue","newValue","deserialize","reviver","replaceNull","parsedObject","isSerializable","serializedValue","htmlEncode","getCurrentLocale","settingsDefs","removeJsonToTypeScriptTypesStuff","defs","def","projectSettingsDocumentSchema","settingsDocumentSchema","localizedStringsDefs","localizedStringsDocumentSchema","menuDocumentSchema"],"mappings":"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E,KAAA,cAAc,IAAIA,CAAY,CAAA,CACzC,CACH,CAOA,MAAM,qBAAwC,CACtC,MAAAC,EAAS,CAAC,GAAG,KAAK,aAAa,EAAE,IAAKD,GAAiBA,EAAA,CAAc,EACrEE,EAAU,MAAM,QAAQ,IAAID,CAAM,EACxC,YAAK,cAAc,QACZC,EAAQ,MAAM,CAACC,EAAuBC,KACtCD,GACH,QAAQ,MAAM,yBAAyB,KAAK,IAAI,2BAA2BC,CAAK,UAAU,EAErFD,EACR,CACH,CACF,CCrCA,IAAIE,GAAI,OAAO,eACXC,GAAI,CAAC,EAAG,EAAG/E,IAAM,KAAK,EAAI8E,GAAE,EAAG,EAAG,CAAE,WAAY,GAAI,aAAc,GAAI,SAAU,GAAI,MAAO9E,CAAC,CAAE,EAAI,EAAE,CAAC,EAAIA,EACzGgF,EAAI,CAAC,EAAG,EAAGhF,KAAO+E,GAAE,EAAG,OAAO,GAAK,SAAW,EAAI,GAAK,EAAG/E,CAAC,EAAGA,GAWlE,MAAMiF,EAAI,CACR,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MAEA,MAEA,MAEA,MAEA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,MAEA,MAEA,MAEA,MAEA,MACA,MACA,MACA,MAEA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MAEA,MACA,MACA,KACF,EAAGC,EAAI,CACL,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,MACA,KACF,EAAGC,GAAI,CACL,UACA,SACA,YACA,UACA,cACA,SACA,SACA,OACA,WACA,WACA,UACA,UACA,eACA,eACA,OACA,WACA,kBACA,MACA,SACA,WACA,eACA,gBACA,SACA,WACA,eACA,UACA,kBACA,QACA,OACA,OACA,UACA,QACA,QACA,QACA,WACA,YACA,SACA,YACA,UACA,UACA,OACA,OACA,OACA,OACA,SACA,gBACA,gBACA,YACA,YACA,cACA,aACA,kBACA,kBACA,YACA,YACA,QACA,WACA,UACA,QACA,UACA,UACA,SACA,SACA,SACA,OACA,aACA,QACA,SACA,eACA,oBACA,0BACA,SACA,qBACA,sBACA,UACA,qBACA,cACA,cACA,cACA,cACA,mBACA,mBACA,qBACA,YACA,OACA,oBAGA,uBACA,uBACA,sBACA,yBACA,wBACA,qBACA,UACA,UACA,UACA,UACA,UACA,UACA,UACA,eACA,cACA,eACA,oBACA,qBACA,0BACA,0BACA,eACA,eACA,YACA,gBACA,cACA,eACA,iBACA,wBACA,mBACA,WACA,QACA,aACA,aACA,aACA,2BACA,4BACA,YACF,EAAGC,GAAIC,KACP,SAASC,EAAE,EAAG,EAAI,GAAI,CACpB,OAAO,IAAM,EAAI,EAAE,YAAa,GAAG,KAAKF,GAAIA,GAAE,CAAC,EAAI,CACrD,CACA,SAASG,EAAE,EAAG,CACZ,OAAOD,EAAE,CAAC,EAAI,CAChB,CACA,SAASE,GAAE,EAAG,CACZ,MAAM,EAAI,OAAO,GAAK,SAAWF,EAAE,CAAC,EAAI,EACxC,OAAO,GAAK,IAAM,GAAK,EACzB,CACA,SAASG,GAAE,EAAG,CACZ,OAAQ,OAAO,GAAK,SAAWH,EAAE,CAAC,EAAI,IAAM,EAC9C,CACA,SAASI,GAAE,EAAG,CACZ,OAAO,GAAK,EACd,CACA,SAASC,GAAE,EAAG,CACZ,MAAM,EAAI,OAAO,GAAK,SAAWL,EAAE,CAAC,EAAI,EACxC,OAAOM,GAAE,CAAC,GAAK,CAACF,GAAE,CA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CE,KAAM,2CACR,CACF,CACF,EACA,kCAAmC,CACjC,YAAa,yDACb,MAAO,CACL,CACE,KAAM,4BACR,EACA,CACE,KAAM,qCACR,CACF,CACF,EACA,mBAAoB,CAClB,YAAa,8DACb,MAAO,CACL,CACE,KAAM,qBACR,EACA,CACE,KAAM,yBACR,CACF,CACF,EACA,gBAAiB,CACf,YAAa,8CACb,KAAM,SACN,WAAY,CACV,yBAA0B,CACxB,YACE,q4CACF,MAAO,CACL,CACE,KAAM,MACR,EACA,CACE,KAAM,QACR,EACA,CACE,KAAM,QACN,MAAO,CACL,MAAO,CACL,CACE,KAAM,QACR,EACA,CACE,KAAM,QACN,MAAO,CAAE,KAAM,QAAS,CAC1B,CACF,CACF,CACF,CACF,CACF,EACA,yBAA0B,CACxB,YACE,ugDACF,MAAO,CACL,CACE,KAAM,MACR,EACA,CACE,KAAM,QACR,EACA,CACE,KAAM,QACN,MAAO,CACL,MAAO,CACL,CACE,KAAM,QACR,EACA,CACE,KAAM,QACN,MAAO,CAAE,KAAM,QAAS,CAC1B,CACF,CACF,CACF,CACF,CACF,EACA,qBAAsB,CACpB,YACE,ybACF,MAAO,CACL,CACE,KAAM,MACR,EACA,CACE,KAAM,QACR,EACA,CACE,KAAM,QACN,MAAO,CAAE,KAAM,QAAS,CAC1B,CACF,CACF,EACA,qBAAsB,CACpB,YACE,mdACF,MAAO,CACL,CACE,KAAM,MACR,EACA,CACE,KAAM,QACR,EACA,CACE,KAAM,QACN,MAAO,CAAE,KAAM,QAAS,CAC1B,CACF,CACF,CACF,CACF,EACA,qBAAsB,CACpB,YACE,sFACF,MAAO,CACL,CACE,KAAM,uBACR,EACA,CACE,KAAM,QACN,MAAO,CACL,KAAM,uBACR,CACF,CACF,CACF,EACA,cAAe,CACb,YAAa,wCACb,KAAM,SACN,WAAY,CACV,MAAO,CACL,YAAa,qEACb,KAAM,qBACR,EACA,YAAa,CACX,YAAa,yEACb,KAAM,qBACR,EACA,WAAY,CACV,KAAM,2BACR,CACF,EACA,SAAU,CAAC,QAAS,YAAY,CAClC,EACA,kBAAmB,CACjB,YAAa,0EACb,KAAM,SACN,kBAAmB,CACjB,sBAAuB,CACrB,KAAM,iBACR,CACF,EACA,qBAAsB,EACxB,EACA,QAAS,CACP,YAAa,gDACb,MAAO,CACL,CACE,KAAM,oCACR,CACF,CACF,EACA,2BAA4B,CAC1B,YAAa,yDACb,MAAO,CACL,CACE,KAAM,qBACR,EACA,CACE,KAAM,qCACR,CACF,CACF,EACA,YAAa,CACX,YAAa,sDACb,MAAO,CACL,CACE,KAAM,mBACR,EACA,CACE,KAAM,SACN,WAAY,CACV,MAAO,CACL,YAAa,uEACb,KAAM,qBACR,EACA,YAAa,CACX,YAAa,2EACb,KAAM,qBACR,CACF,EACA,SAAU,CAAC,OAAO,CACpB,CACF,CACF,EACA,yBAA0B,CACxB,YACE,2FACF,KAAM,6BACR,EACA,sBAAuB,CACrB,YACE,wFACF,KAAM,6BACR,EACA,oBAAqB,CACnB,YAAa,qEACb,KAAM,SACN,kBAAmB,CACjB,0BAA2B,CACzB,KAAM,mBACR,CACF,EACA,qBAAsB,EACxB,EACA,UAAW,CACT,YAAa,mDACb,MAAO,CACL,CACE,KAAM,kCACR,CACF,CACF,EACA,yBAA0B,CACxB,YAAa,uDACb,MAAO,CACL,CACE,KAAM,mBACR,EACA,CACE,KAAM,qCACR,CACF,CACF,EACA,4BAA6B,CAC3B,YACE,0NACF,IAAK,CACH,MAAO,CACL,CACE,KAAM,SACN,SAAU,CAAC,cAAc,CAC3B,EACA,CACE,KAAM,SACN,SAAU,CAAC,MAAM,CACnB,CACF,CACF,CACF,EACA,UAAW,CACT,YAAa,oDACb,KAAM,SACN,WAAY,CACV,QAAS,CACP,YAAa,sCACb,KAAM,KACR,EACA,YAAa,CACX,YACE,2HACF,KAAM,YACR,CACF,EACA,SAAU,CAAC,SAAS,CACtB,EACA,YAAa,CACX,YAAa,iFACb,KAAM,SACN,QAAS,mBACT,OAAQ,aACV,EACA,GAAI,CACF,YAAa,GACb,KAAM,SACN,QAAS,0BACT,OAAQ,IACV,CACF,EAUO,SAASC,EAAiCC,EAAW,CACrDA,GAIL,OAAO,OAAOA,CAAI,EAAE,QAASC,GAAa,CACxC,GAAKA,EAAI,KAIL,IAFA,WAAYA,GAAK,OAAOA,EAAI,OAE5BA,EAAI,OAAS,MAAO,CACtB,OAAOA,EAAI,KACX,MACF,CAEIA,EAAI,OAAS,UACfF,EAAiCE,EAAI,UAAU,EACjD,CACD,CACH,CAEAF,EAAiCD,CAAY,EAGtC,MAAMI,GAAgC,CAC3C,QAAS,+CACT,MAAO,gCACP,YACE,8FACF,MAAO,CACL,CACE,KAAM,8BACR,EACA,CACE,KAAM,QACN,MAAO,CACL,KAAM,8BACR,CACF,CACF,EAEA,MAAOJ,CACT,EAEA,OAAO,OAAOI,EAA6B,EAGpC,MAAMC,GAAyB,CACpC,QAAS,+CACT,MAAO,wBACP,YACE,sFACF,MAAO,CACL,CACE,KAAM,uBACR,EACA,CACE,KAAM,QACN,MAAO,CACL,KAAM,uBACR,CACF,CACF,EAEA,MAAOL,CACT,EAEA,OAAO,OAAOK,EAAsB,ECniBpC,MAAMC,GAAuB,CAC3B,gBAAiB,CACf,YACE,2IACF,KAAM,SACN,kBAAmB,CACjB,mBAAoB,CAClB,KAAM,8BACR,CACF,EACA,qBAAsB,EACxB,EACA,qBAAsB,CACpB,YAAa,kDACb,KAAM,QACR,EACA,gBAAiB,CACf,YACE,8IACF,KAAM,SACN,kBAAmB,CACjB,mBAAoB,CAClB,KAAM,wBACR,CACF,EACA,qBAAsB,EACxB,EACA,eAAgB,CACd,YAAa,0EACb,KAAM,SACN,WAAY,CACV,YAAa,CACX,YACE,sPACF,KAAM,qBACR,EACA,MAAO,CACL,YACE,4IACF,KAAM,QACR,CACF,CACF,EACA,YAAa,CACX,YAAa,iFACb,KAAM,SACN,QAAS,mBACT,OAAQ,aACV,CACF,EAEAL,EAAiCK,EAAoB,EAG9C,MAAMC,GAAiC,CAC5C,QAAS,+CACT,MAAO,qCACP,YACE,gGACF,KAAM,SACN,WAAY,CACV,SAAU,CACR,KAAM,yBACR,EACA,iBAAkB,CAChB,KAAM,SACN,qBAAsB,CACpB,KAAM,yBACR,CACF,CACF,EACA,MAAOD,EACT,EAEA,OAAO,OAAOC,EAA8B,ECyBrC,MAAMC,GAAqB,CAChC,MAAO,uBACP,KAAM,SACN,WAAY,CACV,SAAU,CACR,YAAa,qCACb,KAAM,yBACR,EACA,sBAAuB,CACrB,YAAa,8DACb,KAAM,yBACR,EACA,0BAA2B,CACzB,YAAa,kEACb,KAAM,0BACR,EACA,aAAc,CACZ,YAAa,mDACb,KAAM,SACN,kBAAmB,CACjB,0BAA2B,CACzB,KAAM,4BACR,CACF,EACA,qBAAsB,EACxB,CACF,EACA,SAAU,CAAC,WAAY,wBAAyB,4BAA6B,cAAc,EAC3F,qBAAsB,GACtB,MAAO,CACL,YAAa,CACX,YACE,2FACF,KAAM,SACN,QAAS,kBACX,EACA,eAAgB,CACd,YACE,oGACF,KAAM,SACN,QAAS,yBACX,EACA,mBAAoB,CAClB,YACE,uFACF,KAAM,SACN,kBAAmB,CACjB,0BAA2B,CACzB,YAAa,qCACb,KAAM,SACN,WAAY,CACV,MAAO,CACL,YAAa,6CACb,KAAM,qBACR,EACA,cAAe,CACb,YACE,wFACF,KAAM,QACR,EACA,MAAO,CACL,YACE,6EACF,KAAM,QACR,EACA,aAAc,CACZ,YACE,8EACF,KAAM,SACR,CACF,EACA,SAAU,CAAC,QAAS,OAAO,EAC3B,qBAAsB,EACxB,CACF,EACA,WAAY,CACV,aAAc,CACZ,YACE,qFACF,KAAM,SACR,CACF,CACF,EACA,WAAY,CACV,YACE,uJACF,KAAM,SACN,kBAAmB,CACjB,0BAA2B,CACzB,YAAa,wCACb,KAAM,SACN,MAAO,CACL,CACE,WAAY,CACV,OAAQ,CACN,YACE,wEACF,KAAM,wBACR,EACA,MAAO,CACL,YACE,yGACF,KAAM,QACR,EACA,aAAc,CACZ,YACE,iFACF,KAAM,SACR,CACF,EACA,SAAU,CAAC,OAAO,EAClB,qBAAsB,EACxB,EACA,CACE,WAAY,CACV,SAAU,CACR,YAAa,8DACb,KAAM,wBACR,EACA,MAAO,CACL,YACE,yGACF,KAAM,QACR,EACA,aAAc,CACZ,YACE,iFACF,KAAM,SACR,CACF,EACA,SAAU,CAAC,WAAY,OAAO,EAC9B,qBAAsB,EACxB,CACF,CACF,CACF,EACA,qBAAsB,EACxB,EACA,SAAU,CACR,YACE,mGACF,KAAM,SACN,MAAO,CACL,CACE,WAAY,CACV,GAAI,CACF,YAAa,6CACb,KAAM,wBACR,CACF,EACA,SAAU,CAAC,IAAI,CACjB,EACA,CACE,WAAY,CACV,QAAS,CACP,YAAa,mEACb,KAAM,wBACR,EACA,eAAgB,CACd,YAAa,mDACb,KAAM,QACR,EACA,cAAe,CACb,YAAa,kDACb,KAAM,QACR,CACF,EACA,SAAU,CAAC,SAAS,CACtB,CACF,EACA,WAAY,CACV,MAAO,CACL,YAAa,4DACb,KAAM,qBACR,EACA,QAAS,CACP,YACE,uFACF,KAAM,qBACR,EACA,YAAa,CACX,YACE,6GACF,KAAM,qBACR,EACA,cAAe,CACb,YACE,wFACF,KAAM,QACR,EACA,MAAO,CACL,YAAa,wCACb,KAAM,wBACR,EACA,MAAO,CACL,YACE,qGACF,KAAM,QACR,CACF,EACA,SAAU,CAAC,QAAS,QAAS,OAAO,EACpC,sBAAuB,EACzB,EACA,eAAgB,CACd,YAAa,2BACb,KAAM,SACN,WAAY,CACV,OAAQ,CACN,YAAa,kCACb,KAAM,oBACR,EACA,MAAO,CACL,YAAa,8CACb,KAAM,QACN,MAAO,CAAE,KAAM,kBAAmB,EAClC,YAAa,EACf,CACF,EACA,SAAU,CAAC,SAAU,OAAO,CAC9B,EACA,iBAAkB,CAChB,YAAa,+CACb,KAAM,SACN,MAAO,CAAC,CAAE,KAAM,yBAA0B,EAC1C,sBAAuB,EACzB,EACA,gBAAiB,CACf,YAAa,sDACb,KAAM,SACN,MAAO,CACL,CAAE,KAAM,wBAAyB,EACjC,CACE,WAAY,CACV,QAAS,CACP,YAAa,mCACb,KAAM,4BACR,CACF,EACA,SAAU,CAAC,SAAS,CACtB,CACF,EACA,sBAAuB,EACzB,EACA,mBAAoB,CAClB,YAAa,qDACb,KAAM,SACN,WAAY,CACV,gBAAiB,CACf,YACE,mFACF,KAAM,SACR,EACA,QAAS,CACP,YAAa,iEACb,KAAM,yBACR,EACA,YAAa,CACX,YAAa,sEACb,KAAM,0BACR,CACF,EACA,qBAAsB,EACxB,CACF,CACF,EAEA,OAAO,OAAOA,EAAkB","x_google_ignoreList":[11,12,13,17]} \ No newline at end of file diff --git a/lib/platform-bible-utils/dist/index.d.ts b/lib/platform-bible-utils/dist/index.d.ts index d9f4e00f6e..bc510b1343 100644 --- a/lib/platform-bible-utils/dist/index.d.ts +++ b/lib/platform-bible-utils/dist/index.d.ts @@ -1231,6 +1231,25 @@ export declare function substring(string: string, begin: number, end?: number): export declare function toArray(string: string): string[]; /** Determine whether the string is a `LocalizeKey` meant to be localized in Platform.Bible. */ export declare function isLocalizeKey(str: string): str is LocalizeKey; +/** + * Escape RegExp special characters. + * + * You can also use this to escape a string that is inserted into the middle of a regex, for + * example, into a character class. + * + * All credit to [`escape-string-regexp`](https://www.npmjs.com/package/escape-string-regexp) - this + * function is simply copied directly from there to allow a common js export + * + * @example + * + * import escapeStringRegexp from 'platform-bible-utils'; + * + * const escapedString = escapeStringRegexp('How much $ for a 🦄?'); + * //=> 'How much \\$ for a 🦄\\?' + * + * new RegExp(escapedString); + */ +export declare function escapeStringRegexp(string: string): string; /** * Check that two objects are deeply equal, comparing members of each object and such * diff --git a/lib/platform-bible-utils/dist/index.js b/lib/platform-bible-utils/dist/index.js index 3751375be7..24ad5d3742 100644 --- a/lib/platform-bible-utils/dist/index.js +++ b/lib/platform-bible-utils/dist/index.js @@ -1602,6 +1602,11 @@ function dt(t) { function Or(t) { return Ne(t, "%") && ct(t, "%"); } +function $r(t) { + if (typeof t != "string") + throw new TypeError("Expected a string"); + return t.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d"); +} const we = [ { shortName: "ERR", fullNames: ["ERROR"], chapters: -1 }, { shortName: "GEN", fullNames: ["Genesis"], chapters: 50 }, @@ -1673,22 +1678,22 @@ const we = [ ], mt = 1, gt = we.length - 1, bt = 1, yt = 1, vt = (t) => { var e; return ((e = we[t]) == null ? void 0 : e.chapters) ?? -1; -}, $r = (t, e) => ({ +}, Sr = (t, e) => ({ bookNum: Math.max(mt, Math.min(t.bookNum + e, gt)), chapterNum: 1, verseNum: 1 -}), Sr = (t, e) => ({ +}), jr = (t, e) => ({ ...t, chapterNum: Math.min( Math.max(bt, t.chapterNum + e), vt(t.bookNum) ), verseNum: 1 -}), jr = (t, e) => ({ +}), Ir = (t, e) => ({ ...t, verseNum: Math.max(yt, t.verseNum + e) }); -async function Ir(t, e, r) { +async function Cr(t, e, r) { const s = E.bookNumberToId(t); if (!Ne(Intl.getCanonicalLocales(e)[0], "zh")) return r({ @@ -1701,7 +1706,7 @@ async function Ir(t, e, r) { }), i = te(n, "-"); return te(i[0], "ÿ08")[0].trim(); } -const Cr = (t) => (...e) => t.map((s) => s(...e)).every((s) => s), Pr = (t) => async (...e) => { +const Pr = (t) => (...e) => t.map((s) => s(...e)).every((s) => s), Tr = (t) => async (...e) => { const r = t.map(async (s) => s(...e)); return (await Promise.all(r)).every((s) => s); }; @@ -1962,7 +1967,7 @@ function Ht(t, e) { if (s !== null) return typeof s == "object" ? r(s) : s; } -function Tr(t) { +function Ar(t) { try { const e = ce(t); return e === ce(Ht(e)); @@ -1970,8 +1975,8 @@ function Tr(t) { return !1; } } -const Ar = (t) => t.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\//g, "/"); -function Dr() { +const Dr = (t) => t.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/\//g, "/"); +function Mr() { return typeof navigator < "u" && navigator.languages ? navigator.languages[0] : new Me().resolvedOptions().locale; } const X = { @@ -2672,8 +2677,8 @@ export { hr as NumberFormat, Re as PlatformEventEmitter, pr as UnsubscriberAsyncList, - Pr as aggregateUnsubscriberAsyncs, - Cr as aggregateUnsubscribers, + Tr as aggregateUnsubscriberAsyncs, + Pr as aggregateUnsubscribers, mr as at, C as charAt, gr as codePointAt, @@ -2683,18 +2688,19 @@ export { Xt as deepEqual, Ht as deserialize, ct as endsWith, + $r as escapeStringRegexp, br as formatReplacementString, ur as getAllObjectFunctionNames, vt as getChaptersForBook, - Dr as getCurrentLocale, + Mr as getCurrentLocale, or as getErrorMessage, - Ir as getLocalizedIdFromBookNumber, + Cr as getLocalizedIdFromBookNumber, ir as groupBy, - Ar as htmlEncode, + Dr as htmlEncode, ht as includes, B as indexOf, Or as isLocalizeKey, - Tr as isSerializable, + Ar as isSerializable, Be as isString, Kt as isSubset, pt as lastIndexOf, @@ -2702,9 +2708,9 @@ export { Zt as menuDocumentSchema, sr as newGuid, yr as normalize, - $r as offsetBook, - Sr as offsetChapter, - jr as offsetVerse, + Sr as offsetBook, + jr as offsetChapter, + Ir as offsetVerse, vr as ordinalCompare, Nr as padEnd, wr as padStart, diff --git a/lib/platform-bible-utils/dist/index.js.map b/lib/platform-bible-utils/dist/index.js.map index 5f17367a2c..da0997147b 100644 --- a/lib/platform-bible-utils/dist/index.js.map +++ b/lib/platform-bible-utils/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sources":["../src/async-variable.ts","../src/intl-collator.ts","../src/intl-date-time-format.ts","../src/platform-event-emitter.model.ts","../src/util.ts","../src/document-combiner.ts","../src/mutex.ts","../src/mutex-map.ts","../src/non-validating-document-combiner.ts","../src/intl-number-format.ts","../src/unsubscriber-async-list.ts","../../../node_modules/@sillsdev/scripture/dist/index.es.js","../../../node_modules/char-regex/index.js","../../../node_modules/stringz/dist/index.js","../src/string-util.ts","../src/scripture-util.ts","../src/unsubscriber.ts","../../../node_modules/fast-equals/dist/esm/index.mjs","../src/equality-checking.ts","../src/subset-checking.ts","../src/serialization.ts","../src/intl-util.ts","../src/settings.model.ts","../src/localized-strings.model.ts","../src/menus.model.ts"],"sourcesContent":["/** This class provides a convenient way for one task to wait on a variable that another task sets. */\nexport default class AsyncVariable {\n private readonly variableName: string;\n private readonly promiseToValue: Promise;\n private resolver: ((value: T) => void) | undefined;\n private rejecter: ((reason: string | undefined) => void) | undefined;\n\n /**\n * Creates an instance of the class\n *\n * @param variableName Name to use when logging about this variable\n * @param rejectIfNotSettledWithinMS Milliseconds to wait before verifying if the promise was\n * settled (resolved or rejected); will reject if it has not settled by that time. Use -1 if you\n * do not want a timeout at all.\n */\n constructor(variableName: string, rejectIfNotSettledWithinMS: number = 10000) {\n this.variableName = variableName;\n this.promiseToValue = new Promise((resolve, reject) => {\n this.resolver = resolve;\n this.rejecter = reject;\n });\n if (rejectIfNotSettledWithinMS > 0) {\n setTimeout(() => {\n if (this.rejecter) {\n this.rejecter(`Timeout reached when waiting for ${this.variableName} to settle`);\n this.complete();\n }\n }, rejectIfNotSettledWithinMS);\n }\n Object.seal(this);\n }\n\n /**\n * Get this variable's promise to a value. This always returns the same promise even after the\n * value has been resolved or rejected.\n *\n * @returns The promise for the value to be set\n */\n get promise(): Promise {\n return this.promiseToValue;\n }\n\n /**\n * A simple way to see if this variable's promise was resolved or rejected already\n *\n * @returns Whether the variable was already resolved or rejected\n */\n get hasSettled(): boolean {\n return Object.isFrozen(this);\n }\n\n /**\n * Resolve this variable's promise to the given value\n *\n * @param value This variable's promise will resolve to this value\n * @param throwIfAlreadySettled Determines whether to throw if the variable was already resolved\n * or rejected\n */\n resolveToValue(value: T, throwIfAlreadySettled: boolean = false): void {\n if (this.resolver) {\n console.debug(`${this.variableName} is being resolved now`);\n this.resolver(value);\n this.complete();\n } else {\n if (throwIfAlreadySettled) throw Error(`${this.variableName} was already settled`);\n console.debug(`Ignoring subsequent resolution of ${this.variableName}`);\n }\n }\n\n /**\n * Reject this variable's promise for the value with the given reason\n *\n * @param reason This variable's promise will be rejected with this reason\n * @param throwIfAlreadySettled Determines whether to throw if the variable was already resolved\n * or rejected\n */\n rejectWithReason(reason: string, throwIfAlreadySettled: boolean = false): void {\n if (this.rejecter) {\n console.debug(`${this.variableName} is being rejected now`);\n this.rejecter(reason);\n this.complete();\n } else {\n if (throwIfAlreadySettled) throw Error(`${this.variableName} was already settled`);\n console.debug(`Ignoring subsequent rejection of ${this.variableName}`);\n }\n }\n\n /** Prevent any further updates to this variable */\n private complete(): void {\n this.resolver = undefined;\n this.rejecter = undefined;\n Object.freeze(this);\n }\n}\n","/** Enables language-sensitive string comparison. Wraps Intl.Collator */\nexport default class Collator {\n private collator: Intl.Collator;\n\n constructor(locales?: string | string[], options?: Intl.CollatorOptions) {\n this.collator = new Intl.Collator(locales, options);\n }\n\n /**\n * Compares two strings according to the sort order of this Collator object\n *\n * @param string1 String to compare\n * @param string2 String to compare\n * @returns A number indicating how string1 and string2 compare to each other according to the\n * sort order of this Collator object. Negative value if string1 comes before string2. Positive\n * value if string1 comes after string2. 0 if they are considered equal.\n */\n compare(string1: string, string2: string): number {\n return this.collator.compare(string1, string2);\n }\n\n /**\n * Returns a new object with properties reflecting the locale and collation options computed\n * during initialization of this collator object.\n *\n * @returns ResolvedCollatorOptions object\n */\n resolvedOptions(): Intl.ResolvedCollatorOptions {\n return this.collator.resolvedOptions();\n }\n}\n","/** Enables language-sensitive data and time formatting. Wraps Intl.DateTimeFormat */\nexport default class DateTimeFormat {\n private dateTimeFormatter: Intl.DateTimeFormat;\n\n constructor(locales?: string | string[], options?: Intl.DateTimeFormatOptions) {\n this.dateTimeFormatter = new Intl.DateTimeFormat(locales, options);\n }\n\n /**\n * Formats a date according to the locale and formatting option for this DateTimeFormat object\n *\n * @param date The date to format\n * @returns String representing the given date formatted according to the locale and formatting\n * options of this DateTimeFormat object\n */\n format(date: Date): string {\n return this.dateTimeFormatter.format(date);\n }\n\n /**\n * Formats a date range in the most concise way based on the locales and options provided when\n * instantiating this DateTimeFormat object\n *\n * @param startDate Date object representing start of the date range\n * @param endDate Date object representing the end of the date range\n * @returns String representing the given date range formatted according to the locale and\n * formatting options of this DateTimeFormat object\n */\n formatRange(startDate: Date, endDate: Date): string {\n return this.dateTimeFormatter.formatRange(startDate, endDate);\n }\n\n /**\n * Returns an array of locale-specific tokens representing each part of the formatted date range\n * produced by this DateTimeFormat object\n *\n * @param startDate Date object representing start of the date range\n * @param endDate Date object representing the end of the date range\n * @returns Array of DateTimeRangeFormatPart objects\n */\n formatRangeToParts(startDate: Date, endDate: Date): Intl.DateTimeRangeFormatPart[] {\n return this.dateTimeFormatter.formatRangeToParts(startDate, endDate);\n }\n\n /**\n * Allows locale-aware formatting of strings produced by this DateTimeFormat object\n *\n * @param date The date to format\n * @returns Array of DateTimeFormatPart objects\n */\n formatToParts(date: Date): Intl.DateTimeFormatPart[] {\n return this.dateTimeFormatter.formatToParts(date);\n }\n\n /**\n * Returns a new object with properties reflecting the locale and date and time formatting options\n * computed during initialization of this DateTimeFormat object\n *\n * @returns ResolvedDateTimeFormatOptions object\n */\n resolvedOptions(): Intl.ResolvedDateTimeFormatOptions {\n return this.dateTimeFormatter.resolvedOptions();\n }\n}\n","/** Interfaces, classes, and functions related to events and event emitters */\n\nimport { Dispose } from './disposal.model';\nimport { PlatformEvent, PlatformEventHandler } from './platform-event';\n\n/**\n * Event manager - accepts subscriptions to an event and runs the subscription callbacks when the\n * event is emitted Use eventEmitter.event(callback) to subscribe to the event. Use\n * eventEmitter.emit(event) to run the subscriptions. Generally, this EventEmitter should be\n * private, and its event should be public. That way, the emitter is not publicized, but anyone can\n * subscribe to the event.\n */\nexport default class PlatformEventEmitter implements Dispose {\n /**\n * Subscribes a function to run when this event is emitted.\n *\n * @param callback Function to run with the event when it is emitted\n * @returns Unsubscriber function to run to stop calling the passed-in function when the event is\n * emitted\n * @alias event\n */\n subscribe = this.event;\n\n /** All callback functions that will run when this event is emitted. Lazy loaded */\n private subscriptions?: PlatformEventHandler[];\n /** Event for listeners to subscribe to. Lazy loaded */\n private lazyEvent?: PlatformEvent;\n /** Whether this emitter has been disposed */\n private isDisposed = false;\n\n /**\n * Event for listeners to subscribe to. Subscribes a function to run when this event is emitted.\n * Use like `const unsubscriber = event(callback)`\n *\n * @param callback Function to run with the event when it is emitted\n * @returns Unsubscriber function to run to stop calling the passed-in function when the event is\n * emitted\n */\n get event(): PlatformEvent {\n this.assertNotDisposed();\n\n if (!this.lazyEvent) {\n this.lazyEvent = (callback) => {\n if (!callback || typeof callback !== 'function')\n throw new Error(`Event handler callback must be a function!`);\n\n // Initialize this.subscriptions if it does not exist\n if (!this.subscriptions) this.subscriptions = [];\n\n this.subscriptions.push(callback);\n\n return () => {\n if (!this.subscriptions) return false; // Did not find any subscribed callbacks\n\n const callbackIndex = this.subscriptions.indexOf(callback);\n\n if (callbackIndex < 0) return false; // Did not find this callback in the subscriptions\n\n // Remove the callback\n this.subscriptions.splice(callbackIndex, 1);\n\n return true;\n };\n };\n }\n return this.lazyEvent;\n }\n\n /** Disposes of this event, preparing it to release from memory */\n dispose = () => {\n return this.disposeFn();\n };\n\n /**\n * Runs the subscriptions for the event\n *\n * @param event Event data to provide to subscribed callbacks\n */\n emit = (event: T) => {\n // Do not do anything other than emitFn here. This emit is just binding `this` to emitFn\n this.emitFn(event);\n };\n\n /**\n * Function that runs the subscriptions for the event. Added here so children can override emit\n * and still call the base functionality. See NetworkEventEmitter.emit for example\n */\n protected emitFn(event: T) {\n this.assertNotDisposed();\n\n this.subscriptions?.forEach((callback) => callback(event));\n }\n\n /** Check to make sure this emitter is not disposed. Throw if it is */\n protected assertNotDisposed() {\n if (this.isDisposed) throw new Error('Emitter is disposed');\n }\n\n /**\n * Disposes of this event, preparing it to release from memory. Added here so children can\n * override emit and still call the base functionality.\n */\n protected disposeFn() {\n this.assertNotDisposed();\n\n this.isDisposed = true;\n this.subscriptions = undefined;\n this.lazyEvent = undefined;\n return Promise.resolve(true);\n }\n}\n","/** Collection of functions, objects, and types that are used as helpers in other services. */\n\n// Thanks to blubberdiblub at https://stackoverflow.com/a/68141099/217579\nexport function newGuid(): string {\n return '00-0-4-1-000'.replace(/[^-]/g, (s) =>\n // @ts-expect-error ts(2363) this works fine\n // eslint-disable-next-line no-bitwise\n (((Math.random() + ~~s) * 0x10000) >> s).toString(16).padStart(4, '0'),\n );\n}\n\n// thanks to DRAX at https://stackoverflow.com/a/9436948\n/**\n * Determine whether the object is a string\n *\n * @param o Object to determine if it is a string\n * @returns True if the object is a string; false otherwise\n */\nexport function isString(o: unknown): o is string {\n return typeof o === 'string' || o instanceof String;\n}\n\n/**\n * If deepClone isn't used when copying properties between objects, you may be left with dangling\n * references between the source and target of property copying operations.\n *\n * @param obj Object to clone\n * @returns Duplicate copy of `obj` without any references back to the original one\n */\nexport function deepClone(obj: T): T {\n // Assert the return type matches what is expected\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n return JSON.parse(JSON.stringify(obj)) as T;\n}\n\n/**\n * Get a function that reduces calls to the function passed in\n *\n * @param fn The function to debounce\n * @param delay How much delay in milliseconds after the most recent call to the debounced function\n * to call the function\n * @returns Function that, when called, only calls the function passed in at maximum every delay ms\n */\n// We don't know the parameter types since this function can be anything\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function debounce void>(fn: T, delay = 300): T {\n if (isString(fn)) throw new Error('Tried to debounce a string! Could be XSS');\n let timeout: ReturnType;\n // Ensure the right return type.\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n return ((...args) => {\n clearTimeout(timeout);\n timeout = setTimeout(() => fn(...args), delay);\n }) as T;\n}\n\n/**\n * Groups each item in the array of items into a map according to the keySelector\n *\n * @param items Array of items to group by\n * @param keySelector Function to run on each item to get the key for the group to which it belongs\n * @param valueSelector Function to run on each item to get the value it should have in the group\n * (like map function). If not provided, uses the item itself\n * @returns Map of keys to groups of values corresponding to each item\n */\nexport function groupBy(items: T[], keySelector: (item: T) => K): Map>;\nexport function groupBy(\n items: T[],\n keySelector: (item: T) => K,\n valueSelector: (item: T, key: K) => V,\n): Map>;\nexport function groupBy(\n items: T[],\n keySelector: (item: T) => K,\n valueSelector?: (item: T, key: K) => V,\n): Map> {\n const map = new Map>();\n items.forEach((item) => {\n const key = keySelector(item);\n const group = map.get(key);\n const value = valueSelector ? valueSelector(item, key) : item;\n if (group) group.push(value);\n else map.set(key, [value]);\n });\n return map;\n}\n\n// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript\ntype ErrorWithMessage = {\n message: string;\n};\n\n// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript\nfunction isErrorWithMessage(error: unknown): error is ErrorWithMessage {\n return (\n typeof error === 'object' &&\n // We're potentially dealing with objects we didn't create, so they might contain `null`\n // eslint-disable-next-line no-null/no-null\n error !== null &&\n 'message' in error &&\n // Type assert `error` to check it's `message`.\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n typeof (error as Record).message === 'string'\n );\n}\n\n// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript\n/**\n * Function to get an error from the object (useful for getting an error in a catch block)\n *\n * @param error Error object whose message to get\n * @returns Message of the error - if object has message, returns message. Otherwise tries to\n * stringify\n */\nfunction toErrorWithMessage(maybeError: unknown): ErrorWithMessage {\n if (isErrorWithMessage(maybeError)) return maybeError;\n\n try {\n return new Error(JSON.stringify(maybeError));\n } catch {\n // fallback in case there's an error stringifying the maybeError\n // like with circular references for example.\n return new Error(String(maybeError));\n }\n}\n\n// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript\n/**\n * Function to get an error message from the object (useful for getting error message in a catch\n * block)\n *\n * @example `try {...} catch (e) { logger.info(getErrorMessage(e)) }`\n *\n * @param error Error object whose message to get\n * @returns Message of the error - if object has message, returns message. Otherwise tries to\n * stringify\n */\nexport function getErrorMessage(error: unknown) {\n return toErrorWithMessage(error).message;\n}\n\n/** Asynchronously waits for the specified number of milliseconds. (wraps setTimeout in a promise) */\nexport function wait(ms: number) {\n // eslint-disable-next-line no-promise-executor-return\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Runs the specified function and will timeout if it takes longer than the specified wait time\n *\n * @param fn The function to run\n * @param maxWaitTimeInMS The maximum amount of time to wait for the function to resolve\n * @returns Promise that resolves to the resolved value of the function or undefined if it ran\n * longer than the specified wait time\n */\nexport function waitForDuration(fn: () => Promise, maxWaitTimeInMS: number) {\n const timeout = wait(maxWaitTimeInMS).then(() => undefined);\n return Promise.any([timeout, fn()]);\n}\n\n/**\n * Get all functions on an object and its prototype chain (so we don't miss any class methods or any\n * object methods). Note that the functions on the final item in the prototype chain (i.e., Object)\n * are skipped to avoid including functions like `__defineGetter__`, `__defineSetter__`, `toString`,\n * etc.\n *\n * @param obj Object whose functions to get\n * @param objId Optional ID of the object to use for debug logging\n * @returns Array of all function names on an object\n */\n// Note: lodash has something that MIGHT do the same thing as this. Investigate for https://github.com/paranext/paranext-core/issues/134\nexport function getAllObjectFunctionNames(\n obj: { [property: string]: unknown },\n objId: string = 'obj',\n): Set {\n const objectFunctionNames = new Set();\n\n // Get all function properties directly defined on the object\n Object.getOwnPropertyNames(obj).forEach((property) => {\n try {\n if (typeof obj[property] === 'function') objectFunctionNames.add(property);\n } catch (error) {\n console.debug(`Skipping ${property} on ${objId} due to error: ${error}`);\n }\n });\n\n // Walk up the prototype chain and get additional function properties, skipping the functions\n // provided by the final (Object) prototype\n let objectPrototype = Object.getPrototypeOf(obj);\n while (objectPrototype && Object.getPrototypeOf(objectPrototype)) {\n Object.getOwnPropertyNames(objectPrototype).forEach((property) => {\n try {\n if (typeof obj[property] === 'function') objectFunctionNames.add(property);\n } catch (error) {\n console.debug(`Skipping ${property} on ${objId}'s prototype due to error: ${error}`);\n }\n });\n objectPrototype = Object.getPrototypeOf(objectPrototype);\n }\n\n return objectFunctionNames;\n}\n\n/**\n * Creates a synchronous proxy for an asynchronous object. The proxy allows calling methods on an\n * object that is asynchronously fetched using a provided asynchronous function.\n *\n * @param getObject - A function that returns a promise resolving to the object whose asynchronous\n * methods to call.\n * @param objectToProxy - An optional object that is the object that is proxied. If a property is\n * accessed that does exist on this object, it will be returned. If a property is accessed that\n * does not exist on this object, it will be considered to be an asynchronous method called on the\n * object returned from getObject.\n * @returns A synchronous proxy for the asynchronous object.\n */\nexport function createSyncProxyForAsyncObject(\n getObject: (args?: unknown[]) => Promise,\n objectToProxy: Partial = {},\n): T {\n // objectToProxy will have only the synchronously accessed properties of T on it, and this proxy\n // makes the async methods that do not exist yet available synchronously so we have all of T\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n return new Proxy(objectToProxy as T, {\n get(target, prop) {\n // We don't have any type information for T, so we assume methodName exists on it and will let JavaScript throw if it doesn't exist\n // @ts-expect-error 7053\n if (prop in target) return target[prop];\n return async (...args: unknown[]) => {\n // 7053: We don't have any type information for T, so we assume methodName exists on it and will let JavaScript throw if it doesn't exist\n // 2556: The args here are the parameters for the method specified\n // @ts-expect-error 7053 2556\n return (await getObject())[prop](...args);\n };\n },\n });\n}\n\n/** Within type T, recursively change all properties to be optional */\nexport type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial } : T;\n\n/** Within type T, recursively change properties that were of type A to be of type B */\nexport type ReplaceType = T extends A\n ? B\n : T extends object\n ? { [K in keyof T]: ReplaceType }\n : T;\n\n// Thanks to jcalz at https://stackoverflow.com/a/50375286\n/**\n * Converts a union type to an intersection type (`|` to `&`).\n *\n * Note: this utility type is for use on object types. It may fail on other types.\n *\n * @example\n *\n * ```typescript\n * type TypeOne = { one: string };\n * type TypeTwo = { two: number };\n * type TypeThree = { three: string };\n *\n * type TypeNums = { one: TypeOne; two: TypeTwo; three: TypeThree };\n * const numNames = ['one', 'two'] as const;\n * type TypeNumNames = typeof numNames;\n *\n * // Same as `TypeOne | TypeTwo`\n * // `{ one: string } | { two: number }`\n * type TypeOneTwoUnion = TypeNums[TypeNumNames[number]];\n *\n * // Same as `TypeOne & TypeTwo`\n * // `{ one: string; two: number }`\n * type TypeOneTwoIntersection = UnionToIntersection;\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type UnionToIntersection = (U extends any ? (x: U) => void : never) extends (\n x: infer I,\n) => void\n ? I\n : never;\n","import PlatformEventEmitter from './platform-event-emitter.model';\nimport { deepClone } from './util';\n\ntype JsonObjectLike = { [key: string]: unknown };\ntype JsonArrayLike = unknown[];\n\nexport type JsonDocumentLike = JsonObjectLike | JsonArrayLike;\n\n/**\n * Options for DocumentCombiner objects\n *\n * - `copyDocuments`: If true, this instance will perform a deep copy of all provided documents before\n * composing the output. If false, then changes made to provided documents after they are\n * contributed will be reflected in the next time output is composed.\n * - `ignoreDuplicateProperties`: If true, then duplicate properties are skipped if they are seen in\n * contributed documents. If false, then throw when duplicate properties are seen in contributed\n * documents.\n */\nexport type DocumentCombinerOptions = {\n copyDocuments: boolean;\n ignoreDuplicateProperties: boolean;\n};\n\n/**\n * Base class for any code that wants to compose JSON documents (primarily in the form of JS objects\n * or arrays) together into a single output document.\n */\nexport default class DocumentCombiner {\n protected baseDocument: JsonDocumentLike;\n protected readonly contributions = new Map();\n protected latestOutput: JsonDocumentLike | undefined;\n protected readonly options: DocumentCombinerOptions;\n private readonly onDidRebuildEmitter = new PlatformEventEmitter();\n /** Event that emits to announce that the document has been rebuilt and the output has been updated */\n // Need `onDidRebuildEmitter` to be instantiated before this line\n // eslint-disable-next-line @typescript-eslint/member-ordering\n readonly onDidRebuild = this.onDidRebuildEmitter.subscribe;\n\n /**\n * Create a DocumentCombiner instance\n *\n * @param baseDocument This is the first document that will be used when composing the output\n * @param options Options used by this object when combining documents\n */\n protected constructor(baseDocument: JsonDocumentLike, options: DocumentCombinerOptions) {\n // Setting baseDocument redundantly because TS doesn't understand that updateBaseDocument does it\n this.baseDocument = baseDocument;\n this.options = options;\n this.updateBaseDocument(baseDocument);\n }\n\n /**\n * Update the starting document for composition process\n *\n * @param baseDocument Base JSON document/JS object that all other documents are added to\n * @returns Recalculated output document given the new starting state and existing other documents\n */\n updateBaseDocument(baseDocument: JsonDocumentLike): JsonDocumentLike | undefined {\n this.validateBaseDocument(baseDocument);\n this.baseDocument = this.options.copyDocuments ? deepClone(baseDocument) : baseDocument;\n this.baseDocument = this.transformBaseDocumentAfterValidation(this.baseDocument);\n return this.rebuild();\n }\n\n /**\n * Add or update one of the contribution documents for the composition process\n *\n * Note: the order in which contribution documents are added can be considered to be indeterminate\n * as it is currently ordered by however `Map.forEach` provides the contributions. The order\n * matters when merging two arrays into one. Also, when `options.ignoreDuplicateProperties` is\n * `true`, the order also matters when adding the same property to an object that is already\n * provided previously. Please let us know if you have trouble because of indeterminate\n * contribution ordering.\n *\n * @param documentName Name of the contributed document to combine\n * @param document Content of the contributed document to combine\n * @returns Recalculated output document given the new or updated contribution and existing other\n * documents\n */\n addOrUpdateContribution(\n documentName: string,\n document: JsonDocumentLike,\n ): JsonDocumentLike | undefined {\n this.validateContribution(documentName, document);\n const previousDocumentVersion = this.contributions.get(documentName);\n let documentToSet = this.options.copyDocuments && !!document ? deepClone(document) : document;\n documentToSet = this.transformContributionAfterValidation(documentName, documentToSet);\n this.contributions.set(documentName, documentToSet);\n try {\n return this.rebuild();\n } catch (error) {\n // If the output isn't valid after adding/updating the contribution, put it back how it was\n if (previousDocumentVersion) this.contributions.set(documentName, previousDocumentVersion);\n else this.contributions.delete(documentName);\n throw new Error(`Error when setting the document named ${documentName}: ${error}`);\n }\n }\n\n /**\n * Delete one of the contribution documents for the composition process\n *\n * @param documentName Name of the contributed document to delete\n * @returns Recalculated output document given the remaining other documents\n */\n deleteContribution(documentName: string): JsonDocumentLike | undefined {\n const document = this.contributions.get(documentName);\n if (!document) throw new Error(`${documentName} does not exist`);\n this.contributions.delete(documentName);\n try {\n return this.rebuild();\n } catch (error) {\n // If the output isn't valid after deleting the contribution, put it back and rethrow\n this.contributions.set(documentName, document);\n throw new Error(`Error when deleting the document named ${documentName}: ${error}`);\n }\n }\n\n /**\n * Delete all present contribution documents for the composition process and return to the base\n * document\n *\n * @returns Recalculated output document consisting only of the base document\n */\n deleteAllContributions(): JsonDocumentLike | undefined {\n if (this.contributions.size <= 0) return this.latestOutput;\n\n // Save out all contributions\n const contributions = [...this.contributions.entries()];\n\n // Delete all contributions\n contributions.forEach(([contributionName]) => this.contributions.delete(contributionName));\n\n // Rebuild with no contributions\n try {\n return this.rebuild();\n } catch (error) {\n // If the output isn't valid after deleting all contributions, put them back and rethrow\n contributions.forEach(([contributionName, document]) =>\n this.contributions.set(contributionName, document),\n );\n throw new Error(`Error when deleting all contributions: ${error}`);\n }\n }\n\n /**\n * Run the document composition process given the starting document and all contributions. Throws\n * if the output document fails to validate properly.\n *\n * @returns Recalculated output document given the starting and contributed documents\n */\n rebuild(): JsonDocumentLike | undefined {\n // The starting document is the output if there are no other contributions\n if (this.contributions.size === 0) {\n let potentialOutput = deepClone(this.baseDocument);\n potentialOutput = this.transformFinalOutputBeforeValidation(potentialOutput);\n this.validateOutput(potentialOutput);\n this.latestOutput = potentialOutput;\n this.onDidRebuildEmitter.emit(undefined);\n return this.latestOutput;\n }\n\n // Compose the output by validating each document one at a time to pinpoint errors better\n let outputIteration = this.baseDocument;\n this.contributions.forEach((contribution: JsonDocumentLike) => {\n outputIteration = mergeObjects(\n outputIteration,\n contribution,\n this.options.ignoreDuplicateProperties,\n );\n this.validateOutput(outputIteration);\n });\n outputIteration = this.transformFinalOutputBeforeValidation(outputIteration);\n this.validateOutput(outputIteration);\n this.latestOutput = outputIteration;\n this.onDidRebuildEmitter.emit(undefined);\n return this.latestOutput;\n }\n\n /**\n * Transform the starting document that is given to the combiner. This transformation occurs after\n * validating the base document and before combining any contributions.\n *\n * WARNING: If you do not create the combiner with option `copyDocuments: true` or clone inside\n * this method, this method will directly modify the `baseDocument` passed in.\n *\n * @param baseDocument Initial input document. Already validated via `validateBaseDocument`\n * @returns Transformed base document\n */\n // We just don't need `this` here. This is basically a no-op function that is available to child\n // classes to override\n // eslint-disable-next-line class-methods-use-this\n protected transformBaseDocumentAfterValidation(baseDocument: JsonDocumentLike): JsonDocumentLike {\n return baseDocument;\n }\n\n /**\n * Transform the contributed document associated with `documentName`. This transformation occurs\n * after validating the contributed document and before combining with other documents.\n *\n * WARNING: If you do not create the combiner with option `copyDocuments: true` or clone inside\n * this method, this method will directly modify the contributed `document` passed in.\n *\n * @param documentName Name of the contributed document to combine\n * @param document Content of the contributed document to combine. Already validated via\n * `validateContribution`\n * @returns Transformed contributed document\n */\n // We just don't need `this` here. This is basically a no-op function that is available to child\n // classes to override\n // eslint-disable-next-line class-methods-use-this\n protected transformContributionAfterValidation(\n // @ts-expect-error this parameter is unused but may be used in child classes\n documentName: string,\n document: JsonDocumentLike,\n ): JsonDocumentLike {\n return document;\n }\n\n /**\n * Throw an error if the provided document is not a valid starting document.\n *\n * @param baseDocument Base JSON document/JS object that all other documents are added to\n */\n // no-op intended to be overridden by child classes. Can't be static\n // @ts-expect-error ts(6133) parameter doesn't need to be used but still needs the right name\n // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars\n protected validateBaseDocument(baseDocument: JsonDocumentLike): void {}\n\n /**\n * Throw an error if the provided document is not a valid contribution document.\n *\n * @param documentName Name of the contributed document to combine\n * @param document Content of the contributed document to combine\n */\n // no-op intended to be overridden by child classes. Can't be static\n // @ts-expect-error ts(6133) parameter doesn't need to be used but still needs the right name\n // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars\n protected validateContribution(documentName: string, document: JsonDocumentLike): void {}\n\n /**\n * Throw an error if the provided output is not valid.\n *\n * @param output Output document that could potentially be returned to callers\n */\n // no-op intended to be overridden by child classes. Can't be static\n // @ts-expect-error ts(6133) parameter doesn't need to be used but still needs the right name\n // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars\n protected validateOutput(output: JsonDocumentLike): void {}\n\n /**\n * Transform the document that is the composition of the base document and all contribution\n * documents. This is the last step that will be run prior to validation via `validateOutput`\n * before `this.latestOutput` is updated to the new output.\n *\n * @param finalOutput Final output document that could potentially be returned to callers. \"Final\"\n * means no further contribution documents will be merged.\n */\n // no-op intended to be overridden by child classes. Can't be static\n // eslint-disable-next-line class-methods-use-this\n protected transformFinalOutputBeforeValidation(finalOutput: JsonDocumentLike): JsonDocumentLike {\n return finalOutput;\n }\n}\n\n// #region Helper functions\n\n/**\n * Determines if the input values are objects but not arrays\n *\n * @param values Objects to check\n * @returns True if all the values are objects but not arrays\n */\nfunction areNonArrayObjects(...values: unknown[]): boolean {\n let allMatch = true;\n values.forEach((value: unknown) => {\n if (!value || typeof value !== 'object' || Array.isArray(value)) allMatch = false;\n });\n return allMatch;\n}\n\n/**\n * Determines if the input values are arrays\n *\n * @param value Objects to check\n * @returns True if the values are arrays\n */\nfunction areArrayObjects(...values: unknown[]): boolean {\n let allMatch = true;\n values.forEach((value: unknown) => {\n if (!value || typeof value !== 'object' || !Array.isArray(value)) allMatch = false;\n });\n return allMatch;\n}\n\n/**\n * Deep clone and recursively merge the properties of one object (copyFrom) into another\n * (startingPoint). Throws if copyFrom would overwrite values already existing in startingPoint.\n *\n * Does not modify the objects passed in.\n *\n * @param startingPoint Object that is the starting point for the return value\n * @param copyFrom Object whose values are copied into the return value\n * @param ignoreDuplicateProperties Whether to ignore object properties that are present in\n * `copyFrom` that are already present in `startingPoint`. If `false`, throws when an object\n * property in `copyFrom` is already present in `startingPoint`\n * @returns Object that is the combination of the two documents\n */\nfunction mergeObjects(\n startingPoint: JsonDocumentLike,\n copyFrom: JsonDocumentLike,\n ignoreDuplicateProperties: boolean,\n): JsonDocumentLike {\n const retVal = deepClone(startingPoint);\n\n if (!copyFrom) return retVal;\n\n return mergeObjectsInternal(retVal, deepClone(copyFrom), ignoreDuplicateProperties);\n}\n\n/**\n * Recursively merge the properties of one object (copyFrom) into another (startingPoint). Throws if\n * copyFrom would overwrite values already existing in startingPoint.\n *\n * WARNING: Modifies the argument objects in some way. Recommended to use `mergeObjects`\n *\n * @param startingPoint Object that is the starting point for the return value\n * @param copyFrom Object whose values are copied into the return value\n * @param ignoreDuplicateProperties Whether to ignore object properties that are present in\n * `copyFrom` that are already present in `startingPoint`. If `false`, throws when an object\n * property in `copyFrom` is already present in `startingPoint`\n * @returns Object that is the combination of the two documents\n */\nfunction mergeObjectsInternal(\n startingPoint: JsonDocumentLike,\n copyFrom: JsonDocumentLike,\n ignoreDuplicateProperties: boolean,\n): JsonDocumentLike {\n if (!copyFrom) return startingPoint;\n\n if (areNonArrayObjects(startingPoint, copyFrom)) {\n // Merge properties since they are both objects\n\n // We know these are objects from the `if` check\n /* eslint-disable no-type-assertion/no-type-assertion */\n const startingPointObj = startingPoint as JsonObjectLike;\n const copyFromObj = copyFrom as JsonObjectLike;\n /* eslint-enable no-type-assertion/no-type-assertion */\n Object.keys(copyFromObj).forEach((key: string | number) => {\n if (Object.hasOwn(startingPointObj, key)) {\n if (areNonArrayObjects(startingPointObj[key], copyFromObj[key])) {\n startingPointObj[key] = mergeObjectsInternal(\n // We know these are objects from the `if` check\n /* eslint-disable no-type-assertion/no-type-assertion */\n startingPointObj[key] as JsonObjectLike,\n copyFromObj[key] as JsonObjectLike,\n ignoreDuplicateProperties,\n /* eslint-enable no-type-assertion/no-type-assertion */\n );\n } else if (areArrayObjects(startingPointObj[key], copyFromObj[key])) {\n // Concat the arrays since they are both arrays\n\n // We know these are arrays from the `else if` check\n /* eslint-disable no-type-assertion/no-type-assertion */\n startingPointObj[key] = (startingPointObj[key] as JsonArrayLike).concat(\n copyFromObj[key] as JsonArrayLike,\n );\n /* eslint-enable no-type-assertion/no-type-assertion */\n } else if (!ignoreDuplicateProperties)\n throw new Error(`Cannot merge objects: key \"${key}\" already exists in the target object`);\n // Note that the first non-object non-array value that gets placed in a property stays.\n // New values do not override existing ones\n } else {\n startingPointObj[key] = copyFromObj[key];\n }\n });\n } else if (areArrayObjects(startingPoint, copyFrom)) {\n // Concat the arrays since they are both arrays\n\n // Push the contents of copyFrom into startingPoint since it is a const and was already deep cloned\n // We know these are objects from the `else if` check\n /* eslint-disable no-type-assertion/no-type-assertion */\n (startingPoint as JsonArrayLike).push(...(copyFrom as JsonArrayLike));\n /* eslint-enable no-type-assertion/no-type-assertion */\n }\n\n // Note that nothing happens if `startingPoint` is not an object or an array or if `startingPoint`\n // and `copyFrom` are not both object or both arrays. Should we throw? Should we push `copyFrom`'s\n // values into the array? Other? Maybe one day we can add some options to decide what to do in\n // this situation, but YAGNI for now\n\n return startingPoint;\n}\n\n// #endregion\n","import { Mutex as AsyncMutex } from 'async-mutex';\n\n// Extending Mutex from async-mutex so we can add JSDoc\n\n/**\n * Class that allows calling asynchronous functions multiple times at once while only running one at\n * a time.\n *\n * @example\n *\n * ```typescript\n * const mutex = new Mutex();\n *\n * mutex.runExclusive(async () => {\n * // Do some asynchronous stuff\n * console.log('These run one-at-a-time');\n * });\n *\n * mutex.runExclusive(async () => {\n * // Do some asynchronous stuff\n * console.log('These run one-at-a-time');\n * });\n * ```\n *\n * See [`async-mutex`](https://www.npmjs.com/package/async-mutex) for more information.\n */\nclass Mutex extends AsyncMutex {}\n\nexport default Mutex;\n","import Mutex from './mutex';\n\n/** Map of {@link Mutex}es that automatically (lazily) generates a new {@link Mutex} for any new key */\nclass MutexMap {\n private mutexesByID = new Map();\n\n get(mutexID: string): Mutex {\n let retVal = this.mutexesByID.get(mutexID);\n if (retVal) return retVal;\n\n retVal = new Mutex();\n this.mutexesByID.set(mutexID, retVal);\n return retVal;\n }\n}\n\nexport default MutexMap;\n","import DocumentCombiner, { DocumentCombinerOptions, JsonDocumentLike } from './document-combiner';\n\nexport default class NonValidatingDocumentCombiner extends DocumentCombiner {\n // Making the protected base constructor public\n // eslint-disable-next-line @typescript-eslint/no-useless-constructor\n constructor(baseDocument: JsonDocumentLike, options: DocumentCombinerOptions) {\n super(baseDocument, options);\n }\n\n get output(): JsonDocumentLike | undefined {\n return this.latestOutput;\n }\n}\n","/** Enables language-sensitive number formatting. Wraps Intl.NumberFormat */\nexport default class NumberFormat {\n private numberFormatter: Intl.NumberFormat;\n\n constructor(locales?: string | string[], options?: Intl.NumberFormatOptions) {\n this.numberFormatter = new Intl.NumberFormat(locales, options);\n }\n\n /**\n * Formats a number according to the locale and formatting options of this NumberFormat object\n *\n * @param value Number or BigInt to format\n * @returns String representing the given number formatted according to the locale and formatting\n * options of this NumberFormat object\n */\n format(value: number | bigint): string {\n return this.numberFormatter.format(value);\n }\n\n /**\n * Formats a range of numbers according to the locale and formatting options of this NumberFormat\n * object\n *\n * @param startRange Number or bigint representing the start of the range\n * @param endRange Number or bigint representing the end of the range\n * @returns String representing the given range of numbers formatted according to the locale and\n * formatting options of this NumberFormat object\n */\n formatRange(startRange: number | bigint, endRange: number | bigint): string {\n return this.numberFormatter.formatRange(startRange, endRange);\n }\n\n /**\n * Returns an array of objects containing the locale-specific tokens from which it is possible to\n * build custom strings while preserving the locale-specific parts.\n *\n * @param startRange Number or bigint representing start of the range\n * @param endRange Number or bigint representing end of the range\n * @returns Array of NumberRangeFormatPart objects containing the formatted range of numbers in\n * parts\n */\n formatRangeToParts(\n startRange: number | bigint,\n endRange: number | bigint,\n ): Intl.NumberRangeFormatPart[] {\n return this.numberFormatter.formatRangeToParts(startRange, endRange);\n }\n\n /**\n * Allows locale-aware formatting of strings produced by this NumberFormat object\n *\n * @param value Number or bigint to format\n * @returns Array of NumberFormatPart objects containing the formatted number in parts\n */\n formatToParts(value: number | bigint): Intl.NumberFormatPart[] {\n return this.numberFormatter.formatToParts(value);\n }\n\n /**\n * Returns a new object with properties reflecting the locale and number formatting options\n * computed during initialization of this NumberFormat object\n *\n * @returns ResolvedNumberFormatOptions object\n */\n resolvedOptions(): Intl.ResolvedNumberFormatOptions {\n return this.numberFormatter.resolvedOptions();\n }\n}\n","import { Dispose } from './disposal.model';\nimport { Unsubscriber, UnsubscriberAsync } from './unsubscriber';\n\n/** Simple collection for UnsubscriberAsync objects that also provides an easy way to run them. */\nexport default class UnsubscriberAsyncList {\n readonly unsubscribers = new Set();\n\n constructor(private name = 'Anonymous') {}\n\n /**\n * Add unsubscribers to the list. Note that duplicates are not added twice.\n *\n * @param unsubscribers - Objects that were returned from a registration process.\n */\n add(...unsubscribers: (UnsubscriberAsync | Unsubscriber | Dispose)[]) {\n unsubscribers.forEach((unsubscriber) => {\n if ('dispose' in unsubscriber) this.unsubscribers.add(unsubscriber.dispose);\n else this.unsubscribers.add(unsubscriber);\n });\n }\n\n /**\n * Run all unsubscribers added to this list and then clear the list.\n *\n * @returns `true` if all unsubscribers succeeded, `false` otherwise.\n */\n async runAllUnsubscribers(): Promise {\n const unsubs = [...this.unsubscribers].map((unsubscriber) => unsubscriber());\n const results = await Promise.all(unsubs);\n this.unsubscribers.clear();\n return results.every((unsubscriberSucceeded, index) => {\n if (!unsubscriberSucceeded)\n console.error(`UnsubscriberAsyncList ${this.name}: Unsubscriber at index ${index} failed!`);\n\n return unsubscriberSucceeded;\n });\n }\n}\n","var P = Object.defineProperty;\nvar R = (t, e, s) => e in t ? P(t, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : t[e] = s;\nvar n = (t, e, s) => (R(t, typeof e != \"symbol\" ? e + \"\" : e, s), s);\nclass z {\n constructor() {\n n(this, \"books\");\n n(this, \"firstSelectedBookNum\");\n n(this, \"lastSelectedBookNum\");\n n(this, \"count\");\n n(this, \"selectedBookNumbers\");\n n(this, \"selectedBookIds\");\n }\n}\nconst m = [\n \"GEN\",\n \"EXO\",\n \"LEV\",\n \"NUM\",\n \"DEU\",\n \"JOS\",\n \"JDG\",\n \"RUT\",\n \"1SA\",\n \"2SA\",\n // 10\n \"1KI\",\n \"2KI\",\n \"1CH\",\n \"2CH\",\n \"EZR\",\n \"NEH\",\n \"EST\",\n \"JOB\",\n \"PSA\",\n \"PRO\",\n // 20\n \"ECC\",\n \"SNG\",\n \"ISA\",\n \"JER\",\n \"LAM\",\n \"EZK\",\n \"DAN\",\n \"HOS\",\n \"JOL\",\n \"AMO\",\n // 30\n \"OBA\",\n \"JON\",\n \"MIC\",\n \"NAM\",\n \"HAB\",\n \"ZEP\",\n \"HAG\",\n \"ZEC\",\n \"MAL\",\n \"MAT\",\n // 40\n \"MRK\",\n \"LUK\",\n \"JHN\",\n \"ACT\",\n \"ROM\",\n \"1CO\",\n \"2CO\",\n \"GAL\",\n \"EPH\",\n \"PHP\",\n // 50\n \"COL\",\n \"1TH\",\n \"2TH\",\n \"1TI\",\n \"2TI\",\n \"TIT\",\n \"PHM\",\n \"HEB\",\n \"JAS\",\n \"1PE\",\n // 60\n \"2PE\",\n \"1JN\",\n \"2JN\",\n \"3JN\",\n \"JUD\",\n \"REV\",\n \"TOB\",\n \"JDT\",\n \"ESG\",\n \"WIS\",\n // 70\n \"SIR\",\n \"BAR\",\n \"LJE\",\n \"S3Y\",\n \"SUS\",\n \"BEL\",\n \"1MA\",\n \"2MA\",\n \"3MA\",\n \"4MA\",\n // 80\n \"1ES\",\n \"2ES\",\n \"MAN\",\n \"PS2\",\n \"ODA\",\n \"PSS\",\n \"JSA\",\n // actual variant text for JOS, now in LXA text\n \"JDB\",\n // actual variant text for JDG, now in LXA text\n \"TBS\",\n // actual variant text for TOB, now in LXA text\n \"SST\",\n // actual variant text for SUS, now in LXA text // 90\n \"DNT\",\n // actual variant text for DAN, now in LXA text\n \"BLT\",\n // actual variant text for BEL, now in LXA text\n \"XXA\",\n \"XXB\",\n \"XXC\",\n \"XXD\",\n \"XXE\",\n \"XXF\",\n \"XXG\",\n \"FRT\",\n // 100\n \"BAK\",\n \"OTH\",\n \"3ES\",\n // Used previously but really should be 2ES\n \"EZA\",\n // Used to be called 4ES, but not actually in any known project\n \"5EZ\",\n // Used to be called 5ES, but not actually in any known project\n \"6EZ\",\n // Used to be called 6ES, but not actually in any known project\n \"INT\",\n \"CNC\",\n \"GLO\",\n \"TDX\",\n // 110\n \"NDX\",\n \"DAG\",\n \"PS3\",\n \"2BA\",\n \"LBA\",\n \"JUB\",\n \"ENO\",\n \"1MQ\",\n \"2MQ\",\n \"3MQ\",\n // 120\n \"REP\",\n \"4BA\",\n \"LAO\"\n], v = [\n \"XXA\",\n \"XXB\",\n \"XXC\",\n \"XXD\",\n \"XXE\",\n \"XXF\",\n \"XXG\",\n \"FRT\",\n \"BAK\",\n \"OTH\",\n \"INT\",\n \"CNC\",\n \"GLO\",\n \"TDX\",\n \"NDX\"\n], X = [\n \"Genesis\",\n \"Exodus\",\n \"Leviticus\",\n \"Numbers\",\n \"Deuteronomy\",\n \"Joshua\",\n \"Judges\",\n \"Ruth\",\n \"1 Samuel\",\n \"2 Samuel\",\n \"1 Kings\",\n \"2 Kings\",\n \"1 Chronicles\",\n \"2 Chronicles\",\n \"Ezra\",\n \"Nehemiah\",\n \"Esther (Hebrew)\",\n \"Job\",\n \"Psalms\",\n \"Proverbs\",\n \"Ecclesiastes\",\n \"Song of Songs\",\n \"Isaiah\",\n \"Jeremiah\",\n \"Lamentations\",\n \"Ezekiel\",\n \"Daniel (Hebrew)\",\n \"Hosea\",\n \"Joel\",\n \"Amos\",\n \"Obadiah\",\n \"Jonah\",\n \"Micah\",\n \"Nahum\",\n \"Habakkuk\",\n \"Zephaniah\",\n \"Haggai\",\n \"Zechariah\",\n \"Malachi\",\n \"Matthew\",\n \"Mark\",\n \"Luke\",\n \"John\",\n \"Acts\",\n \"Romans\",\n \"1 Corinthians\",\n \"2 Corinthians\",\n \"Galatians\",\n \"Ephesians\",\n \"Philippians\",\n \"Colossians\",\n \"1 Thessalonians\",\n \"2 Thessalonians\",\n \"1 Timothy\",\n \"2 Timothy\",\n \"Titus\",\n \"Philemon\",\n \"Hebrews\",\n \"James\",\n \"1 Peter\",\n \"2 Peter\",\n \"1 John\",\n \"2 John\",\n \"3 John\",\n \"Jude\",\n \"Revelation\",\n \"Tobit\",\n \"Judith\",\n \"Esther Greek\",\n \"Wisdom of Solomon\",\n \"Sirach (Ecclesiasticus)\",\n \"Baruch\",\n \"Letter of Jeremiah\",\n \"Song of 3 Young Men\",\n \"Susanna\",\n \"Bel and the Dragon\",\n \"1 Maccabees\",\n \"2 Maccabees\",\n \"3 Maccabees\",\n \"4 Maccabees\",\n \"1 Esdras (Greek)\",\n \"2 Esdras (Latin)\",\n \"Prayer of Manasseh\",\n \"Psalm 151\",\n \"Odes\",\n \"Psalms of Solomon\",\n // WARNING, if you change the spelling of the *obsolete* tag be sure to update\n // IsObsolete routine\n \"Joshua A. *obsolete*\",\n \"Judges B. *obsolete*\",\n \"Tobit S. *obsolete*\",\n \"Susanna Th. *obsolete*\",\n \"Daniel Th. *obsolete*\",\n \"Bel Th. *obsolete*\",\n \"Extra A\",\n \"Extra B\",\n \"Extra C\",\n \"Extra D\",\n \"Extra E\",\n \"Extra F\",\n \"Extra G\",\n \"Front Matter\",\n \"Back Matter\",\n \"Other Matter\",\n \"3 Ezra *obsolete*\",\n \"Apocalypse of Ezra\",\n \"5 Ezra (Latin Prologue)\",\n \"6 Ezra (Latin Epilogue)\",\n \"Introduction\",\n \"Concordance \",\n \"Glossary \",\n \"Topical Index\",\n \"Names Index\",\n \"Daniel Greek\",\n \"Psalms 152-155\",\n \"2 Baruch (Apocalypse)\",\n \"Letter of Baruch\",\n \"Jubilees\",\n \"Enoch\",\n \"1 Meqabyan\",\n \"2 Meqabyan\",\n \"3 Meqabyan\",\n \"Reproof (Proverbs 25-31)\",\n \"4 Baruch (Rest of Baruch)\",\n \"Laodiceans\"\n], C = K();\nfunction N(t, e = !0) {\n return e && (t = t.toUpperCase()), t in C ? C[t] : 0;\n}\nfunction B(t) {\n return N(t) > 0;\n}\nfunction x(t) {\n const e = typeof t == \"string\" ? N(t) : t;\n return e >= 40 && e <= 66;\n}\nfunction T(t) {\n return (typeof t == \"string\" ? N(t) : t) <= 39;\n}\nfunction O(t) {\n return t <= 66;\n}\nfunction V(t) {\n const e = typeof t == \"string\" ? N(t) : t;\n return I(e) && !O(e);\n}\nfunction* L() {\n for (let t = 1; t <= m.length; t++)\n yield t;\n}\nconst G = 1, S = m.length;\nfunction H() {\n return [\"XXA\", \"XXB\", \"XXC\", \"XXD\", \"XXE\", \"XXF\", \"XXG\"];\n}\nfunction k(t, e = \"***\") {\n const s = t - 1;\n return s < 0 || s >= m.length ? e : m[s];\n}\nfunction A(t) {\n return t <= 0 || t > S ? \"******\" : X[t - 1];\n}\nfunction y(t) {\n return A(N(t));\n}\nfunction I(t) {\n const e = typeof t == \"number\" ? k(t) : t;\n return B(e) && !v.includes(e);\n}\nfunction q(t) {\n const e = typeof t == \"number\" ? k(t) : t;\n return B(e) && v.includes(e);\n}\nfunction U(t) {\n return X[t - 1].includes(\"*obsolete*\");\n}\nfunction K() {\n const t = {};\n for (let e = 0; e < m.length; e++)\n t[m[e]] = e + 1;\n return t;\n}\nconst f = {\n allBookIds: m,\n nonCanonicalIds: v,\n bookIdToNumber: N,\n isBookIdValid: B,\n isBookNT: x,\n isBookOT: T,\n isBookOTNT: O,\n isBookDC: V,\n allBookNumbers: L,\n firstBook: G,\n lastBook: S,\n extraBooks: H,\n bookNumberToId: k,\n bookNumberToEnglishName: A,\n bookIdToEnglishName: y,\n isCanonical: I,\n isExtraMaterial: q,\n isObsolete: U\n};\nvar l = /* @__PURE__ */ ((t) => (t[t.Unknown = 0] = \"Unknown\", t[t.Original = 1] = \"Original\", t[t.Septuagint = 2] = \"Septuagint\", t[t.Vulgate = 3] = \"Vulgate\", t[t.English = 4] = \"English\", t[t.RussianProtestant = 5] = \"RussianProtestant\", t[t.RussianOrthodox = 6] = \"RussianOrthodox\", t))(l || {});\nconst u = class u {\n // private versInfo: Versification;\n constructor(e) {\n n(this, \"name\");\n n(this, \"fullPath\");\n n(this, \"isPresent\");\n n(this, \"hasVerseSegments\");\n n(this, \"isCustomized\");\n n(this, \"baseVersification\");\n n(this, \"scriptureBooks\");\n n(this, \"_type\");\n if (e != null)\n typeof e == \"string\" ? this.name = e : this._type = e;\n else\n throw new Error(\"Argument null\");\n }\n get type() {\n return this._type;\n }\n equals(e) {\n return !e.type || !this.type ? !1 : e.type === this.type;\n }\n};\nn(u, \"Original\", new u(l.Original)), n(u, \"Septuagint\", new u(l.Septuagint)), n(u, \"Vulgate\", new u(l.Vulgate)), n(u, \"English\", new u(l.English)), n(u, \"RussianProtestant\", new u(l.RussianProtestant)), n(u, \"RussianOrthodox\", new u(l.RussianOrthodox));\nlet c = u;\nfunction E(t, e) {\n const s = e[0];\n for (let r = 1; r < e.length; r++)\n t = t.split(e[r]).join(s);\n return t.split(s);\n}\nvar D = /* @__PURE__ */ ((t) => (t[t.Valid = 0] = \"Valid\", t[t.UnknownVersification = 1] = \"UnknownVersification\", t[t.OutOfRange = 2] = \"OutOfRange\", t[t.VerseOutOfOrder = 3] = \"VerseOutOfOrder\", t[t.VerseRepeated = 4] = \"VerseRepeated\", t))(D || {});\nconst i = class i {\n constructor(e, s, r, o) {\n /** Not yet implemented. */\n n(this, \"firstChapter\");\n /** Not yet implemented. */\n n(this, \"lastChapter\");\n /** Not yet implemented. */\n n(this, \"lastVerse\");\n /** Not yet implemented. */\n n(this, \"hasSegmentsDefined\");\n /** Not yet implemented. */\n n(this, \"text\");\n /** Not yet implemented. */\n n(this, \"BBBCCCVVVS\");\n /** Not yet implemented. */\n n(this, \"longHashCode\");\n /** The versification of the reference. */\n n(this, \"versification\");\n n(this, \"rtlMark\", \"‏\");\n n(this, \"_bookNum\", 0);\n n(this, \"_chapterNum\", 0);\n n(this, \"_verseNum\", 0);\n n(this, \"_verse\");\n if (r == null && o == null)\n if (e != null && typeof e == \"string\") {\n const a = e, h = s != null && s instanceof c ? s : void 0;\n this.setEmpty(h), this.parse(a);\n } else if (e != null && typeof e == \"number\") {\n const a = s != null && s instanceof c ? s : void 0;\n this.setEmpty(a), this._verseNum = e % i.chapterDigitShifter, this._chapterNum = Math.floor(\n e % i.bookDigitShifter / i.chapterDigitShifter\n ), this._bookNum = Math.floor(e / i.bookDigitShifter);\n } else if (s == null)\n if (e != null && e instanceof i) {\n const a = e;\n this._bookNum = a.bookNum, this._chapterNum = a.chapterNum, this._verseNum = a.verseNum, this._verse = a.verse, this.versification = a.versification;\n } else {\n if (e == null)\n return;\n const a = e instanceof c ? e : i.defaultVersification;\n this.setEmpty(a);\n }\n else\n throw new Error(\"VerseRef constructor not supported.\");\n else if (e != null && s != null && r != null)\n if (typeof e == \"string\" && typeof s == \"string\" && typeof r == \"string\")\n this.setEmpty(o), this.updateInternal(e, s, r);\n else if (typeof e == \"number\" && typeof s == \"number\" && typeof r == \"number\")\n this._bookNum = e, this._chapterNum = s, this._verseNum = r, this.versification = o ?? i.defaultVersification;\n else\n throw new Error(\"VerseRef constructor not supported.\");\n else\n throw new Error(\"VerseRef constructor not supported.\");\n }\n /**\n * @deprecated Will be removed in v2. Replace `VerseRef.parse('...')` with `new VerseRef('...')`\n * or refactor to use `VerseRef.tryParse('...')` which has a different return type.\n */\n static parse(e, s = i.defaultVersification) {\n const r = new i(s);\n return r.parse(e), r;\n }\n /**\n * Determines if the verse string is in a valid format (does not consider versification).\n */\n static isVerseParseable(e) {\n return e.length > 0 && \"0123456789\".includes(e[0]) && !e.endsWith(this.verseRangeSeparator) && !e.endsWith(this.verseSequenceIndicator);\n }\n /**\n * Tries to parse the specified string into a verse reference.\n * @param str - The string to attempt to parse.\n * @returns success: `true` if the specified string was successfully parsed, `false` otherwise.\n * @returns verseRef: The result of the parse if successful, or empty VerseRef if it failed\n */\n static tryParse(e) {\n let s;\n try {\n return s = i.parse(e), { success: !0, verseRef: s };\n } catch (r) {\n if (r instanceof d)\n return s = new i(), { success: !1, verseRef: s };\n throw r;\n }\n }\n /**\n * Gets the reference as a comparable integer where the book, chapter, and verse each occupy 3\n * digits.\n * @param bookNum - Book number (this is 1-based, not an index).\n * @param chapterNum - Chapter number.\n * @param verseNum - Verse number.\n * @returns The reference as a comparable integer where the book, chapter, and verse each occupy 3\n * digits.\n */\n static getBBBCCCVVV(e, s, r) {\n return e % i.bcvMaxValue * i.bookDigitShifter + (s >= 0 ? s % i.bcvMaxValue * i.chapterDigitShifter : 0) + (r >= 0 ? r % i.bcvMaxValue : 0);\n }\n /**\n * Parses a verse string and gets the leading numeric portion as a number.\n * @param verseStr - verse string to parse\n * @returns true if the entire string could be parsed as a single, simple verse number (1-999);\n * false if the verse string represented a verse bridge, contained segment letters, or was invalid\n */\n static tryGetVerseNum(e) {\n let s;\n if (!e)\n return s = -1, { success: !0, vNum: s };\n s = 0;\n let r;\n for (let o = 0; o < e.length; o++) {\n if (r = e[o], r < \"0\" || r > \"9\")\n return o === 0 && (s = -1), { success: !1, vNum: s };\n if (s = s * 10 + +r - +\"0\", s > i.bcvMaxValue)\n return s = -1, { success: !1, vNum: s };\n }\n return { success: !0, vNum: s };\n }\n /**\n * Checks to see if a VerseRef hasn't been set - all values are the default.\n */\n get isDefault() {\n return this.bookNum === 0 && this.chapterNum === 0 && this.verseNum === 0 && this.versification == null;\n }\n /**\n * Gets whether the verse contains multiple verses.\n */\n get hasMultiple() {\n return this._verse != null && (this._verse.includes(i.verseRangeSeparator) || this._verse.includes(i.verseSequenceIndicator));\n }\n /**\n * Gets or sets the book of the reference. Book is the 3-letter abbreviation in capital letters,\n * e.g. `'MAT'`.\n */\n get book() {\n return f.bookNumberToId(this.bookNum, \"\");\n }\n set book(e) {\n this.bookNum = f.bookIdToNumber(e);\n }\n /**\n * Gets or sets the chapter of the reference,. e.g. `'3'`.\n */\n get chapter() {\n return this.isDefault || this._chapterNum < 0 ? \"\" : this._chapterNum.toString();\n }\n set chapter(e) {\n const s = +e;\n this._chapterNum = Number.isInteger(s) ? s : -1;\n }\n /**\n * Gets or sets the verse of the reference, including range, segments, and sequences, e.g. `'4'`,\n * or `'4b-5a, 7'`.\n */\n get verse() {\n return this._verse != null ? this._verse : this.isDefault || this._verseNum < 0 ? \"\" : this._verseNum.toString();\n }\n set verse(e) {\n const { success: s, vNum: r } = i.tryGetVerseNum(e);\n this._verse = s ? void 0 : e.replace(this.rtlMark, \"\"), this._verseNum = r, !(this._verseNum >= 0) && ({ vNum: this._verseNum } = i.tryGetVerseNum(this._verse));\n }\n /**\n * Get or set Book based on book number, e.g. `42`.\n */\n get bookNum() {\n return this._bookNum;\n }\n set bookNum(e) {\n if (e <= 0 || e > f.lastBook)\n throw new d(\n \"BookNum must be greater than zero and less than or equal to last book\"\n );\n this._bookNum = e;\n }\n /**\n * Gets or sets the chapter number, e.g. `3`. `-1` if not valid.\n */\n get chapterNum() {\n return this._chapterNum;\n }\n set chapterNum(e) {\n this.chapterNum = e;\n }\n /**\n * Gets or sets verse start number, e.g. `4`. `-1` if not valid.\n */\n get verseNum() {\n return this._verseNum;\n }\n set verseNum(e) {\n this._verseNum = e;\n }\n /**\n * String representing the versification (should ONLY be used for serialization/deserialization).\n *\n * @remarks This is for backwards compatibility when ScrVers was an enumeration.\n */\n get versificationStr() {\n var e;\n return (e = this.versification) == null ? void 0 : e.name;\n }\n set versificationStr(e) {\n this.versification = this.versification != null ? new c(e) : void 0;\n }\n /**\n * Determines if the reference is valid.\n */\n get valid() {\n return this.validStatus === 0;\n }\n /**\n * Get the valid status for this reference.\n */\n get validStatus() {\n return this.validateVerse(i.verseRangeSeparators, i.verseSequenceIndicators);\n }\n /**\n * Gets the reference as a comparable integer where the book,\n * chapter, and verse each occupy three digits and the verse is 0.\n */\n get BBBCCC() {\n return i.getBBBCCCVVV(this._bookNum, this._chapterNum, 0);\n }\n /**\n * Gets the reference as a comparable integer where the book,\n * chapter, and verse each occupy three digits. If verse is not null\n * (i.e., this reference represents a complex reference with verse\n * segments or bridge) this cannot be used for an exact comparison.\n */\n get BBBCCCVVV() {\n return i.getBBBCCCVVV(this._bookNum, this._chapterNum, this._verseNum);\n }\n /**\n * Gets whether the verse is defined as an excluded verse in the versification.\n * Does not handle verse ranges.\n */\n // eslint-disable-next-line @typescript-eslint/class-literal-property-style\n get isExcluded() {\n return !1;\n }\n /**\n * Parses the reference in the specified string.\n * Optionally versification can follow reference as in GEN 3:11/4\n * Throw an exception if\n * - invalid book name\n * - chapter number is missing or not a number\n * - verse number is missing or does not start with a number\n * - versification is invalid\n * @param verseStr - string to parse e.g. 'MAT 3:11'\n */\n parse(e) {\n if (e = e.replace(this.rtlMark, \"\"), e.includes(\"/\")) {\n const a = e.split(\"/\");\n if (e = a[0], a.length > 1)\n try {\n const h = +a[1].trim();\n this.versification = new c(l[h]);\n } catch {\n throw new d(\"Invalid reference : \" + e);\n }\n }\n const s = e.trim().split(\" \");\n if (s.length !== 2)\n throw new d(\"Invalid reference : \" + e);\n const r = s[1].split(\":\"), o = +r[0];\n if (r.length !== 2 || f.bookIdToNumber(s[0]) === 0 || !Number.isInteger(o) || o < 0 || !i.isVerseParseable(r[1]))\n throw new d(\"Invalid reference : \" + e);\n this.updateInternal(s[0], r[0], r[1]);\n }\n /**\n * Simplifies this verse ref so that it has no bridging of verses or\n * verse segments like `'1a'`.\n */\n simplify() {\n this._verse = void 0;\n }\n /**\n * Makes a clone of the reference.\n *\n * @returns The cloned VerseRef.\n */\n clone() {\n return new i(this);\n }\n toString() {\n const e = this.book;\n return e === \"\" ? \"\" : `${e} ${this.chapter}:${this.verse}`;\n }\n /**\n * Compares this `VerseRef` with supplied one.\n * @param verseRef - object to compare this one to.\n * @returns `true` if this `VerseRef` is equal to the supplied on, `false` otherwise.\n */\n equals(e) {\n return e instanceof i ? e._bookNum === this._bookNum && e._chapterNum === this._chapterNum && e._verseNum === this._verseNum && e.verse === this.verse && e.versification != null && this.versification != null && e.versification.equals(this.versification) : !1;\n }\n /**\n * Enumerate all individual verses contained in a VerseRef.\n * Verse ranges are indicated by \"-\" and consecutive verses by \",\"s.\n * Examples:\n * GEN 1:2 returns GEN 1:2\n * GEN 1:1a-3b,5 returns GEN 1:1a, GEN 1:2, GEN 1:3b, GEN 1:5\n * GEN 1:2a-2c returns //! ??????\n *\n * @param specifiedVersesOnly - if set to true return only verses that are\n * explicitly specified only, not verses within a range. Defaults to `false`.\n * @param verseRangeSeparators - Verse range separators.\n * Defaults to `VerseRef.verseRangeSeparators`.\n * @param verseSequenceSeparators - Verse sequence separators.\n * Defaults to `VerseRef.verseSequenceIndicators`.\n * @returns An array of all single verse references in this VerseRef.\n */\n allVerses(e = !1, s = i.verseRangeSeparators, r = i.verseSequenceIndicators) {\n if (this._verse == null || this.chapterNum <= 0)\n return [this.clone()];\n const o = [], a = E(this._verse, r);\n for (const h of a.map((g) => E(g, s))) {\n const g = this.clone();\n g.verse = h[0];\n const w = g.verseNum;\n if (o.push(g), h.length > 1) {\n const p = this.clone();\n if (p.verse = h[1], !e)\n for (let b = w + 1; b < p.verseNum; b++) {\n const J = new i(\n this._bookNum,\n this._chapterNum,\n b,\n this.versification\n );\n this.isExcluded || o.push(J);\n }\n o.push(p);\n }\n }\n return o;\n }\n /**\n * Validates a verse number using the supplied separators rather than the defaults.\n */\n validateVerse(e, s) {\n if (!this.verse)\n return this.internalValid;\n let r = 0;\n for (const o of this.allVerses(!0, e, s)) {\n const a = o.internalValid;\n if (a !== 0)\n return a;\n const h = o.BBBCCCVVV;\n if (r > h)\n return 3;\n if (r === h)\n return 4;\n r = h;\n }\n return 0;\n }\n /**\n * Gets whether a single verse reference is valid.\n */\n get internalValid() {\n return this.versification == null ? 1 : this._bookNum <= 0 || this._bookNum > f.lastBook ? 2 : (f.isCanonical(this._bookNum), 0);\n }\n setEmpty(e = i.defaultVersification) {\n this._bookNum = 0, this._chapterNum = -1, this._verse = void 0, this.versification = e;\n }\n updateInternal(e, s, r) {\n this.bookNum = f.bookIdToNumber(e), this.chapter = s, this.verse = r;\n }\n};\nn(i, \"defaultVersification\", c.English), n(i, \"verseRangeSeparator\", \"-\"), n(i, \"verseSequenceIndicator\", \",\"), n(i, \"verseRangeSeparators\", [i.verseRangeSeparator]), n(i, \"verseSequenceIndicators\", [i.verseSequenceIndicator]), n(i, \"chapterDigitShifter\", 1e3), n(i, \"bookDigitShifter\", i.chapterDigitShifter * i.chapterDigitShifter), n(i, \"bcvMaxValue\", i.chapterDigitShifter - 1), /**\n * The valid status of the VerseRef.\n */\nn(i, \"ValidStatusType\", D);\nlet M = i;\nclass d extends Error {\n}\nexport {\n z as BookSet,\n f as Canon,\n c as ScrVers,\n l as ScrVersType,\n M as VerseRef,\n d as VerseRefException\n};\n//# sourceMappingURL=index.es.js.map\n","\"use strict\"\r\n\r\n// Based on: https://github.com/lodash/lodash/blob/6018350ac10d5ce6a5b7db625140b82aeab804df/.internal/unicodeSize.js\r\n\r\nmodule.exports = () => {\r\n\t// Used to compose unicode character classes.\r\n\tconst astralRange = \"\\\\ud800-\\\\udfff\"\r\n\tconst comboMarksRange = \"\\\\u0300-\\\\u036f\"\r\n\tconst comboHalfMarksRange = \"\\\\ufe20-\\\\ufe2f\"\r\n\tconst comboSymbolsRange = \"\\\\u20d0-\\\\u20ff\"\r\n\tconst comboMarksExtendedRange = \"\\\\u1ab0-\\\\u1aff\"\r\n\tconst comboMarksSupplementRange = \"\\\\u1dc0-\\\\u1dff\"\r\n\tconst comboRange = comboMarksRange + comboHalfMarksRange + comboSymbolsRange + comboMarksExtendedRange + comboMarksSupplementRange\r\n\tconst varRange = \"\\\\ufe0e\\\\ufe0f\"\r\n\tconst familyRange = \"\\\\uD83D\\\\uDC69\\\\uD83C\\\\uDFFB\\\\u200D\\\\uD83C\\\\uDF93\"\r\n\r\n\t// Used to compose unicode capture groups.\r\n\tconst astral = `[${astralRange}]`\r\n\tconst combo = `[${comboRange}]`\r\n\tconst fitz = \"\\\\ud83c[\\\\udffb-\\\\udfff]\"\r\n\tconst modifier = `(?:${combo}|${fitz})`\r\n\tconst nonAstral = `[^${astralRange}]`\r\n\tconst regional = \"(?:\\\\uD83C[\\\\uDDE6-\\\\uDDFF]){2}\"\r\n\tconst surrogatePair = \"[\\\\ud800-\\\\udbff][\\\\udc00-\\\\udfff]\"\r\n\tconst zwj = \"\\\\u200d\"\r\n\tconst blackFlag = \"(?:\\\\ud83c\\\\udff4\\\\udb40\\\\udc67\\\\udb40\\\\udc62\\\\udb40(?:\\\\udc65|\\\\udc73|\\\\udc77)\\\\udb40(?:\\\\udc6e|\\\\udc63|\\\\udc6c)\\\\udb40(?:\\\\udc67|\\\\udc74|\\\\udc73)\\\\udb40\\\\udc7f)\"\r\n\tconst family = `[${familyRange}]`\r\n\r\n\t// Used to compose unicode regexes.\r\n\tconst optModifier = `${modifier}?`\r\n\tconst optVar = `[${varRange}]?`\r\n\tconst optJoin = `(?:${zwj}(?:${[nonAstral, regional, surrogatePair].join(\"|\")})${optVar + optModifier})*`\r\n\tconst seq = optVar + optModifier + optJoin\r\n\tconst nonAstralCombo = `${nonAstral}${combo}?`\r\n\tconst symbol = `(?:${[nonAstralCombo, combo, regional, surrogatePair, astral, family].join(\"|\")})`\r\n\r\n\t// Used to match [String symbols](https://mathiasbynens.be/notes/javascript-unicode).\r\n\treturn new RegExp(`${blackFlag}|${fitz}(?=${fitz})|${symbol + seq}`, \"g\")\r\n}\r\n","\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\n// @ts-ignore\nvar char_regex_1 = __importDefault(require(\"char-regex\"));\n/**\n * Converts a string to an array of string chars\n * @param {string} str The string to turn into array\n * @returns {string[]}\n */\nfunction toArray(str) {\n if (typeof str !== 'string') {\n throw new Error('A string is expected as input');\n }\n return str.match(char_regex_1.default()) || [];\n}\nexports.toArray = toArray;\n/**\n * Returns the length of a string\n *\n * @export\n * @param {string} str\n * @returns {number}\n */\nfunction length(str) {\n // Check for input\n if (typeof str !== 'string') {\n throw new Error('Input must be a string');\n }\n var match = str.match(char_regex_1.default());\n return match === null ? 0 : match.length;\n}\nexports.length = length;\n/**\n * Returns a substring by providing start and end position\n *\n * @export\n * @param {string} str\n * @param {number} [begin=0] Starting position\n * @param {number} end End position\n * @returns {string}\n */\nfunction substring(str, begin, end) {\n if (begin === void 0) { begin = 0; }\n // Check for input\n if (typeof str !== 'string') {\n throw new Error('Input must be a string');\n }\n // Even though negative numbers work here, theyre not in the spec\n if (typeof begin !== 'number' || begin < 0) {\n begin = 0;\n }\n if (typeof end === 'number' && end < 0) {\n end = 0;\n }\n var match = str.match(char_regex_1.default());\n if (!match)\n return '';\n return match.slice(begin, end).join('');\n}\nexports.substring = substring;\n/**\n * Returns a substring by providing start position and length\n *\n * @export\n * @param {string} str\n * @param {number} [begin=0] Starting position\n * @param {number} len Desired length\n * @returns {string}\n */\nfunction substr(str, begin, len) {\n if (begin === void 0) { begin = 0; }\n // Check for input\n if (typeof str !== 'string') {\n throw new Error('Input must be a string');\n }\n var strLength = length(str);\n // Fix type\n if (typeof begin !== 'number') {\n begin = parseInt(begin, 10);\n }\n // Return zero-length string if got oversize number.\n if (begin >= strLength) {\n return '';\n }\n // Calculating postive version of negative value.\n if (begin < 0) {\n begin += strLength;\n }\n var end;\n if (typeof len === 'undefined') {\n end = strLength;\n }\n else {\n // Fix type\n if (typeof len !== 'number') {\n len = parseInt(len, 10);\n }\n end = len >= 0 ? len + begin : begin;\n }\n var match = str.match(char_regex_1.default());\n if (!match)\n return '';\n return match.slice(begin, end).join('');\n}\nexports.substr = substr;\n/**\n * Enforces a string to be a certain length by\n * adding or removing characters\n *\n * @export\n * @param {string} str\n * @param {number} [limit=16] Limit\n * @param {string} [padString='#'] The Pad String\n * @param {string} [padPosition='right'] The Pad Position\n * @returns {string}\n */\nfunction limit(str, limit, padString, padPosition) {\n if (limit === void 0) { limit = 16; }\n if (padString === void 0) { padString = '#'; }\n if (padPosition === void 0) { padPosition = 'right'; }\n // Input should be a string, limit should be a number\n if (typeof str !== 'string' || typeof limit !== 'number') {\n throw new Error('Invalid arguments specified');\n }\n // Pad position should be either left or right\n if (['left', 'right'].indexOf(padPosition) === -1) {\n throw new Error('Pad position should be either left or right');\n }\n // Pad string can be anything, we convert it to string\n if (typeof padString !== 'string') {\n padString = String(padString);\n }\n // Calculate string length considering astral code points\n var strLength = length(str);\n if (strLength > limit) {\n return substring(str, 0, limit);\n }\n else if (strLength < limit) {\n var padRepeats = padString.repeat(limit - strLength);\n return padPosition === 'left' ? padRepeats + str : str + padRepeats;\n }\n return str;\n}\nexports.limit = limit;\n/**\n * Returns the index of the first occurrence of a given string\n *\n * @export\n * @param {string} str\n * @param {string} [searchStr] the string to search\n * @param {number} [pos] starting position\n * @returns {number}\n */\nfunction indexOf(str, searchStr, pos) {\n if (pos === void 0) { pos = 0; }\n if (typeof str !== 'string') {\n throw new Error('Input must be a string');\n }\n if (str === '') {\n if (searchStr === '') {\n return 0;\n }\n return -1;\n }\n // fix type\n pos = Number(pos);\n pos = isNaN(pos) ? 0 : pos;\n searchStr = String(searchStr);\n var strArr = toArray(str);\n if (pos >= strArr.length) {\n if (searchStr === '') {\n return strArr.length;\n }\n return -1;\n }\n if (searchStr === '') {\n return pos;\n }\n var searchArr = toArray(searchStr);\n var finded = false;\n var index;\n for (index = pos; index < strArr.length; index += 1) {\n var searchIndex = 0;\n while (searchIndex < searchArr.length &&\n searchArr[searchIndex] === strArr[index + searchIndex]) {\n searchIndex += 1;\n }\n if (searchIndex === searchArr.length &&\n searchArr[searchIndex - 1] === strArr[index + searchIndex - 1]) {\n finded = true;\n break;\n }\n }\n return finded ? index : -1;\n}\nexports.indexOf = indexOf;\n","import { LocalizeKey } from 'menus.model';\nimport {\n indexOf as stringzIndexOf,\n substring as stringzSubstring,\n length as stringzLength,\n toArray as stringzToArray,\n limit as stringzLimit,\n substr as stringzSubstr,\n} from 'stringz';\n\n/**\n * This function mirrors the `at` function from the JavaScript Standard String object. It handles\n * Unicode code points instead of UTF-16 character codes.\n *\n * Finds the Unicode code point at the given index.\n *\n * @param string String to index\n * @param index Position of the character to be returned in range of -length(string) to\n * length(string)\n * @returns New string consisting of the Unicode code point located at the specified offset,\n * undefined if index is out of bounds\n */\nexport function at(string: string, index: number): string | undefined {\n if (index > stringLength(string) || index < -stringLength(string)) return undefined;\n return substr(string, index, 1);\n}\n\n/**\n * This function mirrors the `charAt` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns a new string consisting of the single unicode code point at the given index.\n *\n * @param string String to index\n * @param index Position of the string character to be returned, in the range of 0 to\n * length(string)-1\n * @returns New string consisting of the Unicode code point located at the specified offset, empty\n * string if index is out of bounds\n */\nexport function charAt(string: string, index: number): string {\n if (index < 0 || index > stringLength(string) - 1) return '';\n return substr(string, index, 1);\n}\n\n/**\n * This function mirrors the `codePointAt` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns a non-negative integer that is the Unicode code point value of the character starting at\n * the given index.\n *\n * @param string String to index\n * @param index Position of the string character to be returned, in the range of 0 to\n * length(string)-1\n * @returns Non-negative integer representing the code point value of the character at the given\n * index, or undefined if there is no element at that position\n */\nexport function codePointAt(string: string, index: number): number | undefined {\n if (index < 0 || index > stringLength(string) - 1) return undefined;\n return substr(string, index, 1).codePointAt(0);\n}\n\n/**\n * This function mirrors the `endsWith` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Determines whether a string ends with the characters of this string.\n *\n * @param string String to search through\n * @param searchString Characters to search for at the end of the string\n * @param endPosition End position where searchString is expected to be found. Default is\n * `length(string)`\n * @returns True if it ends with searchString, false if it does not\n */\nexport function endsWith(\n string: string,\n searchString: string,\n endPosition: number = stringLength(string),\n): boolean {\n const lastIndexOfSearchString = lastIndexOf(string, searchString);\n if (lastIndexOfSearchString === -1) return false;\n if (lastIndexOfSearchString + stringLength(searchString) !== endPosition) return false;\n return true;\n}\n\n/**\n * Get the index of the closest closing curly brace in a string.\n *\n * Note: when escaped, gets the index of the curly brace, not the backslash before it.\n *\n * @param str String to search\n * @param index Index at which to start searching. Inclusive of this index\n * @param escaped Whether to search for an escaped or an unescaped closing curly brace\n * @returns Index of closest closing curly brace or -1 if not found\n */\nfunction indexOfClosestClosingCurlyBrace(str: string, index: number, escaped: boolean) {\n if (index < 0) return -1;\n if (escaped) {\n if (charAt(str, index) === '}' && charAt(str, index - 1) === '\\\\') return index;\n const closeCurlyBraceIndex = indexOf(str, '\\\\}', index);\n return closeCurlyBraceIndex >= 0 ? closeCurlyBraceIndex + 1 : closeCurlyBraceIndex;\n }\n\n let i = index;\n const strLength = stringLength(str);\n while (i < strLength) {\n i = indexOf(str, '}', i);\n\n if (i === -1 || charAt(str, i - 1) !== '\\\\') break;\n\n // Didn't find an un-escaped close brace, so keep looking\n i += 1;\n }\n\n return i >= strLength ? -1 : i;\n}\n\n/**\n * Formats a string, replacing {localization key} with the localization (or multiple localizations\n * if there are multiple in the string). Will also remove \\ before curly braces if curly braces are\n * escaped with a backslash in order to preserve the curly braces. E.g. 'Hi, this is {name}! I like\n * `\\{curly braces\\}`! would become Hi, this is Jim! I like {curly braces}!\n *\n * If the key in unescaped braces is not found, just return the key without the braces. Empty\n * unescaped curly braces will just return a string without the braces e.g. ('I am {Nemo}', {\n * 'name': 'Jim'}) would return 'I am Nemo'.\n *\n * @param str String to format\n * @returns Formatted string\n */\nexport function formatReplacementString(str: string, replacers: { [key: string]: string }): string {\n let updatedStr = str;\n\n let i = 0;\n while (i < stringLength(updatedStr)) {\n switch (charAt(updatedStr, i)) {\n case '{':\n if (charAt(updatedStr, i - 1) !== '\\\\') {\n // This character is an un-escaped open curly brace. Try to match and replace\n const closeCurlyBraceIndex = indexOfClosestClosingCurlyBrace(updatedStr, i, false);\n if (closeCurlyBraceIndex >= 0) {\n // We have matching open and close indices. Try to replace the contents\n const replacerKey = substring(updatedStr, i + 1, closeCurlyBraceIndex);\n // Replace with the replacer string or just remove the curly braces\n const replacerString = replacerKey in replacers ? replacers[replacerKey] : replacerKey;\n\n updatedStr = `${substring(updatedStr, 0, i)}${replacerString}${substring(updatedStr, closeCurlyBraceIndex + 1)}`;\n // Put our index at the closing brace adjusted for the new string length minus two\n // because we are removing the curly braces\n // Ex: \"stuff {and} things\"\n // Replacer for and: n'\n // closeCurlyBraceIndex is 10\n // \"stuff n' things\"\n // i = 10 + 2 - 3 - 2 = 7\n i = closeCurlyBraceIndex + stringLength(replacerString) - stringLength(replacerKey) - 2;\n } else {\n // This is an unexpected un-escaped open curly brace with no matching closing curly\n // brace. Just ignore, I guess\n }\n } else {\n // This character is an escaped open curly brace. Remove the escape\n updatedStr = `${substring(updatedStr, 0, i - 1)}${substring(updatedStr, i)}`;\n // Adjust our index because we removed the escape\n i -= 1;\n }\n break;\n case '}':\n if (charAt(updatedStr, i - 1) !== '\\\\') {\n // This character is an un-escaped closing curly brace with no matching open curly\n // brace. Just ignore, I guess\n } else {\n // This character is an escaped closing curly brace. Remove the escape\n updatedStr = `${substring(updatedStr, 0, i - 1)}${substring(updatedStr, i)}`;\n // Adjust our index because we removed the escape\n i -= 1;\n }\n break;\n default:\n // No need to do anything with other characters at this point\n break;\n }\n\n i += 1;\n }\n\n return updatedStr;\n}\n/**\n * This function mirrors the `includes` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Performs a case-sensitive search to determine if searchString is found in string.\n *\n * @param string String to search through\n * @param searchString String to search for\n * @param position Position within the string to start searching for searchString. Default is `0`\n * @returns True if search string is found, false if it is not\n */\nexport function includes(string: string, searchString: string, position: number = 0): boolean {\n const partialString = substring(string, position);\n const indexOfSearchString = indexOf(partialString, searchString);\n if (indexOfSearchString === -1) return false;\n return true;\n}\n\n/**\n * This function mirrors the `indexOf` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns the index of the first occurrence of a given string.\n *\n * @param string String to search through\n * @param searchString The string to search for\n * @param position Start of searching. Default is `0`\n * @returns Index of the first occurrence of a given string\n */\nexport function indexOf(\n string: string,\n searchString: string,\n position: number | undefined = 0,\n): number {\n return stringzIndexOf(string, searchString, position);\n}\n\n/**\n * This function mirrors the `lastIndexOf` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Searches this string and returns the index of the last occurrence of the specified substring.\n *\n * @param string String to search through\n * @param searchString Substring to search for\n * @param position The index at which to begin searching. If omitted, the search begins at the end\n * of the string. Default is `undefined`\n * @returns Index of the last occurrence of searchString found, or -1 if not found.\n */\nexport function lastIndexOf(string: string, searchString: string, position?: number): number {\n let validatedPosition = position === undefined ? stringLength(string) : position;\n\n if (validatedPosition < 0) {\n validatedPosition = 0;\n } else if (validatedPosition >= stringLength(string)) {\n validatedPosition = stringLength(string) - 1;\n }\n\n for (let index = validatedPosition; index >= 0; index--) {\n if (substr(string, index, stringLength(searchString)) === searchString) {\n return index;\n }\n }\n\n return -1;\n}\n\n/**\n * This function mirrors the `length` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes. Since `length` appears to be a\n * reserved keyword, the function was renamed to `stringLength`\n *\n * Returns the length of a string.\n *\n * @param string String to return the length for\n * @returns Number that is length of the starting string\n */\nexport function stringLength(string: string): number {\n return stringzLength(string);\n}\n\n/**\n * This function mirrors the `normalize` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns the Unicode Normalization Form of this string.\n *\n * @param string The starting string\n * @param form Form specifying the Unicode Normalization Form. Default is `'NFC'`\n * @returns A string containing the Unicode Normalization Form of the given string.\n */\nexport function normalize(string: string, form: 'NFC' | 'NFD' | 'NFKC' | 'NFKD' | 'none'): string {\n const upperCaseForm = form.toUpperCase();\n if (upperCaseForm === 'NONE') {\n return string;\n }\n return string.normalize(upperCaseForm);\n}\n\n/**\n * Compares two strings using an ordinal comparison approach based on the specified collation\n * options. This function uses the built-in `localeCompare` method with the 'en' locale and the\n * provided collation options to compare the strings.\n *\n * @param string1 The first string to compare.\n * @param string2 The second string to compare.\n * @param options Optional. The collation options used for comparison.\n * @returns A number indicating the result of the comparison: - Negative value if string1 precedes\n * string2 in sorting order. - Zero if string1 and string2 are equivalent in sorting order. -\n * Positive value if string1 follows string2 in sorting order.\n */\nexport function ordinalCompare(\n string1: string,\n string2: string,\n options?: Intl.CollatorOptions,\n): number {\n return string1.localeCompare(string2, 'en', options);\n}\n\n/**\n * This function mirrors the `padEnd` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Pads this string with another string (multiple times, if needed) until the resulting string\n * reaches the given length. The padding is applied from the end of this string.\n *\n * @param string String to add padding too\n * @param targetLength The length of the resulting string once the starting string has been padded.\n * If value is less than or equal to length(string), then string is returned as is.\n * @param padString The string to pad the current string with. If padString is too long to stay\n * within targetLength, it will be truncated. Default is `\" \"`\n * @returns String with appropriate padding at the end\n */\n// Note: Limit with padString only works when length(padString) = 1, will be fixed with https://github.com/sallar/stringz/pull/59\nexport function padEnd(string: string, targetLength: number, padString: string = ' '): string {\n if (targetLength <= stringLength(string)) return string;\n return stringzLimit(string, targetLength, padString, 'right');\n}\n\n/**\n * This function mirrors the `padStart` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Pads this string with another string (multiple times, if needed) until the resulting string\n * reaches the given length. The padding is applied from the start of this string.\n *\n * @param string String to add padding too\n * @param targetLength The length of the resulting string once the starting string has been padded.\n * If value is less than or equal to length(string), then string is returned as is.\n * @param padString The string to pad the current string with. If padString is too long to stay\n * within the targetLength, it will be truncated from the end. Default is `\" \"`\n * @returns String with of specified targetLength with padString applied from the start\n */\n// Note: Limit with padString only works when length(padString) = 1, will be fixed with https://github.com/sallar/stringz/pull/59\nexport function padStart(string: string, targetLength: number, padString: string = ' '): string {\n if (targetLength <= stringLength(string)) return string;\n return stringzLimit(string, targetLength, padString, 'left');\n}\n\n// This is a helper function that performs a correction on the slice index to make sure it\n// cannot go out of bounds\nfunction correctSliceIndex(length: number, index: number) {\n if (index > length) return length;\n if (index < -length) return 0;\n if (index < 0) return index + length;\n return index;\n}\n\n/**\n * This function mirrors the `slice` function from the JavaScript Standard String object. It handles\n * Unicode code points instead of UTF-16 character codes.\n *\n * Extracts a section of this string and returns it as a new string, without modifying the original\n * string.\n *\n * @param string The starting string\n * @param indexStart The index of the first character to include in the returned substring.\n * @param indexEnd The index of the first character to exclude from the returned substring.\n * @returns A new string containing the extracted section of the string.\n */\nexport function slice(string: string, indexStart: number, indexEnd?: number): string {\n const length: number = stringLength(string);\n if (\n indexStart > length ||\n (indexEnd &&\n ((indexStart > indexEnd &&\n !(indexStart >= 0 && indexStart < length && indexEnd < 0 && indexEnd > -length)) ||\n indexEnd < -length))\n )\n return '';\n\n const newStart = correctSliceIndex(length, indexStart);\n const newEnd = indexEnd ? correctSliceIndex(length, indexEnd) : undefined;\n\n return substring(string, newStart, newEnd);\n}\n\n/**\n * This function mirrors the `split` function from the JavaScript Standard String object. It handles\n * Unicode code points instead of UTF-16 character codes.\n *\n * Takes a pattern and divides the string into an ordered list of substrings by searching for the\n * pattern, puts these substrings into an array, and returns the array.\n *\n * @param string The string to split\n * @param separator The pattern describing where each split should occur\n * @param splitLimit Limit on the number of substrings to be included in the array. Splits the\n * string at each occurrence of specified separator, but stops when limit entries have been placed\n * in the array.\n * @returns An array of strings, split at each point where separator occurs in the starting string.\n * Returns undefined if separator is not found in string.\n */\nexport function split(string: string, separator: string | RegExp, splitLimit?: number): string[] {\n const result: string[] = [];\n\n if (splitLimit !== undefined && splitLimit <= 0) {\n return [string];\n }\n\n if (separator === '') return toArray(string).slice(0, splitLimit);\n\n let regexSeparator = separator;\n if (\n typeof separator === 'string' ||\n (separator instanceof RegExp && !includes(separator.flags, 'g'))\n ) {\n regexSeparator = new RegExp(separator, 'g');\n }\n\n const matches: RegExpMatchArray | null = string.match(regexSeparator);\n\n let currentIndex = 0;\n\n if (!matches) return [string];\n\n for (let index = 0; index < (splitLimit ? splitLimit - 1 : matches.length); index++) {\n const matchIndex = indexOf(string, matches[index], currentIndex);\n const matchLength = stringLength(matches[index]);\n\n result.push(substring(string, currentIndex, matchIndex));\n currentIndex = matchIndex + matchLength;\n\n if (splitLimit !== undefined && result.length === splitLimit) {\n break;\n }\n }\n\n result.push(substring(string, currentIndex));\n\n return result;\n}\n\n/**\n * This function mirrors the `startsWith` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Determines whether the string begins with the characters of a specified string, returning true or\n * false as appropriate.\n *\n * @param string String to search through\n * @param searchString The characters to be searched for at the start of this string.\n * @param position The start position at which searchString is expected to be found (the index of\n * searchString's first character). Default is `0`\n * @returns True if the given characters are found at the beginning of the string, including when\n * searchString is an empty string; otherwise, false.\n */\nexport function startsWith(string: string, searchString: string, position: number = 0): boolean {\n const indexOfSearchString = indexOf(string, searchString, position);\n if (indexOfSearchString !== position) return false;\n return true;\n}\n\n/**\n * This function mirrors the `substr` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns a substring by providing start and length. This function is not exported because it is\n * considered deprecated, however it is still useful as a local helper function.\n *\n * @param string String to be divided\n * @param begin Start position. Default is `Start of string`\n * @param len Length of result. Default is `String length minus start parameter`. Default is `String\n * length minus start parameter`\n * @returns Substring from starting string\n */\nfunction substr(\n string: string,\n begin: number = 0,\n len: number = stringLength(string) - begin,\n): string {\n return stringzSubstr(string, begin, len);\n}\n\n/**\n * This function mirrors the `substring` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns a substring by providing start and end position.\n *\n * @param string String to be divided\n * @param begin Start position\n * @param end End position. Default is `End of string`\n * @returns Substring from starting string\n */\nexport function substring(\n string: string,\n begin: number,\n end: number = stringLength(string),\n): string {\n return stringzSubstring(string, begin, end);\n}\n\n/**\n * This function mirrors the `toArray` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Converts a string to an array of string characters.\n *\n * @param string String to convert to array\n * @returns An array of characters from the starting string\n */\nexport function toArray(string: string): string[] {\n return stringzToArray(string);\n}\n\n/** Determine whether the string is a `LocalizeKey` meant to be localized in Platform.Bible. */\nexport function isLocalizeKey(str: string): str is LocalizeKey {\n return startsWith(str, '%') && endsWith(str, '%');\n}\n\n/** This is an internal-only export for testing purposes and should not be used in development */\nexport const testingStringUtils = {\n indexOfClosestClosingCurlyBrace,\n};\n","import { Canon } from '@sillsdev/scripture';\nimport { BookInfo, ScriptureReference } from './scripture.model';\nimport { split, startsWith } from './string-util';\n\nconst scrBookData: BookInfo[] = [\n { shortName: 'ERR', fullNames: ['ERROR'], chapters: -1 },\n { shortName: 'GEN', fullNames: ['Genesis'], chapters: 50 },\n { shortName: 'EXO', fullNames: ['Exodus'], chapters: 40 },\n { shortName: 'LEV', fullNames: ['Leviticus'], chapters: 27 },\n { shortName: 'NUM', fullNames: ['Numbers'], chapters: 36 },\n { shortName: 'DEU', fullNames: ['Deuteronomy'], chapters: 34 },\n { shortName: 'JOS', fullNames: ['Joshua'], chapters: 24 },\n { shortName: 'JDG', fullNames: ['Judges'], chapters: 21 },\n { shortName: 'RUT', fullNames: ['Ruth'], chapters: 4 },\n { shortName: '1SA', fullNames: ['1 Samuel'], chapters: 31 },\n { shortName: '2SA', fullNames: ['2 Samuel'], chapters: 24 },\n { shortName: '1KI', fullNames: ['1 Kings'], chapters: 22 },\n { shortName: '2KI', fullNames: ['2 Kings'], chapters: 25 },\n { shortName: '1CH', fullNames: ['1 Chronicles'], chapters: 29 },\n { shortName: '2CH', fullNames: ['2 Chronicles'], chapters: 36 },\n { shortName: 'EZR', fullNames: ['Ezra'], chapters: 10 },\n { shortName: 'NEH', fullNames: ['Nehemiah'], chapters: 13 },\n { shortName: 'EST', fullNames: ['Esther'], chapters: 10 },\n { shortName: 'JOB', fullNames: ['Job'], chapters: 42 },\n { shortName: 'PSA', fullNames: ['Psalm', 'Psalms'], chapters: 150 },\n { shortName: 'PRO', fullNames: ['Proverbs'], chapters: 31 },\n { shortName: 'ECC', fullNames: ['Ecclesiastes'], chapters: 12 },\n { shortName: 'SNG', fullNames: ['Song of Solomon', 'Song of Songs'], chapters: 8 },\n { shortName: 'ISA', fullNames: ['Isaiah'], chapters: 66 },\n { shortName: 'JER', fullNames: ['Jeremiah'], chapters: 52 },\n { shortName: 'LAM', fullNames: ['Lamentations'], chapters: 5 },\n { shortName: 'EZK', fullNames: ['Ezekiel'], chapters: 48 },\n { shortName: 'DAN', fullNames: ['Daniel'], chapters: 12 },\n { shortName: 'HOS', fullNames: ['Hosea'], chapters: 14 },\n { shortName: 'JOL', fullNames: ['Joel'], chapters: 3 },\n { shortName: 'AMO', fullNames: ['Amos'], chapters: 9 },\n { shortName: 'OBA', fullNames: ['Obadiah'], chapters: 1 },\n { shortName: 'JON', fullNames: ['Jonah'], chapters: 4 },\n { shortName: 'MIC', fullNames: ['Micah'], chapters: 7 },\n { shortName: 'NAM', fullNames: ['Nahum'], chapters: 3 },\n { shortName: 'HAB', fullNames: ['Habakkuk'], chapters: 3 },\n { shortName: 'ZEP', fullNames: ['Zephaniah'], chapters: 3 },\n { shortName: 'HAG', fullNames: ['Haggai'], chapters: 2 },\n { shortName: 'ZEC', fullNames: ['Zechariah'], chapters: 14 },\n { shortName: 'MAL', fullNames: ['Malachi'], chapters: 4 },\n { shortName: 'MAT', fullNames: ['Matthew'], chapters: 28 },\n { shortName: 'MRK', fullNames: ['Mark'], chapters: 16 },\n { shortName: 'LUK', fullNames: ['Luke'], chapters: 24 },\n { shortName: 'JHN', fullNames: ['John'], chapters: 21 },\n { shortName: 'ACT', fullNames: ['Acts'], chapters: 28 },\n { shortName: 'ROM', fullNames: ['Romans'], chapters: 16 },\n { shortName: '1CO', fullNames: ['1 Corinthians'], chapters: 16 },\n { shortName: '2CO', fullNames: ['2 Corinthians'], chapters: 13 },\n { shortName: 'GAL', fullNames: ['Galatians'], chapters: 6 },\n { shortName: 'EPH', fullNames: ['Ephesians'], chapters: 6 },\n { shortName: 'PHP', fullNames: ['Philippians'], chapters: 4 },\n { shortName: 'COL', fullNames: ['Colossians'], chapters: 4 },\n { shortName: '1TH', fullNames: ['1 Thessalonians'], chapters: 5 },\n { shortName: '2TH', fullNames: ['2 Thessalonians'], chapters: 3 },\n { shortName: '1TI', fullNames: ['1 Timothy'], chapters: 6 },\n { shortName: '2TI', fullNames: ['2 Timothy'], chapters: 4 },\n { shortName: 'TIT', fullNames: ['Titus'], chapters: 3 },\n { shortName: 'PHM', fullNames: ['Philemon'], chapters: 1 },\n { shortName: 'HEB', fullNames: ['Hebrews'], chapters: 13 },\n { shortName: 'JAS', fullNames: ['James'], chapters: 5 },\n { shortName: '1PE', fullNames: ['1 Peter'], chapters: 5 },\n { shortName: '2PE', fullNames: ['2 Peter'], chapters: 3 },\n { shortName: '1JN', fullNames: ['1 John'], chapters: 5 },\n { shortName: '2JN', fullNames: ['2 John'], chapters: 1 },\n { shortName: '3JN', fullNames: ['3 John'], chapters: 1 },\n { shortName: 'JUD', fullNames: ['Jude'], chapters: 1 },\n { shortName: 'REV', fullNames: ['Revelation'], chapters: 22 },\n];\n\nexport const FIRST_SCR_BOOK_NUM = 1;\nexport const LAST_SCR_BOOK_NUM = scrBookData.length - 1;\nexport const FIRST_SCR_CHAPTER_NUM = 1;\nexport const FIRST_SCR_VERSE_NUM = 1;\n\nexport const getChaptersForBook = (bookNum: number): number => {\n return scrBookData[bookNum]?.chapters ?? -1;\n};\n\nexport const offsetBook = (scrRef: ScriptureReference, offset: number): ScriptureReference => ({\n bookNum: Math.max(FIRST_SCR_BOOK_NUM, Math.min(scrRef.bookNum + offset, LAST_SCR_BOOK_NUM)),\n chapterNum: 1,\n verseNum: 1,\n});\n\nexport const offsetChapter = (scrRef: ScriptureReference, offset: number): ScriptureReference => ({\n ...scrRef,\n chapterNum: Math.min(\n Math.max(FIRST_SCR_CHAPTER_NUM, scrRef.chapterNum + offset),\n getChaptersForBook(scrRef.bookNum),\n ),\n verseNum: 1,\n});\n\nexport const offsetVerse = (scrRef: ScriptureReference, offset: number): ScriptureReference => ({\n ...scrRef,\n verseNum: Math.max(FIRST_SCR_VERSE_NUM, scrRef.verseNum + offset),\n});\n\n/**\n * https://github.com/ubsicap/Paratext/blob/master/ParatextData/SILScriptureExtensions.cs#L72\n *\n * Convert book number to a localized Id (a short description of the book). This should be used\n * whenever a book ID (short code) is shown to the user. It is primarily needed for people who do\n * not read Roman script well and need to have books identified in a alternate script (e.g. Chinese\n * or Russian)\n *\n * @param bookNumber\n * @param localizationLanguage In BCP 47 format\n * @param getLocalizedString Function that provides the localized versions of the book ids and names\n * asynchronously.\n * @returns\n */\nexport async function getLocalizedIdFromBookNumber(\n bookNumber: number,\n localizationLanguage: string,\n getLocalizedString: (item: {\n localizeKey: string;\n languagesToSearch?: string[];\n }) => Promise,\n) {\n const id = Canon.bookNumberToId(bookNumber);\n\n if (!startsWith(Intl.getCanonicalLocales(localizationLanguage)[0], 'zh'))\n return getLocalizedString({\n localizeKey: `LocalizedId.${id}`,\n languagesToSearch: [localizationLanguage],\n });\n\n // For Chinese the normal book name is already fairly short.\n const bookName = await getLocalizedString({\n localizeKey: `Book.${id}`,\n languagesToSearch: [localizationLanguage],\n });\n const parts = split(bookName, '-');\n // some entries had a second name inside ideographic parenthesis\n const parts2 = split(parts[0], '\\xff08');\n const retVal = parts2[0].trim();\n return retVal;\n}\n","/** Function to run to dispose of something. Returns true if successfully unsubscribed */\nexport type Unsubscriber = () => boolean;\n\n/**\n * Returns an Unsubscriber function that combines all the unsubscribers passed in.\n *\n * @param unsubscribers All unsubscribers to aggregate into one unsubscriber\n * @returns Function that unsubscribes from all passed in unsubscribers when run\n */\nexport const aggregateUnsubscribers = (unsubscribers: Unsubscriber[]): Unsubscriber => {\n return (...args) => {\n // Run the unsubscriber for each handler\n const unsubs = unsubscribers.map((unsubscriber) => unsubscriber(...args));\n\n // If all the unsubscribers resolve to truthiness, we succeed\n return unsubs.every((success) => success);\n };\n};\n\n/**\n * Function to run to dispose of something that runs asynchronously. The promise resolves to true if\n * successfully unsubscribed\n */\nexport type UnsubscriberAsync = () => Promise;\n\n/**\n * Returns an UnsubscriberAsync function that combines all the unsubscribers passed in.\n *\n * @param unsubscribers - All unsubscribers to aggregate into one unsubscriber.\n * @returns Function that unsubscribes from all passed in unsubscribers when run\n */\nexport const aggregateUnsubscriberAsyncs = (\n unsubscribers: (UnsubscriberAsync | Unsubscriber)[],\n): UnsubscriberAsync => {\n return async (...args) => {\n // Run the unsubscriber for each handler\n const unsubPromises = unsubscribers.map(async (unsubscriber) => unsubscriber(...args));\n\n // If all the unsubscribers resolve to truthiness, we succeed\n return (await Promise.all(unsubPromises)).every((success) => success);\n };\n};\n","var getOwnPropertyNames = Object.getOwnPropertyNames, getOwnPropertySymbols = Object.getOwnPropertySymbols;\nvar hasOwnProperty = Object.prototype.hasOwnProperty;\n/**\n * Combine two comparators into a single comparators.\n */\nfunction combineComparators(comparatorA, comparatorB) {\n return function isEqual(a, b, state) {\n return comparatorA(a, b, state) && comparatorB(a, b, state);\n };\n}\n/**\n * Wrap the provided `areItemsEqual` method to manage the circular state, allowing\n * for circular references to be safely included in the comparison without creating\n * stack overflows.\n */\nfunction createIsCircular(areItemsEqual) {\n return function isCircular(a, b, state) {\n if (!a || !b || typeof a !== 'object' || typeof b !== 'object') {\n return areItemsEqual(a, b, state);\n }\n var cache = state.cache;\n var cachedA = cache.get(a);\n var cachedB = cache.get(b);\n if (cachedA && cachedB) {\n return cachedA === b && cachedB === a;\n }\n cache.set(a, b);\n cache.set(b, a);\n var result = areItemsEqual(a, b, state);\n cache.delete(a);\n cache.delete(b);\n return result;\n };\n}\n/**\n * Get the properties to strictly examine, which include both own properties that are\n * not enumerable and symbol properties.\n */\nfunction getStrictProperties(object) {\n return getOwnPropertyNames(object).concat(getOwnPropertySymbols(object));\n}\n/**\n * Whether the object contains the property passed as an own property.\n */\nvar hasOwn = Object.hasOwn ||\n (function (object, property) {\n return hasOwnProperty.call(object, property);\n });\n/**\n * Whether the values passed are strictly equal or both NaN.\n */\nfunction sameValueZeroEqual(a, b) {\n return a || b ? a === b : a === b || (a !== a && b !== b);\n}\n\nvar OWNER = '_owner';\nvar getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor, keys = Object.keys;\n/**\n * Whether the arrays are equal in value.\n */\nfunction areArraysEqual(a, b, state) {\n var index = a.length;\n if (b.length !== index) {\n return false;\n }\n while (index-- > 0) {\n if (!state.equals(a[index], b[index], index, index, a, b, state)) {\n return false;\n }\n }\n return true;\n}\n/**\n * Whether the dates passed are equal in value.\n */\nfunction areDatesEqual(a, b) {\n return sameValueZeroEqual(a.getTime(), b.getTime());\n}\n/**\n * Whether the `Map`s are equal in value.\n */\nfunction areMapsEqual(a, b, state) {\n if (a.size !== b.size) {\n return false;\n }\n var matchedIndices = {};\n var aIterable = a.entries();\n var index = 0;\n var aResult;\n var bResult;\n while ((aResult = aIterable.next())) {\n if (aResult.done) {\n break;\n }\n var bIterable = b.entries();\n var hasMatch = false;\n var matchIndex = 0;\n while ((bResult = bIterable.next())) {\n if (bResult.done) {\n break;\n }\n var _a = aResult.value, aKey = _a[0], aValue = _a[1];\n var _b = bResult.value, bKey = _b[0], bValue = _b[1];\n if (!hasMatch &&\n !matchedIndices[matchIndex] &&\n (hasMatch =\n state.equals(aKey, bKey, index, matchIndex, a, b, state) &&\n state.equals(aValue, bValue, aKey, bKey, a, b, state))) {\n matchedIndices[matchIndex] = true;\n }\n matchIndex++;\n }\n if (!hasMatch) {\n return false;\n }\n index++;\n }\n return true;\n}\n/**\n * Whether the objects are equal in value.\n */\nfunction areObjectsEqual(a, b, state) {\n var properties = keys(a);\n var index = properties.length;\n if (keys(b).length !== index) {\n return false;\n }\n var property;\n // Decrementing `while` showed faster results than either incrementing or\n // decrementing `for` loop and than an incrementing `while` loop. Declarative\n // methods like `some` / `every` were not used to avoid incurring the garbage\n // cost of anonymous callbacks.\n while (index-- > 0) {\n property = properties[index];\n if (property === OWNER &&\n (a.$$typeof || b.$$typeof) &&\n a.$$typeof !== b.$$typeof) {\n return false;\n }\n if (!hasOwn(b, property) ||\n !state.equals(a[property], b[property], property, property, a, b, state)) {\n return false;\n }\n }\n return true;\n}\n/**\n * Whether the objects are equal in value with strict property checking.\n */\nfunction areObjectsEqualStrict(a, b, state) {\n var properties = getStrictProperties(a);\n var index = properties.length;\n if (getStrictProperties(b).length !== index) {\n return false;\n }\n var property;\n var descriptorA;\n var descriptorB;\n // Decrementing `while` showed faster results than either incrementing or\n // decrementing `for` loop and than an incrementing `while` loop. Declarative\n // methods like `some` / `every` were not used to avoid incurring the garbage\n // cost of anonymous callbacks.\n while (index-- > 0) {\n property = properties[index];\n if (property === OWNER &&\n (a.$$typeof || b.$$typeof) &&\n a.$$typeof !== b.$$typeof) {\n return false;\n }\n if (!hasOwn(b, property)) {\n return false;\n }\n if (!state.equals(a[property], b[property], property, property, a, b, state)) {\n return false;\n }\n descriptorA = getOwnPropertyDescriptor(a, property);\n descriptorB = getOwnPropertyDescriptor(b, property);\n if ((descriptorA || descriptorB) &&\n (!descriptorA ||\n !descriptorB ||\n descriptorA.configurable !== descriptorB.configurable ||\n descriptorA.enumerable !== descriptorB.enumerable ||\n descriptorA.writable !== descriptorB.writable)) {\n return false;\n }\n }\n return true;\n}\n/**\n * Whether the primitive wrappers passed are equal in value.\n */\nfunction arePrimitiveWrappersEqual(a, b) {\n return sameValueZeroEqual(a.valueOf(), b.valueOf());\n}\n/**\n * Whether the regexps passed are equal in value.\n */\nfunction areRegExpsEqual(a, b) {\n return a.source === b.source && a.flags === b.flags;\n}\n/**\n * Whether the `Set`s are equal in value.\n */\nfunction areSetsEqual(a, b, state) {\n if (a.size !== b.size) {\n return false;\n }\n var matchedIndices = {};\n var aIterable = a.values();\n var aResult;\n var bResult;\n while ((aResult = aIterable.next())) {\n if (aResult.done) {\n break;\n }\n var bIterable = b.values();\n var hasMatch = false;\n var matchIndex = 0;\n while ((bResult = bIterable.next())) {\n if (bResult.done) {\n break;\n }\n if (!hasMatch &&\n !matchedIndices[matchIndex] &&\n (hasMatch = state.equals(aResult.value, bResult.value, aResult.value, bResult.value, a, b, state))) {\n matchedIndices[matchIndex] = true;\n }\n matchIndex++;\n }\n if (!hasMatch) {\n return false;\n }\n }\n return true;\n}\n/**\n * Whether the TypedArray instances are equal in value.\n */\nfunction areTypedArraysEqual(a, b) {\n var index = a.length;\n if (b.length !== index) {\n return false;\n }\n while (index-- > 0) {\n if (a[index] !== b[index]) {\n return false;\n }\n }\n return true;\n}\n\nvar ARGUMENTS_TAG = '[object Arguments]';\nvar BOOLEAN_TAG = '[object Boolean]';\nvar DATE_TAG = '[object Date]';\nvar MAP_TAG = '[object Map]';\nvar NUMBER_TAG = '[object Number]';\nvar OBJECT_TAG = '[object Object]';\nvar REG_EXP_TAG = '[object RegExp]';\nvar SET_TAG = '[object Set]';\nvar STRING_TAG = '[object String]';\nvar isArray = Array.isArray;\nvar isTypedArray = typeof ArrayBuffer === 'function' && ArrayBuffer.isView\n ? ArrayBuffer.isView\n : null;\nvar assign = Object.assign;\nvar getTag = Object.prototype.toString.call.bind(Object.prototype.toString);\n/**\n * Create a comparator method based on the type-specific equality comparators passed.\n */\nfunction createEqualityComparator(_a) {\n var areArraysEqual = _a.areArraysEqual, areDatesEqual = _a.areDatesEqual, areMapsEqual = _a.areMapsEqual, areObjectsEqual = _a.areObjectsEqual, arePrimitiveWrappersEqual = _a.arePrimitiveWrappersEqual, areRegExpsEqual = _a.areRegExpsEqual, areSetsEqual = _a.areSetsEqual, areTypedArraysEqual = _a.areTypedArraysEqual;\n /**\n * compare the value of the two objects and return true if they are equivalent in values\n */\n return function comparator(a, b, state) {\n // If the items are strictly equal, no need to do a value comparison.\n if (a === b) {\n return true;\n }\n // If the items are not non-nullish objects, then the only possibility\n // of them being equal but not strictly is if they are both `NaN`. Since\n // `NaN` is uniquely not equal to itself, we can use self-comparison of\n // both objects, which is faster than `isNaN()`.\n if (a == null ||\n b == null ||\n typeof a !== 'object' ||\n typeof b !== 'object') {\n return a !== a && b !== b;\n }\n var constructor = a.constructor;\n // Checks are listed in order of commonality of use-case:\n // 1. Common complex object types (plain object, array)\n // 2. Common data values (date, regexp)\n // 3. Less-common complex object types (map, set)\n // 4. Less-common data values (promise, primitive wrappers)\n // Inherently this is both subjective and assumptive, however\n // when reviewing comparable libraries in the wild this order\n // appears to be generally consistent.\n // Constructors should match, otherwise there is potential for false positives\n // between class and subclass or custom object and POJO.\n if (constructor !== b.constructor) {\n return false;\n }\n // `isPlainObject` only checks against the object's own realm. Cross-realm\n // comparisons are rare, and will be handled in the ultimate fallback, so\n // we can avoid capturing the string tag.\n if (constructor === Object) {\n return areObjectsEqual(a, b, state);\n }\n // `isArray()` works on subclasses and is cross-realm, so we can avoid capturing\n // the string tag or doing an `instanceof` check.\n if (isArray(a)) {\n return areArraysEqual(a, b, state);\n }\n // `isTypedArray()` works on all possible TypedArray classes, so we can avoid\n // capturing the string tag or comparing against all possible constructors.\n if (isTypedArray != null && isTypedArray(a)) {\n return areTypedArraysEqual(a, b, state);\n }\n // Try to fast-path equality checks for other complex object types in the\n // same realm to avoid capturing the string tag. Strict equality is used\n // instead of `instanceof` because it is more performant for the common\n // use-case. If someone is subclassing a native class, it will be handled\n // with the string tag comparison.\n if (constructor === Date) {\n return areDatesEqual(a, b, state);\n }\n if (constructor === RegExp) {\n return areRegExpsEqual(a, b, state);\n }\n if (constructor === Map) {\n return areMapsEqual(a, b, state);\n }\n if (constructor === Set) {\n return areSetsEqual(a, b, state);\n }\n // Since this is a custom object, capture the string tag to determing its type.\n // This is reasonably performant in modern environments like v8 and SpiderMonkey.\n var tag = getTag(a);\n if (tag === DATE_TAG) {\n return areDatesEqual(a, b, state);\n }\n if (tag === REG_EXP_TAG) {\n return areRegExpsEqual(a, b, state);\n }\n if (tag === MAP_TAG) {\n return areMapsEqual(a, b, state);\n }\n if (tag === SET_TAG) {\n return areSetsEqual(a, b, state);\n }\n if (tag === OBJECT_TAG) {\n // The exception for value comparison is custom `Promise`-like class instances. These should\n // be treated the same as standard `Promise` objects, which means strict equality, and if\n // it reaches this point then that strict equality comparison has already failed.\n return (typeof a.then !== 'function' &&\n typeof b.then !== 'function' &&\n areObjectsEqual(a, b, state));\n }\n // If an arguments tag, it should be treated as a standard object.\n if (tag === ARGUMENTS_TAG) {\n return areObjectsEqual(a, b, state);\n }\n // As the penultimate fallback, check if the values passed are primitive wrappers. This\n // is very rare in modern JS, which is why it is deprioritized compared to all other object\n // types.\n if (tag === BOOLEAN_TAG || tag === NUMBER_TAG || tag === STRING_TAG) {\n return arePrimitiveWrappersEqual(a, b, state);\n }\n // If not matching any tags that require a specific type of comparison, then we hard-code false because\n // the only thing remaining is strict equality, which has already been compared. This is for a few reasons:\n // - Certain types that cannot be introspected (e.g., `WeakMap`). For these types, this is the only\n // comparison that can be made.\n // - For types that can be introspected, but rarely have requirements to be compared\n // (`ArrayBuffer`, `DataView`, etc.), the cost is avoided to prioritize the common\n // use-cases (may be included in a future release, if requested enough).\n // - For types that can be introspected but do not have an objective definition of what\n // equality is (`Error`, etc.), the subjective decision is to be conservative and strictly compare.\n // In all cases, these decisions should be reevaluated based on changes to the language and\n // common development practices.\n return false;\n };\n}\n/**\n * Create the configuration object used for building comparators.\n */\nfunction createEqualityComparatorConfig(_a) {\n var circular = _a.circular, createCustomConfig = _a.createCustomConfig, strict = _a.strict;\n var config = {\n areArraysEqual: strict\n ? areObjectsEqualStrict\n : areArraysEqual,\n areDatesEqual: areDatesEqual,\n areMapsEqual: strict\n ? combineComparators(areMapsEqual, areObjectsEqualStrict)\n : areMapsEqual,\n areObjectsEqual: strict\n ? areObjectsEqualStrict\n : areObjectsEqual,\n arePrimitiveWrappersEqual: arePrimitiveWrappersEqual,\n areRegExpsEqual: areRegExpsEqual,\n areSetsEqual: strict\n ? combineComparators(areSetsEqual, areObjectsEqualStrict)\n : areSetsEqual,\n areTypedArraysEqual: strict\n ? areObjectsEqualStrict\n : areTypedArraysEqual,\n };\n if (createCustomConfig) {\n config = assign({}, config, createCustomConfig(config));\n }\n if (circular) {\n var areArraysEqual$1 = createIsCircular(config.areArraysEqual);\n var areMapsEqual$1 = createIsCircular(config.areMapsEqual);\n var areObjectsEqual$1 = createIsCircular(config.areObjectsEqual);\n var areSetsEqual$1 = createIsCircular(config.areSetsEqual);\n config = assign({}, config, {\n areArraysEqual: areArraysEqual$1,\n areMapsEqual: areMapsEqual$1,\n areObjectsEqual: areObjectsEqual$1,\n areSetsEqual: areSetsEqual$1,\n });\n }\n return config;\n}\n/**\n * Default equality comparator pass-through, used as the standard `isEqual` creator for\n * use inside the built comparator.\n */\nfunction createInternalEqualityComparator(compare) {\n return function (a, b, _indexOrKeyA, _indexOrKeyB, _parentA, _parentB, state) {\n return compare(a, b, state);\n };\n}\n/**\n * Create the `isEqual` function used by the consuming application.\n */\nfunction createIsEqual(_a) {\n var circular = _a.circular, comparator = _a.comparator, createState = _a.createState, equals = _a.equals, strict = _a.strict;\n if (createState) {\n return function isEqual(a, b) {\n var _a = createState(), _b = _a.cache, cache = _b === void 0 ? circular ? new WeakMap() : undefined : _b, meta = _a.meta;\n return comparator(a, b, {\n cache: cache,\n equals: equals,\n meta: meta,\n strict: strict,\n });\n };\n }\n if (circular) {\n return function isEqual(a, b) {\n return comparator(a, b, {\n cache: new WeakMap(),\n equals: equals,\n meta: undefined,\n strict: strict,\n });\n };\n }\n var state = {\n cache: undefined,\n equals: equals,\n meta: undefined,\n strict: strict,\n };\n return function isEqual(a, b) {\n return comparator(a, b, state);\n };\n}\n\n/**\n * Whether the items passed are deeply-equal in value.\n */\nvar deepEqual = createCustomEqual();\n/**\n * Whether the items passed are deeply-equal in value based on strict comparison.\n */\nvar strictDeepEqual = createCustomEqual({ strict: true });\n/**\n * Whether the items passed are deeply-equal in value, including circular references.\n */\nvar circularDeepEqual = createCustomEqual({ circular: true });\n/**\n * Whether the items passed are deeply-equal in value, including circular references,\n * based on strict comparison.\n */\nvar strictCircularDeepEqual = createCustomEqual({\n circular: true,\n strict: true,\n});\n/**\n * Whether the items passed are shallowly-equal in value.\n */\nvar shallowEqual = createCustomEqual({\n createInternalComparator: function () { return sameValueZeroEqual; },\n});\n/**\n * Whether the items passed are shallowly-equal in value based on strict comparison\n */\nvar strictShallowEqual = createCustomEqual({\n strict: true,\n createInternalComparator: function () { return sameValueZeroEqual; },\n});\n/**\n * Whether the items passed are shallowly-equal in value, including circular references.\n */\nvar circularShallowEqual = createCustomEqual({\n circular: true,\n createInternalComparator: function () { return sameValueZeroEqual; },\n});\n/**\n * Whether the items passed are shallowly-equal in value, including circular references,\n * based on strict comparison.\n */\nvar strictCircularShallowEqual = createCustomEqual({\n circular: true,\n createInternalComparator: function () { return sameValueZeroEqual; },\n strict: true,\n});\n/**\n * Create a custom equality comparison method.\n *\n * This can be done to create very targeted comparisons in extreme hot-path scenarios\n * where the standard methods are not performant enough, but can also be used to provide\n * support for legacy environments that do not support expected features like\n * `RegExp.prototype.flags` out of the box.\n */\nfunction createCustomEqual(options) {\n if (options === void 0) { options = {}; }\n var _a = options.circular, circular = _a === void 0 ? false : _a, createCustomInternalComparator = options.createInternalComparator, createState = options.createState, _b = options.strict, strict = _b === void 0 ? false : _b;\n var config = createEqualityComparatorConfig(options);\n var comparator = createEqualityComparator(config);\n var equals = createCustomInternalComparator\n ? createCustomInternalComparator(comparator)\n : createInternalEqualityComparator(comparator);\n return createIsEqual({ circular: circular, comparator: comparator, createState: createState, equals: equals, strict: strict });\n}\n\nexport { circularDeepEqual, circularShallowEqual, createCustomEqual, deepEqual, sameValueZeroEqual, shallowEqual, strictCircularDeepEqual, strictCircularShallowEqual, strictDeepEqual, strictShallowEqual };\n//# sourceMappingURL=index.mjs.map\n","// There is a circular version https://www.npmjs.com/package/fast-equals#circulardeepequal that I\n// think allows comparing React refs (which have circular references in particular places that this\n// library would ignore). Maybe we can change to that version sometime if needed.\nimport { deepEqual as isEqualDeep } from 'fast-equals';\n\n/**\n * Check that two objects are deeply equal, comparing members of each object and such\n *\n * @param a The first object to compare\n * @param b The second object to compare\n *\n * WARNING: Objects like arrays from different iframes have different constructor function\n * references even if they do the same thing, so this deep equality comparison fails objects that\n * look the same but have different constructors because different constructors could produce\n * false positives in [a few specific\n * situations](https://github.com/planttheidea/fast-equals/blob/a41afc0a240ad5a472e47b53791e9be017f52281/src/comparator.ts#L96).\n * This means that two objects like arrays from different iframes that look the same will fail\n * this check. Please use some other means to check deep equality in those situations.\n *\n * Note: This deep equality check considers `undefined` values on keys of objects NOT to be equal to\n * not specifying the key at all. For example, `{ stuff: 3, things: undefined }` and `{ stuff: 3\n * }` are not considered equal in this case\n *\n * - For more information and examples, see [this\n * CodeSandbox](https://codesandbox.io/s/deepequallibrarycomparison-4g4kk4?file=/src/index.mjs).\n *\n * @returns True if a and b are deeply equal; false otherwise\n */\nexport default function deepEqual(a: unknown, b: unknown) {\n return isEqualDeep(a, b);\n}\n","import deepEqual from './equality-checking';\n\n/**\n * Check if one object is a subset of the other object. \"Subset\" means that all properties of one\n * object are present in the other object, and if they are present that all values of those\n * properties are deeply equal. Sub-objects are also checked to be subsets of the corresponding\n * sub-object in the other object.\n *\n * @example ObjB is a subset of objA given these objects:\n *\n * ```ts\n * objA = { name: 'Alice', age: 30, address: { city: 'Seattle', state: 'Washington' } };\n * objB = { name: 'Alice', address: { city: 'Seattle' } };\n * ```\n *\n * It is important to note that only arrays of primitives (i.e., booleans, numbers, strings) are\n * supported. In particular, objects in arrays will not be checked for deep equality. Also, presence\n * in an array is all this checks, not the number of times that an item appears in an array. `[1,\n * 1]` is a subset of `[1]`.\n *\n * @param objectWithAllProperties Object to be checked if it is a superset of\n * `objectWithPartialProperties`\n * @param objectWithPartialProperties Object to be checked if it is a subset of\n * `objectWithAllProperties`\n * @returns True if `objectWithAllProperties` contains all the properties of\n * `objectWithPartialProperties` and all values of those properties are deeply equal\n */\nexport default function isSubset(\n objectWithAllProperties: unknown,\n objectWithPartialProperties: unknown,\n): boolean {\n if (typeof objectWithAllProperties !== typeof objectWithPartialProperties) return false;\n\n // For this function we're saying that all falsy things of the same type are equal to each other\n if (!objectWithAllProperties && !objectWithPartialProperties) return true;\n\n if (Array.isArray(objectWithAllProperties)) {\n // We know these are arrays from the line above\n /* eslint-disable no-type-assertion/no-type-assertion */\n const partialArray = objectWithPartialProperties as Array;\n const allArray = objectWithAllProperties as Array;\n /* eslint-enable no-type-assertion/no-type-assertion */\n\n if (partialArray.length === 0) return true;\n\n // This only works with arrays of primitives.\n // If someone cares about checking arrays of objects this needs updating.\n return partialArray.every((item) => allArray.includes(item));\n }\n\n if (typeof objectWithAllProperties !== 'object')\n return deepEqual(objectWithAllProperties, objectWithPartialProperties);\n\n // We know these are objects that potentially have properties because of the earlier checks\n /* eslint-disable no-type-assertion/no-type-assertion */\n const partialObj = objectWithPartialProperties as Record;\n const allObj = objectWithAllProperties as Record;\n /* eslint-enable no-type-assertion/no-type-assertion */\n\n let retVal = true;\n Object.keys(partialObj).forEach((key) => {\n if (!retVal) return;\n if (!Object.hasOwn(allObj, key)) retVal = false;\n else if (!isSubset(allObj[key], partialObj[key])) retVal = false;\n });\n return retVal;\n}\n","/**\n * Converts a JavaScript value to a JSON string, changing `undefined` properties in the JavaScript\n * object to `null` properties in the JSON string.\n *\n * WARNING: `null` values will become `undefined` values after passing through {@link serialize} then\n * {@link deserialize}. For example, `{ a: 1, b: undefined, c: null }` will become `{ a: 1, b:\n * undefined, c: undefined }`. If you are passing around user data that needs to retain `null`\n * values, you should wrap them yourself in a string before using this function. Alternatively, you\n * can write your own replacer that will preserve `null` in a way that you can recover later.\n *\n * @param value A JavaScript value, usually an object or array, to be converted.\n * @param replacer A function that transforms the results. Note that all `undefined` values returned\n * by the replacer will be further transformed into `null` in the JSON string.\n * @param space Adds indentation, white space, and line break characters to the return-value JSON\n * text to make it easier to read. See the `space` parameter of `JSON.stringify` for more\n * details.\n */\nexport function serialize(\n value: unknown,\n replacer?: (this: unknown, key: string, value: unknown) => unknown,\n space?: string | number,\n): string {\n const undefinedReplacer = (replacerKey: string, replacerValue: unknown) => {\n let newValue = replacerValue;\n if (replacer) newValue = replacer(replacerKey, newValue);\n // All `undefined` values become `null` on the way from JS objects into JSON strings\n // eslint-disable-next-line no-null/no-null\n if (newValue === undefined) newValue = null;\n return newValue;\n };\n return JSON.stringify(value, undefinedReplacer, space);\n}\n\n/**\n * Converts a JSON string into a value, converting all `null` properties from JSON into `undefined`\n * in the returned JavaScript value/object.\n *\n * WARNING: `null` values will become `undefined` values after passing through {@link serialize} then\n * {@link deserialize}. For example, `{ a: 1, b: undefined, c: null }` will become `{ a: 1, b:\n * undefined, c: undefined }`. If you are passing around user data that needs to retain `null`\n * values, you should wrap them yourself in a string before using this function. Alternatively, you\n * can write your own replacer that will preserve `null` in a way that you can recover later.\n *\n * @param value A valid JSON string.\n * @param reviver A function that transforms the results. This function is called for each member of\n * the object. If a member contains nested objects, the nested objects are transformed before the\n * parent object is. Note that `null` values are converted into `undefined` values after the\n * reviver has run.\n */\nexport function deserialize(\n value: string,\n reviver?: (this: unknown, key: string, value: unknown) => unknown,\n // Need to use `any` instead of `unknown` here to match the signature of JSON.parse\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): any {\n // Helper function to replace `null` with `undefined` on a per property basis. This can't be done\n // with our own reviver because `JSON.parse` removes `undefined` properties from the return value.\n function replaceNull(obj: Record): Record {\n Object.keys(obj).forEach((key: string | number) => {\n // We only want to replace `null`, not other falsy values\n // eslint-disable-next-line no-null/no-null\n if (obj[key] === null) obj[key] = undefined;\n // If the property is an object, recursively call the helper function on it\n else if (typeof obj[key] === 'object')\n // Since the object came from a string, we know the keys will not be symbols\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n obj[key] = replaceNull(obj[key] as Record);\n });\n return obj;\n }\n\n const parsedObject = JSON.parse(value, reviver);\n // Explicitly convert the value 'null' that isn't stored as a property on an object to 'undefined'\n // eslint-disable-next-line no-null/no-null\n if (parsedObject === null) return undefined;\n if (typeof parsedObject === 'object') return replaceNull(parsedObject);\n return parsedObject;\n}\n\n/**\n * Check to see if the value is serializable without losing information\n *\n * @param value Value to test\n * @returns True if serializable; false otherwise\n *\n * Note: the values `undefined` and `null` are serializable (on their own or in an array), but\n * `null` values get transformed into `undefined` when serializing/deserializing.\n *\n * WARNING: This is inefficient right now as it stringifies, parses, stringifies, and === the value.\n * Please only use this if you need to\n *\n * DISCLAIMER: this does not successfully detect that values are not serializable in some cases:\n *\n * - Losses of removed properties like functions and `Map`s\n * - Class instances (not deserializable into class instances without special code)\n *\n * We intend to improve this in the future if it becomes important to do so. See [`JSON.stringify`\n * documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description)\n * for more information.\n */\nexport function isSerializable(value: unknown): boolean {\n try {\n const serializedValue = serialize(value);\n return serializedValue === serialize(deserialize(serializedValue));\n } catch (e) {\n return false;\n }\n}\n\n/**\n * HTML Encodes the provided string. Thanks to ChatGPT\n *\n * @param str String to HTML encode\n * @returns HTML-encoded string\n */\nexport const htmlEncode = (str: string): string =>\n str\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/\\//g, '/');\n","import DateTimeFormat from './intl-date-time-format';\n\n/**\n * Retrieves the current locale of the user's environment.\n *\n * @returns A string representing the current locale. If the locale cannot be determined, the\n * function returns an empty string.\n */\nexport default function getCurrentLocale(): string {\n // Use navigator when available\n if (typeof navigator !== 'undefined' && navigator.languages) {\n return navigator.languages[0];\n }\n // For Node.js\n return new DateTimeFormat().resolvedOptions().locale;\n}\n","//----------------------------------------------------------------------------------------------\n// NOTE: If you change any of the types, make sure the JSON schema at the end of this file gets\n// changed so they align.\n//----------------------------------------------------------------------------------------------\n\nimport { LocalizeKey, ReferencedItem } from 'menus.model';\n\n/** The data an extension provides to inform Platform.Bible of the settings it provides */\nexport type SettingsContribution = SettingsGroup | SettingsGroup[];\n/** A description of an extension's setting entry */\nexport type Setting = ExtensionControlledSetting;\n/** Setting definition that is validated by the extension. */\nexport type ExtensionControlledSetting = SettingBase & ModifierExtensionControlled;\n/** Base information needed to describe a setting entry */\nexport type SettingBase = StateBase & {\n [k: string]: unknown;\n /** LocalizeKey that displays in the settings dialog as the setting name */\n label: LocalizeKey;\n /** LocalizeKey that displays in the settings dialog to describe the setting */\n description?: LocalizeKey;\n};\n/** The data an extension provides to inform Platform.Bible of the project settings it provides */\nexport type ProjectSettingsContribution = ProjectSettingsGroup | ProjectSettingsGroup[];\n/** A description of an extension's setting entry */\nexport type ProjectSetting = ExtensionControlledProjectSetting;\n/** Setting definition that is validated by the extension. */\nexport type ExtensionControlledProjectSetting = ProjectSettingBase & ModifierExtensionControlled;\n/** Base information needed to describe a project setting entry */\nexport type ProjectSettingBase = SettingBase & ModifierProject;\n/** A description of an extension's user state entry */\nexport type UserState = ExtensionControlledState;\n/** State definition that is validated by the extension. */\nexport type ExtensionControlledState = StateBase & ModifierExtensionControlled;\n/** Group of related settings definitions */\nexport interface SettingsGroup {\n [k: string]: unknown;\n /** LocalizeKey that displays in the settings dialog as the group name */\n label: LocalizeKey;\n /** LocalizeKey that displays in the settings dialog to describe the group */\n description?: LocalizeKey;\n properties: SettingProperties;\n}\n/** Object whose keys are setting IDs and whose values are settings objects */\nexport interface SettingProperties {\n [k: ReferencedItem]: Setting;\n}\n/** Base information needed to describe a state entry */\nexport interface StateBase {\n [k: string]: unknown;\n /** Default value for the state/setting */\n default: unknown;\n /**\n * A state/setting ID whose value to set to this state/setting's starting value the first time\n * this state/setting is loaded\n */\n derivesFrom?: ReferencedItem;\n}\n/**\n * Modifies state/setting type to be extension-controlled. \"Extension-controlled\" means the\n * extension provides the component and the validator for the state/setting, so the state/setting is\n * controlled by the extension.\n */\nexport interface ModifierExtensionControlled {\n [k: string]: unknown;\n platformType?: undefined;\n type?: undefined;\n}\n/** Group of related settings definitions */\nexport interface ProjectSettingsGroup {\n [k: string]: unknown;\n /** LocalizeKey that displays in the project settings dialog as the group name */\n label: LocalizeKey;\n /** LocalizeKey that displays in the project settings dialog to describe the group */\n description?: LocalizeKey;\n properties: ProjectSettingProperties;\n}\n/** Object whose keys are setting IDs and whose values are settings objects */\nexport interface ProjectSettingProperties {\n [k: ReferencedItem]: ProjectSetting;\n}\n\n// Note: we removed the index signature on ModifierProject to avoid having it on\n// `ProjectMetadataFilterOptions`. Unfortunately adding \"additionalProperties\": false on the json\n// schema messes up validation. Please remove the index signature again in the future if you\n// regenerate types\nexport interface ModifierProject {\n /**\n * String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s\n * (using the\n * [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)\n * function) to determine if they should be included.\n *\n * If this is one string, it will be matched against `projectInterface`s. If this is an array,\n * each entry is handled based on its type (at least one entry must match for this filter\n * condition to pass):\n *\n * - If the entry is a string, it will be matched against each `projectInterface`. If any match, the\n * project will pass this filter condition\n * - If the entry is an array of strings, each will be matched against each `projectInterface`. If\n * every string matches against at least one `projectInterface`, the project will pass this\n * filter condition\n *\n * In other words, each entry in the first-level array is `OR`'ed together. Each entry in\n * second-level arrays (arrays within the first-level array) are `AND`'ed together.\n *\n * Defaults to all {@link ProjectInterfaces}, so all projects that do not match\n * `excludeProjectInterfaces` will be included\n *\n * @example\n *\n * ```typescript\n * includeProjectInterfaces: ['one', ['two', 'three']];\n * ```\n *\n * This filter condition will succeed on projects whose `projectInterface`s fulfill at least one\n * of the following conditions (At least one entry in the array must match):\n *\n * - Include `one`\n * - Include both `two` and `three`.\n */\n includeProjectInterfaces?: undefined | string | (string | string[])[];\n /**\n * String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s\n * (using the\n * [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)\n * function) to determine if they should absolutely not be included even if they match with\n * `includeProjectInterfaces`.\n *\n * If this is one string, it will be matched against `projectInterface`s. If this is an array,\n * each entry is handled based on its type (at least one entry must match for this filter\n * condition to exclude the project):\n *\n * - If the entry is a string, it will be matched against each `projectInterface`. If any match, the\n * project will pass this filter condition and exclude the project\n * - If the entry is an array of strings, each will be matched against each `projectInterface`. If\n * every string matches against at least one `projectInterface`, the project will pass this\n * filter condition and exclude the project\n *\n * In other words, each entry in the first-level array is `OR`'ed together. Each entry in\n * second-level arrays (arrays within the first-level array) are `AND`'ed together.\n *\n * Defaults to no {@link ProjectInterfaces}, so all projects that match `includeProjectInterfaces`\n * will be included\n *\n * @example\n *\n * ```typescript\n * excludeProjectInterfaces: ['one', ['two', 'three']];\n * ```\n *\n * This filter condition will succeed and exclude projects whose `projectInterface`s fulfill at\n * least one of the following conditions (At least one entry in the array must match):\n *\n * - Include `one`\n * - Include both `two` and `three`.\n */\n excludeProjectInterfaces?: undefined | string | (string | string[])[];\n /**\n * String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory\n * Ids that provided each project's metadata (using the\n * [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)\n * function) to determine if the projects should be included.\n *\n * Defaults to all Project Data Provider Factory Ids, so all projects that do not match\n * `excludePdpFactoryIds` will be included\n */\n includePdpFactoryIds?: undefined | string | string[];\n /**\n * String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory\n * Ids that provided each project's metadata (using the\n * [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)\n * function) to determine if the projects should absolutely not be included even if they match\n * with `includeProjectInterfaces`.\n *\n * Defaults to none, so all projects that match `includePdpFactoryIds` will be included\n */\n excludePdpFactoryIds?: undefined | string | string[];\n}\n\n/** The data an extension provides to inform Platform.Bible of the user state it provides */\nexport interface UserStateContribution {\n [k: ReferencedItem]: UserState;\n}\n/** The data an extension provides to inform Platform.Bible of the project state it provides */\nexport interface ProjectStateContribution {\n [k: ReferencedItem]: UserState;\n}\n\n//----------------------------------------------------------------------------------------------\n// NOTE: If you change the schema below, make sure the TS types above get changed so they align.\n//----------------------------------------------------------------------------------------------\nconst settingsDefs = {\n projectSettingsContribution: {\n description:\n 'The data an extension provides to inform Platform.Bible of the project settings it provides',\n anyOf: [\n {\n $ref: '#/$defs/projectSettingsGroup',\n },\n {\n type: 'array',\n items: {\n $ref: '#/$defs/projectSettingsGroup',\n },\n },\n ],\n },\n projectSettingsGroup: {\n description: 'Group of related settings definitions',\n type: 'object',\n properties: {\n label: {\n description: 'localizeKey that displays in the project settings dialog as the group name',\n $ref: '#/$defs/localizeKey',\n },\n description: {\n description:\n 'localizeKey that displays in the project settings dialog to describe the group',\n $ref: '#/$defs/localizeKey',\n },\n properties: {\n $ref: '#/$defs/projectSettingProperties',\n },\n },\n required: ['label', 'properties'],\n },\n projectSettingProperties: {\n description: 'Object whose keys are setting IDs and whose values are settings objects',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n $ref: '#/$defs/projectSetting',\n },\n },\n additionalProperties: false,\n },\n projectSetting: {\n description: \"A description of an extension's setting entry\",\n anyOf: [\n {\n $ref: '#/$defs/extensionControlledProjectSetting',\n },\n ],\n },\n extensionControlledProjectSetting: {\n description: 'Setting definition that is validated by the extension.',\n allOf: [\n {\n $ref: '#/$defs/projectSettingBase',\n },\n {\n $ref: '#/$defs/modifierExtensionControlled',\n },\n ],\n },\n projectSettingBase: {\n description: 'Base information needed to describe a project setting entry',\n allOf: [\n {\n $ref: '#/$defs/settingBase',\n },\n {\n $ref: '#/$defs/modifierProject',\n },\n ],\n },\n modifierProject: {\n description: 'Modifies setting type to be project setting',\n type: 'object',\n properties: {\n includeProjectInterfaces: {\n description:\n \"String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if they should be included.\\n\\nIf this is one string, it will be matched against `projectInterface`s. If this is an array, each entry is handled based on its type (at least one entry must match for this filter condition to pass):\\n\\n- If the entry is a string, it will be matched against each `projectInterface`. If any match, the project will pass this filter condition\\n- If the entry is an array of strings, each will be matched against each `projectInterface`. If every string matches against at least one `projectInterface`, the project will pass this filter condition\\n\\nIn other words, each entry in the first-level array is `OR`'ed together. Each entry in second-level arrays (arrays within the first-level array) are `AND`'ed together.\\n\\nDefaults to all {@link ProjectInterfaces}, so all projects that do not match `excludeProjectInterfaces` will be included\\n\\n@example\\n\\n```typescript\\nincludeProjectInterfaces: ['one', ['two', 'three']];\\n```\\n\\nThis filter condition will succeed on projects whose `projectInterface`s fulfill at least one of the following conditions (At least one entry in the array must match):\\n\\n- Include `one`\\n- Include both `two` and `three`.\",\n anyOf: [\n {\n type: 'null',\n },\n {\n type: 'string',\n },\n {\n type: 'array',\n items: {\n anyOf: [\n {\n type: 'string',\n },\n {\n type: 'array',\n items: { type: 'string' },\n },\n ],\n },\n },\n ],\n },\n excludeProjectInterfaces: {\n description:\n \"String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if they should absolutely not be included even if they match with `includeProjectInterfaces`.\\n\\nIf this is one string, it will be matched against `projectInterface`s. If this is an array, each entry is handled based on its type (at least one entry must match for this filter condition to exclude the project):\\n\\n- If the entry is a string, it will be matched against each `projectInterface`. If any match, the project will pass this filter condition and exclude the project\\n- If the entry is an array of strings, each will be matched against each `projectInterface`. If every string matches against at least one `projectInterface`, the project will pass this filter condition and exclude the project\\n\\nIn other words, each entry in the first-level array is `OR`'ed together. Each entry in second-level arrays (arrays within the first-level array) are `AND`'ed together.\\n\\nDefaults to no {@link ProjectInterfaces}, so all projects that match `includeProjectInterfaces` will be included\\n\\n@example\\n\\n```typescript\\nexcludeProjectInterfaces: ['one', ['two', 'three']];\\n```\\n\\nThis filter condition will succeed and exclude projects whose `projectInterface`s fulfill at least one of the following conditions (At least one entry in the array must match):\\n\\n- Include `one`\\n- Include both `two` and `three`.\",\n anyOf: [\n {\n type: 'null',\n },\n {\n type: 'string',\n },\n {\n type: 'array',\n items: {\n anyOf: [\n {\n type: 'string',\n },\n {\n type: 'array',\n items: { type: 'string' },\n },\n ],\n },\n },\n ],\n },\n includePdpFactoryIds: {\n description:\n \"String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory Ids that provided each project's metadata (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if the projects should be included.\\n\\nDefaults to all Project Data Provider Factory Ids, so all projects that do not match `excludePdpFactoryIds` will be included\",\n anyOf: [\n {\n type: 'null',\n },\n {\n type: 'string',\n },\n {\n type: 'array',\n items: { type: 'string' },\n },\n ],\n },\n excludePdpFactoryIds: {\n description:\n \"String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory Ids that provided each project's metadata (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if the projects should absolutely not be included even if they match with `includeProjectInterfaces`.\\n\\nDefaults to none, so all projects that match `includePdpFactoryIds` will be included\",\n anyOf: [\n {\n type: 'null',\n },\n {\n type: 'string',\n },\n {\n type: 'array',\n items: { type: 'string' },\n },\n ],\n },\n },\n },\n settingsContribution: {\n description:\n 'The data an extension provides to inform Platform.Bible of the settings it provides',\n anyOf: [\n {\n $ref: '#/$defs/settingsGroup',\n },\n {\n type: 'array',\n items: {\n $ref: '#/$defs/settingsGroup',\n },\n },\n ],\n },\n settingsGroup: {\n description: 'Group of related settings definitions',\n type: 'object',\n properties: {\n label: {\n description: 'localizeKey that displays in the settings dialog as the group name',\n $ref: '#/$defs/localizeKey',\n },\n description: {\n description: 'localizeKey that displays in the settings dialog to describe the group',\n $ref: '#/$defs/localizeKey',\n },\n properties: {\n $ref: '#/$defs/settingProperties',\n },\n },\n required: ['label', 'properties'],\n },\n settingProperties: {\n description: 'Object whose keys are setting IDs and whose values are settings objects',\n type: 'object',\n patternProperties: {\n '^[\\\\w-]+\\\\.[\\\\w-]+$': {\n $ref: '#/$defs/setting',\n },\n },\n additionalProperties: false,\n },\n setting: {\n description: \"A description of an extension's setting entry\",\n anyOf: [\n {\n $ref: '#/$defs/extensionControlledSetting',\n },\n ],\n },\n extensionControlledSetting: {\n description: 'Setting definition that is validated by the extension.',\n allOf: [\n {\n $ref: '#/$defs/settingBase',\n },\n {\n $ref: '#/$defs/modifierExtensionControlled',\n },\n ],\n },\n settingBase: {\n description: 'Base information needed to describe a setting entry',\n allOf: [\n {\n $ref: '#/$defs/stateBase',\n },\n {\n type: 'object',\n properties: {\n label: {\n description: 'localizeKey that displays in the settings dialog as the setting name',\n $ref: '#/$defs/localizeKey',\n },\n description: {\n description: 'localizeKey that displays in the settings dialog to describe the setting',\n $ref: '#/$defs/localizeKey',\n },\n },\n required: ['label'],\n },\n ],\n },\n projectStateContribution: {\n description:\n 'The data an extension provides to inform Platform.Bible of the project state it provides',\n $ref: '#/$defs/userStateProperties',\n },\n userStateContribution: {\n description:\n 'The data an extension provides to inform Platform.Bible of the user state it provides',\n $ref: '#/$defs/userStateProperties',\n },\n userStateProperties: {\n description: 'Object whose keys are state IDs and whose values are state objects',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n $ref: '#/$defs/userState',\n },\n },\n additionalProperties: false,\n },\n userState: {\n description: \"A description of an extension's user state entry\",\n anyOf: [\n {\n $ref: '#/$defs/extensionControlledState',\n },\n ],\n },\n extensionControlledState: {\n description: 'State definition that is validated by the extension.',\n allOf: [\n {\n $ref: '#/$defs/stateBase',\n },\n {\n $ref: '#/$defs/modifierExtensionControlled',\n },\n ],\n },\n modifierExtensionControlled: {\n description:\n 'Modifies state/setting type to be extension-controlled. \"Extension-controlled\" means the extension provides the component and the validator for the state/setting, so the state/setting is controlled by the extension.',\n not: {\n anyOf: [\n {\n type: 'object',\n required: ['platformType'],\n },\n {\n type: 'object',\n required: ['type'],\n },\n ],\n },\n },\n stateBase: {\n description: 'Base information needed to describe a state entry',\n type: 'object',\n properties: {\n default: {\n description: 'default value for the state/setting',\n type: 'any',\n },\n derivesFrom: {\n description:\n \"a state/setting ID whose value to set to this state/setting's starting value the first time this state/setting is loaded\",\n $ref: '#/$defs/id',\n },\n },\n required: ['default'],\n },\n localizeKey: {\n description: \"Identifier for a string that will be localized based on the user's UI language\",\n type: 'string',\n pattern: '^%[\\\\w\\\\-\\\\.]+%$',\n tsType: 'LocalizeKey',\n },\n id: {\n description: '',\n type: 'string',\n pattern: '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$',\n tsType: 'Id',\n },\n};\n\n/**\n * Json-schema-to-typescript has some added stuff that isn't actually compatible with JSON schema,\n * so we remove them here\n *\n * @param defs The `$defs` property of a JSON schema (will be modified in place)\n */\n// JSON schema types are weird, so we'll just be careful\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function removeJsonToTypeScriptTypesStuff(defs: any) {\n if (!defs) return;\n\n // JSON schema types are weird, so we'll just be careful\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Object.values(defs).forEach((def: any) => {\n if (!def.type) return;\n\n if ('tsType' in def) delete def.tsType;\n\n if (def.type === 'any') {\n delete def.type;\n return;\n }\n\n if (def.type === 'object') {\n removeJsonToTypeScriptTypesStuff(def.properties);\n }\n });\n}\n\nremoveJsonToTypeScriptTypesStuff(settingsDefs);\n\n/** JSON schema object that aligns with the ProjectSettingsContribution type */\nexport const projectSettingsDocumentSchema = {\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n title: 'Project Settings Contribution',\n description:\n 'The data an extension provides to inform Platform.Bible of the project settings it provides',\n anyOf: [\n {\n $ref: '#/$defs/projectSettingsGroup',\n },\n {\n type: 'array',\n items: {\n $ref: '#/$defs/projectSettingsGroup',\n },\n },\n ],\n\n $defs: settingsDefs,\n};\n\nObject.freeze(projectSettingsDocumentSchema);\n\n/** JSON schema object that aligns with the {@link SettingsContribution} type */\nexport const settingsDocumentSchema = {\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n title: 'Settings Contribution',\n description:\n 'The data an extension provides to inform Platform.Bible of the settings it provides',\n anyOf: [\n {\n $ref: '#/$defs/settingsGroup',\n },\n {\n type: 'array',\n items: {\n $ref: '#/$defs/settingsGroup',\n },\n },\n ],\n\n $defs: settingsDefs,\n};\n\nObject.freeze(settingsDocumentSchema);\n","//----------------------------------------------------------------------------------------------\n// NOTE: If you change any of the types, make sure the JSON schema at the end of this file gets\n// changed so they align.\n//----------------------------------------------------------------------------------------------\n\nimport { LocalizeKey } from 'menus.model';\nimport { removeJsonToTypeScriptTypesStuff } from './settings.model';\n\n/** Localized string value associated with this key */\nexport type LocalizedStringValue = string;\n\n/** The data an extension provides to inform Platform.Bible of the localized strings it provides. */\nexport interface LocalizedStringDataContribution {\n [k: string]: unknown;\n metadata?: StringsMetadata;\n localizedStrings?: {\n [k: string]: LanguageStrings;\n };\n}\n/**\n * Map whose keys are localized string keys and whose values provide additional non-locale-specific\n * information about the localized string key\n */\nexport interface StringsMetadata {\n [k: LocalizeKey]: StringMetadata;\n}\n/** Additional non-locale-specific information about a localized string key */\nexport interface StringMetadata {\n [k: string]: unknown;\n /**\n * Localized string key from which to get this value if one does not exist in the specified\n * language. If a new key/value pair needs to be made to replace an existing one, this could help\n * smooth over the transition if the meanings are close enough\n */\n fallbackKey?: LocalizeKey;\n /**\n * Additional information provided by developers in English to help the translator to know how to\n * translate this localized string accurately\n */\n notes?: string;\n}\n/**\n * Map whose keys are localized string keys and whose values provide information about how to\n * localize strings for the localized string key\n */\nexport interface LanguageStrings {\n [k: LocalizeKey]: LocalizedStringValue;\n}\n\n//----------------------------------------------------------------------------------------------\n// NOTE: If you change the schema below, make sure the TS types above get changed so they align.\n//----------------------------------------------------------------------------------------------\n\nconst localizedStringsDefs = {\n languageStrings: {\n description:\n 'Map whose keys are localized string keys and whose values provide information about how to localize strings for the localized string key',\n type: 'object',\n patternProperties: {\n '^%[\\\\w\\\\-\\\\.]+%$': {\n $ref: '#/$defs/localizedStringValue',\n },\n },\n additionalProperties: false,\n },\n localizedStringValue: {\n description: 'Localized string value associated with this key',\n type: 'string',\n },\n stringsMetadata: {\n description:\n 'Map whose keys are localized string keys and whose values provide additional non-locale-specific information about the localized string key',\n type: 'object',\n patternProperties: {\n '^%[\\\\w\\\\-\\\\.]+%$': {\n $ref: '#/$defs/stringMetadata',\n },\n },\n additionalProperties: false,\n },\n stringMetadata: {\n description: 'Additional non-locale-specific information about a localized string key',\n type: 'object',\n properties: {\n fallbackKey: {\n description:\n 'Localized string key from which to get this value if one does not exist in the specified language. If a new key/value pair needs to be made to replace an existing one, this could help smooth over the transition if the meanings are close enough',\n $ref: '#/$defs/localizeKey',\n },\n notes: {\n description:\n 'Additional information provided by developers in English to help the translator to know how to translate this localized string accurately',\n type: 'string',\n },\n },\n },\n localizeKey: {\n description: \"Identifier for a string that will be localized based on the user's UI language\",\n type: 'string',\n pattern: '^%[\\\\w\\\\-\\\\.]+%$',\n tsType: 'LocalizeKey',\n },\n};\n\nremoveJsonToTypeScriptTypesStuff(localizedStringsDefs);\n\n/** JSON schema object that aligns with the LocalizedStringDataContribution type */\nexport const localizedStringsDocumentSchema = {\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n title: 'Localized String Data Contribution',\n description:\n 'The data an extension provides to inform Platform.Bible of the localized strings it provides.',\n type: 'object',\n properties: {\n metadata: {\n $ref: '#/$defs/stringsMetadata',\n },\n localizedStrings: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/languageStrings',\n },\n },\n },\n $defs: localizedStringsDefs,\n};\n\nObject.freeze(localizedStringsDocumentSchema);\n","//----------------------------------------------------------------------------------------------\n// NOTE: If you change any of the types, make sure the JSON schema at the end of this file gets\n// changed so they align.\n//----------------------------------------------------------------------------------------------\n\nimport { ReplaceType } from './util';\n\n/** Identifier for a string that will be localized in a menu based on the user's UI language */\nexport type LocalizeKey = `%${string}%`;\n\n/** Name of some UI element (i.e., tab, column, group, menu item) or some PAPI object (i.e., command) */\nexport type ReferencedItem = `${string}.${string}`;\n\nexport type OrderedItem = {\n /** Relative order of this item compared to other items in the same parent/scope (sorted ascending) */\n order: number;\n};\n\nexport type OrderedExtensibleContainer = OrderedItem & {\n /** Determines whether other items can be added to this after it has been defined */\n isExtensible?: boolean;\n};\n\n/** Group of menu items that belongs in a column */\nexport type MenuGroupDetailsInColumn = OrderedExtensibleContainer & {\n /** ID of column in which this group resides */\n column: ReferencedItem;\n};\n\n/** Group of menu items that belongs in a submenu */\nexport type MenuGroupDetailsInSubMenu = OrderedExtensibleContainer & {\n /** ID of menu item hosting the submenu in which this group resides */\n menuItem: ReferencedItem;\n};\n\n/** Column that includes header text in a menu */\nexport type MenuColumnWithHeader = OrderedExtensibleContainer & {\n /** Key that represents the text of the header text of the column */\n label: LocalizeKey;\n};\n\nexport type MenuItemBase = OrderedItem & {\n /** Menu group to which this menu item belongs */\n group: ReferencedItem;\n /** Key that represents the text of this menu item to display */\n label: LocalizeKey;\n /** Key that represents words the platform should reference when users are searching for menu items */\n searchTerms?: LocalizeKey;\n /** Key that represents the text to display if a mouse pointer hovers over the menu item */\n tooltip?: LocalizeKey;\n /** Additional information provided by developers to help people who perform localization */\n localizeNotes: string;\n};\n\n/** Menu item that hosts a submenu */\nexport type MenuItemContainingSubmenu = MenuItemBase & {\n /** ID for this menu item that holds a submenu */\n id: ReferencedItem;\n};\n\n/** Menu item that runs a command */\nexport type MenuItemContainingCommand = MenuItemBase & {\n /** Name of the PAPI command to run when this menu item is selected. */\n command: ReferencedItem;\n /** Path to the icon to display after the menu text */\n iconPathAfter?: string;\n /** Path to the icon to display before the menu text */\n iconPathBefore?: string;\n};\n\n/**\n * Group of menu items that can be combined with other groups to form a single context menu/submenu.\n * Groups are separated using a line within the menu/submenu.\n */\nexport type GroupsInSingleColumnMenu = {\n /** Named menu group */\n [property: ReferencedItem]: OrderedExtensibleContainer | MenuGroupDetailsInSubMenu;\n};\n\n/**\n * Group of menu items that can be combined with other groups to form a single menu/submenu within a\n * multi-column menu. Groups are separated using a line within the menu/submenu.\n */\nexport type GroupsInMultiColumnMenu = {\n /** Named menu group */\n [property: ReferencedItem]: MenuGroupDetailsInColumn | MenuGroupDetailsInSubMenu;\n};\n\n/** Group of columns that can be combined with other columns to form a multi-column menu */\nexport type ColumnsWithHeaders = {\n /** Named column of a menu */\n [property: ReferencedItem]: MenuColumnWithHeader;\n /** Defines whether columns can be added to this multi-column menu */\n isExtensible?: boolean;\n};\n\n/** Menu that contains a column without a header */\nexport type SingleColumnMenu = {\n /** Groups that belong in this menu */\n groups: GroupsInSingleColumnMenu;\n /** List of menu items that belong in this menu */\n items: (MenuItemContainingCommand | MenuItemContainingSubmenu)[];\n};\n\n/** Menu that contains multiple columns with headers */\nexport type MultiColumnMenu = {\n /** Columns that belong in this menu */\n columns: ColumnsWithHeaders;\n /** Groups that belong in this menu */\n groups: GroupsInMultiColumnMenu;\n /** List of menu items that belong in this menu */\n items: (MenuItemContainingCommand | MenuItemContainingSubmenu)[];\n};\n\n/** Menus for one single web view */\nexport type WebViewMenu = {\n /** Indicates whether the platform default menus should be included for this webview */\n includeDefaults: boolean | undefined;\n /** Menu that opens when you click on the top left corner of a tab */\n topMenu: MultiColumnMenu | undefined;\n /** Menu that opens when you right click on the main body/area of a tab */\n contextMenu: SingleColumnMenu | undefined;\n};\n\n/** Menus for all web views */\nexport type WebViewMenus = {\n /** Named web view */\n [property: ReferencedItem]: WebViewMenu;\n};\n\n/** Platform.Bible menus before they are localized */\nexport type PlatformMenus = {\n /** Top level menu for the application */\n mainMenu: MultiColumnMenu;\n /** Menus that apply per web view in the application */\n webViewMenus: WebViewMenus;\n /** Default context menu for web views that don't specify their own */\n defaultWebViewContextMenu: SingleColumnMenu;\n /** Default top menu for web views that don't specify their own */\n defaultWebViewTopMenu: MultiColumnMenu;\n};\n\n/**\n * Type that converts any menu type before it is localized to what it is after it is localized. This\n * can be applied to any menu type as needed.\n */\nexport type Localized = ReplaceType, ReferencedItem, string>;\n\n//----------------------------------------------------------------------------------------------\n// NOTE: If you change the schema below, make sure the TS types above get changed so they align.\n//----------------------------------------------------------------------------------------------\n/** JSON schema object that aligns with the PlatformMenus type */\nexport const menuDocumentSchema = {\n title: 'Platform.Bible menus',\n type: 'object',\n properties: {\n mainMenu: {\n description: 'Top level menu for the application',\n $ref: '#/$defs/multiColumnMenu',\n },\n defaultWebViewTopMenu: {\n description: \"Default top menu for web views that don't specify their own\",\n $ref: '#/$defs/multiColumnMenu',\n },\n defaultWebViewContextMenu: {\n description: \"Default context menu for web views that don't specify their own\",\n $ref: '#/$defs/singleColumnMenu',\n },\n webViewMenus: {\n description: 'Menus that apply per web view in the application',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n $ref: '#/$defs/menusForOneWebView',\n },\n },\n additionalProperties: false,\n },\n },\n required: ['mainMenu', 'defaultWebViewTopMenu', 'defaultWebViewContextMenu', 'webViewMenus'],\n additionalProperties: false,\n $defs: {\n localizeKey: {\n description:\n \"Identifier for a string that will be localized in a menu based on the user's UI language\",\n type: 'string',\n pattern: '^%[\\\\w\\\\-\\\\.]+%$',\n },\n referencedItem: {\n description:\n 'Name of some UI element (i.e., tab, column, group, menu item) or some PAPI object (i.e., command)',\n type: 'string',\n pattern: '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$',\n },\n columnsWithHeaders: {\n description:\n 'Group of columns that can be combined with other columns to form a multi-column menu',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n description: 'Single column with a header string',\n type: 'object',\n properties: {\n label: {\n description: 'Header text for this this column in the UI',\n $ref: '#/$defs/localizeKey',\n },\n localizeNotes: {\n description:\n 'Additional information provided by developers to help people who perform localization',\n type: 'string',\n },\n order: {\n description:\n 'Relative order of this column compared to other columns (sorted ascending)',\n type: 'number',\n },\n isExtensible: {\n description:\n 'Defines whether contributions are allowed to add menu groups to this column',\n type: 'boolean',\n },\n },\n required: ['label', 'order'],\n additionalProperties: false,\n },\n },\n properties: {\n isExtensible: {\n description:\n 'Defines whether contributions are allowed to add columns to this multi-column menu',\n type: 'boolean',\n },\n },\n },\n menuGroups: {\n description:\n 'Group of menu items that can be combined with other groups to form a single menu/submenu. Groups are separated using a line within the menu/submenu.',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n description: 'Single group that contains menu items',\n type: 'object',\n oneOf: [\n {\n properties: {\n column: {\n description:\n 'Column where this group belongs, not required for single column menus',\n $ref: '#/$defs/referencedItem',\n },\n order: {\n description:\n 'Relative order of this group compared to other groups in the same column or submenu (sorted ascending)',\n type: 'number',\n },\n isExtensible: {\n description:\n 'Defines whether contributions are allowed to add menu items to this menu group',\n type: 'boolean',\n },\n },\n required: ['order'],\n additionalProperties: false,\n },\n {\n properties: {\n menuItem: {\n description: 'Menu item that anchors the submenu where this group belongs',\n $ref: '#/$defs/referencedItem',\n },\n order: {\n description:\n 'Relative order of this group compared to other groups in the same column or submenu (sorted ascending)',\n type: 'number',\n },\n isExtensible: {\n description:\n 'Defines whether contributions are allowed to add menu items to this menu group',\n type: 'boolean',\n },\n },\n required: ['menuItem', 'order'],\n additionalProperties: false,\n },\n ],\n },\n },\n additionalProperties: false,\n },\n menuItem: {\n description:\n 'Single item in a menu that can be clicked on to take an action or can be the parent of a submenu',\n type: 'object',\n oneOf: [\n {\n properties: {\n id: {\n description: 'ID for this menu item that holds a submenu',\n $ref: '#/$defs/referencedItem',\n },\n },\n required: ['id'],\n },\n {\n properties: {\n command: {\n description: 'Name of the PAPI command to run when this menu item is selected.',\n $ref: '#/$defs/referencedItem',\n },\n iconPathBefore: {\n description: 'Path to the icon to display before the menu text',\n type: 'string',\n },\n iconPathAfter: {\n description: 'Path to the icon to display after the menu text',\n type: 'string',\n },\n },\n required: ['command'],\n },\n ],\n properties: {\n label: {\n description: 'Key that represents the text of this menu item to display',\n $ref: '#/$defs/localizeKey',\n },\n tooltip: {\n description:\n 'Key that represents the text to display if a mouse pointer hovers over the menu item',\n $ref: '#/$defs/localizeKey',\n },\n searchTerms: {\n description:\n 'Key that represents additional words the platform should reference when users are searching for menu items',\n $ref: '#/$defs/localizeKey',\n },\n localizeNotes: {\n description:\n 'Additional information provided by developers to help people who perform localization',\n type: 'string',\n },\n group: {\n description: 'Group to which this menu item belongs',\n $ref: '#/$defs/referencedItem',\n },\n order: {\n description:\n 'Relative order of this menu item compared to other menu items in the same group (sorted ascending)',\n type: 'number',\n },\n },\n required: ['label', 'group', 'order'],\n unevaluatedProperties: false,\n },\n groupsAndItems: {\n description: 'Core schema for a column',\n type: 'object',\n properties: {\n groups: {\n description: 'Groups that belong in this menu',\n $ref: '#/$defs/menuGroups',\n },\n items: {\n description: 'List of menu items that belong in this menu',\n type: 'array',\n items: { $ref: '#/$defs/menuItem' },\n uniqueItems: true,\n },\n },\n required: ['groups', 'items'],\n },\n singleColumnMenu: {\n description: 'Menu that contains a column without a header',\n type: 'object',\n allOf: [{ $ref: '#/$defs/groupsAndItems' }],\n unevaluatedProperties: false,\n },\n multiColumnMenu: {\n description: 'Menu that can contain multiple columns with headers',\n type: 'object',\n allOf: [\n { $ref: '#/$defs/groupsAndItems' },\n {\n properties: {\n columns: {\n description: 'Columns that belong in this menu',\n $ref: '#/$defs/columnsWithHeaders',\n },\n },\n required: ['columns'],\n },\n ],\n unevaluatedProperties: false,\n },\n menusForOneWebView: {\n description: 'Set of menus that are associated with a single tab',\n type: 'object',\n properties: {\n includeDefaults: {\n description:\n 'Indicates whether the platform default menus should be included for this webview',\n type: 'boolean',\n },\n topMenu: {\n description: 'Menu that opens when you click on the top left corner of a tab',\n $ref: '#/$defs/multiColumnMenu',\n },\n contextMenu: {\n description: 'Menu that opens when you right click on the main body/area of a tab',\n $ref: '#/$defs/singleColumnMenu',\n },\n },\n additionalProperties: false,\n },\n },\n};\n\nObject.freeze(menuDocumentSchema);\n"],"names":["AsyncVariable","variableName","rejectIfNotSettledWithinMS","__publicField","resolve","reject","value","throwIfAlreadySettled","reason","Collator","locales","options","string1","string2","DateTimeFormat","date","startDate","endDate","PlatformEventEmitter","event","callback","callbackIndex","_a","newGuid","s","isString","o","deepClone","obj","debounce","fn","delay","timeout","args","groupBy","items","keySelector","valueSelector","map","item","key","group","isErrorWithMessage","error","toErrorWithMessage","maybeError","getErrorMessage","wait","ms","waitForDuration","maxWaitTimeInMS","getAllObjectFunctionNames","objId","objectFunctionNames","property","objectPrototype","createSyncProxyForAsyncObject","getObject","objectToProxy","target","prop","DocumentCombiner","baseDocument","documentName","document","previousDocumentVersion","documentToSet","contributions","contributionName","potentialOutput","outputIteration","contribution","mergeObjects","output","finalOutput","areNonArrayObjects","values","allMatch","areArrayObjects","startingPoint","copyFrom","ignoreDuplicateProperties","retVal","mergeObjectsInternal","startingPointObj","copyFromObj","Mutex","AsyncMutex","MutexMap","mutexID","NonValidatingDocumentCombiner","NumberFormat","startRange","endRange","UnsubscriberAsyncList","name","unsubscribers","unsubscriber","unsubs","results","unsubscriberSucceeded","index","P","R","n","m","v","X","C","K","N","B","x","T","O","V","I","L","G","S","H","k","A","y","q","U","f","l","u","c","E","r","D","i","a","h","d","g","w","b","J","charRegex","astralRange","comboMarksRange","comboHalfMarksRange","comboSymbolsRange","comboMarksExtendedRange","comboMarksSupplementRange","comboRange","varRange","familyRange","astral","combo","fitz","modifier","nonAstral","regional","surrogatePair","zwj","blackFlag","family","optModifier","optVar","optJoin","seq","symbol","__importDefault","this","mod","dist","char_regex_1","require$$0","toArray","str","toArray_1","length","match","length_1","substring","begin","end","substring_1","substr","len","strLength","substr_1","limit","padString","padPosition","padRepeats","limit_1","indexOf","searchStr","pos","strArr","searchArr","finded","searchIndex","indexOf_1","at","string","stringLength","charAt","codePointAt","endsWith","searchString","endPosition","lastIndexOfSearchString","lastIndexOf","indexOfClosestClosingCurlyBrace","escaped","closeCurlyBraceIndex","formatReplacementString","replacers","updatedStr","replacerKey","replacerString","includes","position","partialString","stringzIndexOf","validatedPosition","stringzLength","normalize","form","upperCaseForm","ordinalCompare","padEnd","targetLength","stringzLimit","padStart","correctSliceIndex","slice","indexStart","indexEnd","newStart","newEnd","split","separator","splitLimit","result","regexSeparator","matches","currentIndex","matchIndex","matchLength","startsWith","stringzSubstr","stringzSubstring","stringzToArray","isLocalizeKey","scrBookData","FIRST_SCR_BOOK_NUM","LAST_SCR_BOOK_NUM","FIRST_SCR_CHAPTER_NUM","FIRST_SCR_VERSE_NUM","getChaptersForBook","bookNum","offsetBook","scrRef","offset","offsetChapter","offsetVerse","getLocalizedIdFromBookNumber","bookNumber","localizationLanguage","getLocalizedString","id","Canon","bookName","parts","aggregateUnsubscribers","success","aggregateUnsubscriberAsyncs","unsubPromises","getOwnPropertyNames","getOwnPropertySymbols","hasOwnProperty","combineComparators","comparatorA","comparatorB","state","createIsCircular","areItemsEqual","cache","cachedA","cachedB","getStrictProperties","object","hasOwn","sameValueZeroEqual","OWNER","getOwnPropertyDescriptor","keys","areArraysEqual","areDatesEqual","areMapsEqual","matchedIndices","aIterable","aResult","bResult","bIterable","hasMatch","aKey","aValue","_b","bKey","bValue","areObjectsEqual","properties","areObjectsEqualStrict","descriptorA","descriptorB","arePrimitiveWrappersEqual","areRegExpsEqual","areSetsEqual","areTypedArraysEqual","ARGUMENTS_TAG","BOOLEAN_TAG","DATE_TAG","MAP_TAG","NUMBER_TAG","OBJECT_TAG","REG_EXP_TAG","SET_TAG","STRING_TAG","isArray","isTypedArray","assign","getTag","createEqualityComparator","constructor","tag","createEqualityComparatorConfig","circular","createCustomConfig","strict","config","areArraysEqual$1","areMapsEqual$1","areObjectsEqual$1","areSetsEqual$1","createInternalEqualityComparator","compare","_indexOrKeyA","_indexOrKeyB","_parentA","_parentB","createIsEqual","comparator","createState","equals","meta","deepEqual","createCustomEqual","createCustomInternalComparator","isEqualDeep","isSubset","objectWithAllProperties","objectWithPartialProperties","partialArray","allArray","partialObj","allObj","serialize","replacer","space","replacerValue","newValue","deserialize","reviver","replaceNull","parsedObject","isSerializable","serializedValue","htmlEncode","getCurrentLocale","settingsDefs","removeJsonToTypeScriptTypesStuff","defs","def","projectSettingsDocumentSchema","settingsDocumentSchema","localizedStringsDefs","localizedStringsDocumentSchema","menuDocumentSchema"],"mappings":";;;;AACA,MAAqBA,GAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcpC,YAAYC,GAAsBC,IAAqC,KAAO;AAb7D,IAAAC,EAAA;AACA,IAAAA,EAAA;AACT,IAAAA,EAAA;AACA,IAAAA,EAAA;AAWN,SAAK,eAAeF,GACpB,KAAK,iBAAiB,IAAI,QAAW,CAACG,GAASC,MAAW;AACxD,WAAK,WAAWD,GAChB,KAAK,WAAWC;AAAA,IAAA,CACjB,GACGH,IAA6B,KAC/B,WAAW,MAAM;AACf,MAAI,KAAK,aACP,KAAK,SAAS,oCAAoC,KAAK,YAAY,YAAY,GAC/E,KAAK,SAAS;AAAA,OAEfA,CAA0B,GAE/B,OAAO,KAAK,IAAI;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,IAAI,UAAsB;AACxB,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,aAAsB;AACjB,WAAA,OAAO,SAAS,IAAI;AAAA,EAC7B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,eAAeI,GAAUC,IAAiC,IAAa;AACrE,QAAI,KAAK;AACP,cAAQ,MAAM,GAAG,KAAK,YAAY,wBAAwB,GAC1D,KAAK,SAASD,CAAK,GACnB,KAAK,SAAS;AAAA,SACT;AACD,UAAAC;AAAuB,cAAM,MAAM,GAAG,KAAK,YAAY,sBAAsB;AACjF,cAAQ,MAAM,qCAAqC,KAAK,YAAY,EAAE;AAAA,IACxE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,iBAAiBC,GAAgBD,IAAiC,IAAa;AAC7E,QAAI,KAAK;AACP,cAAQ,MAAM,GAAG,KAAK,YAAY,wBAAwB,GAC1D,KAAK,SAASC,CAAM,GACpB,KAAK,SAAS;AAAA,SACT;AACD,UAAAD;AAAuB,cAAM,MAAM,GAAG,KAAK,YAAY,sBAAsB;AACjF,cAAQ,MAAM,oCAAoC,KAAK,YAAY,EAAE;AAAA,IACvE;AAAA,EACF;AAAA;AAAA,EAGQ,WAAiB;AACvB,SAAK,WAAW,QAChB,KAAK,WAAW,QAChB,OAAO,OAAO,IAAI;AAAA,EACpB;AACF;AC5FA,MAAqBE,GAAS;AAAA,EAG5B,YAAYC,GAA6BC,GAAgC;AAFjE,IAAAR,EAAA;AAGN,SAAK,WAAW,IAAI,KAAK,SAASO,GAASC,CAAO;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,QAAQC,GAAiBC,GAAyB;AAChD,WAAO,KAAK,SAAS,QAAQD,GAASC,CAAO;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,kBAAgD;AACvC,WAAA,KAAK,SAAS;EACvB;AACF;AC7BA,MAAqBC,GAAe;AAAA,EAGlC,YAAYJ,GAA6BC,GAAsC;AAFvE,IAAAR,EAAA;AAGN,SAAK,oBAAoB,IAAI,KAAK,eAAeO,GAASC,CAAO;AAAA,EACnE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAOI,GAAoB;AAClB,WAAA,KAAK,kBAAkB,OAAOA,CAAI;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,YAAYC,GAAiBC,GAAuB;AAClD,WAAO,KAAK,kBAAkB,YAAYD,GAAWC,CAAO;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,mBAAmBD,GAAiBC,GAA+C;AACjF,WAAO,KAAK,kBAAkB,mBAAmBD,GAAWC,CAAO;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,cAAcF,GAAuC;AAC5C,WAAA,KAAK,kBAAkB,cAAcA,CAAI;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,kBAAsD;AAC7C,WAAA,KAAK,kBAAkB;EAChC;AACF;ACnDA,MAAqBG,GAA2C;AAAA,EAAhE;AASE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAAAf,EAAA,mBAAY,KAAK;AAGT;AAAA,IAAAA,EAAA;AAEA;AAAA,IAAAA,EAAA;AAEA;AAAA,IAAAA,EAAA,oBAAa;AAyCrB;AAAA,IAAAA,EAAA,iBAAU,MACD,KAAK;AAQd;AAAA;AAAA;AAAA;AAAA;AAAA,IAAAA,EAAA,cAAO,CAACgB,MAAa;AAEnB,WAAK,OAAOA,CAAK;AAAA,IAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA1CnB,IAAI,QAA0B;AAC5B,gBAAK,kBAAkB,GAElB,KAAK,cACH,KAAA,YAAY,CAACC,MAAa;AACzB,UAAA,CAACA,KAAY,OAAOA,KAAa;AAC7B,cAAA,IAAI,MAAM,4CAA4C;AAG9D,aAAK,KAAK,kBAAe,KAAK,gBAAgB,KAEzC,KAAA,cAAc,KAAKA,CAAQ,GAEzB,MAAM;AACX,YAAI,CAAC,KAAK;AAAsB,iBAAA;AAEhC,cAAMC,IAAgB,KAAK,cAAc,QAAQD,CAAQ;AAEzD,eAAIC,IAAgB,IAAU,MAGzB,KAAA,cAAc,OAAOA,GAAe,CAAC,GAEnC;AAAA,MAAA;AAAA,IACT,IAGG,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBU,OAAOF,GAAU;;AACzB,SAAK,kBAAkB,IAEvBG,IAAA,KAAK,kBAAL,QAAAA,EAAoB,QAAQ,CAACF,MAAaA,EAASD,CAAK;AAAA,EAC1D;AAAA;AAAA,EAGU,oBAAoB;AAC5B,QAAI,KAAK;AAAkB,YAAA,IAAI,MAAM,qBAAqB;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA;AAAA,EAMU,YAAY;AACpB,gBAAK,kBAAkB,GAEvB,KAAK,aAAa,IAClB,KAAK,gBAAgB,QACrB,KAAK,YAAY,QACV,QAAQ,QAAQ,EAAI;AAAA,EAC7B;AACF;AC3GO,SAASI,KAAkB;AAChC,SAAO,eAAe;AAAA,IAAQ;AAAA,IAAS,CAACC;AAAA;AAAA;AAAA,QAGnC,KAAK,WAAW,CAAC,CAACA,KAAK,SAAYA,GAAG,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG;AAAA;AAAA,EAAA;AAEzE;AASO,SAASC,GAASC,GAAyB;AACzC,SAAA,OAAOA,KAAM,YAAYA,aAAa;AAC/C;AASO,SAASC,EAAaC,GAAW;AAGtC,SAAO,KAAK,MAAM,KAAK,UAAUA,CAAG,CAAC;AACvC;AAYgB,SAAAC,GAA6CC,GAAOC,IAAQ,KAAQ;AAClF,MAAIN,GAASK,CAAE;AAAS,UAAA,IAAI,MAAM,0CAA0C;AACxE,MAAAE;AAGJ,SAAQ,IAAIC,MAAS;AACnB,iBAAaD,CAAO,GACpBA,IAAU,WAAW,MAAMF,EAAG,GAAGG,CAAI,GAAGF,CAAK;AAAA,EAAA;AAEjD;AAiBgB,SAAAG,GACdC,GACAC,GACAC,GACsB;AAChB,QAAAC,wBAAU;AACV,SAAAH,EAAA,QAAQ,CAACI,MAAS;AAChB,UAAAC,IAAMJ,EAAYG,CAAI,GACtBE,IAAQH,EAAI,IAAIE,CAAG,GACnBlC,IAAQ+B,IAAgBA,EAAcE,GAAMC,CAAG,IAAID;AACrD,IAAAE,IAAOA,EAAM,KAAKnC,CAAK,IACtBgC,EAAI,IAAIE,GAAK,CAAClC,CAAK,CAAC;AAAA,EAAA,CAC1B,GACMgC;AACT;AAQA,SAASI,GAAmBC,GAA2C;AACrE,SACE,OAAOA,KAAU;AAAA;AAAA,EAGjBA,MAAU,QACV,aAAaA;AAAA;AAAA,EAGb,OAAQA,EAAkC,WAAY;AAE1D;AAUA,SAASC,GAAmBC,GAAuC;AACjE,MAAIH,GAAmBG,CAAU;AAAU,WAAAA;AAEvC,MAAA;AACF,WAAO,IAAI,MAAM,KAAK,UAAUA,CAAU,CAAC;AAAA,EAAA,QACrC;AAGN,WAAO,IAAI,MAAM,OAAOA,CAAU,CAAC;AAAA,EACrC;AACF;AAaO,SAASC,GAAgBH,GAAgB;AACvC,SAAAC,GAAmBD,CAAK,EAAE;AACnC;AAGO,SAASI,GAAKC,GAAY;AAE/B,SAAO,IAAI,QAAc,CAAC5C,MAAY,WAAWA,GAAS4C,CAAE,CAAC;AAC/D;AAUgB,SAAAC,GAAyBnB,GAA4BoB,GAAyB;AAC5F,QAAMlB,IAAUe,GAAKG,CAAe,EAAE,KAAK,MAAA;AAAA,GAAe;AAC1D,SAAO,QAAQ,IAAI,CAAClB,GAASF,EAAA,CAAI,CAAC;AACpC;AAagB,SAAAqB,GACdvB,GACAwB,IAAgB,OACH;AACP,QAAAC,wBAA0B;AAGhC,SAAO,oBAAoBzB,CAAG,EAAE,QAAQ,CAAC0B,MAAa;AAChD,QAAA;AACE,MAAA,OAAO1B,EAAI0B,CAAQ,KAAM,cAAYD,EAAoB,IAAIC,CAAQ;AAAA,aAClEX,GAAO;AACd,cAAQ,MAAM,YAAYW,CAAQ,OAAOF,CAAK,kBAAkBT,CAAK,EAAE;AAAA,IACzE;AAAA,EAAA,CACD;AAIG,MAAAY,IAAkB,OAAO,eAAe3B,CAAG;AAC/C,SAAO2B,KAAmB,OAAO,eAAeA,CAAe;AAC7D,WAAO,oBAAoBA,CAAe,EAAE,QAAQ,CAACD,MAAa;AAC5D,UAAA;AACE,QAAA,OAAO1B,EAAI0B,CAAQ,KAAM,cAAYD,EAAoB,IAAIC,CAAQ;AAAA,eAClEX,GAAO;AACd,gBAAQ,MAAM,YAAYW,CAAQ,OAAOF,CAAK,8BAA8BT,CAAK,EAAE;AAAA,MACrF;AAAA,IAAA,CACD,GACiBY,IAAA,OAAO,eAAeA,CAAe;AAGlD,SAAAF;AACT;AAcO,SAASG,GACdC,GACAC,IAA4B,IACzB;AAII,SAAA,IAAI,MAAMA,GAAoB;AAAA,IACnC,IAAIC,GAAQC,GAAM;AAGhB,aAAIA,KAAQD,IAAeA,EAAOC,CAAI,IAC/B,UAAU3B,OAIP,MAAMwB,EAAU,GAAGG,CAAI,EAAE,GAAG3B,CAAI;AAAA,IAE5C;AAAA,EAAA,CACD;AACH;AChNA,MAAqB4B,GAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiB1B,YAAYC,GAAgCnD,GAAkC;AAhB9E,IAAAR,EAAA;AACS,IAAAA,EAAA,2CAAoB;AAC7B,IAAAA,EAAA;AACS,IAAAA,EAAA;AACF,IAAAA,EAAA,6BAAsB,IAAIe;AAIlC;AAAA;AAAA;AAAA,IAAAf,EAAA,sBAAe,KAAK,oBAAoB;AAU/C,SAAK,eAAe2D,GACpB,KAAK,UAAUnD,GACf,KAAK,mBAAmBmD,CAAY;AAAA,EACtC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,mBAAmBA,GAA8D;AAC/E,gBAAK,qBAAqBA,CAAY,GACtC,KAAK,eAAe,KAAK,QAAQ,gBAAgBnC,EAAUmC,CAAY,IAAIA,GAC3E,KAAK,eAAe,KAAK,qCAAqC,KAAK,YAAY,GACxE,KAAK;EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,wBACEC,GACAC,GAC8B;AACzB,SAAA,qBAAqBD,GAAcC,CAAQ;AAChD,UAAMC,IAA0B,KAAK,cAAc,IAAIF,CAAY;AAC/D,QAAAG,IAAgB,KAAK,QAAQ,iBAAmBF,IAAWrC,EAAUqC,CAAQ,IAAIA;AACrE,IAAAE,IAAA,KAAK,qCAAqCH,GAAcG,CAAa,GAChF,KAAA,cAAc,IAAIH,GAAcG,CAAa;AAC9C,QAAA;AACF,aAAO,KAAK;aACLvB,GAAO;AAEV,YAAAsB,IAA8B,KAAA,cAAc,IAAIF,GAAcE,CAAuB,IAC/E,KAAA,cAAc,OAAOF,CAAY,GACrC,IAAI,MAAM,yCAAyCA,CAAY,KAAKpB,CAAK,EAAE;AAAA,IACnF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,mBAAmBoB,GAAoD;AACrE,UAAMC,IAAW,KAAK,cAAc,IAAID,CAAY;AACpD,QAAI,CAACC;AAAU,YAAM,IAAI,MAAM,GAAGD,CAAY,iBAAiB;AAC1D,SAAA,cAAc,OAAOA,CAAY;AAClC,QAAA;AACF,aAAO,KAAK;aACLpB,GAAO;AAET,iBAAA,cAAc,IAAIoB,GAAcC,CAAQ,GACvC,IAAI,MAAM,0CAA0CD,CAAY,KAAKpB,CAAK,EAAE;AAAA,IACpF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,yBAAuD;AACjD,QAAA,KAAK,cAAc,QAAQ;AAAG,aAAO,KAAK;AAG9C,UAAMwB,IAAgB,CAAC,GAAG,KAAK,cAAc,QAAS,CAAA;AAGxC,IAAAA,EAAA,QAAQ,CAAC,CAACC,CAAgB,MAAM,KAAK,cAAc,OAAOA,CAAgB,CAAC;AAGrF,QAAA;AACF,aAAO,KAAK;aACLzB,GAAO;AAEA,YAAAwB,EAAA;AAAA,QAAQ,CAAC,CAACC,GAAkBJ,CAAQ,MAChD,KAAK,cAAc,IAAII,GAAkBJ,CAAQ;AAAA,MAAA,GAE7C,IAAI,MAAM,0CAA0CrB,CAAK,EAAE;AAAA,IACnE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,UAAwC;AAElC,QAAA,KAAK,cAAc,SAAS,GAAG;AAC7B,UAAA0B,IAAkB1C,EAAU,KAAK,YAAY;AAC/B,aAAA0C,IAAA,KAAK,qCAAqCA,CAAe,GAC3E,KAAK,eAAeA,CAAe,GACnC,KAAK,eAAeA,GACf,KAAA,oBAAoB,KAAK,MAAS,GAChC,KAAK;AAAA,IACd;AAGA,QAAIC,IAAkB,KAAK;AACtB,gBAAA,cAAc,QAAQ,CAACC,MAAmC;AAC3C,MAAAD,IAAAE;AAAA,QAChBF;AAAA,QACAC;AAAA,QACA,KAAK,QAAQ;AAAA,MAAA,GAEf,KAAK,eAAeD,CAAe;AAAA,IAAA,CACpC,GACiBA,IAAA,KAAK,qCAAqCA,CAAe,GAC3E,KAAK,eAAeA,CAAe,GACnC,KAAK,eAAeA,GACf,KAAA,oBAAoB,KAAK,MAAS,GAChC,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeU,qCAAqCR,GAAkD;AACxF,WAAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBU,qCAERC,GACAC,GACkB;AACX,WAAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUU,qBAAqBF,GAAsC;AAAA,EAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAW5D,qBAAqBC,GAAsBC,GAAkC;AAAA,EAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAU9E,eAAeS,GAAgC;AAAA,EAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYhD,qCAAqCC,GAAiD;AACvF,WAAAA;AAAA,EACT;AACF;AAUA,SAASC,KAAsBC,GAA4B;AACzD,MAAIC,IAAW;AACR,SAAAD,EAAA,QAAQ,CAACtE,MAAmB;AACjC,KAAI,CAACA,KAAS,OAAOA,KAAU,YAAY,MAAM,QAAQA,CAAK,OAAcuE,IAAA;AAAA,EAAA,CAC7E,GACMA;AACT;AAQA,SAASC,KAAmBF,GAA4B;AACtD,MAAIC,IAAW;AACR,SAAAD,EAAA,QAAQ,CAACtE,MAAmB;AAC7B,KAAA,CAACA,KAAS,OAAOA,KAAU,YAAY,CAAC,MAAM,QAAQA,CAAK,OAAcuE,IAAA;AAAA,EAAA,CAC9E,GACMA;AACT;AAeA,SAASL,GACPO,GACAC,GACAC,GACkB;AACZ,QAAAC,IAASvD,EAAUoD,CAAa;AAEtC,SAAKC,IAEEG,GAAqBD,GAAQvD,EAAUqD,CAAQ,GAAGC,CAAyB,IAF5DC;AAGxB;AAeA,SAASC,GACPJ,GACAC,GACAC,GACkB;AAClB,MAAI,CAACD;AAAiB,WAAAD;AAElB,MAAAJ,EAAmBI,GAAeC,CAAQ,GAAG;AAK/C,UAAMI,IAAmBL,GACnBM,IAAcL;AAEpB,WAAO,KAAKK,CAAW,EAAE,QAAQ,CAAC7C,MAAyB;AACzD,UAAI,OAAO,OAAO4C,GAAkB5C,CAAG;AACrC,YAAImC,EAAmBS,EAAiB5C,CAAG,GAAG6C,EAAY7C,CAAG,CAAC;AAC5D,UAAA4C,EAAiB5C,CAAG,IAAI2C;AAAA;AAAA;AAAA,YAGtBC,EAAiB5C,CAAG;AAAA,YACpB6C,EAAY7C,CAAG;AAAA,YACfyC;AAAA;AAAA,UAAA;AAAA,iBAGOH,EAAgBM,EAAiB5C,CAAG,GAAG6C,EAAY7C,CAAG,CAAC;AAKhE,UAAA4C,EAAiB5C,CAAG,IAAK4C,EAAiB5C,CAAG,EAAoB;AAAA,YAC/D6C,EAAY7C,CAAG;AAAA,UAAA;AAAA,iBAGR,CAACyC;AACV,gBAAM,IAAI,MAAM,8BAA8BzC,CAAG,uCAAuC;AAAA;AAIzE,QAAA4C,EAAA5C,CAAG,IAAI6C,EAAY7C,CAAG;AAAA,IACzC,CACD;AAAA,EACQ;AAAA,IAAAsC,EAAgBC,GAAeC,CAAQ,KAM/CD,EAAgC,KAAK,GAAIC,CAA0B;AAS/D,SAAAD;AACT;AC7WA,MAAMO,WAAcC,GAAW;AAAC;ACvBhC,MAAMC,GAAS;AAAA,EAAf;AACU,IAAArF,EAAA,yCAAkB;;EAE1B,IAAIsF,GAAwB;AAC1B,QAAIP,IAAS,KAAK,YAAY,IAAIO,CAAO;AACrC,WAAAP,MAEJA,IAAS,IAAII,MACR,KAAA,YAAY,IAAIG,GAASP,CAAM,GAC7BA;AAAA,EACT;AACF;ACZA,MAAqBQ,WAAsC7B,GAAiB;AAAA;AAAA;AAAA,EAG1E,YAAYC,GAAgCnD,GAAkC;AAC5E,UAAMmD,GAAcnD,CAAO;AAAA,EAC7B;AAAA,EAEA,IAAI,SAAuC;AACzC,WAAO,KAAK;AAAA,EACd;AACF;ACXA,MAAqBgF,GAAa;AAAA,EAGhC,YAAYjF,GAA6BC,GAAoC;AAFrE,IAAAR,EAAA;AAGN,SAAK,kBAAkB,IAAI,KAAK,aAAaO,GAASC,CAAO;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAOL,GAAgC;AAC9B,WAAA,KAAK,gBAAgB,OAAOA,CAAK;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,YAAYsF,GAA6BC,GAAmC;AAC1E,WAAO,KAAK,gBAAgB,YAAYD,GAAYC,CAAQ;AAAA,EAC9D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,mBACED,GACAC,GAC8B;AAC9B,WAAO,KAAK,gBAAgB,mBAAmBD,GAAYC,CAAQ;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,cAAcvF,GAAiD;AACtD,WAAA,KAAK,gBAAgB,cAAcA,CAAK;AAAA,EACjD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,kBAAoD;AAC3C,WAAA,KAAK,gBAAgB;EAC9B;AACF;AC/DA,MAAqBwF,GAAsB;AAAA,EAGzC,YAAoBC,IAAO,aAAa;AAF/B,IAAA5F,EAAA,2CAAoB;AAET,SAAA,OAAA4F;AAAA,EAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOzC,OAAOC,GAA+D;AACtD,IAAAA,EAAA,QAAQ,CAACC,MAAiB;AACtC,MAAI,aAAaA,IAAmB,KAAA,cAAc,IAAIA,EAAa,OAAO,IAChE,KAAA,cAAc,IAAIA,CAAY;AAAA,IAAA,CACzC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,sBAAwC;AACtC,UAAAC,IAAS,CAAC,GAAG,KAAK,aAAa,EAAE,IAAI,CAACD,MAAiBA,EAAA,CAAc,GACrEE,IAAU,MAAM,QAAQ,IAAID,CAAM;AACxC,gBAAK,cAAc,SACZC,EAAQ,MAAM,CAACC,GAAuBC,OACtCD,KACH,QAAQ,MAAM,yBAAyB,KAAK,IAAI,2BAA2BC,CAAK,UAAU,GAErFD,EACR;AAAA,EACH;AACF;ACrCA,IAAIE,KAAI,OAAO,gBACXC,KAAI,CAAC,GAAG,GAAG/E,MAAM,KAAK,IAAI8E,GAAE,GAAG,GAAG,EAAE,YAAY,IAAI,cAAc,IAAI,UAAU,IAAI,OAAO9E,EAAC,CAAE,IAAI,EAAE,CAAC,IAAIA,GACzGgF,IAAI,CAAC,GAAG,GAAGhF,OAAO+E,GAAE,GAAG,OAAO,KAAK,WAAW,IAAI,KAAK,GAAG/E,CAAC,GAAGA;AAWlE,MAAMiF,IAAI;AAAA,EACR;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AACF,GAAGC,IAAI;AAAA,EACL;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAGC,KAAI;AAAA,EACL;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,E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","x_google_ignoreList":[11,12,13,17]} \ No newline at end of file +{"version":3,"file":"index.js","sources":["../src/async-variable.ts","../src/intl-collator.ts","../src/intl-date-time-format.ts","../src/platform-event-emitter.model.ts","../src/util.ts","../src/document-combiner.ts","../src/mutex.ts","../src/mutex-map.ts","../src/non-validating-document-combiner.ts","../src/intl-number-format.ts","../src/unsubscriber-async-list.ts","../../../node_modules/@sillsdev/scripture/dist/index.es.js","../../../node_modules/char-regex/index.js","../../../node_modules/stringz/dist/index.js","../src/string-util.ts","../src/scripture-util.ts","../src/unsubscriber.ts","../../../node_modules/fast-equals/dist/esm/index.mjs","../src/equality-checking.ts","../src/subset-checking.ts","../src/serialization.ts","../src/intl-util.ts","../src/settings.model.ts","../src/localized-strings.model.ts","../src/menus.model.ts"],"sourcesContent":["/** This class provides a convenient way for one task to wait on a variable that another task sets. */\nexport default class AsyncVariable {\n private readonly variableName: string;\n private readonly promiseToValue: Promise;\n private resolver: ((value: T) => void) | undefined;\n private rejecter: ((reason: string | undefined) => void) | undefined;\n\n /**\n * Creates an instance of the class\n *\n * @param variableName Name to use when logging about this variable\n * @param rejectIfNotSettledWithinMS Milliseconds to wait before verifying if the promise was\n * settled (resolved or rejected); will reject if it has not settled by that time. Use -1 if you\n * do not want a timeout at all.\n */\n constructor(variableName: string, rejectIfNotSettledWithinMS: number = 10000) {\n this.variableName = variableName;\n this.promiseToValue = new Promise((resolve, reject) => {\n this.resolver = resolve;\n this.rejecter = reject;\n });\n if (rejectIfNotSettledWithinMS > 0) {\n setTimeout(() => {\n if (this.rejecter) {\n this.rejecter(`Timeout reached when waiting for ${this.variableName} to settle`);\n this.complete();\n }\n }, rejectIfNotSettledWithinMS);\n }\n Object.seal(this);\n }\n\n /**\n * Get this variable's promise to a value. This always returns the same promise even after the\n * value has been resolved or rejected.\n *\n * @returns The promise for the value to be set\n */\n get promise(): Promise {\n return this.promiseToValue;\n }\n\n /**\n * A simple way to see if this variable's promise was resolved or rejected already\n *\n * @returns Whether the variable was already resolved or rejected\n */\n get hasSettled(): boolean {\n return Object.isFrozen(this);\n }\n\n /**\n * Resolve this variable's promise to the given value\n *\n * @param value This variable's promise will resolve to this value\n * @param throwIfAlreadySettled Determines whether to throw if the variable was already resolved\n * or rejected\n */\n resolveToValue(value: T, throwIfAlreadySettled: boolean = false): void {\n if (this.resolver) {\n console.debug(`${this.variableName} is being resolved now`);\n this.resolver(value);\n this.complete();\n } else {\n if (throwIfAlreadySettled) throw Error(`${this.variableName} was already settled`);\n console.debug(`Ignoring subsequent resolution of ${this.variableName}`);\n }\n }\n\n /**\n * Reject this variable's promise for the value with the given reason\n *\n * @param reason This variable's promise will be rejected with this reason\n * @param throwIfAlreadySettled Determines whether to throw if the variable was already resolved\n * or rejected\n */\n rejectWithReason(reason: string, throwIfAlreadySettled: boolean = false): void {\n if (this.rejecter) {\n console.debug(`${this.variableName} is being rejected now`);\n this.rejecter(reason);\n this.complete();\n } else {\n if (throwIfAlreadySettled) throw Error(`${this.variableName} was already settled`);\n console.debug(`Ignoring subsequent rejection of ${this.variableName}`);\n }\n }\n\n /** Prevent any further updates to this variable */\n private complete(): void {\n this.resolver = undefined;\n this.rejecter = undefined;\n Object.freeze(this);\n }\n}\n","/** Enables language-sensitive string comparison. Wraps Intl.Collator */\nexport default class Collator {\n private collator: Intl.Collator;\n\n constructor(locales?: string | string[], options?: Intl.CollatorOptions) {\n this.collator = new Intl.Collator(locales, options);\n }\n\n /**\n * Compares two strings according to the sort order of this Collator object\n *\n * @param string1 String to compare\n * @param string2 String to compare\n * @returns A number indicating how string1 and string2 compare to each other according to the\n * sort order of this Collator object. Negative value if string1 comes before string2. Positive\n * value if string1 comes after string2. 0 if they are considered equal.\n */\n compare(string1: string, string2: string): number {\n return this.collator.compare(string1, string2);\n }\n\n /**\n * Returns a new object with properties reflecting the locale and collation options computed\n * during initialization of this collator object.\n *\n * @returns ResolvedCollatorOptions object\n */\n resolvedOptions(): Intl.ResolvedCollatorOptions {\n return this.collator.resolvedOptions();\n }\n}\n","/** Enables language-sensitive data and time formatting. Wraps Intl.DateTimeFormat */\nexport default class DateTimeFormat {\n private dateTimeFormatter: Intl.DateTimeFormat;\n\n constructor(locales?: string | string[], options?: Intl.DateTimeFormatOptions) {\n this.dateTimeFormatter = new Intl.DateTimeFormat(locales, options);\n }\n\n /**\n * Formats a date according to the locale and formatting option for this DateTimeFormat object\n *\n * @param date The date to format\n * @returns String representing the given date formatted according to the locale and formatting\n * options of this DateTimeFormat object\n */\n format(date: Date): string {\n return this.dateTimeFormatter.format(date);\n }\n\n /**\n * Formats a date range in the most concise way based on the locales and options provided when\n * instantiating this DateTimeFormat object\n *\n * @param startDate Date object representing start of the date range\n * @param endDate Date object representing the end of the date range\n * @returns String representing the given date range formatted according to the locale and\n * formatting options of this DateTimeFormat object\n */\n formatRange(startDate: Date, endDate: Date): string {\n return this.dateTimeFormatter.formatRange(startDate, endDate);\n }\n\n /**\n * Returns an array of locale-specific tokens representing each part of the formatted date range\n * produced by this DateTimeFormat object\n *\n * @param startDate Date object representing start of the date range\n * @param endDate Date object representing the end of the date range\n * @returns Array of DateTimeRangeFormatPart objects\n */\n formatRangeToParts(startDate: Date, endDate: Date): Intl.DateTimeRangeFormatPart[] {\n return this.dateTimeFormatter.formatRangeToParts(startDate, endDate);\n }\n\n /**\n * Allows locale-aware formatting of strings produced by this DateTimeFormat object\n *\n * @param date The date to format\n * @returns Array of DateTimeFormatPart objects\n */\n formatToParts(date: Date): Intl.DateTimeFormatPart[] {\n return this.dateTimeFormatter.formatToParts(date);\n }\n\n /**\n * Returns a new object with properties reflecting the locale and date and time formatting options\n * computed during initialization of this DateTimeFormat object\n *\n * @returns ResolvedDateTimeFormatOptions object\n */\n resolvedOptions(): Intl.ResolvedDateTimeFormatOptions {\n return this.dateTimeFormatter.resolvedOptions();\n }\n}\n","/** Interfaces, classes, and functions related to events and event emitters */\n\nimport { Dispose } from './disposal.model';\nimport { PlatformEvent, PlatformEventHandler } from './platform-event';\n\n/**\n * Event manager - accepts subscriptions to an event and runs the subscription callbacks when the\n * event is emitted Use eventEmitter.event(callback) to subscribe to the event. Use\n * eventEmitter.emit(event) to run the subscriptions. Generally, this EventEmitter should be\n * private, and its event should be public. That way, the emitter is not publicized, but anyone can\n * subscribe to the event.\n */\nexport default class PlatformEventEmitter implements Dispose {\n /**\n * Subscribes a function to run when this event is emitted.\n *\n * @param callback Function to run with the event when it is emitted\n * @returns Unsubscriber function to run to stop calling the passed-in function when the event is\n * emitted\n * @alias event\n */\n subscribe = this.event;\n\n /** All callback functions that will run when this event is emitted. Lazy loaded */\n private subscriptions?: PlatformEventHandler[];\n /** Event for listeners to subscribe to. Lazy loaded */\n private lazyEvent?: PlatformEvent;\n /** Whether this emitter has been disposed */\n private isDisposed = false;\n\n /**\n * Event for listeners to subscribe to. Subscribes a function to run when this event is emitted.\n * Use like `const unsubscriber = event(callback)`\n *\n * @param callback Function to run with the event when it is emitted\n * @returns Unsubscriber function to run to stop calling the passed-in function when the event is\n * emitted\n */\n get event(): PlatformEvent {\n this.assertNotDisposed();\n\n if (!this.lazyEvent) {\n this.lazyEvent = (callback) => {\n if (!callback || typeof callback !== 'function')\n throw new Error(`Event handler callback must be a function!`);\n\n // Initialize this.subscriptions if it does not exist\n if (!this.subscriptions) this.subscriptions = [];\n\n this.subscriptions.push(callback);\n\n return () => {\n if (!this.subscriptions) return false; // Did not find any subscribed callbacks\n\n const callbackIndex = this.subscriptions.indexOf(callback);\n\n if (callbackIndex < 0) return false; // Did not find this callback in the subscriptions\n\n // Remove the callback\n this.subscriptions.splice(callbackIndex, 1);\n\n return true;\n };\n };\n }\n return this.lazyEvent;\n }\n\n /** Disposes of this event, preparing it to release from memory */\n dispose = () => {\n return this.disposeFn();\n };\n\n /**\n * Runs the subscriptions for the event\n *\n * @param event Event data to provide to subscribed callbacks\n */\n emit = (event: T) => {\n // Do not do anything other than emitFn here. This emit is just binding `this` to emitFn\n this.emitFn(event);\n };\n\n /**\n * Function that runs the subscriptions for the event. Added here so children can override emit\n * and still call the base functionality. See NetworkEventEmitter.emit for example\n */\n protected emitFn(event: T) {\n this.assertNotDisposed();\n\n this.subscriptions?.forEach((callback) => callback(event));\n }\n\n /** Check to make sure this emitter is not disposed. Throw if it is */\n protected assertNotDisposed() {\n if (this.isDisposed) throw new Error('Emitter is disposed');\n }\n\n /**\n * Disposes of this event, preparing it to release from memory. Added here so children can\n * override emit and still call the base functionality.\n */\n protected disposeFn() {\n this.assertNotDisposed();\n\n this.isDisposed = true;\n this.subscriptions = undefined;\n this.lazyEvent = undefined;\n return Promise.resolve(true);\n }\n}\n","/** Collection of functions, objects, and types that are used as helpers in other services. */\n\n// Thanks to blubberdiblub at https://stackoverflow.com/a/68141099/217579\nexport function newGuid(): string {\n return '00-0-4-1-000'.replace(/[^-]/g, (s) =>\n // @ts-expect-error ts(2363) this works fine\n // eslint-disable-next-line no-bitwise\n (((Math.random() + ~~s) * 0x10000) >> s).toString(16).padStart(4, '0'),\n );\n}\n\n// thanks to DRAX at https://stackoverflow.com/a/9436948\n/**\n * Determine whether the object is a string\n *\n * @param o Object to determine if it is a string\n * @returns True if the object is a string; false otherwise\n */\nexport function isString(o: unknown): o is string {\n return typeof o === 'string' || o instanceof String;\n}\n\n/**\n * If deepClone isn't used when copying properties between objects, you may be left with dangling\n * references between the source and target of property copying operations.\n *\n * @param obj Object to clone\n * @returns Duplicate copy of `obj` without any references back to the original one\n */\nexport function deepClone(obj: T): T {\n // Assert the return type matches what is expected\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n return JSON.parse(JSON.stringify(obj)) as T;\n}\n\n/**\n * Get a function that reduces calls to the function passed in\n *\n * @param fn The function to debounce\n * @param delay How much delay in milliseconds after the most recent call to the debounced function\n * to call the function\n * @returns Function that, when called, only calls the function passed in at maximum every delay ms\n */\n// We don't know the parameter types since this function can be anything\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function debounce void>(fn: T, delay = 300): T {\n if (isString(fn)) throw new Error('Tried to debounce a string! Could be XSS');\n let timeout: ReturnType;\n // Ensure the right return type.\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n return ((...args) => {\n clearTimeout(timeout);\n timeout = setTimeout(() => fn(...args), delay);\n }) as T;\n}\n\n/**\n * Groups each item in the array of items into a map according to the keySelector\n *\n * @param items Array of items to group by\n * @param keySelector Function to run on each item to get the key for the group to which it belongs\n * @param valueSelector Function to run on each item to get the value it should have in the group\n * (like map function). If not provided, uses the item itself\n * @returns Map of keys to groups of values corresponding to each item\n */\nexport function groupBy(items: T[], keySelector: (item: T) => K): Map>;\nexport function groupBy(\n items: T[],\n keySelector: (item: T) => K,\n valueSelector: (item: T, key: K) => V,\n): Map>;\nexport function groupBy(\n items: T[],\n keySelector: (item: T) => K,\n valueSelector?: (item: T, key: K) => V,\n): Map> {\n const map = new Map>();\n items.forEach((item) => {\n const key = keySelector(item);\n const group = map.get(key);\n const value = valueSelector ? valueSelector(item, key) : item;\n if (group) group.push(value);\n else map.set(key, [value]);\n });\n return map;\n}\n\n// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript\ntype ErrorWithMessage = {\n message: string;\n};\n\n// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript\nfunction isErrorWithMessage(error: unknown): error is ErrorWithMessage {\n return (\n typeof error === 'object' &&\n // We're potentially dealing with objects we didn't create, so they might contain `null`\n // eslint-disable-next-line no-null/no-null\n error !== null &&\n 'message' in error &&\n // Type assert `error` to check it's `message`.\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n typeof (error as Record).message === 'string'\n );\n}\n\n// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript\n/**\n * Function to get an error from the object (useful for getting an error in a catch block)\n *\n * @param error Error object whose message to get\n * @returns Message of the error - if object has message, returns message. Otherwise tries to\n * stringify\n */\nfunction toErrorWithMessage(maybeError: unknown): ErrorWithMessage {\n if (isErrorWithMessage(maybeError)) return maybeError;\n\n try {\n return new Error(JSON.stringify(maybeError));\n } catch {\n // fallback in case there's an error stringifying the maybeError\n // like with circular references for example.\n return new Error(String(maybeError));\n }\n}\n\n// From https://kentcdodds.com/blog/get-a-catch-block-error-message-with-typescript\n/**\n * Function to get an error message from the object (useful for getting error message in a catch\n * block)\n *\n * @example `try {...} catch (e) { logger.info(getErrorMessage(e)) }`\n *\n * @param error Error object whose message to get\n * @returns Message of the error - if object has message, returns message. Otherwise tries to\n * stringify\n */\nexport function getErrorMessage(error: unknown) {\n return toErrorWithMessage(error).message;\n}\n\n/** Asynchronously waits for the specified number of milliseconds. (wraps setTimeout in a promise) */\nexport function wait(ms: number) {\n // eslint-disable-next-line no-promise-executor-return\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n/**\n * Runs the specified function and will timeout if it takes longer than the specified wait time\n *\n * @param fn The function to run\n * @param maxWaitTimeInMS The maximum amount of time to wait for the function to resolve\n * @returns Promise that resolves to the resolved value of the function or undefined if it ran\n * longer than the specified wait time\n */\nexport function waitForDuration(fn: () => Promise, maxWaitTimeInMS: number) {\n const timeout = wait(maxWaitTimeInMS).then(() => undefined);\n return Promise.any([timeout, fn()]);\n}\n\n/**\n * Get all functions on an object and its prototype chain (so we don't miss any class methods or any\n * object methods). Note that the functions on the final item in the prototype chain (i.e., Object)\n * are skipped to avoid including functions like `__defineGetter__`, `__defineSetter__`, `toString`,\n * etc.\n *\n * @param obj Object whose functions to get\n * @param objId Optional ID of the object to use for debug logging\n * @returns Array of all function names on an object\n */\n// Note: lodash has something that MIGHT do the same thing as this. Investigate for https://github.com/paranext/paranext-core/issues/134\nexport function getAllObjectFunctionNames(\n obj: { [property: string]: unknown },\n objId: string = 'obj',\n): Set {\n const objectFunctionNames = new Set();\n\n // Get all function properties directly defined on the object\n Object.getOwnPropertyNames(obj).forEach((property) => {\n try {\n if (typeof obj[property] === 'function') objectFunctionNames.add(property);\n } catch (error) {\n console.debug(`Skipping ${property} on ${objId} due to error: ${error}`);\n }\n });\n\n // Walk up the prototype chain and get additional function properties, skipping the functions\n // provided by the final (Object) prototype\n let objectPrototype = Object.getPrototypeOf(obj);\n while (objectPrototype && Object.getPrototypeOf(objectPrototype)) {\n Object.getOwnPropertyNames(objectPrototype).forEach((property) => {\n try {\n if (typeof obj[property] === 'function') objectFunctionNames.add(property);\n } catch (error) {\n console.debug(`Skipping ${property} on ${objId}'s prototype due to error: ${error}`);\n }\n });\n objectPrototype = Object.getPrototypeOf(objectPrototype);\n }\n\n return objectFunctionNames;\n}\n\n/**\n * Creates a synchronous proxy for an asynchronous object. The proxy allows calling methods on an\n * object that is asynchronously fetched using a provided asynchronous function.\n *\n * @param getObject - A function that returns a promise resolving to the object whose asynchronous\n * methods to call.\n * @param objectToProxy - An optional object that is the object that is proxied. If a property is\n * accessed that does exist on this object, it will be returned. If a property is accessed that\n * does not exist on this object, it will be considered to be an asynchronous method called on the\n * object returned from getObject.\n * @returns A synchronous proxy for the asynchronous object.\n */\nexport function createSyncProxyForAsyncObject(\n getObject: (args?: unknown[]) => Promise,\n objectToProxy: Partial = {},\n): T {\n // objectToProxy will have only the synchronously accessed properties of T on it, and this proxy\n // makes the async methods that do not exist yet available synchronously so we have all of T\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n return new Proxy(objectToProxy as T, {\n get(target, prop) {\n // We don't have any type information for T, so we assume methodName exists on it and will let JavaScript throw if it doesn't exist\n // @ts-expect-error 7053\n if (prop in target) return target[prop];\n return async (...args: unknown[]) => {\n // 7053: We don't have any type information for T, so we assume methodName exists on it and will let JavaScript throw if it doesn't exist\n // 2556: The args here are the parameters for the method specified\n // @ts-expect-error 7053 2556\n return (await getObject())[prop](...args);\n };\n },\n });\n}\n\n/** Within type T, recursively change all properties to be optional */\nexport type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial } : T;\n\n/** Within type T, recursively change properties that were of type A to be of type B */\nexport type ReplaceType = T extends A\n ? B\n : T extends object\n ? { [K in keyof T]: ReplaceType }\n : T;\n\n// Thanks to jcalz at https://stackoverflow.com/a/50375286\n/**\n * Converts a union type to an intersection type (`|` to `&`).\n *\n * Note: this utility type is for use on object types. It may fail on other types.\n *\n * @example\n *\n * ```typescript\n * type TypeOne = { one: string };\n * type TypeTwo = { two: number };\n * type TypeThree = { three: string };\n *\n * type TypeNums = { one: TypeOne; two: TypeTwo; three: TypeThree };\n * const numNames = ['one', 'two'] as const;\n * type TypeNumNames = typeof numNames;\n *\n * // Same as `TypeOne | TypeTwo`\n * // `{ one: string } | { two: number }`\n * type TypeOneTwoUnion = TypeNums[TypeNumNames[number]];\n *\n * // Same as `TypeOne & TypeTwo`\n * // `{ one: string; two: number }`\n * type TypeOneTwoIntersection = UnionToIntersection;\n * ```\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type UnionToIntersection = (U extends any ? (x: U) => void : never) extends (\n x: infer I,\n) => void\n ? I\n : never;\n","import PlatformEventEmitter from './platform-event-emitter.model';\nimport { deepClone } from './util';\n\ntype JsonObjectLike = { [key: string]: unknown };\ntype JsonArrayLike = unknown[];\n\nexport type JsonDocumentLike = JsonObjectLike | JsonArrayLike;\n\n/**\n * Options for DocumentCombiner objects\n *\n * - `copyDocuments`: If true, this instance will perform a deep copy of all provided documents before\n * composing the output. If false, then changes made to provided documents after they are\n * contributed will be reflected in the next time output is composed.\n * - `ignoreDuplicateProperties`: If true, then duplicate properties are skipped if they are seen in\n * contributed documents. If false, then throw when duplicate properties are seen in contributed\n * documents.\n */\nexport type DocumentCombinerOptions = {\n copyDocuments: boolean;\n ignoreDuplicateProperties: boolean;\n};\n\n/**\n * Base class for any code that wants to compose JSON documents (primarily in the form of JS objects\n * or arrays) together into a single output document.\n */\nexport default class DocumentCombiner {\n protected baseDocument: JsonDocumentLike;\n protected readonly contributions = new Map();\n protected latestOutput: JsonDocumentLike | undefined;\n protected readonly options: DocumentCombinerOptions;\n private readonly onDidRebuildEmitter = new PlatformEventEmitter();\n /** Event that emits to announce that the document has been rebuilt and the output has been updated */\n // Need `onDidRebuildEmitter` to be instantiated before this line\n // eslint-disable-next-line @typescript-eslint/member-ordering\n readonly onDidRebuild = this.onDidRebuildEmitter.subscribe;\n\n /**\n * Create a DocumentCombiner instance\n *\n * @param baseDocument This is the first document that will be used when composing the output\n * @param options Options used by this object when combining documents\n */\n protected constructor(baseDocument: JsonDocumentLike, options: DocumentCombinerOptions) {\n // Setting baseDocument redundantly because TS doesn't understand that updateBaseDocument does it\n this.baseDocument = baseDocument;\n this.options = options;\n this.updateBaseDocument(baseDocument);\n }\n\n /**\n * Update the starting document for composition process\n *\n * @param baseDocument Base JSON document/JS object that all other documents are added to\n * @returns Recalculated output document given the new starting state and existing other documents\n */\n updateBaseDocument(baseDocument: JsonDocumentLike): JsonDocumentLike | undefined {\n this.validateBaseDocument(baseDocument);\n this.baseDocument = this.options.copyDocuments ? deepClone(baseDocument) : baseDocument;\n this.baseDocument = this.transformBaseDocumentAfterValidation(this.baseDocument);\n return this.rebuild();\n }\n\n /**\n * Add or update one of the contribution documents for the composition process\n *\n * Note: the order in which contribution documents are added can be considered to be indeterminate\n * as it is currently ordered by however `Map.forEach` provides the contributions. The order\n * matters when merging two arrays into one. Also, when `options.ignoreDuplicateProperties` is\n * `true`, the order also matters when adding the same property to an object that is already\n * provided previously. Please let us know if you have trouble because of indeterminate\n * contribution ordering.\n *\n * @param documentName Name of the contributed document to combine\n * @param document Content of the contributed document to combine\n * @returns Recalculated output document given the new or updated contribution and existing other\n * documents\n */\n addOrUpdateContribution(\n documentName: string,\n document: JsonDocumentLike,\n ): JsonDocumentLike | undefined {\n this.validateContribution(documentName, document);\n const previousDocumentVersion = this.contributions.get(documentName);\n let documentToSet = this.options.copyDocuments && !!document ? deepClone(document) : document;\n documentToSet = this.transformContributionAfterValidation(documentName, documentToSet);\n this.contributions.set(documentName, documentToSet);\n try {\n return this.rebuild();\n } catch (error) {\n // If the output isn't valid after adding/updating the contribution, put it back how it was\n if (previousDocumentVersion) this.contributions.set(documentName, previousDocumentVersion);\n else this.contributions.delete(documentName);\n throw new Error(`Error when setting the document named ${documentName}: ${error}`);\n }\n }\n\n /**\n * Delete one of the contribution documents for the composition process\n *\n * @param documentName Name of the contributed document to delete\n * @returns Recalculated output document given the remaining other documents\n */\n deleteContribution(documentName: string): JsonDocumentLike | undefined {\n const document = this.contributions.get(documentName);\n if (!document) throw new Error(`${documentName} does not exist`);\n this.contributions.delete(documentName);\n try {\n return this.rebuild();\n } catch (error) {\n // If the output isn't valid after deleting the contribution, put it back and rethrow\n this.contributions.set(documentName, document);\n throw new Error(`Error when deleting the document named ${documentName}: ${error}`);\n }\n }\n\n /**\n * Delete all present contribution documents for the composition process and return to the base\n * document\n *\n * @returns Recalculated output document consisting only of the base document\n */\n deleteAllContributions(): JsonDocumentLike | undefined {\n if (this.contributions.size <= 0) return this.latestOutput;\n\n // Save out all contributions\n const contributions = [...this.contributions.entries()];\n\n // Delete all contributions\n contributions.forEach(([contributionName]) => this.contributions.delete(contributionName));\n\n // Rebuild with no contributions\n try {\n return this.rebuild();\n } catch (error) {\n // If the output isn't valid after deleting all contributions, put them back and rethrow\n contributions.forEach(([contributionName, document]) =>\n this.contributions.set(contributionName, document),\n );\n throw new Error(`Error when deleting all contributions: ${error}`);\n }\n }\n\n /**\n * Run the document composition process given the starting document and all contributions. Throws\n * if the output document fails to validate properly.\n *\n * @returns Recalculated output document given the starting and contributed documents\n */\n rebuild(): JsonDocumentLike | undefined {\n // The starting document is the output if there are no other contributions\n if (this.contributions.size === 0) {\n let potentialOutput = deepClone(this.baseDocument);\n potentialOutput = this.transformFinalOutputBeforeValidation(potentialOutput);\n this.validateOutput(potentialOutput);\n this.latestOutput = potentialOutput;\n this.onDidRebuildEmitter.emit(undefined);\n return this.latestOutput;\n }\n\n // Compose the output by validating each document one at a time to pinpoint errors better\n let outputIteration = this.baseDocument;\n this.contributions.forEach((contribution: JsonDocumentLike) => {\n outputIteration = mergeObjects(\n outputIteration,\n contribution,\n this.options.ignoreDuplicateProperties,\n );\n this.validateOutput(outputIteration);\n });\n outputIteration = this.transformFinalOutputBeforeValidation(outputIteration);\n this.validateOutput(outputIteration);\n this.latestOutput = outputIteration;\n this.onDidRebuildEmitter.emit(undefined);\n return this.latestOutput;\n }\n\n /**\n * Transform the starting document that is given to the combiner. This transformation occurs after\n * validating the base document and before combining any contributions.\n *\n * WARNING: If you do not create the combiner with option `copyDocuments: true` or clone inside\n * this method, this method will directly modify the `baseDocument` passed in.\n *\n * @param baseDocument Initial input document. Already validated via `validateBaseDocument`\n * @returns Transformed base document\n */\n // We just don't need `this` here. This is basically a no-op function that is available to child\n // classes to override\n // eslint-disable-next-line class-methods-use-this\n protected transformBaseDocumentAfterValidation(baseDocument: JsonDocumentLike): JsonDocumentLike {\n return baseDocument;\n }\n\n /**\n * Transform the contributed document associated with `documentName`. This transformation occurs\n * after validating the contributed document and before combining with other documents.\n *\n * WARNING: If you do not create the combiner with option `copyDocuments: true` or clone inside\n * this method, this method will directly modify the contributed `document` passed in.\n *\n * @param documentName Name of the contributed document to combine\n * @param document Content of the contributed document to combine. Already validated via\n * `validateContribution`\n * @returns Transformed contributed document\n */\n // We just don't need `this` here. This is basically a no-op function that is available to child\n // classes to override\n // eslint-disable-next-line class-methods-use-this\n protected transformContributionAfterValidation(\n // @ts-expect-error this parameter is unused but may be used in child classes\n documentName: string,\n document: JsonDocumentLike,\n ): JsonDocumentLike {\n return document;\n }\n\n /**\n * Throw an error if the provided document is not a valid starting document.\n *\n * @param baseDocument Base JSON document/JS object that all other documents are added to\n */\n // no-op intended to be overridden by child classes. Can't be static\n // @ts-expect-error ts(6133) parameter doesn't need to be used but still needs the right name\n // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars\n protected validateBaseDocument(baseDocument: JsonDocumentLike): void {}\n\n /**\n * Throw an error if the provided document is not a valid contribution document.\n *\n * @param documentName Name of the contributed document to combine\n * @param document Content of the contributed document to combine\n */\n // no-op intended to be overridden by child classes. Can't be static\n // @ts-expect-error ts(6133) parameter doesn't need to be used but still needs the right name\n // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars\n protected validateContribution(documentName: string, document: JsonDocumentLike): void {}\n\n /**\n * Throw an error if the provided output is not valid.\n *\n * @param output Output document that could potentially be returned to callers\n */\n // no-op intended to be overridden by child classes. Can't be static\n // @ts-expect-error ts(6133) parameter doesn't need to be used but still needs the right name\n // eslint-disable-next-line class-methods-use-this, @typescript-eslint/no-unused-vars\n protected validateOutput(output: JsonDocumentLike): void {}\n\n /**\n * Transform the document that is the composition of the base document and all contribution\n * documents. This is the last step that will be run prior to validation via `validateOutput`\n * before `this.latestOutput` is updated to the new output.\n *\n * @param finalOutput Final output document that could potentially be returned to callers. \"Final\"\n * means no further contribution documents will be merged.\n */\n // no-op intended to be overridden by child classes. Can't be static\n // eslint-disable-next-line class-methods-use-this\n protected transformFinalOutputBeforeValidation(finalOutput: JsonDocumentLike): JsonDocumentLike {\n return finalOutput;\n }\n}\n\n// #region Helper functions\n\n/**\n * Determines if the input values are objects but not arrays\n *\n * @param values Objects to check\n * @returns True if all the values are objects but not arrays\n */\nfunction areNonArrayObjects(...values: unknown[]): boolean {\n let allMatch = true;\n values.forEach((value: unknown) => {\n if (!value || typeof value !== 'object' || Array.isArray(value)) allMatch = false;\n });\n return allMatch;\n}\n\n/**\n * Determines if the input values are arrays\n *\n * @param value Objects to check\n * @returns True if the values are arrays\n */\nfunction areArrayObjects(...values: unknown[]): boolean {\n let allMatch = true;\n values.forEach((value: unknown) => {\n if (!value || typeof value !== 'object' || !Array.isArray(value)) allMatch = false;\n });\n return allMatch;\n}\n\n/**\n * Deep clone and recursively merge the properties of one object (copyFrom) into another\n * (startingPoint). Throws if copyFrom would overwrite values already existing in startingPoint.\n *\n * Does not modify the objects passed in.\n *\n * @param startingPoint Object that is the starting point for the return value\n * @param copyFrom Object whose values are copied into the return value\n * @param ignoreDuplicateProperties Whether to ignore object properties that are present in\n * `copyFrom` that are already present in `startingPoint`. If `false`, throws when an object\n * property in `copyFrom` is already present in `startingPoint`\n * @returns Object that is the combination of the two documents\n */\nfunction mergeObjects(\n startingPoint: JsonDocumentLike,\n copyFrom: JsonDocumentLike,\n ignoreDuplicateProperties: boolean,\n): JsonDocumentLike {\n const retVal = deepClone(startingPoint);\n\n if (!copyFrom) return retVal;\n\n return mergeObjectsInternal(retVal, deepClone(copyFrom), ignoreDuplicateProperties);\n}\n\n/**\n * Recursively merge the properties of one object (copyFrom) into another (startingPoint). Throws if\n * copyFrom would overwrite values already existing in startingPoint.\n *\n * WARNING: Modifies the argument objects in some way. Recommended to use `mergeObjects`\n *\n * @param startingPoint Object that is the starting point for the return value\n * @param copyFrom Object whose values are copied into the return value\n * @param ignoreDuplicateProperties Whether to ignore object properties that are present in\n * `copyFrom` that are already present in `startingPoint`. If `false`, throws when an object\n * property in `copyFrom` is already present in `startingPoint`\n * @returns Object that is the combination of the two documents\n */\nfunction mergeObjectsInternal(\n startingPoint: JsonDocumentLike,\n copyFrom: JsonDocumentLike,\n ignoreDuplicateProperties: boolean,\n): JsonDocumentLike {\n if (!copyFrom) return startingPoint;\n\n if (areNonArrayObjects(startingPoint, copyFrom)) {\n // Merge properties since they are both objects\n\n // We know these are objects from the `if` check\n /* eslint-disable no-type-assertion/no-type-assertion */\n const startingPointObj = startingPoint as JsonObjectLike;\n const copyFromObj = copyFrom as JsonObjectLike;\n /* eslint-enable no-type-assertion/no-type-assertion */\n Object.keys(copyFromObj).forEach((key: string | number) => {\n if (Object.hasOwn(startingPointObj, key)) {\n if (areNonArrayObjects(startingPointObj[key], copyFromObj[key])) {\n startingPointObj[key] = mergeObjectsInternal(\n // We know these are objects from the `if` check\n /* eslint-disable no-type-assertion/no-type-assertion */\n startingPointObj[key] as JsonObjectLike,\n copyFromObj[key] as JsonObjectLike,\n ignoreDuplicateProperties,\n /* eslint-enable no-type-assertion/no-type-assertion */\n );\n } else if (areArrayObjects(startingPointObj[key], copyFromObj[key])) {\n // Concat the arrays since they are both arrays\n\n // We know these are arrays from the `else if` check\n /* eslint-disable no-type-assertion/no-type-assertion */\n startingPointObj[key] = (startingPointObj[key] as JsonArrayLike).concat(\n copyFromObj[key] as JsonArrayLike,\n );\n /* eslint-enable no-type-assertion/no-type-assertion */\n } else if (!ignoreDuplicateProperties)\n throw new Error(`Cannot merge objects: key \"${key}\" already exists in the target object`);\n // Note that the first non-object non-array value that gets placed in a property stays.\n // New values do not override existing ones\n } else {\n startingPointObj[key] = copyFromObj[key];\n }\n });\n } else if (areArrayObjects(startingPoint, copyFrom)) {\n // Concat the arrays since they are both arrays\n\n // Push the contents of copyFrom into startingPoint since it is a const and was already deep cloned\n // We know these are objects from the `else if` check\n /* eslint-disable no-type-assertion/no-type-assertion */\n (startingPoint as JsonArrayLike).push(...(copyFrom as JsonArrayLike));\n /* eslint-enable no-type-assertion/no-type-assertion */\n }\n\n // Note that nothing happens if `startingPoint` is not an object or an array or if `startingPoint`\n // and `copyFrom` are not both object or both arrays. Should we throw? Should we push `copyFrom`'s\n // values into the array? Other? Maybe one day we can add some options to decide what to do in\n // this situation, but YAGNI for now\n\n return startingPoint;\n}\n\n// #endregion\n","import { Mutex as AsyncMutex } from 'async-mutex';\n\n// Extending Mutex from async-mutex so we can add JSDoc\n\n/**\n * Class that allows calling asynchronous functions multiple times at once while only running one at\n * a time.\n *\n * @example\n *\n * ```typescript\n * const mutex = new Mutex();\n *\n * mutex.runExclusive(async () => {\n * // Do some asynchronous stuff\n * console.log('These run one-at-a-time');\n * });\n *\n * mutex.runExclusive(async () => {\n * // Do some asynchronous stuff\n * console.log('These run one-at-a-time');\n * });\n * ```\n *\n * See [`async-mutex`](https://www.npmjs.com/package/async-mutex) for more information.\n */\nclass Mutex extends AsyncMutex {}\n\nexport default Mutex;\n","import Mutex from './mutex';\n\n/** Map of {@link Mutex}es that automatically (lazily) generates a new {@link Mutex} for any new key */\nclass MutexMap {\n private mutexesByID = new Map();\n\n get(mutexID: string): Mutex {\n let retVal = this.mutexesByID.get(mutexID);\n if (retVal) return retVal;\n\n retVal = new Mutex();\n this.mutexesByID.set(mutexID, retVal);\n return retVal;\n }\n}\n\nexport default MutexMap;\n","import DocumentCombiner, { DocumentCombinerOptions, JsonDocumentLike } from './document-combiner';\n\nexport default class NonValidatingDocumentCombiner extends DocumentCombiner {\n // Making the protected base constructor public\n // eslint-disable-next-line @typescript-eslint/no-useless-constructor\n constructor(baseDocument: JsonDocumentLike, options: DocumentCombinerOptions) {\n super(baseDocument, options);\n }\n\n get output(): JsonDocumentLike | undefined {\n return this.latestOutput;\n }\n}\n","/** Enables language-sensitive number formatting. Wraps Intl.NumberFormat */\nexport default class NumberFormat {\n private numberFormatter: Intl.NumberFormat;\n\n constructor(locales?: string | string[], options?: Intl.NumberFormatOptions) {\n this.numberFormatter = new Intl.NumberFormat(locales, options);\n }\n\n /**\n * Formats a number according to the locale and formatting options of this NumberFormat object\n *\n * @param value Number or BigInt to format\n * @returns String representing the given number formatted according to the locale and formatting\n * options of this NumberFormat object\n */\n format(value: number | bigint): string {\n return this.numberFormatter.format(value);\n }\n\n /**\n * Formats a range of numbers according to the locale and formatting options of this NumberFormat\n * object\n *\n * @param startRange Number or bigint representing the start of the range\n * @param endRange Number or bigint representing the end of the range\n * @returns String representing the given range of numbers formatted according to the locale and\n * formatting options of this NumberFormat object\n */\n formatRange(startRange: number | bigint, endRange: number | bigint): string {\n return this.numberFormatter.formatRange(startRange, endRange);\n }\n\n /**\n * Returns an array of objects containing the locale-specific tokens from which it is possible to\n * build custom strings while preserving the locale-specific parts.\n *\n * @param startRange Number or bigint representing start of the range\n * @param endRange Number or bigint representing end of the range\n * @returns Array of NumberRangeFormatPart objects containing the formatted range of numbers in\n * parts\n */\n formatRangeToParts(\n startRange: number | bigint,\n endRange: number | bigint,\n ): Intl.NumberRangeFormatPart[] {\n return this.numberFormatter.formatRangeToParts(startRange, endRange);\n }\n\n /**\n * Allows locale-aware formatting of strings produced by this NumberFormat object\n *\n * @param value Number or bigint to format\n * @returns Array of NumberFormatPart objects containing the formatted number in parts\n */\n formatToParts(value: number | bigint): Intl.NumberFormatPart[] {\n return this.numberFormatter.formatToParts(value);\n }\n\n /**\n * Returns a new object with properties reflecting the locale and number formatting options\n * computed during initialization of this NumberFormat object\n *\n * @returns ResolvedNumberFormatOptions object\n */\n resolvedOptions(): Intl.ResolvedNumberFormatOptions {\n return this.numberFormatter.resolvedOptions();\n }\n}\n","import { Dispose } from './disposal.model';\nimport { Unsubscriber, UnsubscriberAsync } from './unsubscriber';\n\n/** Simple collection for UnsubscriberAsync objects that also provides an easy way to run them. */\nexport default class UnsubscriberAsyncList {\n readonly unsubscribers = new Set();\n\n constructor(private name = 'Anonymous') {}\n\n /**\n * Add unsubscribers to the list. Note that duplicates are not added twice.\n *\n * @param unsubscribers - Objects that were returned from a registration process.\n */\n add(...unsubscribers: (UnsubscriberAsync | Unsubscriber | Dispose)[]) {\n unsubscribers.forEach((unsubscriber) => {\n if ('dispose' in unsubscriber) this.unsubscribers.add(unsubscriber.dispose);\n else this.unsubscribers.add(unsubscriber);\n });\n }\n\n /**\n * Run all unsubscribers added to this list and then clear the list.\n *\n * @returns `true` if all unsubscribers succeeded, `false` otherwise.\n */\n async runAllUnsubscribers(): Promise {\n const unsubs = [...this.unsubscribers].map((unsubscriber) => unsubscriber());\n const results = await Promise.all(unsubs);\n this.unsubscribers.clear();\n return results.every((unsubscriberSucceeded, index) => {\n if (!unsubscriberSucceeded)\n console.error(`UnsubscriberAsyncList ${this.name}: Unsubscriber at index ${index} failed!`);\n\n return unsubscriberSucceeded;\n });\n }\n}\n","var P = Object.defineProperty;\nvar R = (t, e, s) => e in t ? P(t, e, { enumerable: !0, configurable: !0, writable: !0, value: s }) : t[e] = s;\nvar n = (t, e, s) => (R(t, typeof e != \"symbol\" ? e + \"\" : e, s), s);\nclass z {\n constructor() {\n n(this, \"books\");\n n(this, \"firstSelectedBookNum\");\n n(this, \"lastSelectedBookNum\");\n n(this, \"count\");\n n(this, \"selectedBookNumbers\");\n n(this, \"selectedBookIds\");\n }\n}\nconst m = [\n \"GEN\",\n \"EXO\",\n \"LEV\",\n \"NUM\",\n \"DEU\",\n \"JOS\",\n \"JDG\",\n \"RUT\",\n \"1SA\",\n \"2SA\",\n // 10\n \"1KI\",\n \"2KI\",\n \"1CH\",\n \"2CH\",\n \"EZR\",\n \"NEH\",\n \"EST\",\n \"JOB\",\n \"PSA\",\n \"PRO\",\n // 20\n \"ECC\",\n \"SNG\",\n \"ISA\",\n \"JER\",\n \"LAM\",\n \"EZK\",\n \"DAN\",\n \"HOS\",\n \"JOL\",\n \"AMO\",\n // 30\n \"OBA\",\n \"JON\",\n \"MIC\",\n \"NAM\",\n \"HAB\",\n \"ZEP\",\n \"HAG\",\n \"ZEC\",\n \"MAL\",\n \"MAT\",\n // 40\n \"MRK\",\n \"LUK\",\n \"JHN\",\n \"ACT\",\n \"ROM\",\n \"1CO\",\n \"2CO\",\n \"GAL\",\n \"EPH\",\n \"PHP\",\n // 50\n \"COL\",\n \"1TH\",\n \"2TH\",\n \"1TI\",\n \"2TI\",\n \"TIT\",\n \"PHM\",\n \"HEB\",\n \"JAS\",\n \"1PE\",\n // 60\n \"2PE\",\n \"1JN\",\n \"2JN\",\n \"3JN\",\n \"JUD\",\n \"REV\",\n \"TOB\",\n \"JDT\",\n \"ESG\",\n \"WIS\",\n // 70\n \"SIR\",\n \"BAR\",\n \"LJE\",\n \"S3Y\",\n \"SUS\",\n \"BEL\",\n \"1MA\",\n \"2MA\",\n \"3MA\",\n \"4MA\",\n // 80\n \"1ES\",\n \"2ES\",\n \"MAN\",\n \"PS2\",\n \"ODA\",\n \"PSS\",\n \"JSA\",\n // actual variant text for JOS, now in LXA text\n \"JDB\",\n // actual variant text for JDG, now in LXA text\n \"TBS\",\n // actual variant text for TOB, now in LXA text\n \"SST\",\n // actual variant text for SUS, now in LXA text // 90\n \"DNT\",\n // actual variant text for DAN, now in LXA text\n \"BLT\",\n // actual variant text for BEL, now in LXA text\n \"XXA\",\n \"XXB\",\n \"XXC\",\n \"XXD\",\n \"XXE\",\n \"XXF\",\n \"XXG\",\n \"FRT\",\n // 100\n \"BAK\",\n \"OTH\",\n \"3ES\",\n // Used previously but really should be 2ES\n \"EZA\",\n // Used to be called 4ES, but not actually in any known project\n \"5EZ\",\n // Used to be called 5ES, but not actually in any known project\n \"6EZ\",\n // Used to be called 6ES, but not actually in any known project\n \"INT\",\n \"CNC\",\n \"GLO\",\n \"TDX\",\n // 110\n \"NDX\",\n \"DAG\",\n \"PS3\",\n \"2BA\",\n \"LBA\",\n \"JUB\",\n \"ENO\",\n \"1MQ\",\n \"2MQ\",\n \"3MQ\",\n // 120\n \"REP\",\n \"4BA\",\n \"LAO\"\n], v = [\n \"XXA\",\n \"XXB\",\n \"XXC\",\n \"XXD\",\n \"XXE\",\n \"XXF\",\n \"XXG\",\n \"FRT\",\n \"BAK\",\n \"OTH\",\n \"INT\",\n \"CNC\",\n \"GLO\",\n \"TDX\",\n \"NDX\"\n], X = [\n \"Genesis\",\n \"Exodus\",\n \"Leviticus\",\n \"Numbers\",\n \"Deuteronomy\",\n \"Joshua\",\n \"Judges\",\n \"Ruth\",\n \"1 Samuel\",\n \"2 Samuel\",\n \"1 Kings\",\n \"2 Kings\",\n \"1 Chronicles\",\n \"2 Chronicles\",\n \"Ezra\",\n \"Nehemiah\",\n \"Esther (Hebrew)\",\n \"Job\",\n \"Psalms\",\n \"Proverbs\",\n \"Ecclesiastes\",\n \"Song of Songs\",\n \"Isaiah\",\n \"Jeremiah\",\n \"Lamentations\",\n \"Ezekiel\",\n \"Daniel (Hebrew)\",\n \"Hosea\",\n \"Joel\",\n \"Amos\",\n \"Obadiah\",\n \"Jonah\",\n \"Micah\",\n \"Nahum\",\n \"Habakkuk\",\n \"Zephaniah\",\n \"Haggai\",\n \"Zechariah\",\n \"Malachi\",\n \"Matthew\",\n \"Mark\",\n \"Luke\",\n \"John\",\n \"Acts\",\n \"Romans\",\n \"1 Corinthians\",\n \"2 Corinthians\",\n \"Galatians\",\n \"Ephesians\",\n \"Philippians\",\n \"Colossians\",\n \"1 Thessalonians\",\n \"2 Thessalonians\",\n \"1 Timothy\",\n \"2 Timothy\",\n \"Titus\",\n \"Philemon\",\n \"Hebrews\",\n \"James\",\n \"1 Peter\",\n \"2 Peter\",\n \"1 John\",\n \"2 John\",\n \"3 John\",\n \"Jude\",\n \"Revelation\",\n \"Tobit\",\n \"Judith\",\n \"Esther Greek\",\n \"Wisdom of Solomon\",\n \"Sirach (Ecclesiasticus)\",\n \"Baruch\",\n \"Letter of Jeremiah\",\n \"Song of 3 Young Men\",\n \"Susanna\",\n \"Bel and the Dragon\",\n \"1 Maccabees\",\n \"2 Maccabees\",\n \"3 Maccabees\",\n \"4 Maccabees\",\n \"1 Esdras (Greek)\",\n \"2 Esdras (Latin)\",\n \"Prayer of Manasseh\",\n \"Psalm 151\",\n \"Odes\",\n \"Psalms of Solomon\",\n // WARNING, if you change the spelling of the *obsolete* tag be sure to update\n // IsObsolete routine\n \"Joshua A. *obsolete*\",\n \"Judges B. *obsolete*\",\n \"Tobit S. *obsolete*\",\n \"Susanna Th. *obsolete*\",\n \"Daniel Th. *obsolete*\",\n \"Bel Th. *obsolete*\",\n \"Extra A\",\n \"Extra B\",\n \"Extra C\",\n \"Extra D\",\n \"Extra E\",\n \"Extra F\",\n \"Extra G\",\n \"Front Matter\",\n \"Back Matter\",\n \"Other Matter\",\n \"3 Ezra *obsolete*\",\n \"Apocalypse of Ezra\",\n \"5 Ezra (Latin Prologue)\",\n \"6 Ezra (Latin Epilogue)\",\n \"Introduction\",\n \"Concordance \",\n \"Glossary \",\n \"Topical Index\",\n \"Names Index\",\n \"Daniel Greek\",\n \"Psalms 152-155\",\n \"2 Baruch (Apocalypse)\",\n \"Letter of Baruch\",\n \"Jubilees\",\n \"Enoch\",\n \"1 Meqabyan\",\n \"2 Meqabyan\",\n \"3 Meqabyan\",\n \"Reproof (Proverbs 25-31)\",\n \"4 Baruch (Rest of Baruch)\",\n \"Laodiceans\"\n], C = K();\nfunction N(t, e = !0) {\n return e && (t = t.toUpperCase()), t in C ? C[t] : 0;\n}\nfunction B(t) {\n return N(t) > 0;\n}\nfunction x(t) {\n const e = typeof t == \"string\" ? N(t) : t;\n return e >= 40 && e <= 66;\n}\nfunction T(t) {\n return (typeof t == \"string\" ? N(t) : t) <= 39;\n}\nfunction O(t) {\n return t <= 66;\n}\nfunction V(t) {\n const e = typeof t == \"string\" ? N(t) : t;\n return I(e) && !O(e);\n}\nfunction* L() {\n for (let t = 1; t <= m.length; t++)\n yield t;\n}\nconst G = 1, S = m.length;\nfunction H() {\n return [\"XXA\", \"XXB\", \"XXC\", \"XXD\", \"XXE\", \"XXF\", \"XXG\"];\n}\nfunction k(t, e = \"***\") {\n const s = t - 1;\n return s < 0 || s >= m.length ? e : m[s];\n}\nfunction A(t) {\n return t <= 0 || t > S ? \"******\" : X[t - 1];\n}\nfunction y(t) {\n return A(N(t));\n}\nfunction I(t) {\n const e = typeof t == \"number\" ? k(t) : t;\n return B(e) && !v.includes(e);\n}\nfunction q(t) {\n const e = typeof t == \"number\" ? k(t) : t;\n return B(e) && v.includes(e);\n}\nfunction U(t) {\n return X[t - 1].includes(\"*obsolete*\");\n}\nfunction K() {\n const t = {};\n for (let e = 0; e < m.length; e++)\n t[m[e]] = e + 1;\n return t;\n}\nconst f = {\n allBookIds: m,\n nonCanonicalIds: v,\n bookIdToNumber: N,\n isBookIdValid: B,\n isBookNT: x,\n isBookOT: T,\n isBookOTNT: O,\n isBookDC: V,\n allBookNumbers: L,\n firstBook: G,\n lastBook: S,\n extraBooks: H,\n bookNumberToId: k,\n bookNumberToEnglishName: A,\n bookIdToEnglishName: y,\n isCanonical: I,\n isExtraMaterial: q,\n isObsolete: U\n};\nvar l = /* @__PURE__ */ ((t) => (t[t.Unknown = 0] = \"Unknown\", t[t.Original = 1] = \"Original\", t[t.Septuagint = 2] = \"Septuagint\", t[t.Vulgate = 3] = \"Vulgate\", t[t.English = 4] = \"English\", t[t.RussianProtestant = 5] = \"RussianProtestant\", t[t.RussianOrthodox = 6] = \"RussianOrthodox\", t))(l || {});\nconst u = class u {\n // private versInfo: Versification;\n constructor(e) {\n n(this, \"name\");\n n(this, \"fullPath\");\n n(this, \"isPresent\");\n n(this, \"hasVerseSegments\");\n n(this, \"isCustomized\");\n n(this, \"baseVersification\");\n n(this, \"scriptureBooks\");\n n(this, \"_type\");\n if (e != null)\n typeof e == \"string\" ? this.name = e : this._type = e;\n else\n throw new Error(\"Argument null\");\n }\n get type() {\n return this._type;\n }\n equals(e) {\n return !e.type || !this.type ? !1 : e.type === this.type;\n }\n};\nn(u, \"Original\", new u(l.Original)), n(u, \"Septuagint\", new u(l.Septuagint)), n(u, \"Vulgate\", new u(l.Vulgate)), n(u, \"English\", new u(l.English)), n(u, \"RussianProtestant\", new u(l.RussianProtestant)), n(u, \"RussianOrthodox\", new u(l.RussianOrthodox));\nlet c = u;\nfunction E(t, e) {\n const s = e[0];\n for (let r = 1; r < e.length; r++)\n t = t.split(e[r]).join(s);\n return t.split(s);\n}\nvar D = /* @__PURE__ */ ((t) => (t[t.Valid = 0] = \"Valid\", t[t.UnknownVersification = 1] = \"UnknownVersification\", t[t.OutOfRange = 2] = \"OutOfRange\", t[t.VerseOutOfOrder = 3] = \"VerseOutOfOrder\", t[t.VerseRepeated = 4] = \"VerseRepeated\", t))(D || {});\nconst i = class i {\n constructor(e, s, r, o) {\n /** Not yet implemented. */\n n(this, \"firstChapter\");\n /** Not yet implemented. */\n n(this, \"lastChapter\");\n /** Not yet implemented. */\n n(this, \"lastVerse\");\n /** Not yet implemented. */\n n(this, \"hasSegmentsDefined\");\n /** Not yet implemented. */\n n(this, \"text\");\n /** Not yet implemented. */\n n(this, \"BBBCCCVVVS\");\n /** Not yet implemented. */\n n(this, \"longHashCode\");\n /** The versification of the reference. */\n n(this, \"versification\");\n n(this, \"rtlMark\", \"‏\");\n n(this, \"_bookNum\", 0);\n n(this, \"_chapterNum\", 0);\n n(this, \"_verseNum\", 0);\n n(this, \"_verse\");\n if (r == null && o == null)\n if (e != null && typeof e == \"string\") {\n const a = e, h = s != null && s instanceof c ? s : void 0;\n this.setEmpty(h), this.parse(a);\n } else if (e != null && typeof e == \"number\") {\n const a = s != null && s instanceof c ? s : void 0;\n this.setEmpty(a), this._verseNum = e % i.chapterDigitShifter, this._chapterNum = Math.floor(\n e % i.bookDigitShifter / i.chapterDigitShifter\n ), this._bookNum = Math.floor(e / i.bookDigitShifter);\n } else if (s == null)\n if (e != null && e instanceof i) {\n const a = e;\n this._bookNum = a.bookNum, this._chapterNum = a.chapterNum, this._verseNum = a.verseNum, this._verse = a.verse, this.versification = a.versification;\n } else {\n if (e == null)\n return;\n const a = e instanceof c ? e : i.defaultVersification;\n this.setEmpty(a);\n }\n else\n throw new Error(\"VerseRef constructor not supported.\");\n else if (e != null && s != null && r != null)\n if (typeof e == \"string\" && typeof s == \"string\" && typeof r == \"string\")\n this.setEmpty(o), this.updateInternal(e, s, r);\n else if (typeof e == \"number\" && typeof s == \"number\" && typeof r == \"number\")\n this._bookNum = e, this._chapterNum = s, this._verseNum = r, this.versification = o ?? i.defaultVersification;\n else\n throw new Error(\"VerseRef constructor not supported.\");\n else\n throw new Error(\"VerseRef constructor not supported.\");\n }\n /**\n * @deprecated Will be removed in v2. Replace `VerseRef.parse('...')` with `new VerseRef('...')`\n * or refactor to use `VerseRef.tryParse('...')` which has a different return type.\n */\n static parse(e, s = i.defaultVersification) {\n const r = new i(s);\n return r.parse(e), r;\n }\n /**\n * Determines if the verse string is in a valid format (does not consider versification).\n */\n static isVerseParseable(e) {\n return e.length > 0 && \"0123456789\".includes(e[0]) && !e.endsWith(this.verseRangeSeparator) && !e.endsWith(this.verseSequenceIndicator);\n }\n /**\n * Tries to parse the specified string into a verse reference.\n * @param str - The string to attempt to parse.\n * @returns success: `true` if the specified string was successfully parsed, `false` otherwise.\n * @returns verseRef: The result of the parse if successful, or empty VerseRef if it failed\n */\n static tryParse(e) {\n let s;\n try {\n return s = i.parse(e), { success: !0, verseRef: s };\n } catch (r) {\n if (r instanceof d)\n return s = new i(), { success: !1, verseRef: s };\n throw r;\n }\n }\n /**\n * Gets the reference as a comparable integer where the book, chapter, and verse each occupy 3\n * digits.\n * @param bookNum - Book number (this is 1-based, not an index).\n * @param chapterNum - Chapter number.\n * @param verseNum - Verse number.\n * @returns The reference as a comparable integer where the book, chapter, and verse each occupy 3\n * digits.\n */\n static getBBBCCCVVV(e, s, r) {\n return e % i.bcvMaxValue * i.bookDigitShifter + (s >= 0 ? s % i.bcvMaxValue * i.chapterDigitShifter : 0) + (r >= 0 ? r % i.bcvMaxValue : 0);\n }\n /**\n * Parses a verse string and gets the leading numeric portion as a number.\n * @param verseStr - verse string to parse\n * @returns true if the entire string could be parsed as a single, simple verse number (1-999);\n * false if the verse string represented a verse bridge, contained segment letters, or was invalid\n */\n static tryGetVerseNum(e) {\n let s;\n if (!e)\n return s = -1, { success: !0, vNum: s };\n s = 0;\n let r;\n for (let o = 0; o < e.length; o++) {\n if (r = e[o], r < \"0\" || r > \"9\")\n return o === 0 && (s = -1), { success: !1, vNum: s };\n if (s = s * 10 + +r - +\"0\", s > i.bcvMaxValue)\n return s = -1, { success: !1, vNum: s };\n }\n return { success: !0, vNum: s };\n }\n /**\n * Checks to see if a VerseRef hasn't been set - all values are the default.\n */\n get isDefault() {\n return this.bookNum === 0 && this.chapterNum === 0 && this.verseNum === 0 && this.versification == null;\n }\n /**\n * Gets whether the verse contains multiple verses.\n */\n get hasMultiple() {\n return this._verse != null && (this._verse.includes(i.verseRangeSeparator) || this._verse.includes(i.verseSequenceIndicator));\n }\n /**\n * Gets or sets the book of the reference. Book is the 3-letter abbreviation in capital letters,\n * e.g. `'MAT'`.\n */\n get book() {\n return f.bookNumberToId(this.bookNum, \"\");\n }\n set book(e) {\n this.bookNum = f.bookIdToNumber(e);\n }\n /**\n * Gets or sets the chapter of the reference,. e.g. `'3'`.\n */\n get chapter() {\n return this.isDefault || this._chapterNum < 0 ? \"\" : this._chapterNum.toString();\n }\n set chapter(e) {\n const s = +e;\n this._chapterNum = Number.isInteger(s) ? s : -1;\n }\n /**\n * Gets or sets the verse of the reference, including range, segments, and sequences, e.g. `'4'`,\n * or `'4b-5a, 7'`.\n */\n get verse() {\n return this._verse != null ? this._verse : this.isDefault || this._verseNum < 0 ? \"\" : this._verseNum.toString();\n }\n set verse(e) {\n const { success: s, vNum: r } = i.tryGetVerseNum(e);\n this._verse = s ? void 0 : e.replace(this.rtlMark, \"\"), this._verseNum = r, !(this._verseNum >= 0) && ({ vNum: this._verseNum } = i.tryGetVerseNum(this._verse));\n }\n /**\n * Get or set Book based on book number, e.g. `42`.\n */\n get bookNum() {\n return this._bookNum;\n }\n set bookNum(e) {\n if (e <= 0 || e > f.lastBook)\n throw new d(\n \"BookNum must be greater than zero and less than or equal to last book\"\n );\n this._bookNum = e;\n }\n /**\n * Gets or sets the chapter number, e.g. `3`. `-1` if not valid.\n */\n get chapterNum() {\n return this._chapterNum;\n }\n set chapterNum(e) {\n this.chapterNum = e;\n }\n /**\n * Gets or sets verse start number, e.g. `4`. `-1` if not valid.\n */\n get verseNum() {\n return this._verseNum;\n }\n set verseNum(e) {\n this._verseNum = e;\n }\n /**\n * String representing the versification (should ONLY be used for serialization/deserialization).\n *\n * @remarks This is for backwards compatibility when ScrVers was an enumeration.\n */\n get versificationStr() {\n var e;\n return (e = this.versification) == null ? void 0 : e.name;\n }\n set versificationStr(e) {\n this.versification = this.versification != null ? new c(e) : void 0;\n }\n /**\n * Determines if the reference is valid.\n */\n get valid() {\n return this.validStatus === 0;\n }\n /**\n * Get the valid status for this reference.\n */\n get validStatus() {\n return this.validateVerse(i.verseRangeSeparators, i.verseSequenceIndicators);\n }\n /**\n * Gets the reference as a comparable integer where the book,\n * chapter, and verse each occupy three digits and the verse is 0.\n */\n get BBBCCC() {\n return i.getBBBCCCVVV(this._bookNum, this._chapterNum, 0);\n }\n /**\n * Gets the reference as a comparable integer where the book,\n * chapter, and verse each occupy three digits. If verse is not null\n * (i.e., this reference represents a complex reference with verse\n * segments or bridge) this cannot be used for an exact comparison.\n */\n get BBBCCCVVV() {\n return i.getBBBCCCVVV(this._bookNum, this._chapterNum, this._verseNum);\n }\n /**\n * Gets whether the verse is defined as an excluded verse in the versification.\n * Does not handle verse ranges.\n */\n // eslint-disable-next-line @typescript-eslint/class-literal-property-style\n get isExcluded() {\n return !1;\n }\n /**\n * Parses the reference in the specified string.\n * Optionally versification can follow reference as in GEN 3:11/4\n * Throw an exception if\n * - invalid book name\n * - chapter number is missing or not a number\n * - verse number is missing or does not start with a number\n * - versification is invalid\n * @param verseStr - string to parse e.g. 'MAT 3:11'\n */\n parse(e) {\n if (e = e.replace(this.rtlMark, \"\"), e.includes(\"/\")) {\n const a = e.split(\"/\");\n if (e = a[0], a.length > 1)\n try {\n const h = +a[1].trim();\n this.versification = new c(l[h]);\n } catch {\n throw new d(\"Invalid reference : \" + e);\n }\n }\n const s = e.trim().split(\" \");\n if (s.length !== 2)\n throw new d(\"Invalid reference : \" + e);\n const r = s[1].split(\":\"), o = +r[0];\n if (r.length !== 2 || f.bookIdToNumber(s[0]) === 0 || !Number.isInteger(o) || o < 0 || !i.isVerseParseable(r[1]))\n throw new d(\"Invalid reference : \" + e);\n this.updateInternal(s[0], r[0], r[1]);\n }\n /**\n * Simplifies this verse ref so that it has no bridging of verses or\n * verse segments like `'1a'`.\n */\n simplify() {\n this._verse = void 0;\n }\n /**\n * Makes a clone of the reference.\n *\n * @returns The cloned VerseRef.\n */\n clone() {\n return new i(this);\n }\n toString() {\n const e = this.book;\n return e === \"\" ? \"\" : `${e} ${this.chapter}:${this.verse}`;\n }\n /**\n * Compares this `VerseRef` with supplied one.\n * @param verseRef - object to compare this one to.\n * @returns `true` if this `VerseRef` is equal to the supplied on, `false` otherwise.\n */\n equals(e) {\n return e instanceof i ? e._bookNum === this._bookNum && e._chapterNum === this._chapterNum && e._verseNum === this._verseNum && e.verse === this.verse && e.versification != null && this.versification != null && e.versification.equals(this.versification) : !1;\n }\n /**\n * Enumerate all individual verses contained in a VerseRef.\n * Verse ranges are indicated by \"-\" and consecutive verses by \",\"s.\n * Examples:\n * GEN 1:2 returns GEN 1:2\n * GEN 1:1a-3b,5 returns GEN 1:1a, GEN 1:2, GEN 1:3b, GEN 1:5\n * GEN 1:2a-2c returns //! ??????\n *\n * @param specifiedVersesOnly - if set to true return only verses that are\n * explicitly specified only, not verses within a range. Defaults to `false`.\n * @param verseRangeSeparators - Verse range separators.\n * Defaults to `VerseRef.verseRangeSeparators`.\n * @param verseSequenceSeparators - Verse sequence separators.\n * Defaults to `VerseRef.verseSequenceIndicators`.\n * @returns An array of all single verse references in this VerseRef.\n */\n allVerses(e = !1, s = i.verseRangeSeparators, r = i.verseSequenceIndicators) {\n if (this._verse == null || this.chapterNum <= 0)\n return [this.clone()];\n const o = [], a = E(this._verse, r);\n for (const h of a.map((g) => E(g, s))) {\n const g = this.clone();\n g.verse = h[0];\n const w = g.verseNum;\n if (o.push(g), h.length > 1) {\n const p = this.clone();\n if (p.verse = h[1], !e)\n for (let b = w + 1; b < p.verseNum; b++) {\n const J = new i(\n this._bookNum,\n this._chapterNum,\n b,\n this.versification\n );\n this.isExcluded || o.push(J);\n }\n o.push(p);\n }\n }\n return o;\n }\n /**\n * Validates a verse number using the supplied separators rather than the defaults.\n */\n validateVerse(e, s) {\n if (!this.verse)\n return this.internalValid;\n let r = 0;\n for (const o of this.allVerses(!0, e, s)) {\n const a = o.internalValid;\n if (a !== 0)\n return a;\n const h = o.BBBCCCVVV;\n if (r > h)\n return 3;\n if (r === h)\n return 4;\n r = h;\n }\n return 0;\n }\n /**\n * Gets whether a single verse reference is valid.\n */\n get internalValid() {\n return this.versification == null ? 1 : this._bookNum <= 0 || this._bookNum > f.lastBook ? 2 : (f.isCanonical(this._bookNum), 0);\n }\n setEmpty(e = i.defaultVersification) {\n this._bookNum = 0, this._chapterNum = -1, this._verse = void 0, this.versification = e;\n }\n updateInternal(e, s, r) {\n this.bookNum = f.bookIdToNumber(e), this.chapter = s, this.verse = r;\n }\n};\nn(i, \"defaultVersification\", c.English), n(i, \"verseRangeSeparator\", \"-\"), n(i, \"verseSequenceIndicator\", \",\"), n(i, \"verseRangeSeparators\", [i.verseRangeSeparator]), n(i, \"verseSequenceIndicators\", [i.verseSequenceIndicator]), n(i, \"chapterDigitShifter\", 1e3), n(i, \"bookDigitShifter\", i.chapterDigitShifter * i.chapterDigitShifter), n(i, \"bcvMaxValue\", i.chapterDigitShifter - 1), /**\n * The valid status of the VerseRef.\n */\nn(i, \"ValidStatusType\", D);\nlet M = i;\nclass d extends Error {\n}\nexport {\n z as BookSet,\n f as Canon,\n c as ScrVers,\n l as ScrVersType,\n M as VerseRef,\n d as VerseRefException\n};\n//# sourceMappingURL=index.es.js.map\n","\"use strict\"\r\n\r\n// Based on: https://github.com/lodash/lodash/blob/6018350ac10d5ce6a5b7db625140b82aeab804df/.internal/unicodeSize.js\r\n\r\nmodule.exports = () => {\r\n\t// Used to compose unicode character classes.\r\n\tconst astralRange = \"\\\\ud800-\\\\udfff\"\r\n\tconst comboMarksRange = \"\\\\u0300-\\\\u036f\"\r\n\tconst comboHalfMarksRange = \"\\\\ufe20-\\\\ufe2f\"\r\n\tconst comboSymbolsRange = \"\\\\u20d0-\\\\u20ff\"\r\n\tconst comboMarksExtendedRange = \"\\\\u1ab0-\\\\u1aff\"\r\n\tconst comboMarksSupplementRange = \"\\\\u1dc0-\\\\u1dff\"\r\n\tconst comboRange = comboMarksRange + comboHalfMarksRange + comboSymbolsRange + comboMarksExtendedRange + comboMarksSupplementRange\r\n\tconst varRange = \"\\\\ufe0e\\\\ufe0f\"\r\n\tconst familyRange = \"\\\\uD83D\\\\uDC69\\\\uD83C\\\\uDFFB\\\\u200D\\\\uD83C\\\\uDF93\"\r\n\r\n\t// Used to compose unicode capture groups.\r\n\tconst astral = `[${astralRange}]`\r\n\tconst combo = `[${comboRange}]`\r\n\tconst fitz = \"\\\\ud83c[\\\\udffb-\\\\udfff]\"\r\n\tconst modifier = `(?:${combo}|${fitz})`\r\n\tconst nonAstral = `[^${astralRange}]`\r\n\tconst regional = \"(?:\\\\uD83C[\\\\uDDE6-\\\\uDDFF]){2}\"\r\n\tconst surrogatePair = \"[\\\\ud800-\\\\udbff][\\\\udc00-\\\\udfff]\"\r\n\tconst zwj = \"\\\\u200d\"\r\n\tconst blackFlag = \"(?:\\\\ud83c\\\\udff4\\\\udb40\\\\udc67\\\\udb40\\\\udc62\\\\udb40(?:\\\\udc65|\\\\udc73|\\\\udc77)\\\\udb40(?:\\\\udc6e|\\\\udc63|\\\\udc6c)\\\\udb40(?:\\\\udc67|\\\\udc74|\\\\udc73)\\\\udb40\\\\udc7f)\"\r\n\tconst family = `[${familyRange}]`\r\n\r\n\t// Used to compose unicode regexes.\r\n\tconst optModifier = `${modifier}?`\r\n\tconst optVar = `[${varRange}]?`\r\n\tconst optJoin = `(?:${zwj}(?:${[nonAstral, regional, surrogatePair].join(\"|\")})${optVar + optModifier})*`\r\n\tconst seq = optVar + optModifier + optJoin\r\n\tconst nonAstralCombo = `${nonAstral}${combo}?`\r\n\tconst symbol = `(?:${[nonAstralCombo, combo, regional, surrogatePair, astral, family].join(\"|\")})`\r\n\r\n\t// Used to match [String symbols](https://mathiasbynens.be/notes/javascript-unicode).\r\n\treturn new RegExp(`${blackFlag}|${fitz}(?=${fitz})|${symbol + seq}`, \"g\")\r\n}\r\n","\"use strict\";\nvar __importDefault = (this && this.__importDefault) || function (mod) {\n return (mod && mod.__esModule) ? mod : { \"default\": mod };\n};\nObject.defineProperty(exports, \"__esModule\", { value: true });\n// @ts-ignore\nvar char_regex_1 = __importDefault(require(\"char-regex\"));\n/**\n * Converts a string to an array of string chars\n * @param {string} str The string to turn into array\n * @returns {string[]}\n */\nfunction toArray(str) {\n if (typeof str !== 'string') {\n throw new Error('A string is expected as input');\n }\n return str.match(char_regex_1.default()) || [];\n}\nexports.toArray = toArray;\n/**\n * Returns the length of a string\n *\n * @export\n * @param {string} str\n * @returns {number}\n */\nfunction length(str) {\n // Check for input\n if (typeof str !== 'string') {\n throw new Error('Input must be a string');\n }\n var match = str.match(char_regex_1.default());\n return match === null ? 0 : match.length;\n}\nexports.length = length;\n/**\n * Returns a substring by providing start and end position\n *\n * @export\n * @param {string} str\n * @param {number} [begin=0] Starting position\n * @param {number} end End position\n * @returns {string}\n */\nfunction substring(str, begin, end) {\n if (begin === void 0) { begin = 0; }\n // Check for input\n if (typeof str !== 'string') {\n throw new Error('Input must be a string');\n }\n // Even though negative numbers work here, theyre not in the spec\n if (typeof begin !== 'number' || begin < 0) {\n begin = 0;\n }\n if (typeof end === 'number' && end < 0) {\n end = 0;\n }\n var match = str.match(char_regex_1.default());\n if (!match)\n return '';\n return match.slice(begin, end).join('');\n}\nexports.substring = substring;\n/**\n * Returns a substring by providing start position and length\n *\n * @export\n * @param {string} str\n * @param {number} [begin=0] Starting position\n * @param {number} len Desired length\n * @returns {string}\n */\nfunction substr(str, begin, len) {\n if (begin === void 0) { begin = 0; }\n // Check for input\n if (typeof str !== 'string') {\n throw new Error('Input must be a string');\n }\n var strLength = length(str);\n // Fix type\n if (typeof begin !== 'number') {\n begin = parseInt(begin, 10);\n }\n // Return zero-length string if got oversize number.\n if (begin >= strLength) {\n return '';\n }\n // Calculating postive version of negative value.\n if (begin < 0) {\n begin += strLength;\n }\n var end;\n if (typeof len === 'undefined') {\n end = strLength;\n }\n else {\n // Fix type\n if (typeof len !== 'number') {\n len = parseInt(len, 10);\n }\n end = len >= 0 ? len + begin : begin;\n }\n var match = str.match(char_regex_1.default());\n if (!match)\n return '';\n return match.slice(begin, end).join('');\n}\nexports.substr = substr;\n/**\n * Enforces a string to be a certain length by\n * adding or removing characters\n *\n * @export\n * @param {string} str\n * @param {number} [limit=16] Limit\n * @param {string} [padString='#'] The Pad String\n * @param {string} [padPosition='right'] The Pad Position\n * @returns {string}\n */\nfunction limit(str, limit, padString, padPosition) {\n if (limit === void 0) { limit = 16; }\n if (padString === void 0) { padString = '#'; }\n if (padPosition === void 0) { padPosition = 'right'; }\n // Input should be a string, limit should be a number\n if (typeof str !== 'string' || typeof limit !== 'number') {\n throw new Error('Invalid arguments specified');\n }\n // Pad position should be either left or right\n if (['left', 'right'].indexOf(padPosition) === -1) {\n throw new Error('Pad position should be either left or right');\n }\n // Pad string can be anything, we convert it to string\n if (typeof padString !== 'string') {\n padString = String(padString);\n }\n // Calculate string length considering astral code points\n var strLength = length(str);\n if (strLength > limit) {\n return substring(str, 0, limit);\n }\n else if (strLength < limit) {\n var padRepeats = padString.repeat(limit - strLength);\n return padPosition === 'left' ? padRepeats + str : str + padRepeats;\n }\n return str;\n}\nexports.limit = limit;\n/**\n * Returns the index of the first occurrence of a given string\n *\n * @export\n * @param {string} str\n * @param {string} [searchStr] the string to search\n * @param {number} [pos] starting position\n * @returns {number}\n */\nfunction indexOf(str, searchStr, pos) {\n if (pos === void 0) { pos = 0; }\n if (typeof str !== 'string') {\n throw new Error('Input must be a string');\n }\n if (str === '') {\n if (searchStr === '') {\n return 0;\n }\n return -1;\n }\n // fix type\n pos = Number(pos);\n pos = isNaN(pos) ? 0 : pos;\n searchStr = String(searchStr);\n var strArr = toArray(str);\n if (pos >= strArr.length) {\n if (searchStr === '') {\n return strArr.length;\n }\n return -1;\n }\n if (searchStr === '') {\n return pos;\n }\n var searchArr = toArray(searchStr);\n var finded = false;\n var index;\n for (index = pos; index < strArr.length; index += 1) {\n var searchIndex = 0;\n while (searchIndex < searchArr.length &&\n searchArr[searchIndex] === strArr[index + searchIndex]) {\n searchIndex += 1;\n }\n if (searchIndex === searchArr.length &&\n searchArr[searchIndex - 1] === strArr[index + searchIndex - 1]) {\n finded = true;\n break;\n }\n }\n return finded ? index : -1;\n}\nexports.indexOf = indexOf;\n","import { LocalizeKey } from 'menus.model';\nimport {\n indexOf as stringzIndexOf,\n substring as stringzSubstring,\n length as stringzLength,\n toArray as stringzToArray,\n limit as stringzLimit,\n substr as stringzSubstr,\n} from 'stringz';\n\n/**\n * This function mirrors the `at` function from the JavaScript Standard String object. It handles\n * Unicode code points instead of UTF-16 character codes.\n *\n * Finds the Unicode code point at the given index.\n *\n * @param string String to index\n * @param index Position of the character to be returned in range of -length(string) to\n * length(string)\n * @returns New string consisting of the Unicode code point located at the specified offset,\n * undefined if index is out of bounds\n */\nexport function at(string: string, index: number): string | undefined {\n if (index > stringLength(string) || index < -stringLength(string)) return undefined;\n return substr(string, index, 1);\n}\n\n/**\n * This function mirrors the `charAt` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns a new string consisting of the single unicode code point at the given index.\n *\n * @param string String to index\n * @param index Position of the string character to be returned, in the range of 0 to\n * length(string)-1\n * @returns New string consisting of the Unicode code point located at the specified offset, empty\n * string if index is out of bounds\n */\nexport function charAt(string: string, index: number): string {\n if (index < 0 || index > stringLength(string) - 1) return '';\n return substr(string, index, 1);\n}\n\n/**\n * This function mirrors the `codePointAt` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns a non-negative integer that is the Unicode code point value of the character starting at\n * the given index.\n *\n * @param string String to index\n * @param index Position of the string character to be returned, in the range of 0 to\n * length(string)-1\n * @returns Non-negative integer representing the code point value of the character at the given\n * index, or undefined if there is no element at that position\n */\nexport function codePointAt(string: string, index: number): number | undefined {\n if (index < 0 || index > stringLength(string) - 1) return undefined;\n return substr(string, index, 1).codePointAt(0);\n}\n\n/**\n * This function mirrors the `endsWith` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Determines whether a string ends with the characters of this string.\n *\n * @param string String to search through\n * @param searchString Characters to search for at the end of the string\n * @param endPosition End position where searchString is expected to be found. Default is\n * `length(string)`\n * @returns True if it ends with searchString, false if it does not\n */\nexport function endsWith(\n string: string,\n searchString: string,\n endPosition: number = stringLength(string),\n): boolean {\n const lastIndexOfSearchString = lastIndexOf(string, searchString);\n if (lastIndexOfSearchString === -1) return false;\n if (lastIndexOfSearchString + stringLength(searchString) !== endPosition) return false;\n return true;\n}\n\n/**\n * Get the index of the closest closing curly brace in a string.\n *\n * Note: when escaped, gets the index of the curly brace, not the backslash before it.\n *\n * @param str String to search\n * @param index Index at which to start searching. Inclusive of this index\n * @param escaped Whether to search for an escaped or an unescaped closing curly brace\n * @returns Index of closest closing curly brace or -1 if not found\n */\nfunction indexOfClosestClosingCurlyBrace(str: string, index: number, escaped: boolean) {\n if (index < 0) return -1;\n if (escaped) {\n if (charAt(str, index) === '}' && charAt(str, index - 1) === '\\\\') return index;\n const closeCurlyBraceIndex = indexOf(str, '\\\\}', index);\n return closeCurlyBraceIndex >= 0 ? closeCurlyBraceIndex + 1 : closeCurlyBraceIndex;\n }\n\n let i = index;\n const strLength = stringLength(str);\n while (i < strLength) {\n i = indexOf(str, '}', i);\n\n if (i === -1 || charAt(str, i - 1) !== '\\\\') break;\n\n // Didn't find an un-escaped close brace, so keep looking\n i += 1;\n }\n\n return i >= strLength ? -1 : i;\n}\n\n/**\n * Formats a string, replacing {localization key} with the localization (or multiple localizations\n * if there are multiple in the string). Will also remove \\ before curly braces if curly braces are\n * escaped with a backslash in order to preserve the curly braces. E.g. 'Hi, this is {name}! I like\n * `\\{curly braces\\}`! would become Hi, this is Jim! I like {curly braces}!\n *\n * If the key in unescaped braces is not found, just return the key without the braces. Empty\n * unescaped curly braces will just return a string without the braces e.g. ('I am {Nemo}', {\n * 'name': 'Jim'}) would return 'I am Nemo'.\n *\n * @param str String to format\n * @returns Formatted string\n */\nexport function formatReplacementString(str: string, replacers: { [key: string]: string }): string {\n let updatedStr = str;\n\n let i = 0;\n while (i < stringLength(updatedStr)) {\n switch (charAt(updatedStr, i)) {\n case '{':\n if (charAt(updatedStr, i - 1) !== '\\\\') {\n // This character is an un-escaped open curly brace. Try to match and replace\n const closeCurlyBraceIndex = indexOfClosestClosingCurlyBrace(updatedStr, i, false);\n if (closeCurlyBraceIndex >= 0) {\n // We have matching open and close indices. Try to replace the contents\n const replacerKey = substring(updatedStr, i + 1, closeCurlyBraceIndex);\n // Replace with the replacer string or just remove the curly braces\n const replacerString = replacerKey in replacers ? replacers[replacerKey] : replacerKey;\n\n updatedStr = `${substring(updatedStr, 0, i)}${replacerString}${substring(updatedStr, closeCurlyBraceIndex + 1)}`;\n // Put our index at the closing brace adjusted for the new string length minus two\n // because we are removing the curly braces\n // Ex: \"stuff {and} things\"\n // Replacer for and: n'\n // closeCurlyBraceIndex is 10\n // \"stuff n' things\"\n // i = 10 + 2 - 3 - 2 = 7\n i = closeCurlyBraceIndex + stringLength(replacerString) - stringLength(replacerKey) - 2;\n } else {\n // This is an unexpected un-escaped open curly brace with no matching closing curly\n // brace. Just ignore, I guess\n }\n } else {\n // This character is an escaped open curly brace. Remove the escape\n updatedStr = `${substring(updatedStr, 0, i - 1)}${substring(updatedStr, i)}`;\n // Adjust our index because we removed the escape\n i -= 1;\n }\n break;\n case '}':\n if (charAt(updatedStr, i - 1) !== '\\\\') {\n // This character is an un-escaped closing curly brace with no matching open curly\n // brace. Just ignore, I guess\n } else {\n // This character is an escaped closing curly brace. Remove the escape\n updatedStr = `${substring(updatedStr, 0, i - 1)}${substring(updatedStr, i)}`;\n // Adjust our index because we removed the escape\n i -= 1;\n }\n break;\n default:\n // No need to do anything with other characters at this point\n break;\n }\n\n i += 1;\n }\n\n return updatedStr;\n}\n/**\n * This function mirrors the `includes` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Performs a case-sensitive search to determine if searchString is found in string.\n *\n * @param string String to search through\n * @param searchString String to search for\n * @param position Position within the string to start searching for searchString. Default is `0`\n * @returns True if search string is found, false if it is not\n */\nexport function includes(string: string, searchString: string, position: number = 0): boolean {\n const partialString = substring(string, position);\n const indexOfSearchString = indexOf(partialString, searchString);\n if (indexOfSearchString === -1) return false;\n return true;\n}\n\n/**\n * This function mirrors the `indexOf` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns the index of the first occurrence of a given string.\n *\n * @param string String to search through\n * @param searchString The string to search for\n * @param position Start of searching. Default is `0`\n * @returns Index of the first occurrence of a given string\n */\nexport function indexOf(\n string: string,\n searchString: string,\n position: number | undefined = 0,\n): number {\n return stringzIndexOf(string, searchString, position);\n}\n\n/**\n * This function mirrors the `lastIndexOf` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Searches this string and returns the index of the last occurrence of the specified substring.\n *\n * @param string String to search through\n * @param searchString Substring to search for\n * @param position The index at which to begin searching. If omitted, the search begins at the end\n * of the string. Default is `undefined`\n * @returns Index of the last occurrence of searchString found, or -1 if not found.\n */\nexport function lastIndexOf(string: string, searchString: string, position?: number): number {\n let validatedPosition = position === undefined ? stringLength(string) : position;\n\n if (validatedPosition < 0) {\n validatedPosition = 0;\n } else if (validatedPosition >= stringLength(string)) {\n validatedPosition = stringLength(string) - 1;\n }\n\n for (let index = validatedPosition; index >= 0; index--) {\n if (substr(string, index, stringLength(searchString)) === searchString) {\n return index;\n }\n }\n\n return -1;\n}\n\n/**\n * This function mirrors the `length` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes. Since `length` appears to be a\n * reserved keyword, the function was renamed to `stringLength`\n *\n * Returns the length of a string.\n *\n * @param string String to return the length for\n * @returns Number that is length of the starting string\n */\nexport function stringLength(string: string): number {\n return stringzLength(string);\n}\n\n/**\n * This function mirrors the `normalize` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns the Unicode Normalization Form of this string.\n *\n * @param string The starting string\n * @param form Form specifying the Unicode Normalization Form. Default is `'NFC'`\n * @returns A string containing the Unicode Normalization Form of the given string.\n */\nexport function normalize(string: string, form: 'NFC' | 'NFD' | 'NFKC' | 'NFKD' | 'none'): string {\n const upperCaseForm = form.toUpperCase();\n if (upperCaseForm === 'NONE') {\n return string;\n }\n return string.normalize(upperCaseForm);\n}\n\n/**\n * Compares two strings using an ordinal comparison approach based on the specified collation\n * options. This function uses the built-in `localeCompare` method with the 'en' locale and the\n * provided collation options to compare the strings.\n *\n * @param string1 The first string to compare.\n * @param string2 The second string to compare.\n * @param options Optional. The collation options used for comparison.\n * @returns A number indicating the result of the comparison: - Negative value if string1 precedes\n * string2 in sorting order. - Zero if string1 and string2 are equivalent in sorting order. -\n * Positive value if string1 follows string2 in sorting order.\n */\nexport function ordinalCompare(\n string1: string,\n string2: string,\n options?: Intl.CollatorOptions,\n): number {\n return string1.localeCompare(string2, 'en', options);\n}\n\n/**\n * This function mirrors the `padEnd` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Pads this string with another string (multiple times, if needed) until the resulting string\n * reaches the given length. The padding is applied from the end of this string.\n *\n * @param string String to add padding too\n * @param targetLength The length of the resulting string once the starting string has been padded.\n * If value is less than or equal to length(string), then string is returned as is.\n * @param padString The string to pad the current string with. If padString is too long to stay\n * within targetLength, it will be truncated. Default is `\" \"`\n * @returns String with appropriate padding at the end\n */\n// Note: Limit with padString only works when length(padString) = 1, will be fixed with https://github.com/sallar/stringz/pull/59\nexport function padEnd(string: string, targetLength: number, padString: string = ' '): string {\n if (targetLength <= stringLength(string)) return string;\n return stringzLimit(string, targetLength, padString, 'right');\n}\n\n/**\n * This function mirrors the `padStart` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Pads this string with another string (multiple times, if needed) until the resulting string\n * reaches the given length. The padding is applied from the start of this string.\n *\n * @param string String to add padding too\n * @param targetLength The length of the resulting string once the starting string has been padded.\n * If value is less than or equal to length(string), then string is returned as is.\n * @param padString The string to pad the current string with. If padString is too long to stay\n * within the targetLength, it will be truncated from the end. Default is `\" \"`\n * @returns String with of specified targetLength with padString applied from the start\n */\n// Note: Limit with padString only works when length(padString) = 1, will be fixed with https://github.com/sallar/stringz/pull/59\nexport function padStart(string: string, targetLength: number, padString: string = ' '): string {\n if (targetLength <= stringLength(string)) return string;\n return stringzLimit(string, targetLength, padString, 'left');\n}\n\n// This is a helper function that performs a correction on the slice index to make sure it\n// cannot go out of bounds\nfunction correctSliceIndex(length: number, index: number) {\n if (index > length) return length;\n if (index < -length) return 0;\n if (index < 0) return index + length;\n return index;\n}\n\n/**\n * This function mirrors the `slice` function from the JavaScript Standard String object. It handles\n * Unicode code points instead of UTF-16 character codes.\n *\n * Extracts a section of this string and returns it as a new string, without modifying the original\n * string.\n *\n * @param string The starting string\n * @param indexStart The index of the first character to include in the returned substring.\n * @param indexEnd The index of the first character to exclude from the returned substring.\n * @returns A new string containing the extracted section of the string.\n */\nexport function slice(string: string, indexStart: number, indexEnd?: number): string {\n const length: number = stringLength(string);\n if (\n indexStart > length ||\n (indexEnd &&\n ((indexStart > indexEnd &&\n !(indexStart >= 0 && indexStart < length && indexEnd < 0 && indexEnd > -length)) ||\n indexEnd < -length))\n )\n return '';\n\n const newStart = correctSliceIndex(length, indexStart);\n const newEnd = indexEnd ? correctSliceIndex(length, indexEnd) : undefined;\n\n return substring(string, newStart, newEnd);\n}\n\n/**\n * This function mirrors the `split` function from the JavaScript Standard String object. It handles\n * Unicode code points instead of UTF-16 character codes.\n *\n * Takes a pattern and divides the string into an ordered list of substrings by searching for the\n * pattern, puts these substrings into an array, and returns the array.\n *\n * @param string The string to split\n * @param separator The pattern describing where each split should occur\n * @param splitLimit Limit on the number of substrings to be included in the array. Splits the\n * string at each occurrence of specified separator, but stops when limit entries have been placed\n * in the array.\n * @returns An array of strings, split at each point where separator occurs in the starting string.\n * Returns undefined if separator is not found in string.\n */\nexport function split(string: string, separator: string | RegExp, splitLimit?: number): string[] {\n const result: string[] = [];\n\n if (splitLimit !== undefined && splitLimit <= 0) {\n return [string];\n }\n\n if (separator === '') return toArray(string).slice(0, splitLimit);\n\n let regexSeparator = separator;\n if (\n typeof separator === 'string' ||\n (separator instanceof RegExp && !includes(separator.flags, 'g'))\n ) {\n regexSeparator = new RegExp(separator, 'g');\n }\n\n const matches: RegExpMatchArray | null = string.match(regexSeparator);\n\n let currentIndex = 0;\n\n if (!matches) return [string];\n\n for (let index = 0; index < (splitLimit ? splitLimit - 1 : matches.length); index++) {\n const matchIndex = indexOf(string, matches[index], currentIndex);\n const matchLength = stringLength(matches[index]);\n\n result.push(substring(string, currentIndex, matchIndex));\n currentIndex = matchIndex + matchLength;\n\n if (splitLimit !== undefined && result.length === splitLimit) {\n break;\n }\n }\n\n result.push(substring(string, currentIndex));\n\n return result;\n}\n\n/**\n * This function mirrors the `startsWith` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Determines whether the string begins with the characters of a specified string, returning true or\n * false as appropriate.\n *\n * @param string String to search through\n * @param searchString The characters to be searched for at the start of this string.\n * @param position The start position at which searchString is expected to be found (the index of\n * searchString's first character). Default is `0`\n * @returns True if the given characters are found at the beginning of the string, including when\n * searchString is an empty string; otherwise, false.\n */\nexport function startsWith(string: string, searchString: string, position: number = 0): boolean {\n const indexOfSearchString = indexOf(string, searchString, position);\n if (indexOfSearchString !== position) return false;\n return true;\n}\n\n/**\n * This function mirrors the `substr` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns a substring by providing start and length. This function is not exported because it is\n * considered deprecated, however it is still useful as a local helper function.\n *\n * @param string String to be divided\n * @param begin Start position. Default is `Start of string`\n * @param len Length of result. Default is `String length minus start parameter`. Default is `String\n * length minus start parameter`\n * @returns Substring from starting string\n */\nfunction substr(\n string: string,\n begin: number = 0,\n len: number = stringLength(string) - begin,\n): string {\n return stringzSubstr(string, begin, len);\n}\n\n/**\n * This function mirrors the `substring` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Returns a substring by providing start and end position.\n *\n * @param string String to be divided\n * @param begin Start position\n * @param end End position. Default is `End of string`\n * @returns Substring from starting string\n */\nexport function substring(\n string: string,\n begin: number,\n end: number = stringLength(string),\n): string {\n return stringzSubstring(string, begin, end);\n}\n\n/**\n * This function mirrors the `toArray` function from the JavaScript Standard String object. It\n * handles Unicode code points instead of UTF-16 character codes.\n *\n * Converts a string to an array of string characters.\n *\n * @param string String to convert to array\n * @returns An array of characters from the starting string\n */\nexport function toArray(string: string): string[] {\n return stringzToArray(string);\n}\n\n/** Determine whether the string is a `LocalizeKey` meant to be localized in Platform.Bible. */\nexport function isLocalizeKey(str: string): str is LocalizeKey {\n return startsWith(str, '%') && endsWith(str, '%');\n}\n\n/**\n * Escape RegExp special characters.\n *\n * You can also use this to escape a string that is inserted into the middle of a regex, for\n * example, into a character class.\n *\n * All credit to [`escape-string-regexp`](https://www.npmjs.com/package/escape-string-regexp) - this\n * function is simply copied directly from there to allow a common js export\n *\n * @example\n *\n * import escapeStringRegexp from 'platform-bible-utils';\n *\n * const escapedString = escapeStringRegexp('How much $ for a 🦄?');\n * //=> 'How much \\\\$ for a 🦄\\\\?'\n *\n * new RegExp(escapedString);\n */\nexport function escapeStringRegexp(string: string): string {\n if (typeof string !== 'string') {\n throw new TypeError('Expected a string');\n }\n\n // Escape characters with special meaning either inside or outside character sets.\n // Use a simple backslash escape when it’s always valid, and a `\\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar.\n return string.replace(/[|\\\\{}()[\\]^$+*?.]/g, '\\\\$&').replace(/-/g, '\\\\x2d');\n}\n\n/** This is an internal-only export for testing purposes and should not be used in development */\nexport const testingStringUtils = {\n indexOfClosestClosingCurlyBrace,\n};\n","import { Canon } from '@sillsdev/scripture';\nimport { BookInfo, ScriptureReference } from './scripture.model';\nimport { split, startsWith } from './string-util';\n\nconst scrBookData: BookInfo[] = [\n { shortName: 'ERR', fullNames: ['ERROR'], chapters: -1 },\n { shortName: 'GEN', fullNames: ['Genesis'], chapters: 50 },\n { shortName: 'EXO', fullNames: ['Exodus'], chapters: 40 },\n { shortName: 'LEV', fullNames: ['Leviticus'], chapters: 27 },\n { shortName: 'NUM', fullNames: ['Numbers'], chapters: 36 },\n { shortName: 'DEU', fullNames: ['Deuteronomy'], chapters: 34 },\n { shortName: 'JOS', fullNames: ['Joshua'], chapters: 24 },\n { shortName: 'JDG', fullNames: ['Judges'], chapters: 21 },\n { shortName: 'RUT', fullNames: ['Ruth'], chapters: 4 },\n { shortName: '1SA', fullNames: ['1 Samuel'], chapters: 31 },\n { shortName: '2SA', fullNames: ['2 Samuel'], chapters: 24 },\n { shortName: '1KI', fullNames: ['1 Kings'], chapters: 22 },\n { shortName: '2KI', fullNames: ['2 Kings'], chapters: 25 },\n { shortName: '1CH', fullNames: ['1 Chronicles'], chapters: 29 },\n { shortName: '2CH', fullNames: ['2 Chronicles'], chapters: 36 },\n { shortName: 'EZR', fullNames: ['Ezra'], chapters: 10 },\n { shortName: 'NEH', fullNames: ['Nehemiah'], chapters: 13 },\n { shortName: 'EST', fullNames: ['Esther'], chapters: 10 },\n { shortName: 'JOB', fullNames: ['Job'], chapters: 42 },\n { shortName: 'PSA', fullNames: ['Psalm', 'Psalms'], chapters: 150 },\n { shortName: 'PRO', fullNames: ['Proverbs'], chapters: 31 },\n { shortName: 'ECC', fullNames: ['Ecclesiastes'], chapters: 12 },\n { shortName: 'SNG', fullNames: ['Song of Solomon', 'Song of Songs'], chapters: 8 },\n { shortName: 'ISA', fullNames: ['Isaiah'], chapters: 66 },\n { shortName: 'JER', fullNames: ['Jeremiah'], chapters: 52 },\n { shortName: 'LAM', fullNames: ['Lamentations'], chapters: 5 },\n { shortName: 'EZK', fullNames: ['Ezekiel'], chapters: 48 },\n { shortName: 'DAN', fullNames: ['Daniel'], chapters: 12 },\n { shortName: 'HOS', fullNames: ['Hosea'], chapters: 14 },\n { shortName: 'JOL', fullNames: ['Joel'], chapters: 3 },\n { shortName: 'AMO', fullNames: ['Amos'], chapters: 9 },\n { shortName: 'OBA', fullNames: ['Obadiah'], chapters: 1 },\n { shortName: 'JON', fullNames: ['Jonah'], chapters: 4 },\n { shortName: 'MIC', fullNames: ['Micah'], chapters: 7 },\n { shortName: 'NAM', fullNames: ['Nahum'], chapters: 3 },\n { shortName: 'HAB', fullNames: ['Habakkuk'], chapters: 3 },\n { shortName: 'ZEP', fullNames: ['Zephaniah'], chapters: 3 },\n { shortName: 'HAG', fullNames: ['Haggai'], chapters: 2 },\n { shortName: 'ZEC', fullNames: ['Zechariah'], chapters: 14 },\n { shortName: 'MAL', fullNames: ['Malachi'], chapters: 4 },\n { shortName: 'MAT', fullNames: ['Matthew'], chapters: 28 },\n { shortName: 'MRK', fullNames: ['Mark'], chapters: 16 },\n { shortName: 'LUK', fullNames: ['Luke'], chapters: 24 },\n { shortName: 'JHN', fullNames: ['John'], chapters: 21 },\n { shortName: 'ACT', fullNames: ['Acts'], chapters: 28 },\n { shortName: 'ROM', fullNames: ['Romans'], chapters: 16 },\n { shortName: '1CO', fullNames: ['1 Corinthians'], chapters: 16 },\n { shortName: '2CO', fullNames: ['2 Corinthians'], chapters: 13 },\n { shortName: 'GAL', fullNames: ['Galatians'], chapters: 6 },\n { shortName: 'EPH', fullNames: ['Ephesians'], chapters: 6 },\n { shortName: 'PHP', fullNames: ['Philippians'], chapters: 4 },\n { shortName: 'COL', fullNames: ['Colossians'], chapters: 4 },\n { shortName: '1TH', fullNames: ['1 Thessalonians'], chapters: 5 },\n { shortName: '2TH', fullNames: ['2 Thessalonians'], chapters: 3 },\n { shortName: '1TI', fullNames: ['1 Timothy'], chapters: 6 },\n { shortName: '2TI', fullNames: ['2 Timothy'], chapters: 4 },\n { shortName: 'TIT', fullNames: ['Titus'], chapters: 3 },\n { shortName: 'PHM', fullNames: ['Philemon'], chapters: 1 },\n { shortName: 'HEB', fullNames: ['Hebrews'], chapters: 13 },\n { shortName: 'JAS', fullNames: ['James'], chapters: 5 },\n { shortName: '1PE', fullNames: ['1 Peter'], chapters: 5 },\n { shortName: '2PE', fullNames: ['2 Peter'], chapters: 3 },\n { shortName: '1JN', fullNames: ['1 John'], chapters: 5 },\n { shortName: '2JN', fullNames: ['2 John'], chapters: 1 },\n { shortName: '3JN', fullNames: ['3 John'], chapters: 1 },\n { shortName: 'JUD', fullNames: ['Jude'], chapters: 1 },\n { shortName: 'REV', fullNames: ['Revelation'], chapters: 22 },\n];\n\nexport const FIRST_SCR_BOOK_NUM = 1;\nexport const LAST_SCR_BOOK_NUM = scrBookData.length - 1;\nexport const FIRST_SCR_CHAPTER_NUM = 1;\nexport const FIRST_SCR_VERSE_NUM = 1;\n\nexport const getChaptersForBook = (bookNum: number): number => {\n return scrBookData[bookNum]?.chapters ?? -1;\n};\n\nexport const offsetBook = (scrRef: ScriptureReference, offset: number): ScriptureReference => ({\n bookNum: Math.max(FIRST_SCR_BOOK_NUM, Math.min(scrRef.bookNum + offset, LAST_SCR_BOOK_NUM)),\n chapterNum: 1,\n verseNum: 1,\n});\n\nexport const offsetChapter = (scrRef: ScriptureReference, offset: number): ScriptureReference => ({\n ...scrRef,\n chapterNum: Math.min(\n Math.max(FIRST_SCR_CHAPTER_NUM, scrRef.chapterNum + offset),\n getChaptersForBook(scrRef.bookNum),\n ),\n verseNum: 1,\n});\n\nexport const offsetVerse = (scrRef: ScriptureReference, offset: number): ScriptureReference => ({\n ...scrRef,\n verseNum: Math.max(FIRST_SCR_VERSE_NUM, scrRef.verseNum + offset),\n});\n\n/**\n * https://github.com/ubsicap/Paratext/blob/master/ParatextData/SILScriptureExtensions.cs#L72\n *\n * Convert book number to a localized Id (a short description of the book). This should be used\n * whenever a book ID (short code) is shown to the user. It is primarily needed for people who do\n * not read Roman script well and need to have books identified in a alternate script (e.g. Chinese\n * or Russian)\n *\n * @param bookNumber\n * @param localizationLanguage In BCP 47 format\n * @param getLocalizedString Function that provides the localized versions of the book ids and names\n * asynchronously.\n * @returns\n */\nexport async function getLocalizedIdFromBookNumber(\n bookNumber: number,\n localizationLanguage: string,\n getLocalizedString: (item: {\n localizeKey: string;\n languagesToSearch?: string[];\n }) => Promise,\n) {\n const id = Canon.bookNumberToId(bookNumber);\n\n if (!startsWith(Intl.getCanonicalLocales(localizationLanguage)[0], 'zh'))\n return getLocalizedString({\n localizeKey: `LocalizedId.${id}`,\n languagesToSearch: [localizationLanguage],\n });\n\n // For Chinese the normal book name is already fairly short.\n const bookName = await getLocalizedString({\n localizeKey: `Book.${id}`,\n languagesToSearch: [localizationLanguage],\n });\n const parts = split(bookName, '-');\n // some entries had a second name inside ideographic parenthesis\n const parts2 = split(parts[0], '\\xff08');\n const retVal = parts2[0].trim();\n return retVal;\n}\n","/** Function to run to dispose of something. Returns true if successfully unsubscribed */\nexport type Unsubscriber = () => boolean;\n\n/**\n * Returns an Unsubscriber function that combines all the unsubscribers passed in.\n *\n * @param unsubscribers All unsubscribers to aggregate into one unsubscriber\n * @returns Function that unsubscribes from all passed in unsubscribers when run\n */\nexport const aggregateUnsubscribers = (unsubscribers: Unsubscriber[]): Unsubscriber => {\n return (...args) => {\n // Run the unsubscriber for each handler\n const unsubs = unsubscribers.map((unsubscriber) => unsubscriber(...args));\n\n // If all the unsubscribers resolve to truthiness, we succeed\n return unsubs.every((success) => success);\n };\n};\n\n/**\n * Function to run to dispose of something that runs asynchronously. The promise resolves to true if\n * successfully unsubscribed\n */\nexport type UnsubscriberAsync = () => Promise;\n\n/**\n * Returns an UnsubscriberAsync function that combines all the unsubscribers passed in.\n *\n * @param unsubscribers - All unsubscribers to aggregate into one unsubscriber.\n * @returns Function that unsubscribes from all passed in unsubscribers when run\n */\nexport const aggregateUnsubscriberAsyncs = (\n unsubscribers: (UnsubscriberAsync | Unsubscriber)[],\n): UnsubscriberAsync => {\n return async (...args) => {\n // Run the unsubscriber for each handler\n const unsubPromises = unsubscribers.map(async (unsubscriber) => unsubscriber(...args));\n\n // If all the unsubscribers resolve to truthiness, we succeed\n return (await Promise.all(unsubPromises)).every((success) => success);\n };\n};\n","var getOwnPropertyNames = Object.getOwnPropertyNames, getOwnPropertySymbols = Object.getOwnPropertySymbols;\nvar hasOwnProperty = Object.prototype.hasOwnProperty;\n/**\n * Combine two comparators into a single comparators.\n */\nfunction combineComparators(comparatorA, comparatorB) {\n return function isEqual(a, b, state) {\n return comparatorA(a, b, state) && comparatorB(a, b, state);\n };\n}\n/**\n * Wrap the provided `areItemsEqual` method to manage the circular state, allowing\n * for circular references to be safely included in the comparison without creating\n * stack overflows.\n */\nfunction createIsCircular(areItemsEqual) {\n return function isCircular(a, b, state) {\n if (!a || !b || typeof a !== 'object' || typeof b !== 'object') {\n return areItemsEqual(a, b, state);\n }\n var cache = state.cache;\n var cachedA = cache.get(a);\n var cachedB = cache.get(b);\n if (cachedA && cachedB) {\n return cachedA === b && cachedB === a;\n }\n cache.set(a, b);\n cache.set(b, a);\n var result = areItemsEqual(a, b, state);\n cache.delete(a);\n cache.delete(b);\n return result;\n };\n}\n/**\n * Get the properties to strictly examine, which include both own properties that are\n * not enumerable and symbol properties.\n */\nfunction getStrictProperties(object) {\n return getOwnPropertyNames(object).concat(getOwnPropertySymbols(object));\n}\n/**\n * Whether the object contains the property passed as an own property.\n */\nvar hasOwn = Object.hasOwn ||\n (function (object, property) {\n return hasOwnProperty.call(object, property);\n });\n/**\n * Whether the values passed are strictly equal or both NaN.\n */\nfunction sameValueZeroEqual(a, b) {\n return a || b ? a === b : a === b || (a !== a && b !== b);\n}\n\nvar OWNER = '_owner';\nvar getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor, keys = Object.keys;\n/**\n * Whether the arrays are equal in value.\n */\nfunction areArraysEqual(a, b, state) {\n var index = a.length;\n if (b.length !== index) {\n return false;\n }\n while (index-- > 0) {\n if (!state.equals(a[index], b[index], index, index, a, b, state)) {\n return false;\n }\n }\n return true;\n}\n/**\n * Whether the dates passed are equal in value.\n */\nfunction areDatesEqual(a, b) {\n return sameValueZeroEqual(a.getTime(), b.getTime());\n}\n/**\n * Whether the `Map`s are equal in value.\n */\nfunction areMapsEqual(a, b, state) {\n if (a.size !== b.size) {\n return false;\n }\n var matchedIndices = {};\n var aIterable = a.entries();\n var index = 0;\n var aResult;\n var bResult;\n while ((aResult = aIterable.next())) {\n if (aResult.done) {\n break;\n }\n var bIterable = b.entries();\n var hasMatch = false;\n var matchIndex = 0;\n while ((bResult = bIterable.next())) {\n if (bResult.done) {\n break;\n }\n var _a = aResult.value, aKey = _a[0], aValue = _a[1];\n var _b = bResult.value, bKey = _b[0], bValue = _b[1];\n if (!hasMatch &&\n !matchedIndices[matchIndex] &&\n (hasMatch =\n state.equals(aKey, bKey, index, matchIndex, a, b, state) &&\n state.equals(aValue, bValue, aKey, bKey, a, b, state))) {\n matchedIndices[matchIndex] = true;\n }\n matchIndex++;\n }\n if (!hasMatch) {\n return false;\n }\n index++;\n }\n return true;\n}\n/**\n * Whether the objects are equal in value.\n */\nfunction areObjectsEqual(a, b, state) {\n var properties = keys(a);\n var index = properties.length;\n if (keys(b).length !== index) {\n return false;\n }\n var property;\n // Decrementing `while` showed faster results than either incrementing or\n // decrementing `for` loop and than an incrementing `while` loop. Declarative\n // methods like `some` / `every` were not used to avoid incurring the garbage\n // cost of anonymous callbacks.\n while (index-- > 0) {\n property = properties[index];\n if (property === OWNER &&\n (a.$$typeof || b.$$typeof) &&\n a.$$typeof !== b.$$typeof) {\n return false;\n }\n if (!hasOwn(b, property) ||\n !state.equals(a[property], b[property], property, property, a, b, state)) {\n return false;\n }\n }\n return true;\n}\n/**\n * Whether the objects are equal in value with strict property checking.\n */\nfunction areObjectsEqualStrict(a, b, state) {\n var properties = getStrictProperties(a);\n var index = properties.length;\n if (getStrictProperties(b).length !== index) {\n return false;\n }\n var property;\n var descriptorA;\n var descriptorB;\n // Decrementing `while` showed faster results than either incrementing or\n // decrementing `for` loop and than an incrementing `while` loop. Declarative\n // methods like `some` / `every` were not used to avoid incurring the garbage\n // cost of anonymous callbacks.\n while (index-- > 0) {\n property = properties[index];\n if (property === OWNER &&\n (a.$$typeof || b.$$typeof) &&\n a.$$typeof !== b.$$typeof) {\n return false;\n }\n if (!hasOwn(b, property)) {\n return false;\n }\n if (!state.equals(a[property], b[property], property, property, a, b, state)) {\n return false;\n }\n descriptorA = getOwnPropertyDescriptor(a, property);\n descriptorB = getOwnPropertyDescriptor(b, property);\n if ((descriptorA || descriptorB) &&\n (!descriptorA ||\n !descriptorB ||\n descriptorA.configurable !== descriptorB.configurable ||\n descriptorA.enumerable !== descriptorB.enumerable ||\n descriptorA.writable !== descriptorB.writable)) {\n return false;\n }\n }\n return true;\n}\n/**\n * Whether the primitive wrappers passed are equal in value.\n */\nfunction arePrimitiveWrappersEqual(a, b) {\n return sameValueZeroEqual(a.valueOf(), b.valueOf());\n}\n/**\n * Whether the regexps passed are equal in value.\n */\nfunction areRegExpsEqual(a, b) {\n return a.source === b.source && a.flags === b.flags;\n}\n/**\n * Whether the `Set`s are equal in value.\n */\nfunction areSetsEqual(a, b, state) {\n if (a.size !== b.size) {\n return false;\n }\n var matchedIndices = {};\n var aIterable = a.values();\n var aResult;\n var bResult;\n while ((aResult = aIterable.next())) {\n if (aResult.done) {\n break;\n }\n var bIterable = b.values();\n var hasMatch = false;\n var matchIndex = 0;\n while ((bResult = bIterable.next())) {\n if (bResult.done) {\n break;\n }\n if (!hasMatch &&\n !matchedIndices[matchIndex] &&\n (hasMatch = state.equals(aResult.value, bResult.value, aResult.value, bResult.value, a, b, state))) {\n matchedIndices[matchIndex] = true;\n }\n matchIndex++;\n }\n if (!hasMatch) {\n return false;\n }\n }\n return true;\n}\n/**\n * Whether the TypedArray instances are equal in value.\n */\nfunction areTypedArraysEqual(a, b) {\n var index = a.length;\n if (b.length !== index) {\n return false;\n }\n while (index-- > 0) {\n if (a[index] !== b[index]) {\n return false;\n }\n }\n return true;\n}\n\nvar ARGUMENTS_TAG = '[object Arguments]';\nvar BOOLEAN_TAG = '[object Boolean]';\nvar DATE_TAG = '[object Date]';\nvar MAP_TAG = '[object Map]';\nvar NUMBER_TAG = '[object Number]';\nvar OBJECT_TAG = '[object Object]';\nvar REG_EXP_TAG = '[object RegExp]';\nvar SET_TAG = '[object Set]';\nvar STRING_TAG = '[object String]';\nvar isArray = Array.isArray;\nvar isTypedArray = typeof ArrayBuffer === 'function' && ArrayBuffer.isView\n ? ArrayBuffer.isView\n : null;\nvar assign = Object.assign;\nvar getTag = Object.prototype.toString.call.bind(Object.prototype.toString);\n/**\n * Create a comparator method based on the type-specific equality comparators passed.\n */\nfunction createEqualityComparator(_a) {\n var areArraysEqual = _a.areArraysEqual, areDatesEqual = _a.areDatesEqual, areMapsEqual = _a.areMapsEqual, areObjectsEqual = _a.areObjectsEqual, arePrimitiveWrappersEqual = _a.arePrimitiveWrappersEqual, areRegExpsEqual = _a.areRegExpsEqual, areSetsEqual = _a.areSetsEqual, areTypedArraysEqual = _a.areTypedArraysEqual;\n /**\n * compare the value of the two objects and return true if they are equivalent in values\n */\n return function comparator(a, b, state) {\n // If the items are strictly equal, no need to do a value comparison.\n if (a === b) {\n return true;\n }\n // If the items are not non-nullish objects, then the only possibility\n // of them being equal but not strictly is if they are both `NaN`. Since\n // `NaN` is uniquely not equal to itself, we can use self-comparison of\n // both objects, which is faster than `isNaN()`.\n if (a == null ||\n b == null ||\n typeof a !== 'object' ||\n typeof b !== 'object') {\n return a !== a && b !== b;\n }\n var constructor = a.constructor;\n // Checks are listed in order of commonality of use-case:\n // 1. Common complex object types (plain object, array)\n // 2. Common data values (date, regexp)\n // 3. Less-common complex object types (map, set)\n // 4. Less-common data values (promise, primitive wrappers)\n // Inherently this is both subjective and assumptive, however\n // when reviewing comparable libraries in the wild this order\n // appears to be generally consistent.\n // Constructors should match, otherwise there is potential for false positives\n // between class and subclass or custom object and POJO.\n if (constructor !== b.constructor) {\n return false;\n }\n // `isPlainObject` only checks against the object's own realm. Cross-realm\n // comparisons are rare, and will be handled in the ultimate fallback, so\n // we can avoid capturing the string tag.\n if (constructor === Object) {\n return areObjectsEqual(a, b, state);\n }\n // `isArray()` works on subclasses and is cross-realm, so we can avoid capturing\n // the string tag or doing an `instanceof` check.\n if (isArray(a)) {\n return areArraysEqual(a, b, state);\n }\n // `isTypedArray()` works on all possible TypedArray classes, so we can avoid\n // capturing the string tag or comparing against all possible constructors.\n if (isTypedArray != null && isTypedArray(a)) {\n return areTypedArraysEqual(a, b, state);\n }\n // Try to fast-path equality checks for other complex object types in the\n // same realm to avoid capturing the string tag. Strict equality is used\n // instead of `instanceof` because it is more performant for the common\n // use-case. If someone is subclassing a native class, it will be handled\n // with the string tag comparison.\n if (constructor === Date) {\n return areDatesEqual(a, b, state);\n }\n if (constructor === RegExp) {\n return areRegExpsEqual(a, b, state);\n }\n if (constructor === Map) {\n return areMapsEqual(a, b, state);\n }\n if (constructor === Set) {\n return areSetsEqual(a, b, state);\n }\n // Since this is a custom object, capture the string tag to determing its type.\n // This is reasonably performant in modern environments like v8 and SpiderMonkey.\n var tag = getTag(a);\n if (tag === DATE_TAG) {\n return areDatesEqual(a, b, state);\n }\n if (tag === REG_EXP_TAG) {\n return areRegExpsEqual(a, b, state);\n }\n if (tag === MAP_TAG) {\n return areMapsEqual(a, b, state);\n }\n if (tag === SET_TAG) {\n return areSetsEqual(a, b, state);\n }\n if (tag === OBJECT_TAG) {\n // The exception for value comparison is custom `Promise`-like class instances. These should\n // be treated the same as standard `Promise` objects, which means strict equality, and if\n // it reaches this point then that strict equality comparison has already failed.\n return (typeof a.then !== 'function' &&\n typeof b.then !== 'function' &&\n areObjectsEqual(a, b, state));\n }\n // If an arguments tag, it should be treated as a standard object.\n if (tag === ARGUMENTS_TAG) {\n return areObjectsEqual(a, b, state);\n }\n // As the penultimate fallback, check if the values passed are primitive wrappers. This\n // is very rare in modern JS, which is why it is deprioritized compared to all other object\n // types.\n if (tag === BOOLEAN_TAG || tag === NUMBER_TAG || tag === STRING_TAG) {\n return arePrimitiveWrappersEqual(a, b, state);\n }\n // If not matching any tags that require a specific type of comparison, then we hard-code false because\n // the only thing remaining is strict equality, which has already been compared. This is for a few reasons:\n // - Certain types that cannot be introspected (e.g., `WeakMap`). For these types, this is the only\n // comparison that can be made.\n // - For types that can be introspected, but rarely have requirements to be compared\n // (`ArrayBuffer`, `DataView`, etc.), the cost is avoided to prioritize the common\n // use-cases (may be included in a future release, if requested enough).\n // - For types that can be introspected but do not have an objective definition of what\n // equality is (`Error`, etc.), the subjective decision is to be conservative and strictly compare.\n // In all cases, these decisions should be reevaluated based on changes to the language and\n // common development practices.\n return false;\n };\n}\n/**\n * Create the configuration object used for building comparators.\n */\nfunction createEqualityComparatorConfig(_a) {\n var circular = _a.circular, createCustomConfig = _a.createCustomConfig, strict = _a.strict;\n var config = {\n areArraysEqual: strict\n ? areObjectsEqualStrict\n : areArraysEqual,\n areDatesEqual: areDatesEqual,\n areMapsEqual: strict\n ? combineComparators(areMapsEqual, areObjectsEqualStrict)\n : areMapsEqual,\n areObjectsEqual: strict\n ? areObjectsEqualStrict\n : areObjectsEqual,\n arePrimitiveWrappersEqual: arePrimitiveWrappersEqual,\n areRegExpsEqual: areRegExpsEqual,\n areSetsEqual: strict\n ? combineComparators(areSetsEqual, areObjectsEqualStrict)\n : areSetsEqual,\n areTypedArraysEqual: strict\n ? areObjectsEqualStrict\n : areTypedArraysEqual,\n };\n if (createCustomConfig) {\n config = assign({}, config, createCustomConfig(config));\n }\n if (circular) {\n var areArraysEqual$1 = createIsCircular(config.areArraysEqual);\n var areMapsEqual$1 = createIsCircular(config.areMapsEqual);\n var areObjectsEqual$1 = createIsCircular(config.areObjectsEqual);\n var areSetsEqual$1 = createIsCircular(config.areSetsEqual);\n config = assign({}, config, {\n areArraysEqual: areArraysEqual$1,\n areMapsEqual: areMapsEqual$1,\n areObjectsEqual: areObjectsEqual$1,\n areSetsEqual: areSetsEqual$1,\n });\n }\n return config;\n}\n/**\n * Default equality comparator pass-through, used as the standard `isEqual` creator for\n * use inside the built comparator.\n */\nfunction createInternalEqualityComparator(compare) {\n return function (a, b, _indexOrKeyA, _indexOrKeyB, _parentA, _parentB, state) {\n return compare(a, b, state);\n };\n}\n/**\n * Create the `isEqual` function used by the consuming application.\n */\nfunction createIsEqual(_a) {\n var circular = _a.circular, comparator = _a.comparator, createState = _a.createState, equals = _a.equals, strict = _a.strict;\n if (createState) {\n return function isEqual(a, b) {\n var _a = createState(), _b = _a.cache, cache = _b === void 0 ? circular ? new WeakMap() : undefined : _b, meta = _a.meta;\n return comparator(a, b, {\n cache: cache,\n equals: equals,\n meta: meta,\n strict: strict,\n });\n };\n }\n if (circular) {\n return function isEqual(a, b) {\n return comparator(a, b, {\n cache: new WeakMap(),\n equals: equals,\n meta: undefined,\n strict: strict,\n });\n };\n }\n var state = {\n cache: undefined,\n equals: equals,\n meta: undefined,\n strict: strict,\n };\n return function isEqual(a, b) {\n return comparator(a, b, state);\n };\n}\n\n/**\n * Whether the items passed are deeply-equal in value.\n */\nvar deepEqual = createCustomEqual();\n/**\n * Whether the items passed are deeply-equal in value based on strict comparison.\n */\nvar strictDeepEqual = createCustomEqual({ strict: true });\n/**\n * Whether the items passed are deeply-equal in value, including circular references.\n */\nvar circularDeepEqual = createCustomEqual({ circular: true });\n/**\n * Whether the items passed are deeply-equal in value, including circular references,\n * based on strict comparison.\n */\nvar strictCircularDeepEqual = createCustomEqual({\n circular: true,\n strict: true,\n});\n/**\n * Whether the items passed are shallowly-equal in value.\n */\nvar shallowEqual = createCustomEqual({\n createInternalComparator: function () { return sameValueZeroEqual; },\n});\n/**\n * Whether the items passed are shallowly-equal in value based on strict comparison\n */\nvar strictShallowEqual = createCustomEqual({\n strict: true,\n createInternalComparator: function () { return sameValueZeroEqual; },\n});\n/**\n * Whether the items passed are shallowly-equal in value, including circular references.\n */\nvar circularShallowEqual = createCustomEqual({\n circular: true,\n createInternalComparator: function () { return sameValueZeroEqual; },\n});\n/**\n * Whether the items passed are shallowly-equal in value, including circular references,\n * based on strict comparison.\n */\nvar strictCircularShallowEqual = createCustomEqual({\n circular: true,\n createInternalComparator: function () { return sameValueZeroEqual; },\n strict: true,\n});\n/**\n * Create a custom equality comparison method.\n *\n * This can be done to create very targeted comparisons in extreme hot-path scenarios\n * where the standard methods are not performant enough, but can also be used to provide\n * support for legacy environments that do not support expected features like\n * `RegExp.prototype.flags` out of the box.\n */\nfunction createCustomEqual(options) {\n if (options === void 0) { options = {}; }\n var _a = options.circular, circular = _a === void 0 ? false : _a, createCustomInternalComparator = options.createInternalComparator, createState = options.createState, _b = options.strict, strict = _b === void 0 ? false : _b;\n var config = createEqualityComparatorConfig(options);\n var comparator = createEqualityComparator(config);\n var equals = createCustomInternalComparator\n ? createCustomInternalComparator(comparator)\n : createInternalEqualityComparator(comparator);\n return createIsEqual({ circular: circular, comparator: comparator, createState: createState, equals: equals, strict: strict });\n}\n\nexport { circularDeepEqual, circularShallowEqual, createCustomEqual, deepEqual, sameValueZeroEqual, shallowEqual, strictCircularDeepEqual, strictCircularShallowEqual, strictDeepEqual, strictShallowEqual };\n//# sourceMappingURL=index.mjs.map\n","// There is a circular version https://www.npmjs.com/package/fast-equals#circulardeepequal that I\n// think allows comparing React refs (which have circular references in particular places that this\n// library would ignore). Maybe we can change to that version sometime if needed.\nimport { deepEqual as isEqualDeep } from 'fast-equals';\n\n/**\n * Check that two objects are deeply equal, comparing members of each object and such\n *\n * @param a The first object to compare\n * @param b The second object to compare\n *\n * WARNING: Objects like arrays from different iframes have different constructor function\n * references even if they do the same thing, so this deep equality comparison fails objects that\n * look the same but have different constructors because different constructors could produce\n * false positives in [a few specific\n * situations](https://github.com/planttheidea/fast-equals/blob/a41afc0a240ad5a472e47b53791e9be017f52281/src/comparator.ts#L96).\n * This means that two objects like arrays from different iframes that look the same will fail\n * this check. Please use some other means to check deep equality in those situations.\n *\n * Note: This deep equality check considers `undefined` values on keys of objects NOT to be equal to\n * not specifying the key at all. For example, `{ stuff: 3, things: undefined }` and `{ stuff: 3\n * }` are not considered equal in this case\n *\n * - For more information and examples, see [this\n * CodeSandbox](https://codesandbox.io/s/deepequallibrarycomparison-4g4kk4?file=/src/index.mjs).\n *\n * @returns True if a and b are deeply equal; false otherwise\n */\nexport default function deepEqual(a: unknown, b: unknown) {\n return isEqualDeep(a, b);\n}\n","import deepEqual from './equality-checking';\n\n/**\n * Check if one object is a subset of the other object. \"Subset\" means that all properties of one\n * object are present in the other object, and if they are present that all values of those\n * properties are deeply equal. Sub-objects are also checked to be subsets of the corresponding\n * sub-object in the other object.\n *\n * @example ObjB is a subset of objA given these objects:\n *\n * ```ts\n * objA = { name: 'Alice', age: 30, address: { city: 'Seattle', state: 'Washington' } };\n * objB = { name: 'Alice', address: { city: 'Seattle' } };\n * ```\n *\n * It is important to note that only arrays of primitives (i.e., booleans, numbers, strings) are\n * supported. In particular, objects in arrays will not be checked for deep equality. Also, presence\n * in an array is all this checks, not the number of times that an item appears in an array. `[1,\n * 1]` is a subset of `[1]`.\n *\n * @param objectWithAllProperties Object to be checked if it is a superset of\n * `objectWithPartialProperties`\n * @param objectWithPartialProperties Object to be checked if it is a subset of\n * `objectWithAllProperties`\n * @returns True if `objectWithAllProperties` contains all the properties of\n * `objectWithPartialProperties` and all values of those properties are deeply equal\n */\nexport default function isSubset(\n objectWithAllProperties: unknown,\n objectWithPartialProperties: unknown,\n): boolean {\n if (typeof objectWithAllProperties !== typeof objectWithPartialProperties) return false;\n\n // For this function we're saying that all falsy things of the same type are equal to each other\n if (!objectWithAllProperties && !objectWithPartialProperties) return true;\n\n if (Array.isArray(objectWithAllProperties)) {\n // We know these are arrays from the line above\n /* eslint-disable no-type-assertion/no-type-assertion */\n const partialArray = objectWithPartialProperties as Array;\n const allArray = objectWithAllProperties as Array;\n /* eslint-enable no-type-assertion/no-type-assertion */\n\n if (partialArray.length === 0) return true;\n\n // This only works with arrays of primitives.\n // If someone cares about checking arrays of objects this needs updating.\n return partialArray.every((item) => allArray.includes(item));\n }\n\n if (typeof objectWithAllProperties !== 'object')\n return deepEqual(objectWithAllProperties, objectWithPartialProperties);\n\n // We know these are objects that potentially have properties because of the earlier checks\n /* eslint-disable no-type-assertion/no-type-assertion */\n const partialObj = objectWithPartialProperties as Record;\n const allObj = objectWithAllProperties as Record;\n /* eslint-enable no-type-assertion/no-type-assertion */\n\n let retVal = true;\n Object.keys(partialObj).forEach((key) => {\n if (!retVal) return;\n if (!Object.hasOwn(allObj, key)) retVal = false;\n else if (!isSubset(allObj[key], partialObj[key])) retVal = false;\n });\n return retVal;\n}\n","/**\n * Converts a JavaScript value to a JSON string, changing `undefined` properties in the JavaScript\n * object to `null` properties in the JSON string.\n *\n * WARNING: `null` values will become `undefined` values after passing through {@link serialize} then\n * {@link deserialize}. For example, `{ a: 1, b: undefined, c: null }` will become `{ a: 1, b:\n * undefined, c: undefined }`. If you are passing around user data that needs to retain `null`\n * values, you should wrap them yourself in a string before using this function. Alternatively, you\n * can write your own replacer that will preserve `null` in a way that you can recover later.\n *\n * @param value A JavaScript value, usually an object or array, to be converted.\n * @param replacer A function that transforms the results. Note that all `undefined` values returned\n * by the replacer will be further transformed into `null` in the JSON string.\n * @param space Adds indentation, white space, and line break characters to the return-value JSON\n * text to make it easier to read. See the `space` parameter of `JSON.stringify` for more\n * details.\n */\nexport function serialize(\n value: unknown,\n replacer?: (this: unknown, key: string, value: unknown) => unknown,\n space?: string | number,\n): string {\n const undefinedReplacer = (replacerKey: string, replacerValue: unknown) => {\n let newValue = replacerValue;\n if (replacer) newValue = replacer(replacerKey, newValue);\n // All `undefined` values become `null` on the way from JS objects into JSON strings\n // eslint-disable-next-line no-null/no-null\n if (newValue === undefined) newValue = null;\n return newValue;\n };\n return JSON.stringify(value, undefinedReplacer, space);\n}\n\n/**\n * Converts a JSON string into a value, converting all `null` properties from JSON into `undefined`\n * in the returned JavaScript value/object.\n *\n * WARNING: `null` values will become `undefined` values after passing through {@link serialize} then\n * {@link deserialize}. For example, `{ a: 1, b: undefined, c: null }` will become `{ a: 1, b:\n * undefined, c: undefined }`. If you are passing around user data that needs to retain `null`\n * values, you should wrap them yourself in a string before using this function. Alternatively, you\n * can write your own replacer that will preserve `null` in a way that you can recover later.\n *\n * @param value A valid JSON string.\n * @param reviver A function that transforms the results. This function is called for each member of\n * the object. If a member contains nested objects, the nested objects are transformed before the\n * parent object is. Note that `null` values are converted into `undefined` values after the\n * reviver has run.\n */\nexport function deserialize(\n value: string,\n reviver?: (this: unknown, key: string, value: unknown) => unknown,\n // Need to use `any` instead of `unknown` here to match the signature of JSON.parse\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n): any {\n // Helper function to replace `null` with `undefined` on a per property basis. This can't be done\n // with our own reviver because `JSON.parse` removes `undefined` properties from the return value.\n function replaceNull(obj: Record): Record {\n Object.keys(obj).forEach((key: string | number) => {\n // We only want to replace `null`, not other falsy values\n // eslint-disable-next-line no-null/no-null\n if (obj[key] === null) obj[key] = undefined;\n // If the property is an object, recursively call the helper function on it\n else if (typeof obj[key] === 'object')\n // Since the object came from a string, we know the keys will not be symbols\n // eslint-disable-next-line no-type-assertion/no-type-assertion\n obj[key] = replaceNull(obj[key] as Record);\n });\n return obj;\n }\n\n const parsedObject = JSON.parse(value, reviver);\n // Explicitly convert the value 'null' that isn't stored as a property on an object to 'undefined'\n // eslint-disable-next-line no-null/no-null\n if (parsedObject === null) return undefined;\n if (typeof parsedObject === 'object') return replaceNull(parsedObject);\n return parsedObject;\n}\n\n/**\n * Check to see if the value is serializable without losing information\n *\n * @param value Value to test\n * @returns True if serializable; false otherwise\n *\n * Note: the values `undefined` and `null` are serializable (on their own or in an array), but\n * `null` values get transformed into `undefined` when serializing/deserializing.\n *\n * WARNING: This is inefficient right now as it stringifies, parses, stringifies, and === the value.\n * Please only use this if you need to\n *\n * DISCLAIMER: this does not successfully detect that values are not serializable in some cases:\n *\n * - Losses of removed properties like functions and `Map`s\n * - Class instances (not deserializable into class instances without special code)\n *\n * We intend to improve this in the future if it becomes important to do so. See [`JSON.stringify`\n * documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#description)\n * for more information.\n */\nexport function isSerializable(value: unknown): boolean {\n try {\n const serializedValue = serialize(value);\n return serializedValue === serialize(deserialize(serializedValue));\n } catch (e) {\n return false;\n }\n}\n\n/**\n * HTML Encodes the provided string. Thanks to ChatGPT\n *\n * @param str String to HTML encode\n * @returns HTML-encoded string\n */\nexport const htmlEncode = (str: string): string =>\n str\n .replace(/&/g, '&')\n .replace(//g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/\\//g, '/');\n","import DateTimeFormat from './intl-date-time-format';\n\n/**\n * Retrieves the current locale of the user's environment.\n *\n * @returns A string representing the current locale. If the locale cannot be determined, the\n * function returns an empty string.\n */\nexport default function getCurrentLocale(): string {\n // Use navigator when available\n if (typeof navigator !== 'undefined' && navigator.languages) {\n return navigator.languages[0];\n }\n // For Node.js\n return new DateTimeFormat().resolvedOptions().locale;\n}\n","//----------------------------------------------------------------------------------------------\n// NOTE: If you change any of the types, make sure the JSON schema at the end of this file gets\n// changed so they align.\n//----------------------------------------------------------------------------------------------\n\nimport { LocalizeKey, ReferencedItem } from 'menus.model';\n\n/** The data an extension provides to inform Platform.Bible of the settings it provides */\nexport type SettingsContribution = SettingsGroup | SettingsGroup[];\n/** A description of an extension's setting entry */\nexport type Setting = ExtensionControlledSetting;\n/** Setting definition that is validated by the extension. */\nexport type ExtensionControlledSetting = SettingBase & ModifierExtensionControlled;\n/** Base information needed to describe a setting entry */\nexport type SettingBase = StateBase & {\n [k: string]: unknown;\n /** LocalizeKey that displays in the settings dialog as the setting name */\n label: LocalizeKey;\n /** LocalizeKey that displays in the settings dialog to describe the setting */\n description?: LocalizeKey;\n};\n/** The data an extension provides to inform Platform.Bible of the project settings it provides */\nexport type ProjectSettingsContribution = ProjectSettingsGroup | ProjectSettingsGroup[];\n/** A description of an extension's setting entry */\nexport type ProjectSetting = ExtensionControlledProjectSetting;\n/** Setting definition that is validated by the extension. */\nexport type ExtensionControlledProjectSetting = ProjectSettingBase & ModifierExtensionControlled;\n/** Base information needed to describe a project setting entry */\nexport type ProjectSettingBase = SettingBase & ModifierProject;\n/** A description of an extension's user state entry */\nexport type UserState = ExtensionControlledState;\n/** State definition that is validated by the extension. */\nexport type ExtensionControlledState = StateBase & ModifierExtensionControlled;\n/** Group of related settings definitions */\nexport interface SettingsGroup {\n [k: string]: unknown;\n /** LocalizeKey that displays in the settings dialog as the group name */\n label: LocalizeKey;\n /** LocalizeKey that displays in the settings dialog to describe the group */\n description?: LocalizeKey;\n properties: SettingProperties;\n}\n/** Object whose keys are setting IDs and whose values are settings objects */\nexport interface SettingProperties {\n [k: ReferencedItem]: Setting;\n}\n/** Base information needed to describe a state entry */\nexport interface StateBase {\n [k: string]: unknown;\n /** Default value for the state/setting */\n default: unknown;\n /**\n * A state/setting ID whose value to set to this state/setting's starting value the first time\n * this state/setting is loaded\n */\n derivesFrom?: ReferencedItem;\n}\n/**\n * Modifies state/setting type to be extension-controlled. \"Extension-controlled\" means the\n * extension provides the component and the validator for the state/setting, so the state/setting is\n * controlled by the extension.\n */\nexport interface ModifierExtensionControlled {\n [k: string]: unknown;\n platformType?: undefined;\n type?: undefined;\n}\n/** Group of related settings definitions */\nexport interface ProjectSettingsGroup {\n [k: string]: unknown;\n /** LocalizeKey that displays in the project settings dialog as the group name */\n label: LocalizeKey;\n /** LocalizeKey that displays in the project settings dialog to describe the group */\n description?: LocalizeKey;\n properties: ProjectSettingProperties;\n}\n/** Object whose keys are setting IDs and whose values are settings objects */\nexport interface ProjectSettingProperties {\n [k: ReferencedItem]: ProjectSetting;\n}\n\n// Note: we removed the index signature on ModifierProject to avoid having it on\n// `ProjectMetadataFilterOptions`. Unfortunately adding \"additionalProperties\": false on the json\n// schema messes up validation. Please remove the index signature again in the future if you\n// regenerate types\nexport interface ModifierProject {\n /**\n * String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s\n * (using the\n * [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)\n * function) to determine if they should be included.\n *\n * If this is one string, it will be matched against `projectInterface`s. If this is an array,\n * each entry is handled based on its type (at least one entry must match for this filter\n * condition to pass):\n *\n * - If the entry is a string, it will be matched against each `projectInterface`. If any match, the\n * project will pass this filter condition\n * - If the entry is an array of strings, each will be matched against each `projectInterface`. If\n * every string matches against at least one `projectInterface`, the project will pass this\n * filter condition\n *\n * In other words, each entry in the first-level array is `OR`'ed together. Each entry in\n * second-level arrays (arrays within the first-level array) are `AND`'ed together.\n *\n * Defaults to all {@link ProjectInterfaces}, so all projects that do not match\n * `excludeProjectInterfaces` will be included\n *\n * @example\n *\n * ```typescript\n * includeProjectInterfaces: ['one', ['two', 'three']];\n * ```\n *\n * This filter condition will succeed on projects whose `projectInterface`s fulfill at least one\n * of the following conditions (At least one entry in the array must match):\n *\n * - Include `one`\n * - Include both `two` and `three`.\n */\n includeProjectInterfaces?: undefined | string | (string | string[])[];\n /**\n * String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s\n * (using the\n * [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)\n * function) to determine if they should absolutely not be included even if they match with\n * `includeProjectInterfaces`.\n *\n * If this is one string, it will be matched against `projectInterface`s. If this is an array,\n * each entry is handled based on its type (at least one entry must match for this filter\n * condition to exclude the project):\n *\n * - If the entry is a string, it will be matched against each `projectInterface`. If any match, the\n * project will pass this filter condition and exclude the project\n * - If the entry is an array of strings, each will be matched against each `projectInterface`. If\n * every string matches against at least one `projectInterface`, the project will pass this\n * filter condition and exclude the project\n *\n * In other words, each entry in the first-level array is `OR`'ed together. Each entry in\n * second-level arrays (arrays within the first-level array) are `AND`'ed together.\n *\n * Defaults to no {@link ProjectInterfaces}, so all projects that match `includeProjectInterfaces`\n * will be included\n *\n * @example\n *\n * ```typescript\n * excludeProjectInterfaces: ['one', ['two', 'three']];\n * ```\n *\n * This filter condition will succeed and exclude projects whose `projectInterface`s fulfill at\n * least one of the following conditions (At least one entry in the array must match):\n *\n * - Include `one`\n * - Include both `two` and `three`.\n */\n excludeProjectInterfaces?: undefined | string | (string | string[])[];\n /**\n * String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory\n * Ids that provided each project's metadata (using the\n * [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)\n * function) to determine if the projects should be included.\n *\n * Defaults to all Project Data Provider Factory Ids, so all projects that do not match\n * `excludePdpFactoryIds` will be included\n */\n includePdpFactoryIds?: undefined | string | string[];\n /**\n * String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory\n * Ids that provided each project's metadata (using the\n * [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test)\n * function) to determine if the projects should absolutely not be included even if they match\n * with `includeProjectInterfaces`.\n *\n * Defaults to none, so all projects that match `includePdpFactoryIds` will be included\n */\n excludePdpFactoryIds?: undefined | string | string[];\n}\n\n/** The data an extension provides to inform Platform.Bible of the user state it provides */\nexport interface UserStateContribution {\n [k: ReferencedItem]: UserState;\n}\n/** The data an extension provides to inform Platform.Bible of the project state it provides */\nexport interface ProjectStateContribution {\n [k: ReferencedItem]: UserState;\n}\n\n//----------------------------------------------------------------------------------------------\n// NOTE: If you change the schema below, make sure the TS types above get changed so they align.\n//----------------------------------------------------------------------------------------------\nconst settingsDefs = {\n projectSettingsContribution: {\n description:\n 'The data an extension provides to inform Platform.Bible of the project settings it provides',\n anyOf: [\n {\n $ref: '#/$defs/projectSettingsGroup',\n },\n {\n type: 'array',\n items: {\n $ref: '#/$defs/projectSettingsGroup',\n },\n },\n ],\n },\n projectSettingsGroup: {\n description: 'Group of related settings definitions',\n type: 'object',\n properties: {\n label: {\n description: 'localizeKey that displays in the project settings dialog as the group name',\n $ref: '#/$defs/localizeKey',\n },\n description: {\n description:\n 'localizeKey that displays in the project settings dialog to describe the group',\n $ref: '#/$defs/localizeKey',\n },\n properties: {\n $ref: '#/$defs/projectSettingProperties',\n },\n },\n required: ['label', 'properties'],\n },\n projectSettingProperties: {\n description: 'Object whose keys are setting IDs and whose values are settings objects',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n $ref: '#/$defs/projectSetting',\n },\n },\n additionalProperties: false,\n },\n projectSetting: {\n description: \"A description of an extension's setting entry\",\n anyOf: [\n {\n $ref: '#/$defs/extensionControlledProjectSetting',\n },\n ],\n },\n extensionControlledProjectSetting: {\n description: 'Setting definition that is validated by the extension.',\n allOf: [\n {\n $ref: '#/$defs/projectSettingBase',\n },\n {\n $ref: '#/$defs/modifierExtensionControlled',\n },\n ],\n },\n projectSettingBase: {\n description: 'Base information needed to describe a project setting entry',\n allOf: [\n {\n $ref: '#/$defs/settingBase',\n },\n {\n $ref: '#/$defs/modifierProject',\n },\n ],\n },\n modifierProject: {\n description: 'Modifies setting type to be project setting',\n type: 'object',\n properties: {\n includeProjectInterfaces: {\n description:\n \"String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if they should be included.\\n\\nIf this is one string, it will be matched against `projectInterface`s. If this is an array, each entry is handled based on its type (at least one entry must match for this filter condition to pass):\\n\\n- If the entry is a string, it will be matched against each `projectInterface`. If any match, the project will pass this filter condition\\n- If the entry is an array of strings, each will be matched against each `projectInterface`. If every string matches against at least one `projectInterface`, the project will pass this filter condition\\n\\nIn other words, each entry in the first-level array is `OR`'ed together. Each entry in second-level arrays (arrays within the first-level array) are `AND`'ed together.\\n\\nDefaults to all {@link ProjectInterfaces}, so all projects that do not match `excludeProjectInterfaces` will be included\\n\\n@example\\n\\n```typescript\\nincludeProjectInterfaces: ['one', ['two', 'three']];\\n```\\n\\nThis filter condition will succeed on projects whose `projectInterface`s fulfill at least one of the following conditions (At least one entry in the array must match):\\n\\n- Include `one`\\n- Include both `two` and `three`.\",\n anyOf: [\n {\n type: 'null',\n },\n {\n type: 'string',\n },\n {\n type: 'array',\n items: {\n anyOf: [\n {\n type: 'string',\n },\n {\n type: 'array',\n items: { type: 'string' },\n },\n ],\n },\n },\n ],\n },\n excludeProjectInterfaces: {\n description:\n \"String representation of `RegExp` pattern(s) to match against projects' `projectInterface`s (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if they should absolutely not be included even if they match with `includeProjectInterfaces`.\\n\\nIf this is one string, it will be matched against `projectInterface`s. If this is an array, each entry is handled based on its type (at least one entry must match for this filter condition to exclude the project):\\n\\n- If the entry is a string, it will be matched against each `projectInterface`. If any match, the project will pass this filter condition and exclude the project\\n- If the entry is an array of strings, each will be matched against each `projectInterface`. If every string matches against at least one `projectInterface`, the project will pass this filter condition and exclude the project\\n\\nIn other words, each entry in the first-level array is `OR`'ed together. Each entry in second-level arrays (arrays within the first-level array) are `AND`'ed together.\\n\\nDefaults to no {@link ProjectInterfaces}, so all projects that match `includeProjectInterfaces` will be included\\n\\n@example\\n\\n```typescript\\nexcludeProjectInterfaces: ['one', ['two', 'three']];\\n```\\n\\nThis filter condition will succeed and exclude projects whose `projectInterface`s fulfill at least one of the following conditions (At least one entry in the array must match):\\n\\n- Include `one`\\n- Include both `two` and `three`.\",\n anyOf: [\n {\n type: 'null',\n },\n {\n type: 'string',\n },\n {\n type: 'array',\n items: {\n anyOf: [\n {\n type: 'string',\n },\n {\n type: 'array',\n items: { type: 'string' },\n },\n ],\n },\n },\n ],\n },\n includePdpFactoryIds: {\n description:\n \"String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory Ids that provided each project's metadata (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if the projects should be included.\\n\\nDefaults to all Project Data Provider Factory Ids, so all projects that do not match `excludePdpFactoryIds` will be included\",\n anyOf: [\n {\n type: 'null',\n },\n {\n type: 'string',\n },\n {\n type: 'array',\n items: { type: 'string' },\n },\n ],\n },\n excludePdpFactoryIds: {\n description:\n \"String representation of `RegExp` pattern(s) to match against the Project Data Provider Factory Ids that provided each project's metadata (using the [`test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) function) to determine if the projects should absolutely not be included even if they match with `includeProjectInterfaces`.\\n\\nDefaults to none, so all projects that match `includePdpFactoryIds` will be included\",\n anyOf: [\n {\n type: 'null',\n },\n {\n type: 'string',\n },\n {\n type: 'array',\n items: { type: 'string' },\n },\n ],\n },\n },\n },\n settingsContribution: {\n description:\n 'The data an extension provides to inform Platform.Bible of the settings it provides',\n anyOf: [\n {\n $ref: '#/$defs/settingsGroup',\n },\n {\n type: 'array',\n items: {\n $ref: '#/$defs/settingsGroup',\n },\n },\n ],\n },\n settingsGroup: {\n description: 'Group of related settings definitions',\n type: 'object',\n properties: {\n label: {\n description: 'localizeKey that displays in the settings dialog as the group name',\n $ref: '#/$defs/localizeKey',\n },\n description: {\n description: 'localizeKey that displays in the settings dialog to describe the group',\n $ref: '#/$defs/localizeKey',\n },\n properties: {\n $ref: '#/$defs/settingProperties',\n },\n },\n required: ['label', 'properties'],\n },\n settingProperties: {\n description: 'Object whose keys are setting IDs and whose values are settings objects',\n type: 'object',\n patternProperties: {\n '^[\\\\w-]+\\\\.[\\\\w-]+$': {\n $ref: '#/$defs/setting',\n },\n },\n additionalProperties: false,\n },\n setting: {\n description: \"A description of an extension's setting entry\",\n anyOf: [\n {\n $ref: '#/$defs/extensionControlledSetting',\n },\n ],\n },\n extensionControlledSetting: {\n description: 'Setting definition that is validated by the extension.',\n allOf: [\n {\n $ref: '#/$defs/settingBase',\n },\n {\n $ref: '#/$defs/modifierExtensionControlled',\n },\n ],\n },\n settingBase: {\n description: 'Base information needed to describe a setting entry',\n allOf: [\n {\n $ref: '#/$defs/stateBase',\n },\n {\n type: 'object',\n properties: {\n label: {\n description: 'localizeKey that displays in the settings dialog as the setting name',\n $ref: '#/$defs/localizeKey',\n },\n description: {\n description: 'localizeKey that displays in the settings dialog to describe the setting',\n $ref: '#/$defs/localizeKey',\n },\n },\n required: ['label'],\n },\n ],\n },\n projectStateContribution: {\n description:\n 'The data an extension provides to inform Platform.Bible of the project state it provides',\n $ref: '#/$defs/userStateProperties',\n },\n userStateContribution: {\n description:\n 'The data an extension provides to inform Platform.Bible of the user state it provides',\n $ref: '#/$defs/userStateProperties',\n },\n userStateProperties: {\n description: 'Object whose keys are state IDs and whose values are state objects',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n $ref: '#/$defs/userState',\n },\n },\n additionalProperties: false,\n },\n userState: {\n description: \"A description of an extension's user state entry\",\n anyOf: [\n {\n $ref: '#/$defs/extensionControlledState',\n },\n ],\n },\n extensionControlledState: {\n description: 'State definition that is validated by the extension.',\n allOf: [\n {\n $ref: '#/$defs/stateBase',\n },\n {\n $ref: '#/$defs/modifierExtensionControlled',\n },\n ],\n },\n modifierExtensionControlled: {\n description:\n 'Modifies state/setting type to be extension-controlled. \"Extension-controlled\" means the extension provides the component and the validator for the state/setting, so the state/setting is controlled by the extension.',\n not: {\n anyOf: [\n {\n type: 'object',\n required: ['platformType'],\n },\n {\n type: 'object',\n required: ['type'],\n },\n ],\n },\n },\n stateBase: {\n description: 'Base information needed to describe a state entry',\n type: 'object',\n properties: {\n default: {\n description: 'default value for the state/setting',\n type: 'any',\n },\n derivesFrom: {\n description:\n \"a state/setting ID whose value to set to this state/setting's starting value the first time this state/setting is loaded\",\n $ref: '#/$defs/id',\n },\n },\n required: ['default'],\n },\n localizeKey: {\n description: \"Identifier for a string that will be localized based on the user's UI language\",\n type: 'string',\n pattern: '^%[\\\\w\\\\-\\\\.]+%$',\n tsType: 'LocalizeKey',\n },\n id: {\n description: '',\n type: 'string',\n pattern: '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$',\n tsType: 'Id',\n },\n};\n\n/**\n * Json-schema-to-typescript has some added stuff that isn't actually compatible with JSON schema,\n * so we remove them here\n *\n * @param defs The `$defs` property of a JSON schema (will be modified in place)\n */\n// JSON schema types are weird, so we'll just be careful\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function removeJsonToTypeScriptTypesStuff(defs: any) {\n if (!defs) return;\n\n // JSON schema types are weird, so we'll just be careful\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n Object.values(defs).forEach((def: any) => {\n if (!def.type) return;\n\n if ('tsType' in def) delete def.tsType;\n\n if (def.type === 'any') {\n delete def.type;\n return;\n }\n\n if (def.type === 'object') {\n removeJsonToTypeScriptTypesStuff(def.properties);\n }\n });\n}\n\nremoveJsonToTypeScriptTypesStuff(settingsDefs);\n\n/** JSON schema object that aligns with the ProjectSettingsContribution type */\nexport const projectSettingsDocumentSchema = {\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n title: 'Project Settings Contribution',\n description:\n 'The data an extension provides to inform Platform.Bible of the project settings it provides',\n anyOf: [\n {\n $ref: '#/$defs/projectSettingsGroup',\n },\n {\n type: 'array',\n items: {\n $ref: '#/$defs/projectSettingsGroup',\n },\n },\n ],\n\n $defs: settingsDefs,\n};\n\nObject.freeze(projectSettingsDocumentSchema);\n\n/** JSON schema object that aligns with the {@link SettingsContribution} type */\nexport const settingsDocumentSchema = {\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n title: 'Settings Contribution',\n description:\n 'The data an extension provides to inform Platform.Bible of the settings it provides',\n anyOf: [\n {\n $ref: '#/$defs/settingsGroup',\n },\n {\n type: 'array',\n items: {\n $ref: '#/$defs/settingsGroup',\n },\n },\n ],\n\n $defs: settingsDefs,\n};\n\nObject.freeze(settingsDocumentSchema);\n","//----------------------------------------------------------------------------------------------\n// NOTE: If you change any of the types, make sure the JSON schema at the end of this file gets\n// changed so they align.\n//----------------------------------------------------------------------------------------------\n\nimport { LocalizeKey } from 'menus.model';\nimport { removeJsonToTypeScriptTypesStuff } from './settings.model';\n\n/** Localized string value associated with this key */\nexport type LocalizedStringValue = string;\n\n/** The data an extension provides to inform Platform.Bible of the localized strings it provides. */\nexport interface LocalizedStringDataContribution {\n [k: string]: unknown;\n metadata?: StringsMetadata;\n localizedStrings?: {\n [k: string]: LanguageStrings;\n };\n}\n/**\n * Map whose keys are localized string keys and whose values provide additional non-locale-specific\n * information about the localized string key\n */\nexport interface StringsMetadata {\n [k: LocalizeKey]: StringMetadata;\n}\n/** Additional non-locale-specific information about a localized string key */\nexport interface StringMetadata {\n [k: string]: unknown;\n /**\n * Localized string key from which to get this value if one does not exist in the specified\n * language. If a new key/value pair needs to be made to replace an existing one, this could help\n * smooth over the transition if the meanings are close enough\n */\n fallbackKey?: LocalizeKey;\n /**\n * Additional information provided by developers in English to help the translator to know how to\n * translate this localized string accurately\n */\n notes?: string;\n}\n/**\n * Map whose keys are localized string keys and whose values provide information about how to\n * localize strings for the localized string key\n */\nexport interface LanguageStrings {\n [k: LocalizeKey]: LocalizedStringValue;\n}\n\n//----------------------------------------------------------------------------------------------\n// NOTE: If you change the schema below, make sure the TS types above get changed so they align.\n//----------------------------------------------------------------------------------------------\n\nconst localizedStringsDefs = {\n languageStrings: {\n description:\n 'Map whose keys are localized string keys and whose values provide information about how to localize strings for the localized string key',\n type: 'object',\n patternProperties: {\n '^%[\\\\w\\\\-\\\\.]+%$': {\n $ref: '#/$defs/localizedStringValue',\n },\n },\n additionalProperties: false,\n },\n localizedStringValue: {\n description: 'Localized string value associated with this key',\n type: 'string',\n },\n stringsMetadata: {\n description:\n 'Map whose keys are localized string keys and whose values provide additional non-locale-specific information about the localized string key',\n type: 'object',\n patternProperties: {\n '^%[\\\\w\\\\-\\\\.]+%$': {\n $ref: '#/$defs/stringMetadata',\n },\n },\n additionalProperties: false,\n },\n stringMetadata: {\n description: 'Additional non-locale-specific information about a localized string key',\n type: 'object',\n properties: {\n fallbackKey: {\n description:\n 'Localized string key from which to get this value if one does not exist in the specified language. If a new key/value pair needs to be made to replace an existing one, this could help smooth over the transition if the meanings are close enough',\n $ref: '#/$defs/localizeKey',\n },\n notes: {\n description:\n 'Additional information provided by developers in English to help the translator to know how to translate this localized string accurately',\n type: 'string',\n },\n },\n },\n localizeKey: {\n description: \"Identifier for a string that will be localized based on the user's UI language\",\n type: 'string',\n pattern: '^%[\\\\w\\\\-\\\\.]+%$',\n tsType: 'LocalizeKey',\n },\n};\n\nremoveJsonToTypeScriptTypesStuff(localizedStringsDefs);\n\n/** JSON schema object that aligns with the LocalizedStringDataContribution type */\nexport const localizedStringsDocumentSchema = {\n $schema: 'https://json-schema.org/draft/2020-12/schema',\n title: 'Localized String Data Contribution',\n description:\n 'The data an extension provides to inform Platform.Bible of the localized strings it provides.',\n type: 'object',\n properties: {\n metadata: {\n $ref: '#/$defs/stringsMetadata',\n },\n localizedStrings: {\n type: 'object',\n additionalProperties: {\n $ref: '#/$defs/languageStrings',\n },\n },\n },\n $defs: localizedStringsDefs,\n};\n\nObject.freeze(localizedStringsDocumentSchema);\n","//----------------------------------------------------------------------------------------------\n// NOTE: If you change any of the types, make sure the JSON schema at the end of this file gets\n// changed so they align.\n//----------------------------------------------------------------------------------------------\n\nimport { ReplaceType } from './util';\n\n/** Identifier for a string that will be localized in a menu based on the user's UI language */\nexport type LocalizeKey = `%${string}%`;\n\n/** Name of some UI element (i.e., tab, column, group, menu item) or some PAPI object (i.e., command) */\nexport type ReferencedItem = `${string}.${string}`;\n\nexport type OrderedItem = {\n /** Relative order of this item compared to other items in the same parent/scope (sorted ascending) */\n order: number;\n};\n\nexport type OrderedExtensibleContainer = OrderedItem & {\n /** Determines whether other items can be added to this after it has been defined */\n isExtensible?: boolean;\n};\n\n/** Group of menu items that belongs in a column */\nexport type MenuGroupDetailsInColumn = OrderedExtensibleContainer & {\n /** ID of column in which this group resides */\n column: ReferencedItem;\n};\n\n/** Group of menu items that belongs in a submenu */\nexport type MenuGroupDetailsInSubMenu = OrderedExtensibleContainer & {\n /** ID of menu item hosting the submenu in which this group resides */\n menuItem: ReferencedItem;\n};\n\n/** Column that includes header text in a menu */\nexport type MenuColumnWithHeader = OrderedExtensibleContainer & {\n /** Key that represents the text of the header text of the column */\n label: LocalizeKey;\n};\n\nexport type MenuItemBase = OrderedItem & {\n /** Menu group to which this menu item belongs */\n group: ReferencedItem;\n /** Key that represents the text of this menu item to display */\n label: LocalizeKey;\n /** Key that represents words the platform should reference when users are searching for menu items */\n searchTerms?: LocalizeKey;\n /** Key that represents the text to display if a mouse pointer hovers over the menu item */\n tooltip?: LocalizeKey;\n /** Additional information provided by developers to help people who perform localization */\n localizeNotes: string;\n};\n\n/** Menu item that hosts a submenu */\nexport type MenuItemContainingSubmenu = MenuItemBase & {\n /** ID for this menu item that holds a submenu */\n id: ReferencedItem;\n};\n\n/** Menu item that runs a command */\nexport type MenuItemContainingCommand = MenuItemBase & {\n /** Name of the PAPI command to run when this menu item is selected. */\n command: ReferencedItem;\n /** Path to the icon to display after the menu text */\n iconPathAfter?: string;\n /** Path to the icon to display before the menu text */\n iconPathBefore?: string;\n};\n\n/**\n * Group of menu items that can be combined with other groups to form a single context menu/submenu.\n * Groups are separated using a line within the menu/submenu.\n */\nexport type GroupsInSingleColumnMenu = {\n /** Named menu group */\n [property: ReferencedItem]: OrderedExtensibleContainer | MenuGroupDetailsInSubMenu;\n};\n\n/**\n * Group of menu items that can be combined with other groups to form a single menu/submenu within a\n * multi-column menu. Groups are separated using a line within the menu/submenu.\n */\nexport type GroupsInMultiColumnMenu = {\n /** Named menu group */\n [property: ReferencedItem]: MenuGroupDetailsInColumn | MenuGroupDetailsInSubMenu;\n};\n\n/** Group of columns that can be combined with other columns to form a multi-column menu */\nexport type ColumnsWithHeaders = {\n /** Named column of a menu */\n [property: ReferencedItem]: MenuColumnWithHeader;\n /** Defines whether columns can be added to this multi-column menu */\n isExtensible?: boolean;\n};\n\n/** Menu that contains a column without a header */\nexport type SingleColumnMenu = {\n /** Groups that belong in this menu */\n groups: GroupsInSingleColumnMenu;\n /** List of menu items that belong in this menu */\n items: (MenuItemContainingCommand | MenuItemContainingSubmenu)[];\n};\n\n/** Menu that contains multiple columns with headers */\nexport type MultiColumnMenu = {\n /** Columns that belong in this menu */\n columns: ColumnsWithHeaders;\n /** Groups that belong in this menu */\n groups: GroupsInMultiColumnMenu;\n /** List of menu items that belong in this menu */\n items: (MenuItemContainingCommand | MenuItemContainingSubmenu)[];\n};\n\n/** Menus for one single web view */\nexport type WebViewMenu = {\n /** Indicates whether the platform default menus should be included for this webview */\n includeDefaults: boolean | undefined;\n /** Menu that opens when you click on the top left corner of a tab */\n topMenu: MultiColumnMenu | undefined;\n /** Menu that opens when you right click on the main body/area of a tab */\n contextMenu: SingleColumnMenu | undefined;\n};\n\n/** Menus for all web views */\nexport type WebViewMenus = {\n /** Named web view */\n [property: ReferencedItem]: WebViewMenu;\n};\n\n/** Platform.Bible menus before they are localized */\nexport type PlatformMenus = {\n /** Top level menu for the application */\n mainMenu: MultiColumnMenu;\n /** Menus that apply per web view in the application */\n webViewMenus: WebViewMenus;\n /** Default context menu for web views that don't specify their own */\n defaultWebViewContextMenu: SingleColumnMenu;\n /** Default top menu for web views that don't specify their own */\n defaultWebViewTopMenu: MultiColumnMenu;\n};\n\n/**\n * Type that converts any menu type before it is localized to what it is after it is localized. This\n * can be applied to any menu type as needed.\n */\nexport type Localized = ReplaceType, ReferencedItem, string>;\n\n//----------------------------------------------------------------------------------------------\n// NOTE: If you change the schema below, make sure the TS types above get changed so they align.\n//----------------------------------------------------------------------------------------------\n/** JSON schema object that aligns with the PlatformMenus type */\nexport const menuDocumentSchema = {\n title: 'Platform.Bible menus',\n type: 'object',\n properties: {\n mainMenu: {\n description: 'Top level menu for the application',\n $ref: '#/$defs/multiColumnMenu',\n },\n defaultWebViewTopMenu: {\n description: \"Default top menu for web views that don't specify their own\",\n $ref: '#/$defs/multiColumnMenu',\n },\n defaultWebViewContextMenu: {\n description: \"Default context menu for web views that don't specify their own\",\n $ref: '#/$defs/singleColumnMenu',\n },\n webViewMenus: {\n description: 'Menus that apply per web view in the application',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n $ref: '#/$defs/menusForOneWebView',\n },\n },\n additionalProperties: false,\n },\n },\n required: ['mainMenu', 'defaultWebViewTopMenu', 'defaultWebViewContextMenu', 'webViewMenus'],\n additionalProperties: false,\n $defs: {\n localizeKey: {\n description:\n \"Identifier for a string that will be localized in a menu based on the user's UI language\",\n type: 'string',\n pattern: '^%[\\\\w\\\\-\\\\.]+%$',\n },\n referencedItem: {\n description:\n 'Name of some UI element (i.e., tab, column, group, menu item) or some PAPI object (i.e., command)',\n type: 'string',\n pattern: '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$',\n },\n columnsWithHeaders: {\n description:\n 'Group of columns that can be combined with other columns to form a multi-column menu',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n description: 'Single column with a header string',\n type: 'object',\n properties: {\n label: {\n description: 'Header text for this this column in the UI',\n $ref: '#/$defs/localizeKey',\n },\n localizeNotes: {\n description:\n 'Additional information provided by developers to help people who perform localization',\n type: 'string',\n },\n order: {\n description:\n 'Relative order of this column compared to other columns (sorted ascending)',\n type: 'number',\n },\n isExtensible: {\n description:\n 'Defines whether contributions are allowed to add menu groups to this column',\n type: 'boolean',\n },\n },\n required: ['label', 'order'],\n additionalProperties: false,\n },\n },\n properties: {\n isExtensible: {\n description:\n 'Defines whether contributions are allowed to add columns to this multi-column menu',\n type: 'boolean',\n },\n },\n },\n menuGroups: {\n description:\n 'Group of menu items that can be combined with other groups to form a single menu/submenu. Groups are separated using a line within the menu/submenu.',\n type: 'object',\n patternProperties: {\n '^[\\\\w\\\\-]+\\\\.[\\\\w\\\\-]+$': {\n description: 'Single group that contains menu items',\n type: 'object',\n oneOf: [\n {\n properties: {\n column: {\n description:\n 'Column where this group belongs, not required for single column menus',\n $ref: '#/$defs/referencedItem',\n },\n order: {\n description:\n 'Relative order of this group compared to other groups in the same column or submenu (sorted ascending)',\n type: 'number',\n },\n isExtensible: {\n description:\n 'Defines whether contributions are allowed to add menu items to this menu group',\n type: 'boolean',\n },\n },\n required: ['order'],\n additionalProperties: false,\n },\n {\n properties: {\n menuItem: {\n description: 'Menu item that anchors the submenu where this group belongs',\n $ref: '#/$defs/referencedItem',\n },\n order: {\n description:\n 'Relative order of this group compared to other groups in the same column or submenu (sorted ascending)',\n type: 'number',\n },\n isExtensible: {\n description:\n 'Defines whether contributions are allowed to add menu items to this menu group',\n type: 'boolean',\n },\n },\n required: ['menuItem', 'order'],\n additionalProperties: false,\n },\n ],\n },\n },\n additionalProperties: false,\n },\n menuItem: {\n description:\n 'Single item in a menu that can be clicked on to take an action or can be the parent of a submenu',\n type: 'object',\n oneOf: [\n {\n properties: {\n id: {\n description: 'ID for this menu item that holds a submenu',\n $ref: '#/$defs/referencedItem',\n },\n },\n required: ['id'],\n },\n {\n properties: {\n command: {\n description: 'Name of the PAPI command to run when this menu item is selected.',\n $ref: '#/$defs/referencedItem',\n },\n iconPathBefore: {\n description: 'Path to the icon to display before the menu text',\n type: 'string',\n },\n iconPathAfter: {\n description: 'Path to the icon to display after the menu text',\n type: 'string',\n },\n },\n required: ['command'],\n },\n ],\n properties: {\n label: {\n description: 'Key that represents the text of this menu item to display',\n $ref: '#/$defs/localizeKey',\n },\n tooltip: {\n description:\n 'Key that represents the text to display if a mouse pointer hovers over the menu item',\n $ref: '#/$defs/localizeKey',\n },\n searchTerms: {\n description:\n 'Key that represents additional words the platform should reference when users are searching for menu items',\n $ref: '#/$defs/localizeKey',\n },\n localizeNotes: {\n description:\n 'Additional information provided by developers to help people who perform localization',\n type: 'string',\n },\n group: {\n description: 'Group to which this menu item belongs',\n $ref: '#/$defs/referencedItem',\n },\n order: {\n description:\n 'Relative order of this menu item compared to other menu items in the same group (sorted ascending)',\n type: 'number',\n },\n },\n required: ['label', 'group', 'order'],\n unevaluatedProperties: false,\n },\n groupsAndItems: {\n description: 'Core schema for a column',\n type: 'object',\n properties: {\n groups: {\n description: 'Groups that belong in this menu',\n $ref: '#/$defs/menuGroups',\n },\n items: {\n description: 'List of menu items that belong in this menu',\n type: 'array',\n items: { $ref: '#/$defs/menuItem' },\n uniqueItems: true,\n },\n },\n required: ['groups', 'items'],\n },\n singleColumnMenu: {\n description: 'Menu that contains a column without a header',\n type: 'object',\n allOf: [{ $ref: '#/$defs/groupsAndItems' }],\n unevaluatedProperties: false,\n },\n multiColumnMenu: {\n description: 'Menu that can contain multiple columns with headers',\n type: 'object',\n allOf: [\n { $ref: '#/$defs/groupsAndItems' },\n {\n properties: {\n columns: {\n description: 'Columns that belong in this menu',\n $ref: '#/$defs/columnsWithHeaders',\n },\n },\n required: ['columns'],\n },\n ],\n unevaluatedProperties: false,\n },\n menusForOneWebView: {\n description: 'Set of menus that are associated with a single tab',\n type: 'object',\n properties: {\n includeDefaults: {\n description:\n 'Indicates whether the platform default menus should be included for this webview',\n type: 'boolean',\n },\n topMenu: {\n description: 'Menu that opens when you click on the top left corner of a tab',\n $ref: '#/$defs/multiColumnMenu',\n },\n contextMenu: {\n description: 'Menu that opens when you right click on the main body/area of a tab',\n $ref: '#/$defs/singleColumnMenu',\n },\n },\n additionalProperties: false,\n },\n },\n};\n\nObject.freeze(menuDocumentSchema);\n"],"names":["AsyncVariable","variableName","rejectIfNotSettledWithinMS","__publicField","resolve","reject","value","throwIfAlreadySettled","reason","Collator","locales","options","string1","string2","DateTimeFormat","date","startDate","endDate","PlatformEventEmitter","event","callback","callbackIndex","_a","newGuid","s","isString","o","deepClone","obj","debounce","fn","delay","timeout","args","groupBy","items","keySelector","valueSelector","map","item","key","group","isErrorWithMessage","error","toErrorWithMessage","maybeError","getErrorMessage","wait","ms","waitForDuration","maxWaitTimeInMS","getAllObjectFunctionNames","objId","objectFunctionNames","property","objectPrototype","createSyncProxyForAsyncObject","getObject","objectToProxy","target","prop","DocumentCombiner","baseDocument","documentName","document","previousDocumentVersion","documentToSet","contributions","contributionName","potentialOutput","outputIteration","contribution","mergeObjects","output","finalOutput","areNonArrayObjects","values","allMatch","areArrayObjects","startingPoint","copyFrom","ignoreDuplicateProperties","retVal","mergeObjectsInternal","startingPointObj","copyFromObj","Mutex","AsyncMutex","MutexMap","mutexID","NonValidatingDocumentCombiner","NumberFormat","startRange","endRange","UnsubscriberAsyncList","name","unsubscribers","unsubscriber","unsubs","results","unsubscriberSucceeded","index","P","R","n","m","v","X","C","K","N","B","x","T","O","V","I","L","G","S","H","k","A","y","q","U","f","l","u","c","E","r","D","i","a","h","d","g","w","b","J","charRegex","astralRange","comboMarksRange","comboHalfMarksRange","comboSymbolsRange","comboMarksExtendedRange","comboMarksSupplementRange","comboRange","varRange","familyRange","astral","combo","fitz","modifier","nonAstral","regional","surrogatePair","zwj","blackFlag","family","optModifier","optVar","optJoin","seq","symbol","__importDefault","this","mod","dist","char_regex_1","require$$0","toArray","str","toArray_1","length","match","length_1","substring","begin","end","substring_1","substr","len","strLength","substr_1","limit","padString","padPosition","padRepeats","limit_1","indexOf","searchStr","pos","strArr","searchArr","finded","searchIndex","indexOf_1","at","string","stringLength","charAt","codePointAt","endsWith","searchString","endPosition","lastIndexOfSearchString","lastIndexOf","indexOfClosestClosingCurlyBrace","escaped","closeCurlyBraceIndex","formatReplacementString","replacers","updatedStr","replacerKey","replacerString","includes","position","partialString","stringzIndexOf","validatedPosition","stringzLength","normalize","form","upperCaseForm","ordinalCompare","padEnd","targetLength","stringzLimit","padStart","correctSliceIndex","slice","indexStart","indexEnd","newStart","newEnd","split","separator","splitLimit","result","regexSeparator","matches","currentIndex","matchIndex","matchLength","startsWith","stringzSubstr","stringzSubstring","stringzToArray","isLocalizeKey","escapeStringRegexp","scrBookData","FIRST_SCR_BOOK_NUM","LAST_SCR_BOOK_NUM","FIRST_SCR_CHAPTER_NUM","FIRST_SCR_VERSE_NUM","getChaptersForBook","bookNum","offsetBook","scrRef","offset","offsetChapter","offsetVerse","getLocalizedIdFromBookNumber","bookNumber","localizationLanguage","getLocalizedString","id","Canon","bookName","parts","aggregateUnsubscribers","success","aggregateUnsubscriberAsyncs","unsubPromises","getOwnPropertyNames","getOwnPropertySymbols","hasOwnProperty","combineComparators","comparatorA","comparatorB","state","createIsCircular","areItemsEqual","cache","cachedA","cachedB","getStrictProperties","object","hasOwn","sameValueZeroEqual","OWNER","getOwnPropertyDescriptor","keys","areArraysEqual","areDatesEqual","areMapsEqual","matchedIndices","aIterable","aResult","bResult","bIterable","hasMatch","aKey","aValue","_b","bKey","bValue","areObjectsEqual","properties","areObjectsEqualStrict","descriptorA","descriptorB","arePrimitiveWrappersEqual","areRegExpsEqual","areSetsEqual","areTypedArraysEqual","ARGUMENTS_TAG","BOOLEAN_TAG","DATE_TAG","MAP_TAG","NUMBER_TAG","OBJECT_TAG","REG_EXP_TAG","SET_TAG","STRING_TAG","isArray","isTypedArray","assign","getTag","createEqualityComparator","constructor","tag","createEqualityComparatorConfig","circular","createCustomConfig","strict","config","areArraysEqual$1","areMapsEqual$1","areObjectsEqual$1","areSetsEqual$1","createInternalEqualityComparator","compare","_indexOrKeyA","_indexOrKeyB","_parentA","_parentB","createIsEqual","comparator","createState","equals","meta","deepEqual","createCustomEqual","createCustomInternalComparator","isEqualDeep","isSubset","objectWithAllProperties","objectWithPartialProperties","partialArray","allArray","partialObj","allObj","serialize","replacer","space","replacerValue","newValue","deserialize","reviver","replaceNull","parsedObject","isSerializable","serializedValue","htmlEncode","getCurrentLocale","settingsDefs","removeJsonToTypeScriptTypesStuff","defs","def","projectSettingsDocumentSchema","settingsDocumentSchema","localizedStringsDefs","localizedStringsDocumentSchema","menuDocumentSchema"],"mappings":"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A,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AACF,GAAGC,IAAI;AAAA,EACL;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAGC,KAAI;AAAA,EACL;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,E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","x_google_ignoreList":[11,12,13,17]} \ No newline at end of file diff --git a/lib/platform-bible-utils/package.json b/lib/platform-bible-utils/package.json index b666127b12..83aed1f17f 100644 --- a/lib/platform-bible-utils/package.json +++ b/lib/platform-bible-utils/package.json @@ -25,9 +25,7 @@ "main": "dist/index.cjs", "module": "dist/index.js", "types": "dist/index.d.ts", - "files": [ - "dist" - ], + "files": ["dist"], "scripts": { "start": "vite --host --open", "build:basic": "tsc && vite build && dts-bundle-generator --config ./dts-bundle-generator.config.ts", @@ -49,6 +47,7 @@ "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", "dts-bundle-generator": "^9.3.1", + "escape-string-regexp": "^5.0.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", diff --git a/lib/platform-bible-utils/src/index.ts b/lib/platform-bible-utils/src/index.ts index d347af3df6..6a4278fb93 100644 --- a/lib/platform-bible-utils/src/index.ts +++ b/lib/platform-bible-utils/src/index.ts @@ -42,6 +42,7 @@ export { charAt, codePointAt, endsWith, + escapeStringRegexp, formatReplacementString, includes, indexOf, diff --git a/lib/platform-bible-utils/src/string-util.test.ts b/lib/platform-bible-utils/src/string-util.test.ts index a549b28b67..9dc2b0165e 100644 --- a/lib/platform-bible-utils/src/string-util.test.ts +++ b/lib/platform-bible-utils/src/string-util.test.ts @@ -3,6 +3,7 @@ import { charAt, codePointAt, endsWith, + escapeStringRegexp, formatReplacementString, includes, indexOf, @@ -587,3 +588,10 @@ describe('toArray', () => { expect(result).toEqual(SHORT_SURROGATE_PAIRS_ARRAY); }); }); + +describe('escapeStringRegexp', () => { + test('properly escapes stuff', () => { + const result = escapeStringRegexp('How much $ for a 🦄?'); + expect(result).toEqual('How much \\$ for a 🦄\\?'); + }); +}); diff --git a/lib/platform-bible-utils/src/string-util.ts b/lib/platform-bible-utils/src/string-util.ts index 4470545a86..59392c3a87 100644 --- a/lib/platform-bible-utils/src/string-util.ts +++ b/lib/platform-bible-utils/src/string-util.ts @@ -515,6 +515,34 @@ export function isLocalizeKey(str: string): str is LocalizeKey { return startsWith(str, '%') && endsWith(str, '%'); } +/** + * Escape RegExp special characters. + * + * You can also use this to escape a string that is inserted into the middle of a regex, for + * example, into a character class. + * + * All credit to [`escape-string-regexp`](https://www.npmjs.com/package/escape-string-regexp) - this + * function is simply copied directly from there to allow a common js export + * + * @example + * + * import escapeStringRegexp from 'platform-bible-utils'; + * + * const escapedString = escapeStringRegexp('How much $ for a 🦄?'); + * //=> 'How much \\$ for a 🦄\\?' + * + * new RegExp(escapedString); + */ +export function escapeStringRegexp(string: string): string { + if (typeof string !== 'string') { + throw new TypeError('Expected a string'); + } + + // Escape characters with special meaning either inside or outside character sets. + // Use a simple backslash escape when it’s always valid, and a `\xnn` escape when the simpler form would be disallowed by Unicode patterns’ stricter grammar. + return string.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&').replace(/-/g, '\\x2d'); +} + /** This is an internal-only export for testing purposes and should not be used in development */ export const testingStringUtils = { indexOfClosestClosingCurlyBrace, diff --git a/package-lock.json b/package-lock.json index bb3a755843..9fc9988f4e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -148,9 +148,6 @@ "version": "0.0.1", "hasInstallScript": true, "license": "MIT", - "workspaces": [ - "src/*" - ], "dependencies": { "@sillsdev/scripture": "^1.4.3", "platform-bible-utils": "file:../lib/platform-bible-utils" @@ -395,6 +392,7 @@ "platform-bible-utils": "file:../../../lib/platform-bible-utils" }, "devDependencies": { + "@biblionexus-foundation/scripture-utilities": "^0.0.2", "@swc/core": "^1.4.11", "@types/node": "^20.12.2", "@types/react": "^18.2.73", @@ -735,6 +733,7 @@ "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", "dts-bundle-generator": "^9.3.1", + "escape-string-regexp": "^5.0.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", @@ -764,6 +763,18 @@ "node": ">=14.0.0" } }, + "lib/platform-bible-utils/node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "lib/platform-bible-utils/node_modules/eslint-config-prettier": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", @@ -46224,6 +46235,7 @@ "@typescript-eslint/parser": "^6.21.0", "async-mutex": "^0.4.1", "dts-bundle-generator": "^9.3.1", + "escape-string-regexp": "^5.0.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-prettier": "^5.1.3", @@ -46247,6 +46259,12 @@ "yargs": "^17.6.0" } }, + "escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true + }, "eslint-config-prettier": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz", @@ -46259,6 +46277,7 @@ "platform-scripture": { "version": "file:extensions/src/platform-scripture", "requires": { + "@biblionexus-foundation/scripture-utilities": "^0.0.2", "@sillsdev/scripture": "^1.4.3", "@swc/core": "^1.4.11", "@types/node": "^20.12.2", diff --git a/src/declarations/papi-shared-types.ts b/src/declarations/papi-shared-types.ts index ebfb59c268..5366c64f4a 100644 --- a/src/declarations/papi-shared-types.ts +++ b/src/declarations/papi-shared-types.ts @@ -8,6 +8,7 @@ declare module 'papi-shared-types' { } from '@shared/models/data-provider.model'; import type { MandatoryProjectDataTypes, + PROJECT_INTERFACE_PLATFORM_BASE, WithProjectDataProviderEngineExtensionDataMethods, } from '@shared/models/project-data-provider.model'; import type { IDisposableDataProvider } from '@shared/models/data-provider.interface'; @@ -137,6 +138,13 @@ declare module 'papi-shared-types' { * @example 'English' */ 'platform.language': string; + /** + * Short name of the project (not necessarily unique). This will be displayed directly in the + * UI. + * + * @example 'WEB' + */ + 'platform.name': string; /** * Localized full name of the project. This will be displayed directly in the UI. * @@ -233,33 +241,56 @@ declare module 'papi-shared-types' { * available, a Project Data Provider Factory that supports that project with some set of * `projectInterface`s creates a new instance of a PDP with the supported `projectInterface`s. * - * Every PDP **must** fulfill the requirements of all PDPs according to - * {@link MandatoryProjectDataTypes}. + * Often, these objects are Layering PDPs, meaning they manipulate data provided by Base PDPs + * which actually control the saving and loading of the data. Base PDPs must implement + * {@link IBaseProjectDataProvider}, which imposes additional requirements. + * + * See more information, including the difference between Base and Layering PDPs, at + * {@link ProjectDataProviderInterfaces}. + */ + export type IProjectDataProvider = + IDataProvider; + + /** + * An object on the papi for interacting with that project data. Created by the papi and layers + * over an {@link IBaseProjectDataProviderEngine} provided by an extension. Sometimes returned from + * getting a project data provider with `papi.projectDataProviders.get` (depending on if the PDP + * supports the `platform.base` `projectInterface`). + * + * Project Data Providers are a specialized version of {@link IDataProvider} that work with + * projects by exposing methods according to a set of `projectInterface`s. For each project + * available, a Project Data Provider Factory that supports that project with some set of + * `projectInterface`s creates a new instance of a PDP with the supported `projectInterface`s. + * + * Every Base PDP **must** fulfill the requirements of this interface in order to support the + * methods the PAPI requires for interacting with project data. + * + * See more information, including the difference between Base and Layering PDPs, at + * {@link ProjectDataProviderInterfaces}. */ - export type IProjectDataProvider = IDataProvider< - TProjectDataTypes & MandatoryProjectDataTypes - > & - WithProjectDataProviderEngineSettingMethods & - WithProjectDataProviderEngineExtensionDataMethods & { - /** - * Subscribe to receive updates to the specified project setting. - * - * Note: By default, this `subscribeSetting` function automatically retrieves the current - * project setting value and runs the provided callback as soon as possible. That way, if you - * want to keep your data up-to-date, you do not also have to run `getSetting`. You can turn - * this functionality off in the `options` parameter. - * - * @param key The string id of the project setting for which to listen to changes - * @param callback Function to run with the updated project setting value - * @param options Various options to adjust how the subscriber emits updates - * @returns Unsubscriber to stop listening for updates - */ - subscribeSetting: ( - key: ProjectSettingName, - callback: (value: ProjectSettingTypes[ProjectSettingName]) => void, - options: DataProviderSubscriberOptions, - ) => Promise; - }; + export type IBaseProjectDataProvider = + IProjectDataProvider & + WithProjectDataProviderEngineSettingMethods & + WithProjectDataProviderEngineExtensionDataMethods & { + /** + * Subscribe to receive updates to the specified project setting. + * + * Note: By default, this `subscribeSetting` function automatically retrieves the current + * project setting value and runs the provided callback as soon as possible. That way, if + * you want to keep your data up-to-date, you do not also have to run `getSetting`. You can + * turn this functionality off in the `options` parameter. + * + * @param key The string id of the project setting for which to listen to changes + * @param callback Function to run with the updated project setting value + * @param options Various options to adjust how the subscriber emits updates + * @returns Unsubscriber to stop listening for updates + */ + subscribeSetting: ( + key: ProjectSettingName, + callback: (value: ProjectSettingTypes[ProjectSettingName]) => void, + options: DataProviderSubscriberOptions, + ) => Promise; + }; /** This is just a simple example so we have more than one. It's not intended to be real. */ export type NotesOnlyProjectDataTypes = MandatoryProjectDataTypes & { @@ -272,37 +303,69 @@ declare module 'papi-shared-types' { * their `.d.ts` file and registering a Project Data Provider factory with the corresponding * `projectInterface`. * - * All Project Data Provider Interfaces' data types **must** extend - * {@link MandatoryProjectDataTypes} like the following example. Please see its documentation for - * information on how Project Data Providers can implement this interface. + * There are two types of Project Data Providers (and Project Data Provider Factories that serve + * them): + * + * 1. Base Project Data Provider - provides project data via some `projectInterface`s for its own + * projects with **its own unique project ids**. These PDPs **must support the `platform.base` + * `projectInterface` by implementing {@link IBaseProjectDataProvider}**. More information + * below. + * 2. Layering Project Data Provider - layers over other PDPs and provides additional + * `projectInterface`s for projects on other PDPs. Likely **does not provide its own unique + * project ids** but rather layers over base PDPs' project ids. These PDPs **do not need to + * support the `platform.base` `projectInterface` and should instead implement + * {@link IProjectDataProvider}**. Instead of providing projects themselves, they likely use the + * `ExtensionData` data type exposed via the `platform.base` `projectInterface` on Base PDPs to + * provide additional project data on top of Base PDPs. + * + * All Base Project Data Provider Interfaces' data types **must** implement + * {@link IBaseProjectDataProvider} (which extends {@link MandatoryProjectDataTypes}) like in the + * following example. Please see its documentation for information on how Project Data Providers + * can implement this interface. * * Note: The keys of this interface are the `projectInterface`s for the associated Project Data * Provider Interfaces. `projectInterface`s represent standardized sets of methods on a PDP. * - * WARNING: Each Project Data Provider **must** fulfill certain requirements for its `getSetting`, - * `setSetting`, and `resetSetting` methods. See {@link MandatoryProjectDataTypes} for more - * information. + * WARNING: Each Base Project Data Provider **must** fulfill certain requirements for its + * `getSetting`, `setSetting`, `resetSetting`, `getExtensionData`, and `setExtensionData` methods. + * See {@link IBaseProjectDataProvider} and {@link MandatoryProjectDataTypes} for more information. * * An extension can extend this interface to add types for the Project Data Provider Interfaces * its registered factory provides by adding the following to its `.d.ts` file (in this example, - * we are adding a Project Data Provider interface for the `MyExtensionProjectInterfaceName` - * `projectInterface`): + * we are adding a Base Project Data Provider interface for the `MyExtensionBaseProjectInterface` + * `projectInterface` and a Layering Project Data Provider interface for the + * `MyExtensionLayeringProjectInterface` `projectInterface`): * * @example * * ```typescript * declare module 'papi-shared-types' { - * export type MyProjectDataTypes = MandatoryProjectDataTypes & { + * export type MyBaseProjectDataTypes = { * MyProjectData: DataProviderDataType; * }; * + * export type MyLayeringProjectDataTypes = { + * MyOtherProjectData: DataProviderDataType; + * }; + * * export interface ProjectDataProviderInterfaces { - * MyExtensionProjectInterfaceName: IDataProvider; + * // Note that the base PDP implements `I**Base**ProjectDataProvider` + * MyExtensionBaseProjectInterface: IBaseProjectDataProvider; + * // Note that the layering PDP only implements `IProjectDataProvider` because the base PDP already + * // provides the `platform.base` data types + * MyExtensionLayeringProjectInterface: IProjectDataProvider; * } * } * ``` */ export interface ProjectDataProviderInterfaces { + /** + * Base `projectInterface` that all PDPs that expose their own unique project ids must + * implement. + * + * There should be a PDP that provides `platform.base` for all available project ids. + */ + [PROJECT_INTERFACE_PLATFORM_BASE]: IBaseProjectDataProvider; 'platform.notesOnly': IProjectDataProvider; 'platform.placeholder': IProjectDataProvider; } diff --git a/src/extension-host/data/core-project-settings-info.data.ts b/src/extension-host/data/core-project-settings-info.data.ts index 6a8494e915..f468a70e38 100644 --- a/src/extension-host/data/core-project-settings-info.data.ts +++ b/src/extension-host/data/core-project-settings-info.data.ts @@ -9,6 +9,10 @@ export const platformProjectSettings: ProjectSettingsContribution = { label: '%project_settings_platform_group1_label%', description: '%project_settings_platform_group1_description%', properties: { + 'platform.name': { + label: '%project_settings_platform_name_label%', + default: '%project_name_missing%', + }, 'platform.fullName': { label: '%project_settings_platform_fullName_label%', default: '%project_full_name_missing%', @@ -25,10 +29,13 @@ export const platformProjectSettings: ProjectSettingsContribution = { }, }; -const fullNameValidator: ProjectSettingValidator<'platform.fullName'> = async (newValue) => { +const nonEmptyStringValidator = async (newValue: string) => { return typeof newValue === 'string' && newValue.length > 0; }; +const nameValidator: ProjectSettingValidator<'platform.name'> = nonEmptyStringValidator; +const fullNameValidator: ProjectSettingValidator<'platform.fullName'> = nonEmptyStringValidator; + // TODO: Validate that strings in the array to match BCP 47 values once the i18n code is ready const languageValidator: ProjectSettingValidator<'platform.language'> = async ( newValue: string, @@ -42,6 +49,7 @@ const isEditableValidator: ProjectSettingValidator<'platform.isEditable'> = asyn /** Info about all settings built into core. Does not contain info for extensions' settings */ export const coreProjectSettingsValidators: Partial = { + 'platform.name': nameValidator, 'platform.fullName': fullNameValidator, 'platform.language': languageValidator, 'platform.isEditable': isEditableValidator, diff --git a/src/extension-host/services/papi-backend.service.ts b/src/extension-host/services/papi-backend.service.ts index 3f7017e611..8a2f1aa120 100644 --- a/src/extension-host/services/papi-backend.service.ts +++ b/src/extension-host/services/papi-backend.service.ts @@ -17,6 +17,7 @@ import internetService, { InternetService } from '@shared/services/internet.serv import dataProviderService, { DataProviderService } from '@shared/services/data-provider.service'; import { DataProviderEngine as PapiDataProviderEngine } from '@shared/models/data-provider-engine.model'; import { ProjectDataProviderEngine as PapiProjectDataProviderEngine } from '@shared/models/project-data-provider-engine.model'; +import { BaseProjectDataProviderEngine as PapiBaseProjectDataProviderEngine } from '@shared/models/base-project-data-provider-engine.model'; import { papiBackendProjectDataProviderService, PapiBackendProjectDataProviderService, @@ -51,6 +52,8 @@ const papi = { DataProviderEngine: PapiDataProviderEngine, /** JSDOC DESTINATION ProjectDataProviderEngine */ ProjectDataProviderEngine: PapiProjectDataProviderEngine, + /** JSDOC DESTINATION BaseProjectDataProviderEngine */ + BaseProjectDataProviderEngine: PapiBaseProjectDataProviderEngine, // Functions /** This is just an alias for internet.fetch */ @@ -104,6 +107,9 @@ Object.freeze(papi.DataProviderEngine); /** JSDOC DESTINATION ProjectDataProviderEngine */ export const { ProjectDataProviderEngine } = papi; Object.freeze(papi.ProjectDataProviderEngine); +/** JSDOC DESTINATION BaseProjectDataProviderEngine */ +export const { BaseProjectDataProviderEngine } = papi; +Object.freeze(papi.BaseProjectDataProviderEngine); /** This is just an alias for internet.fetch */ export const { fetch } = papi; Object.freeze(papi.fetch); diff --git a/src/extension-host/services/project-settings.service-host.test.ts b/src/extension-host/services/project-settings.service-host.test.ts index 945833a4d4..c86ee5a9e4 100644 --- a/src/extension-host/services/project-settings.service-host.test.ts +++ b/src/extension-host/services/project-settings.service-host.test.ts @@ -1,5 +1,7 @@ import { testingProjectSettingsService } from '@extension-host/services/project-settings.service-host'; +import { LocalizationSelectors } from '@shared/services/localization.service-model'; import { ProjectSettingValidator } from '@shared/services/project-settings.service-model'; +import { slice } from 'platform-bible-utils'; jest.mock('@shared/services/network.service', () => ({ ...jest.requireActual('@shared/services/network.service'), @@ -30,6 +32,16 @@ jest.mock('@extension-host/data/core-project-settings-info.data', () => ({ }, }, })); +jest.mock('@shared/services/localization.service', () => ({ + __esModule: true, + default: { + async getLocalizedStrings({ localizeKeys: keys }: LocalizationSelectors): Promise<{ + [localizeKey: string]: string; + }> { + return Object.fromEntries(keys.map((key) => [key, slice(key, 1, -1)])); + }, + }, +})); describe('isValid', () => { it('should return true', async () => { @@ -56,7 +68,7 @@ describe('getDefault', () => { it('should get default value', async () => { const projectSettingKey = 'platform.fullName'; const defaultValue = await testingProjectSettingsService.getDefault(projectSettingKey); - expect(defaultValue).toBe('%test_project_full_name_missing%'); + expect(defaultValue).toBe('test_project_full_name_missing'); }); it('should throw if a default is not present', async () => { @@ -64,7 +76,7 @@ describe('getDefault', () => { await expect( // This key does not exist. We are testing what happens // @ts-expect-error - testingProjectSettingsService.getDefault(projectSettingKey, 'ParatextStandard'), + testingProjectSettingsService.getDefault(projectSettingKey), ).rejects.toThrow(new RegExp(`Could not find project setting ${projectSettingKey}`)); }); }); diff --git a/src/extension-host/services/project-settings.service-host.ts b/src/extension-host/services/project-settings.service-host.ts index 9e2a33ed29..0d25ea94cd 100644 --- a/src/extension-host/services/project-settings.service-host.ts +++ b/src/extension-host/services/project-settings.service-host.ts @@ -13,7 +13,7 @@ import { } from '@shared/services/project-settings.service-model'; import { serializeRequestType } from '@shared/utils/util'; import { ProjectSettingNames, ProjectSettingTypes } from 'papi-shared-types'; -import { includes } from 'platform-bible-utils'; +import { includes, isLocalizeKey, isString } from 'platform-bible-utils'; import ProjectSettingsDocumentCombiner from '@shared/utils/project-settings-document-combiner'; /** @@ -92,7 +92,29 @@ async function getDefault( throw new Error(`Could not find default value for project setting ${key}.`); } - return projectSettingInfo.default; + // If this default is not a localized string key, return it. Otherwise we need to get the + // localized version instead + if (!isString(projectSettingInfo.default) || !isLocalizeKey(projectSettingInfo.default)) + return projectSettingInfo.default; + + const localizedProjectSettingInfo = ( + await projectSettingsDocumentCombiner.getLocalizedProjectSettingsContributionInfo() + )?.settings[key]; + + if (!localizedProjectSettingInfo) { + throw new Error(`Could not find localized project setting ${key}.`); + } + + // We shouldn't be able to hit this anymore since the project settings document combiner should + // throw if this ever happened. But this is still here just in case because this would be a + // serious error + if (!('default' in localizedProjectSettingInfo)) { + throw new Error(`Could not find localized default value for project setting ${key}.`); + } + + // This type is correct. Looks like `ReplaceType` breaks mapped types and just unions the types + // eslint-disable-next-line no-type-assertion/no-type-assertion + return localizedProjectSettingInfo.default as ProjectSettingTypes[ProjectSettingName]; } const { registerValidator } = projectSettingsServiceObjectToProxy; diff --git a/src/extension-host/services/settings.service-host.test.ts b/src/extension-host/services/settings.service-host.test.ts index 4f1a7f2f12..f2d1e50c8a 100644 --- a/src/extension-host/services/settings.service-host.test.ts +++ b/src/extension-host/services/settings.service-host.test.ts @@ -1,4 +1,7 @@ import { testingSettingService } from '@extension-host/services/settings.service-host'; +import { LocalizationSelectors } from '@shared/services/localization.service-model'; +import { SettingNames } from 'papi-shared-types'; +import { slice } from 'platform-bible-utils'; const MOCK_SETTINGS_DATA = { 'platform.interfaceLanguage': ['fre'], @@ -32,6 +35,10 @@ jest.mock('@extension-host/data/core-settings-info.data', () => ({ label: '%platform_group1%', description: '%platform_group1_description%', properties: { + 'platform.name': { + label: '%settings_platform_name_label%', + default: '%missing%', + }, 'platform.verseRef': { label: '%settings_platform_verseRef_label%', default: { bookNum: 1, chapterNum: 1, verseNum: 1 }, @@ -53,6 +60,16 @@ jest.mock('@extension-host/data/core-settings-info.data', () => ({ }, }, })); +jest.mock('@shared/services/localization.service', () => ({ + __esModule: true, + default: { + async getLocalizedStrings({ localizeKeys: keys }: LocalizationSelectors): Promise<{ + [localizeKey: string]: string; + }> { + return Object.fromEntries(keys.map((key) => [key, slice(key, 1, -1)])); + }, + }, +})); test('Get verseRef returns default value', async () => { const result = await settingsProviderEngine.get('platform.verseRef'); @@ -64,6 +81,13 @@ test('Get interfaceLanguage returns stored value', async () => { expect(result).toEqual(MOCK_SETTINGS_DATA['platform.interfaceLanguage']); }); +test('Get default localizeKey returns localized string', async () => { + // This is a fake setting + // eslint-disable-next-line no-type-assertion/no-type-assertion + const result = await settingsProviderEngine.get('platform.name' as SettingNames); + expect(result).toEqual('missing'); +}); + test('No setting exists for key', async () => { // settingsTest.noSettingExists does not exist on SettingNames // @ts-expect-error ts(2345) diff --git a/src/extension-host/services/settings.service-host.ts b/src/extension-host/services/settings.service-host.ts index 8beda1ded5..7f96913000 100644 --- a/src/extension-host/services/settings.service-host.ts +++ b/src/extension-host/services/settings.service-host.ts @@ -24,6 +24,8 @@ import { debounce, deserialize, includes, + isLocalizeKey, + isString, serialize, } from 'platform-bible-utils'; import { joinUriPaths } from '@node/utils/util'; @@ -59,9 +61,9 @@ async function writeSettingsDataToFile(settingsData: Partial) { await nodeFS.writeFile(SETTINGS_FILE_URI, JSON.stringify(settingsData)); } -function getDefaultValueForKey( +async function getDefaultValueForKey( key: SettingName, -): SettingTypes[SettingName] { +): Promise { const settingInfo = settingsDocumentCombiner.getSettingsContributionInfo()?.settings[key]; if (!settingInfo) { throw new Error(`No setting exists for key ${key}`); @@ -73,7 +75,34 @@ function getDefaultValueForKey( throw new Error(`No default value specified for key ${key}`); } - return settingInfo.default; + // If this key is the interface language, return it since we need to use that in the localization + // service. Or if default is not a localized string key, return it. Otherwise we need to get the + // localized version instead + if ( + key === 'platform.interfaceLanguage' || + !isString(settingInfo.default) || + !isLocalizeKey(settingInfo.default) + ) + return settingInfo.default; + + const localizedSettingInfo = ( + await settingsDocumentCombiner.getLocalizedSettingsContributionInfo() + )?.settings[key]; + + if (!localizedSettingInfo) { + throw new Error(`Could not find localized setting ${key}.`); + } + + // We shouldn't be able to hit this anymore since the settings document combiner should + // throw if this ever happened. But this is still here just in case because this would be a + // serious error + if (!('default' in localizedSettingInfo)) { + throw new Error(`Could not find localized default value for setting ${key}.`); + } + + // This type is correct. Looks like `ReplaceType` breaks mapped types and just unions the types + // eslint-disable-next-line no-type-assertion/no-type-assertion + return localizedSettingInfo.default as SettingTypes[SettingName]; } async function validateSetting( diff --git a/src/main/main.ts b/src/main/main.ts index bc24796105..273f822aea 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -27,6 +27,8 @@ import { get } from '@shared/services/project-data-provider.service'; import { VerseRef } from '@sillsdev/scripture'; import { startNetworkObjectStatusService } from '@main/services/network-object-status.service-host'; import { DEV_MODE_RENDERER_INDICATOR } from '@shared/data/platform.data'; +import { startProjectLookupService } from '@main/services/project-lookup.service-host'; +import { PROJECT_INTERFACE_PLATFORM_BASE } from '@shared/models/project-data-provider.model'; const PROCESS_CLOSE_TIME_OUT = 2000; @@ -37,6 +39,9 @@ async function main() { // The network object status service relies on seeing everything else start up later await startNetworkObjectStatusService(); + // The project lookup service relies on the network object status service + await startProjectLookupService(); + // The .NET data provider relies on the network service and nothing else dotnetDataProvider.start(); @@ -339,17 +344,20 @@ async function main() { // Get a data provider and do something with it setTimeout(async () => { - const paratextPdp = await get<'ParatextStandard'>( - 'ParatextStandard', + const usxPdp = await get( + 'platformScripture.USX_Chapter', '32664dc3288a28df2e2bb75ded887fc8f17a15fb', ); - const verse = await paratextPdp.getChapterUSX(new VerseRef('JHN', '1', '1')); + const verse = await usxPdp.getChapterUSX(new VerseRef('JHN', '1', '1')); logger.info(`Got PDP data: ${verse}`); - if (verse !== undefined) - await paratextPdp.setChapterUSX(new VerseRef('JHN', '1', '1'), verse); + if (verse !== undefined) await usxPdp.setChapterUSX(new VerseRef('JHN', '1', '1'), verse); - paratextPdp.setExtensionData( + const basePdp = await get( + PROJECT_INTERFACE_PLATFORM_BASE, + '32664dc3288a28df2e2bb75ded887fc8f17a15fb', + ); + basePdp.setExtensionData( { extensionName: 'foo', dataQualifier: 'fooData' }, 'This is the data from extension foo', ); diff --git a/src/main/services/project-lookup.service-host.ts b/src/main/services/project-lookup.service-host.ts new file mode 100644 index 0000000000..2cb5735277 --- /dev/null +++ b/src/main/services/project-lookup.service-host.ts @@ -0,0 +1,26 @@ +import { + NETWORK_OBJECT_NAME_PROJECT_LOOKUP_SERVICE, + ProjectLookupServiceType, + projectLookupServiceBase, +} from '@shared/models/project-lookup.service-model'; +import networkObjectService from '@shared/services/network-object.service'; + +const projectLookupService = projectLookupServiceBase; + +/** + * Register the network object that mirrors the locally-run project lookup service exposed on the + * PAPI websocket. + * + * This service runs fully locally from `project-lookup.service.ts`. This is here to provide + * lookup-related services to other processes on the PAPI websocket + */ +// To use this service, you should use `project-lookup.service.ts`. +// This is not representative of this file. Maybe there will be a default export later that is more +// representative of the file +// eslint-disable-next-line import/prefer-default-export +export async function startProjectLookupService(): Promise { + await networkObjectService.set( + NETWORK_OBJECT_NAME_PROJECT_LOOKUP_SERVICE, + projectLookupService, + ); +} diff --git a/src/main/services/server-network-connector.service.ts b/src/main/services/server-network-connector.service.ts index e66c5125d2..80d64e4a8a 100644 --- a/src/main/services/server-network-connector.service.ts +++ b/src/main/services/server-network-connector.service.ts @@ -1,6 +1,7 @@ import { CloseEvent, MessageEvent, WebSocket, WebSocketServer } from 'ws'; import { CLIENT_ID_SERVER, + CLIENT_ID_UNKNOWN, ConnectionStatus, InternalEvent, InternalNetworkEventHandler, @@ -19,6 +20,7 @@ import { serialize, newGuid, PlatformEventEmitter, + wait, } from 'platform-bible-utils'; import { ClientConnect, @@ -508,9 +510,31 @@ export default class ServerNetworkConnector implements INetworkConnector { throw new Error(`Received a request but cannot route it without a requestRouter`); // Figure out if we can handle this request or if we need to send it - const responderId = this.requestRouter(requestMessage.requestType); - if (this.connectorInfo.clientId === responderId) { - // This request is ours. Handle the request + let responderId = CLIENT_ID_UNKNOWN; + + // https://github.com/paranext/paranext-core/issues/51 + // If the request type doesn't have a registered handler yet, retry a few times to help with race + // conditions. This approach is hacky but works well enough for now. + const maxAttempts = 10; + const networkWaitTimeMs = 1000; + for (let attemptsRemaining = maxAttempts; attemptsRemaining > 0; attemptsRemaining--) { + responderId = this.requestRouter(requestMessage.requestType); + + if (responderId !== CLIENT_ID_UNKNOWN) break; + + logger.debug( + `Server network connector could not route client ${requestMessage.senderId}'s request ${requestMessage.requestId} of type ${requestMessage.requestType} on attempt ${maxAttempts - attemptsRemaining + 1} of ${maxAttempts}.${attemptsRemaining === 1 ? '' : ' Retrying...'}`, + ); + + // No need to wait again after the last attempt fails + if (attemptsRemaining === 1) break; + + // eslint-disable-next-line no-await-in-loop + await wait(networkWaitTimeMs); + } + + if (this.connectorInfo.clientId === responderId || responderId === CLIENT_ID_UNKNOWN) { + // This request is ours or is unknown. Handle the request if (!this.localRequestHandler) throw new Error('Handling request without a requestHandler!'); // Run the request handler for this request diff --git a/src/renderer/components/dialogs/select-multiple-projects-dialog.component.tsx b/src/renderer/components/dialogs/select-multiple-projects-dialog.component.tsx index 4d75b63e4e..641f406c87 100644 --- a/src/renderer/components/dialogs/select-multiple-projects-dialog.component.tsx +++ b/src/renderer/components/dialogs/select-multiple-projects-dialog.component.tsx @@ -2,11 +2,11 @@ import { ListItemIcon } from '@mui/material'; import { useCallback, useMemo, useState } from 'react'; import FolderOpenIcon from '@mui/icons-material/FolderOpen'; import DoneIcon from '@mui/icons-material/Done'; -import ProjectList from '@renderer/components/projects/project-list.component'; +import ProjectList, { + ProjectMetadataDisplay, +} from '@renderer/components/projects/project-list.component'; import './select-multiple-projects-dialog.component.scss'; -import projectLookupService, { - filterProjectsMetadata, -} from '@shared/services/project-lookup.service'; +import projectLookupService from '@shared/services/project-lookup.service'; import { Button, usePromise } from 'platform-bible-react'; import DIALOG_BASE from '@renderer/components/dialogs/dialog-base.data'; import { @@ -14,6 +14,8 @@ import { DialogTypes, SELECT_MULTIPLE_PROJECTS_DIALOG_TYPE, } from '@renderer/components/dialogs/dialog-definition.model'; +import { papiFrontendProjectDataProviderService } from '@shared/services/project-data-provider.service'; +import { PROJECT_INTERFACE_PLATFORM_BASE } from '@shared/models/project-data-provider.model'; function SelectMultipleProjectsDialog({ prompt, @@ -22,18 +24,43 @@ function SelectMultipleProjectsDialog({ includeProjectIds, includeProjectInterfaces, excludeProjectInterfaces, + includePdpFactoryIds, + excludePdpFactoryIds, selectedProjectIds: initialSelectedProjectIds, }: DialogTypes[typeof SELECT_MULTIPLE_PROJECTS_DIALOG_TYPE]['props']) { const [projects, isLoadingProjects] = usePromise( useCallback(async () => { - const allProjectsMetadata = await projectLookupService.getMetadataForAllProjects(); - return filterProjectsMetadata(allProjectsMetadata, { + const projectsMetadata = await projectLookupService.getMetadataForAllProjects({ excludeProjectIds, includeProjectIds, includeProjectInterfaces, excludeProjectInterfaces, + includePdpFactoryIds, + excludePdpFactoryIds, }); - }, [excludeProjectIds, includeProjectIds, includeProjectInterfaces, excludeProjectInterfaces]), + + // Get project names + const projectsMetadataDisplay: ProjectMetadataDisplay[] = await Promise.all( + projectsMetadata.map(async (projectMetadata) => { + const pdp = await papiFrontendProjectDataProviderService.get( + PROJECT_INTERFACE_PLATFORM_BASE, + projectMetadata.id, + ); + + const name = await pdp.getSetting('platform.name'); + + return { ...projectMetadata, name }; + }), + ); + return projectsMetadataDisplay; + }, [ + excludeProjectIds, + includeProjectIds, + includeProjectInterfaces, + excludeProjectInterfaces, + includePdpFactoryIds, + excludePdpFactoryIds, + ]), useMemo(() => [], []), ); diff --git a/src/renderer/components/dialogs/select-project.dialog.tsx b/src/renderer/components/dialogs/select-project.dialog.tsx index 663dc6d418..ad667396d4 100644 --- a/src/renderer/components/dialogs/select-project.dialog.tsx +++ b/src/renderer/components/dialogs/select-project.dialog.tsx @@ -2,17 +2,19 @@ import { ListItemIcon } from '@mui/material'; import FolderOpenIcon from '@mui/icons-material/FolderOpen'; import './select-project.dialog.scss'; import { useCallback, useMemo } from 'react'; -import ProjectList from '@renderer/components/projects/project-list.component'; +import ProjectList, { + ProjectMetadataDisplay, +} from '@renderer/components/projects/project-list.component'; import { usePromise } from 'platform-bible-react'; -import projectLookupService, { - filterProjectsMetadata, -} from '@shared/services/project-lookup.service'; +import projectLookupService from '@shared/services/project-lookup.service'; import DIALOG_BASE from '@renderer/components/dialogs/dialog-base.data'; import { DialogDefinition, DialogTypes, SELECT_PROJECT_DIALOG_TYPE, } from '@renderer/components/dialogs/dialog-definition.model'; +import { papiFrontendProjectDataProviderService } from '@shared/services/project-data-provider.service'; +import { PROJECT_INTERFACE_PLATFORM_BASE } from '@shared/models/project-data-provider.model'; function SelectProjectDialog({ prompt, @@ -21,17 +23,42 @@ function SelectProjectDialog({ includeProjectIds, includeProjectInterfaces, excludeProjectInterfaces, + includePdpFactoryIds, + excludePdpFactoryIds, }: DialogTypes[typeof SELECT_PROJECT_DIALOG_TYPE]['props']) { const [projects, isLoadingProjects] = usePromise( useCallback(async () => { - const allProjectsMetadata = await projectLookupService.getMetadataForAllProjects(); - return filterProjectsMetadata(allProjectsMetadata, { + const projectsMetadata = await projectLookupService.getMetadataForAllProjects({ excludeProjectIds, includeProjectIds, includeProjectInterfaces, excludeProjectInterfaces, + includePdpFactoryIds, + excludePdpFactoryIds, }); - }, [excludeProjectIds, includeProjectIds, includeProjectInterfaces, excludeProjectInterfaces]), + + // Get project names + const projectsMetadataDisplay: ProjectMetadataDisplay[] = await Promise.all( + projectsMetadata.map(async (projectMetadata) => { + const pdp = await papiFrontendProjectDataProviderService.get( + PROJECT_INTERFACE_PLATFORM_BASE, + projectMetadata.id, + ); + + const name = await pdp.getSetting('platform.name'); + + return { ...projectMetadata, name }; + }), + ); + return projectsMetadataDisplay; + }, [ + excludeProjectIds, + includeProjectIds, + includeProjectInterfaces, + excludeProjectInterfaces, + includePdpFactoryIds, + excludePdpFactoryIds, + ]), useMemo(() => [], []), ); diff --git a/src/renderer/components/projects/project-list.component.tsx b/src/renderer/components/projects/project-list.component.tsx index 1e05245b02..d94978a620 100644 --- a/src/renderer/components/projects/project-list.component.tsx +++ b/src/renderer/components/projects/project-list.component.tsx @@ -4,9 +4,18 @@ import { Checkbox } from 'platform-bible-react'; import { ProjectInterfaces } from 'papi-shared-types'; import { PropsWithChildren, useCallback, JSX } from 'react'; -export type Project = ProjectMetadata & { - id: string; +/** Project metadata and some display information */ +export type ProjectMetadataDisplay = ProjectMetadata & { + /** + * Name to display in the list for this project + * + * Generally this should come from the project setting `platform.name` + */ name: string; +}; + +export type Project = ProjectMetadataDisplay & { + id: string; description: string; isDownloadable: boolean; isDownloaded: boolean; @@ -99,7 +108,7 @@ export function fetchProjects(): Project[] { export type ProjectListProps = PropsWithChildren<{ /** Projects to display in the list */ - projects: ProjectMetadata[]; + projects: ProjectMetadataDisplay[]; /** Handler to perform an action when the project is clicked */ handleSelectProject: (projectId: string) => void; @@ -137,7 +146,7 @@ export default function ProjectList({ children, }: ProjectListProps) { const isSelected = useCallback( - (project: ProjectMetadata) => { + (project: ProjectMetadataDisplay) => { if (isMultiselect && selectedProjectIds) { return selectedProjectIds.includes(project.id); } @@ -146,7 +155,7 @@ export default function ProjectList({ [isMultiselect, selectedProjectIds], ); - const createListItemContents = (project: ProjectMetadata): JSX.Element => { + const createListItemContents = (project: ProjectMetadataDisplay): JSX.Element => { return ( { diff --git a/src/renderer/hooks/papi-hooks/use-project-data.hook.ts b/src/renderer/hooks/papi-hooks/use-project-data.hook.ts index 1725580f7a..c5ed872407 100644 --- a/src/renderer/hooks/papi-hooks/use-project-data.hook.ts +++ b/src/renderer/hooks/papi-hooks/use-project-data.hook.ts @@ -14,7 +14,8 @@ import { /** * React hook to use data from a Project Data Provider * - * @example `useProjectData('ParatextStandard', 'project id').VerseUSFM(...);` + * @example `useProjectData('platformScripture.USFM_BookChapterVerse', 'project + * id').VerseUSFM(...);` */ type UseProjectDataHook = { ( @@ -74,12 +75,13 @@ type UseProjectDataHook = { * Provider with `useProjectData('', '').` and use like any * other React hook. * - * _@example_ Subscribing to Verse USFM info at JHN 11:35 on a `ParatextStandard` project with - * projectId `32664dc3288a28df2e2bb75ded887fc8f17a15fb`: + * _@example_ Subscribing to Verse USFM info at JHN 11:35 on a + * `platformScripture.USFM_BookChapterVerse` project with projectId + * `32664dc3288a28df2e2bb75ded887fc8f17a15fb`: * * ```typescript * const [verse, setVerse, verseIsLoading] = useProjectData( - * 'ParatextStandard', + * 'platformScripture.USFM_BookChapterVerse', * '32664dc3288a28df2e2bb75ded887fc8f17a15fb', * ).VerseUSFM( * useMemo(() => new VerseRef('JHN', '11', '35', ScrVers.English), []), diff --git a/src/renderer/hooks/papi-hooks/use-project-setting.hook.ts b/src/renderer/hooks/papi-hooks/use-project-setting.hook.ts index 3fa8c52090..eb2863b7cd 100644 --- a/src/renderer/hooks/papi-hooks/use-project-setting.hook.ts +++ b/src/renderer/hooks/papi-hooks/use-project-setting.hook.ts @@ -1,10 +1,10 @@ import { useMemo } from 'react'; import { + IBaseProjectDataProvider, ProjectDataProviderInterfaces, ProjectInterfaceDataTypes, ProjectSettingNames, ProjectSettingTypes, - ProjectInterfaces, } from 'papi-shared-types'; import useProjectData from '@renderer/hooks/papi-hooks/use-project-data.hook'; import { @@ -12,20 +12,21 @@ import { DataProviderUpdateInstructions, } from '@shared/models/data-provider.model'; import ExtractDataProviderDataTypes from '@shared/models/extract-data-provider-data-types.model'; -import useProjectDataProvider from './use-project-data-provider.hook'; +import useProjectDataProvider from '@renderer/hooks/papi-hooks/use-project-data-provider.hook'; +import { PROJECT_INTERFACE_PLATFORM_BASE } from '@shared/models/project-data-provider.model'; /** * Gets, sets and resets a project setting on the papi for a specified project. Also notifies * subscribers when the project setting changes and gets updated when the project setting is changed * by others. * - * @param projectInterface `projectInterface` that the project to load must support. The TypeScript - * type for the returned project data provider will have the project data provider interface type - * associated with this `projectInterface`. If the project does not implement this - * `projectInterface` (according to its metadata), an error will be thrown. * @param projectDataProviderSource `projectDataProviderSource` String name of the id of the project * to get OR projectDataProvider (result of `useProjectDataProvider` if you want to consolidate - * and only get the Project Data Provider once) + * and only get the Project Data Provider once). If you provide a project id, this hook will use a + * PDP for this project that supports the `platform.base` `projectInterface`. + * + * Note: If you provide a projectDataProvider directly, it must be an + * {@link IBaseProjectDataProvider} * @param key The string id of the project setting to interact with * * WARNING: MUST BE STABLE - const or wrapped in useState, useMemo, etc. The reference must not be @@ -52,12 +53,10 @@ import useProjectDataProvider from './use-project-data-provider.hook'; * @throws When subscription callback function is called with an update that has an unexpected * message type */ -const useProjectSetting = < - ProjectInterface extends ProjectInterfaces, - ProjectSettingName extends ProjectSettingNames, ->( - projectInterface: ProjectInterface, - projectDataProviderSource: string | ProjectDataProviderInterfaces[ProjectInterface] | undefined, +const useProjectSetting = ( + // Any Base PDP type works. Without `any`, the DataProviderUpdateInstructions types are incompatible + // eslint-disable-next-line @typescript-eslint/no-explicit-any + projectDataProviderSource: string | IBaseProjectDataProvider | undefined, key: ProjectSettingName, defaultValue: ProjectSettingTypes[ProjectSettingName], subscriberOptions?: DataProviderSubscriberOptions, @@ -67,14 +66,20 @@ const useProjectSetting = < resetSetting: (() => void) | undefined, isLoading: boolean, ] => { - const projectDataProvider = useProjectDataProvider(projectInterface, projectDataProviderSource); + const projectDataProvider = useProjectDataProvider( + PROJECT_INTERFACE_PLATFORM_BASE, + // Reduce the type to just the important part. Now that we have `any` here, the DataProviderUpdateInstructions + // are again not compatible without type asserting + // eslint-disable-next-line no-type-assertion/no-type-assertion + projectDataProviderSource as ProjectDataProviderInterfaces[typeof PROJECT_INTERFACE_PLATFORM_BASE], + ); // Unfortunately `Setting` isn't an actual `DataProviderDataType` on Project Data Providers. They // instead manually implement on `IProjectDataProvider` with a generic `ProjectSettingName`. // We must type assert to use it because `useProjectData` only sees actual `DataProviderDataType`s /* eslint-disable no-type-assertion/no-type-assertion */ const [setting, setSetting, isLoading] = ( - useProjectData(projectInterface, projectDataProvider) as { + useProjectData(PROJECT_INTERFACE_PLATFORM_BASE, projectDataProvider) as unknown as { Setting: ( selector: ProjectSettingName, defaultValue: ProjectSettingTypes[ProjectSettingName], @@ -86,7 +91,9 @@ const useProjectSetting = < newData: ProjectSettingTypes[ProjectSettingName], ) => Promise< DataProviderUpdateInstructions< - ExtractDataProviderDataTypes + ExtractDataProviderDataTypes< + ProjectInterfaceDataTypes[typeof PROJECT_INTERFACE_PLATFORM_BASE] + > > >) | undefined, diff --git a/src/shared/data/internal-connection.model.ts b/src/shared/data/internal-connection.model.ts index c22d1cf487..103ade91cd 100644 --- a/src/shared/data/internal-connection.model.ts +++ b/src/shared/data/internal-connection.model.ts @@ -5,6 +5,13 @@ import { ComplexRequest, ComplexResponse, SerializedRequestType } from '@shared/utils/util'; +/** + * Represents when the request router does not know to which client id the request belongs. Server + * should try to determine the correct client id through other means, and client should just send to + * server + */ +export const CLIENT_ID_UNKNOWN = -2; + /** Represents when the client id has not been assigned by the server */ export const CLIENT_ID_UNASSIGNED = -1; diff --git a/src/shared/models/base-project-data-provider-engine.model.ts b/src/shared/models/base-project-data-provider-engine.model.ts new file mode 100644 index 0000000000..4e314c363d --- /dev/null +++ b/src/shared/models/base-project-data-provider-engine.model.ts @@ -0,0 +1,105 @@ +import { + ProjectInterfaces, + ProjectInterfaceDataTypes, + WithProjectDataProviderEngineSettingMethods, + ProjectSettingNames, + ProjectSettingTypes, +} from 'papi-shared-types'; +import { + PROJECT_INTERFACE_PLATFORM_BASE, + WithProjectDataProviderEngineExtensionDataMethods, +} from '@shared/models/project-data-provider.model'; +import { DataProviderDataType } from '@shared/models/data-provider.model'; +import { UnionToIntersection } from 'platform-bible-utils'; +import { + IProjectDataProviderEngine, + ProjectDataProviderEngine, +} from '@shared/models/project-data-provider-engine.model'; + +/** + * The object to return from + * {@link IProjectDataProviderEngineFactory.createProjectDataProviderEngine} that the PAPI registers + * to create a Base Project Data Provider for a specific project. The ProjectDataProviderService + * creates an {@link IBaseProjectDataProvider} on the papi that layers over this engine, providing + * special functionality. + * + * See {@link DataProviderDataTypes} for information on how to make powerful types that work well + * with Intellisense. + * + * Note: papi creates a `notifyUpdate` function on the Project Data Provider Engine if one is not + * provided, so it is not necessary to provide one in order to call `this.notifyUpdate`. However, + * TypeScript does not understand that papi will create one as you are writing your Base Project + * Data Provider Engine, so you can avoid type errors with one of the following options: + * + * 1. If you are using a class to create a Base Project Data Provider Engine, you can extend the + * {@link BaseProjectDataProviderEngine} class, and it will provide `notifyUpdate` as well as + * inform Intellisense that you can run `notifyUpdate` with the `Setting` data type for you: + * + * ```typescript + * class MyPDPE extends BaseProjectDataProviderEngine<['MyProjectData']> implements IBaseProjectDataProviderEngine<['MyProjectData']> { + * ... + * } + * ``` + * + * 2. If you are using an object or class not extending {@link BaseProjectDataProviderEngine} to create + * a Base Project Data Provider Engine, you can add a `notifyUpdate` function (and, with an + * object, add the {@link WithNotifyUpdate} type) to your Base Project Data Provider Engine like + * so: + * + * ```typescript + * const myPDPE: IBaseProjectDataProviderEngine<['MyProjectData']> & WithNotifyUpdate = { + * notifyUpdate(updateInstructions) {}, + * ... + * } + * ``` + * + * OR + * + * ```typescript + * class MyPDPE implements IBaseProjectDataProviderEngine<['MyProjectData']> { + * notifyUpdate(updateInstructions?: DataProviderEngineNotifyUpdate) {} + * ... + * } + * ``` + */ +export type IBaseProjectDataProviderEngine = + IProjectDataProviderEngine< + [typeof PROJECT_INTERFACE_PLATFORM_BASE, ...SupportedProjectInterfaces] + > & + WithProjectDataProviderEngineSettingMethods< + UnionToIntersection & {} + > & + WithProjectDataProviderEngineExtensionDataMethods< + UnionToIntersection & {} + >; + +/** + * JSDOC SOURCE BaseProjectDataProviderEngine + * + * Abstract class that provides a placeholder `notifyUpdate` for Base Project Data Provider Engine + * classes. If a Base Project Data Provider Engine class extends this class, it doesn't have to + * specify its own `notifyUpdate` function in order to use `notifyUpdate`. + * + * Additionally, extending this class informs Intellisense that you can run `notifyUpdate` with the + * `Setting` data type if needed like so: + * + * ```typescript + * this.notifyUpdate('Setting'); + * ``` + * + * @see {@link IBaseProjectDataProviderEngine} for more information on extending this class. + */ +export abstract class BaseProjectDataProviderEngine< + SupportedProjectInterfaces extends ProjectInterfaces[], +> extends ProjectDataProviderEngine< + [typeof PROJECT_INTERFACE_PLATFORM_BASE, ...SupportedProjectInterfaces], + { + // Including `Setting` here so we can emit `Setting` events though the event types are not + // tight enough to use on the actual `Setting` data type and methods + Setting: DataProviderDataType< + ProjectSettingNames, + ProjectSettingTypes[ProjectSettingNames], + ProjectSettingTypes[ProjectSettingNames] + >; + } +> {} diff --git a/src/shared/models/project-data-provider-engine.model.ts b/src/shared/models/project-data-provider-engine.model.ts index 4d3c5c1109..deeee9bc9d 100644 --- a/src/shared/models/project-data-provider-engine.model.ts +++ b/src/shared/models/project-data-provider-engine.model.ts @@ -1,16 +1,6 @@ -import { - ProjectInterfaces, - ProjectInterfaceDataTypes, - WithProjectDataProviderEngineSettingMethods, - ProjectSettingNames, - ProjectSettingTypes, -} from 'papi-shared-types'; -import { - MandatoryProjectDataTypes, - WithProjectDataProviderEngineExtensionDataMethods, -} from '@shared/models/project-data-provider.model'; +import { ProjectInterfaces, ProjectInterfaceDataTypes } from 'papi-shared-types'; import IDataProviderEngine, { DataProviderEngine } from '@shared/models/data-provider-engine.model'; -import { DataProviderDataType } from '@shared/models/data-provider.model'; +import { DataProviderDataTypes } from '@shared/models/data-provider.model'; import { ProjectMetadataWithoutFactoryInfo } from '@shared/models/project-metadata.model'; import { UnionToIntersection } from 'platform-bible-utils'; @@ -24,9 +14,14 @@ import { UnionToIntersection } from 'platform-bible-utils'; * {@link IProjectDataProvider}s. * * Project Data Provider Engine Factories create Project Data Provider Engines for specific - * `projectInterface`s. For each project available, a Project Data Provider Factory that supports - * that project with some set of `projectInterface`s creates a new instance of a PDP with the - * supported `projectInterface`s. + * `projectInterface`s. For each project id available on a Project Data Provider Factory, the + * factory that supports that project with some set of `projectInterface`s creates a new instance of + * a PDP with the supported `projectInterface`s. + * + * A PDP Factory can provide its own unique project ids (Base PDP Factory) or layer over other PDPFs + * and provide additional `projectInterface`s on those projects (Layering PDP Factory). Base PDP + * Factories must create PDPs that support the `platform.base` `projectInterface`. See + * {@link IBaseProjectDataProvider} and {@link ProjectDataProviderInterfaces} for more information. */ export interface IProjectDataProviderEngineFactory< SupportedProjectInterfaces extends ProjectInterfaces[], @@ -97,17 +92,8 @@ export interface IProjectDataProviderEngineFactory< */ export type IProjectDataProviderEngine = IDataProviderEngine< - UnionToIntersection & - MandatoryProjectDataTypes - > & - WithProjectDataProviderEngineSettingMethods< - // @ts-ignore TypeScript thinks there is some unknown data type getting in, but there is not - UnionToIntersection - > & - WithProjectDataProviderEngineExtensionDataMethods< - // @ts-ignore TypeScript thinks there is some unknown data type getting in, but there is not - UnionToIntersection - >; + UnionToIntersection & {} + >; /** * JSDOC SOURCE ProjectDataProviderEngine @@ -127,14 +113,8 @@ export type IProjectDataProviderEngine extends DataProviderEngine< - UnionToIntersection & { - // Including `Setting` here so we can emit `Setting` events though the event types are not - // tight enough to use on the actual `Setting` data type and methods - Setting: DataProviderDataType< - ProjectSettingNames, - ProjectSettingTypes[ProjectSettingNames], - ProjectSettingTypes[ProjectSettingNames] - >; - } + UnionToIntersection & + AdditionalDataTypes > {} diff --git a/src/shared/models/project-data-provider-factory.interface.ts b/src/shared/models/project-data-provider-factory.interface.ts index 74a704df45..2eba22555c 100644 --- a/src/shared/models/project-data-provider-factory.interface.ts +++ b/src/shared/models/project-data-provider-factory.interface.ts @@ -9,7 +9,10 @@ export const PDP_FACTORY_OBJECT_TYPE: string = 'pdpFactory'; * TypeScript-extension-provided {@link IProjectDataProviderEngineFactory} or are created by * independent processes on the `papi`. * - * See {@link IProjectDataProvider} for more information. + * A PDP Factory can provide its own unique project ids (Base PDP Factory) or layer over other PDPFs + * and provide additional `projectInterface`s on those projects (Layering PDP Factory). Base PDP + * Factories must create PDPs that support the `platform.base` `projectInterface`. See + * {@link IBaseProjectDataProvider} and {@link ProjectDataProviderInterfaces} for more information. */ interface IProjectDataProviderFactory extends Dispose { /** Get data about all projects that can be created by this PDP factory */ diff --git a/src/shared/models/project-data-provider.model.ts b/src/shared/models/project-data-provider.model.ts index 8157434454..956efc7bff 100644 --- a/src/shared/models/project-data-provider.model.ts +++ b/src/shared/models/project-data-provider.model.ts @@ -4,6 +4,12 @@ import type { DataProviderUpdateInstructions, } from '@shared/models/data-provider.model'; +/** + * The name of the `projectInterface` representing the essential methods every Base Project Data + * Provider must implement + */ +export const PROJECT_INTERFACE_PLATFORM_BASE = 'platform.base'; + /** Indicates to a PDP what extension data is being referenced */ export type ExtensionDataScope = { /** Name of an extension as provided in its manifest */ diff --git a/src/shared/models/project-lookup.service-model.ts b/src/shared/models/project-lookup.service-model.ts index 1951c89109..dff240427d 100644 --- a/src/shared/models/project-lookup.service-model.ts +++ b/src/shared/models/project-lookup.service-model.ts @@ -1,6 +1,57 @@ +import networkObjectService from '@shared/services/network-object.service'; +import { + ProjectDataProviderFactoryMetadataInfo, + ProjectMetadata, +} from '@shared/models/project-metadata.model'; +import logger from '@shared/services/logger.service'; +import networkObjectStatusService from '@shared/services/network-object-status.service'; import { ProjectInterfaces } from 'papi-shared-types'; -import { ProjectMetadata } from '@shared/models/project-metadata.model'; -import { ModifierProject } from 'platform-bible-utils'; +import IProjectDataProviderFactory, { + PDP_FACTORY_OBJECT_TYPE, +} from '@shared/models/project-data-provider-factory.interface'; +import { + deepClone, + endsWith, + escapeStringRegexp, + ModifierProject, + slice, +} from 'platform-bible-utils'; + +export const NETWORK_OBJECT_NAME_PROJECT_LOOKUP_SERVICE = 'ProjectLookupService'; + +// #region Project data provider factory utilities + +/** + * Suffix on network objects that indicates that the network object is a project data provider + * factory + */ +const PDP_FACTORY_LABEL = '-pdpf'; + +/** + * Transform the well-known pdp factory id into an id for its network object to use + * + * @param pdpFactoryId Id extensions use to identify this pdp factory + * @returns Id for then network object for this pdp factory + */ +export function getPDPFactoryNetworkObjectNameFromId(pdpFactoryId: string) { + return endsWith(pdpFactoryId, PDP_FACTORY_LABEL) + ? pdpFactoryId + : `${pdpFactoryId}${PDP_FACTORY_LABEL}`; +} + +/** + * Transform a network object id for a pdp factory into its well-known pdp factory id + * + * @param pdpFactoryNetworkObjectName Id for then network object for this pdp factory + * @returns Id extensions use to identify this pdp factory + */ +export function getPDPFactoryIdFromNetworkObjectName(pdpFactoryNetworkObjectName: string) { + return endsWith(pdpFactoryNetworkObjectName, PDP_FACTORY_LABEL) + ? slice(pdpFactoryNetworkObjectName, 0, -PDP_FACTORY_LABEL.length) + : pdpFactoryNetworkObjectName; +} + +// #endregion export type ProjectMetadataFilterOptions = ModifierProject & { /** Project IDs to include */ @@ -9,12 +60,18 @@ export type ProjectMetadataFilterOptions = ModifierProject & { excludeProjectIds?: string | string[]; }; +// #region Project lookup service + /** * JSDOC SOURCE projectLookupService * * Provides metadata for projects known by the platform + * + * Note: this service runs locally everywhere in the TypeScript processes. It is also exposed on the + * PAPI websocket. Note these functions are all asynchronous on the PAPI websocket regardless of if + * their types are synchronous locally. */ -export interface ProjectLookupServiceType { +export type ProjectLookupServiceType = { /** * Provide metadata for all projects that have PDP factories * @@ -24,10 +81,13 @@ export interface ProjectLookupServiceType { * no id is specified) for the project will be returned. If you need `projectInterface`s supported * by specific PDP Factories, you can access it at {@link ProjectMetadata.pdpFactoryInfo}. * + * @param options Options for specifying filters for the project metadata retrieved. If a PDP + * Factory Id does not match the filter, it will not be contacted at all for this function call. + * As a result, a PDP factory that intends to layer over other PDP factories **must** specify + * its id in `options.excludePdpFactoryIds` to avoid an infinite loop of calling this function. * @returns ProjectMetadata for all projects stored on the local system */ - getMetadataForAllProjects: () => Promise; - + getMetadataForAllProjects(options?: ProjectMetadataFilterOptions): Promise; /** * Look up metadata for a specific project ID * @@ -44,11 +104,496 @@ export interface ProjectLookupServiceType { * not provided, then look in all available PDP factories for the given project ID. * @returns ProjectMetadata for the given project */ - getMetadataForProject: ( + getMetadataForProject( + projectId: string, + projectInterface?: ProjectInterfaces, + pdpFactoryId?: string, + ): Promise; + /** + * Compare two project ids to determine if they are equal + * + * We're treating project IDs as case insensitive strings. + * + * From + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator: + * + * Only strings that differ in base letters or accents and other diacritic marks compare as + * unequal. Examples: a ≠ b, a ≠ á, a = A. + */ + areProjectIdsEqual(projectIdA: string, projectIdB: string): boolean; + /** Filter an array of {@link ProjectMetadata} in various ways */ + filterProjectsMetadata( + projectsMetadata: ProjectMetadata[], + options: ProjectMetadataFilterOptions, + ): ProjectMetadata[]; + /** + * Get the PDP Factory info whose `projectInterface`s are most minimally matching to the provided + * `projectInterface` + * + * Hopefully this will allow us to get the PDP that most closely matches the `projectInterface`s + * to avoid unnecessary redirects through layered PDPs + * + * @param projectMetadata Metadata for project for which to get minimally matching PDPF + * @param projectInterface Which `projectInterface` to minimally match for + * @returns PDP Factory id whose `projectInterface`s minimally match the provided + * `projectInterface` if at least one PDP Factory was found that supports the `projectInterface` + * provided + */ + getMinimalMatchPdpFactoryId( + projectMetadata: ProjectMetadata, + projectInterface: ProjectInterfaces, + ): string | undefined; +}; + +/** Local object of functions to run locally on each process as part of the project lookup service */ +export const projectLookupServiceBase: ProjectLookupServiceType = { + async getMetadataForAllProjects( + options: ProjectMetadataFilterOptions = {}, + ): Promise { + return internalGetMetadata(options); + }, + async getMetadataForProject( projectId: string, projectInterface?: ProjectInterfaces, pdpFactoryId?: string, - ) => Promise; + ): Promise { + // Wait for an appropriate PDP factory to be registered + const timeoutInMS = 20 * 1000; + if (pdpFactoryId) { + await networkObjectStatusService.waitForNetworkObject( + { + objectType: PDP_FACTORY_OBJECT_TYPE, + id: getPDPFactoryNetworkObjectNameFromId(pdpFactoryId), + }, + timeoutInMS, + ); + } else if (projectInterface) { + await networkObjectStatusService.waitForNetworkObject( + { + objectType: PDP_FACTORY_OBJECT_TYPE, + attributes: { projectInterfaces: [projectInterface] }, + }, + timeoutInMS, + ); + } else { + await networkObjectStatusService.waitForNetworkObject( + { objectType: PDP_FACTORY_OBJECT_TYPE }, + timeoutInMS, + ); + } + + const metadata = await internalGetMetadata( + transformGetMetadataForProjectParametersToFilter(projectId, projectInterface, pdpFactoryId), + ); + + // Get the most minimal match to the projectInterface in question. Hopefully this will give us the + // PDP that most closely matches the projectInterfaces to avoid unnecessary redirects through + // layered PDPs + if (metadata && metadata.length > 0) return metadata[0]; + throw new Error( + `No project found with ID ${projectId}${projectInterface ? ` and project interface ${projectInterface}` : ''}${pdpFactoryId ? ` from ${pdpFactoryId}` : ''}`, + ); + }, + areProjectIdsEqual, + filterProjectsMetadata( + projectsMetadata: ProjectMetadata[], + options: ProjectMetadataFilterOptions, + ): ProjectMetadata[] { + if (!options) return [...projectsMetadata]; + + if ( + !options.excludeProjectIds && + !options.includeProjectIds && + !options.includeProjectInterfaces && + !options.excludeProjectInterfaces && + !options.includePdpFactoryIds && + !options.excludePdpFactoryIds + ) + return [...projectsMetadata]; + + const { + excludeProjectIds, + includeProjectIds, + includeProjectInterfaces, + excludeProjectInterfaces, + includePdpFactoryIds, + excludePdpFactoryIds, + } = ensurePopulatedMetadataFilter(options); + + return projectsMetadata.filter((projectMetadata) => { + // If the project ID isn't in the filters, it's out + if (!isProjectIdIncluded(projectMetadata.id, includeProjectIds, excludeProjectIds)) + return false; + + // If the `projectInterface`s don't match the filters, it's out + if ( + !areProjectInterfacesIncluded( + projectMetadata.projectInterfaces, + includeProjectInterfaces, + excludeProjectInterfaces, + ) + ) + return false; + + // If the pdp factory info doesn't match the filters, it's out + const pdpFactoryIds = Object.keys(projectMetadata.pdpFactoryInfo); + if (!arePdpFactoryIdsIncluded(pdpFactoryIds, includePdpFactoryIds, excludePdpFactoryIds)) + return false; + + return true; + }); + }, + getMinimalMatchPdpFactoryId( + projectMetadata: ProjectMetadata, + projectInterface: ProjectInterfaces, + ): string | undefined { + const minimalMatch = Object.entries(projectMetadata.pdpFactoryInfo).reduce( + (previousPdpfInfoEntry, nextPdpfInfoEntry) => + nextPdpfInfoEntry[1]?.projectInterfaces.includes(projectInterface) && + compareProjectDataProviderFactoryMetadataInfoMinimalMatch( + previousPdpfInfoEntry[1], + nextPdpfInfoEntry[1], + ) > 0 + ? nextPdpfInfoEntry + : previousPdpfInfoEntry, + ['', undefined], + ); + + return minimalMatch[0] && minimalMatch[1] ? minimalMatch[0] : undefined; + }, +}; + +// #endregion + +// #region Project Lookup Service utility functions + +/** + * Note: If there are multiple PDPs available whose metadata matches the conditions provided by the + * parameters, their project metadata will all be combined, so all available `projectInterface`s + * provided by the PDP Factory with the matching id (or all PDP Factories if no id is specified) for + * the project will be returned. If you need `projectInterface`s supported by specific PDP + * Factories, you can access it at {@link ProjectMetadata.pdpFactoryInfo}. + */ +async function internalGetMetadata( + options: ProjectMetadataFilterOptions = {}, +): Promise { + const { + excludeProjectIds, + includeProjectIds, + includeProjectInterfaces, + excludeProjectInterfaces, + includePdpFactoryIds, + excludePdpFactoryIds, + } = ensurePopulatedMetadataFilter(options); + + // Get all registered PDP factories + const networkObjects = await networkObjectStatusService.getAllNetworkObjectDetails(); + const pdpFactoryIds = Object.keys(networkObjects) + .filter((pdpfNetworkObjectName) => { + const details = networkObjects[pdpfNetworkObjectName]; + if ( + details.objectType === PDP_FACTORY_OBJECT_TYPE && + // If a pdp factory id was specified, only get metadata from that pdp factory id. + // This means the ProjectMetadata could be partial in some sense because not all projectInterfaces + // available for that project will be in the ProjectMetadata + arePdpFactoryIdsIncluded( + [getPDPFactoryIdFromNetworkObjectName(pdpfNetworkObjectName)], + includePdpFactoryIds, + excludePdpFactoryIds, + ) + ) + return true; + return false; + }) + .map(getPDPFactoryIdFromNetworkObjectName); + + // For each PDP factory, get all available projects + const allProjectsMetadata = new Map(); + await Promise.all( + pdpFactoryIds.map(async (pdpFactoryId) => { + const pdpFactory = await networkObjectService.get( + getPDPFactoryNetworkObjectNameFromId(pdpFactoryId), + ); + const projectsMetadata = await pdpFactory?.getAvailableProjects(); + if (projectsMetadata) { + const clonedProjectsMetadata = deepClone(projectsMetadata); + clonedProjectsMetadata.forEach((md) => { + // Type assert to add the factory info to the object + // eslint-disable-next-line no-type-assertion/no-type-assertion + const enrichedMd = allProjectsMetadata.get(md.id) ?? (md as ProjectMetadata); + if (!enrichedMd.pdpFactoryInfo) enrichedMd.pdpFactoryInfo = {}; + + if (pdpFactoryId in enrichedMd.pdpFactoryInfo) { + logger.warn( + `Project ${md.id} already has metadata from pdp factory ${pdpFactoryId}. Skipping additional metadata: ${JSON.stringify(md)}`, + ); + return; + } + + // Filter out metadata with the wrong project id + if (!isProjectIdIncluded(md.id, includeProjectIds, excludeProjectIds)) return; + + // Wait to filter metadata by `projectInterface` because we want to return ProjectMetadata + // for a project including all available `projectInterface`s, not just `projectInterface`s + // provided by PDPFs that provide that `projectInterface` + + // This project metadata passes project id and pdpf id! Merge it into the existing metadata + // Put the factory info on + enrichedMd.pdpFactoryInfo[pdpFactoryId] = { + projectInterfaces: [...md.projectInterfaces], + }; + // If there is metadata already in the map, add the new `projectInterface`s + if (allProjectsMetadata.has(md.id)) { + md.projectInterfaces.forEach((newProjectInterface) => { + if (!enrichedMd.projectInterfaces.includes(newProjectInterface)) + enrichedMd.projectInterfaces.push(newProjectInterface); + }); + } else allProjectsMetadata.set(md.id, enrichedMd); + }); + } + }), + ); + + let allProjectsMetadataArray = Array.from(allProjectsMetadata.values()); + + // Filter out metadata without the right `projectInterface` + if (includeProjectInterfaces.length > 0 || excludeProjectInterfaces.length > 0) { + allProjectsMetadataArray = allProjectsMetadataArray.filter((projectMetadata) => + areProjectInterfacesIncluded( + projectMetadata.projectInterfaces, + includeProjectInterfaces, + excludeProjectInterfaces, + ), + ); + } + + return allProjectsMetadataArray; +} + +function transformGetMetadataForProjectParametersToFilter( + projectId?: string, + projectInterface?: ProjectInterfaces, + pdpFactoryId?: string, +) { + // Escape `projectInterface` and `pdpFactoryId` because we don't want regexp matching. These + // fields should match exactly + return { + includeProjectIds: projectId, + includeProjectInterfaces: projectInterface + ? escapeStringRegexp(projectInterface) + : projectInterface, + includePdpFactoryIds: pdpFactoryId ? escapeStringRegexp(pdpFactoryId) : pdpFactoryId, + }; +} + +// #endregion + +// #region Smaller project utilities + +function ensureArray(maybeArray: T | T[] | undefined): T[] { + if (!maybeArray) return []; + + return Array.isArray(maybeArray) ? maybeArray : [maybeArray]; +} + +function transformAndEnsureRegExpArray(stringMaybeArray: string | string[] | undefined): RegExp[] { + if (!stringMaybeArray) return []; + + const stringArray = ensureArray(stringMaybeArray); + + const regExpArray = stringArray.map((str) => new RegExp(str)); + + return regExpArray; +} + +function transformAndEnsureRegExpRegExpArray( + stringStringMaybeArray: string | (string | string[])[] | undefined, +): (RegExp | RegExp[])[] { + if (!stringStringMaybeArray) return []; + + const stringStringArray = ensureArray(stringStringMaybeArray); + + const regExpRegExpArray = stringStringArray.map((stringMaybeStringArray) => + Array.isArray(stringMaybeStringArray) + ? stringMaybeStringArray.map((str) => new RegExp(str)) + : new RegExp(stringMaybeStringArray), + ); + + return regExpRegExpArray; +} + +function ensurePopulatedMetadataFilter(options: ProjectMetadataFilterOptions) { + const { + excludeProjectIds, + includeProjectIds, + includeProjectInterfaces, + excludeProjectInterfaces, + includePdpFactoryIds, + excludePdpFactoryIds, + } = options; + + // Get array of excludeProjectIds + const excludeProjectIdsArray = ensureArray(excludeProjectIds); + + // Get array of includeProjectIds + const includeProjectIdsArray = ensureArray(includeProjectIds); + + // Get array of excludeProjectInterfaces RegExps + const excludeProjectInterfacesRegExps = + transformAndEnsureRegExpRegExpArray(excludeProjectInterfaces); + + // Get array of includeProjectInterfaces RegExps + const includeProjectInterfacesRegExps = + transformAndEnsureRegExpRegExpArray(includeProjectInterfaces); + + // Get array of includePdpFactoryIds RegExps + const includePdpFactoryIdsRegExps = transformAndEnsureRegExpArray(includePdpFactoryIds); + + // Get array of excludePdpFactoryIds RegExps + const excludePdpFactoryIdsRegExps = transformAndEnsureRegExpArray(excludePdpFactoryIds); + + return { + excludeProjectIds: excludeProjectIdsArray, + includeProjectIds: includeProjectIdsArray, + includeProjectInterfaces: includeProjectInterfacesRegExps, + excludeProjectInterfaces: excludeProjectInterfacesRegExps, + includePdpFactoryIds: includePdpFactoryIdsRegExps, + excludePdpFactoryIds: excludePdpFactoryIdsRegExps, + }; +} + +function areProjectIdsEqual(projectIdA: string, projectIdB: string): boolean { + return projectIdA.localeCompare(projectIdB, undefined, { sensitivity: 'accent' }) === 0; +} + +function isProjectIdIncluded( + projectId: string, + includeProjectIds: string[], + excludeProjectIds: string[], +) { + // If the project ID is excluded, it's out + if ( + excludeProjectIds.length > 0 && + excludeProjectIds.some((excludeProjectId) => areProjectIdsEqual(excludeProjectId, projectId)) + ) + return false; + + // If the project ID is not included, it's out + if ( + includeProjectIds.length > 0 && + !includeProjectIds.some((includeProjectId) => areProjectIdsEqual(includeProjectId, projectId)) + ) + return false; + + return true; } -export const projectLookupServiceNetworkObjectName = 'ProjectLookupService'; +function areProjectInterfacesIncluded( + projectInterfaces: ProjectInterfaces[], + includeProjectInterfaces: (RegExp | RegExp[])[], + excludeProjectInterfaces: (RegExp | RegExp[])[], +) { + // If the project interface is excluded, it's out + if ( + excludeProjectInterfaces.length > 0 && + excludeProjectInterfaces.some((excludeRegExp) => + Array.isArray(excludeRegExp) + ? excludeRegExp.every((subExcludeRegExp) => + projectInterfaces.some((projectInterface) => subExcludeRegExp.test(projectInterface)), + ) + : projectInterfaces.some((projectInterface) => excludeRegExp.test(projectInterface)), + ) + ) + return false; + + // If the project interface isn't included, it's out + if ( + includeProjectInterfaces.length > 0 && + !includeProjectInterfaces.some((includeRegExp) => + Array.isArray(includeRegExp) + ? includeRegExp.every((subIncludeRegExp) => + projectInterfaces.some((projectInterface) => subIncludeRegExp.test(projectInterface)), + ) + : projectInterfaces.some((projectInterface) => includeRegExp.test(projectInterface)), + ) + ) + return false; + + return true; +} + +/** All works with the well-known PDP factory ids, not the network object names */ +function arePdpFactoryIdsIncluded( + pdpFactoryIds: string[], + includePdpFactoryIds: RegExp[], + excludePdpFactoryIds: RegExp[], +) { + // If any of the PDP Factory Id are excluded, it's out + if ( + excludePdpFactoryIds.length > 0 && + excludePdpFactoryIds.some((excludeRegExp) => + pdpFactoryIds.some((pdpFactoryId) => excludeRegExp.test(pdpFactoryId)), + ) + ) + return false; + + // If none of the PDP Factory Ids are included, it's out + if ( + includePdpFactoryIds.length > 0 && + (pdpFactoryIds.length === 0 || + !includePdpFactoryIds.some((includeRegExp) => + pdpFactoryIds.some((pdpFactoryId) => includeRegExp.test(pdpFactoryId)), + )) + ) + return false; + + return true; +} + +/** + * Compare function (for array sorting and such) that compares two PDPF Metadata infos by most + * minimal match to the `projectInterface` in question. + * + * Hopefully this will allow us to get the PDP that most closely matches the `projectInterface`s to + * avoid unnecessary redirects through layered PDPs + * + * @param pdpFMetadataInfoA First ProjectDataProviderFactoryMetadataInfo to compare + * @param pdpFMetadataInfoB Second ProjectDataProviderFactoryMetadataInfo to compare + * @returns -1 if a is less than b, 0 if equal, and 1 otherwise + */ +function compareProjectDataProviderFactoryMetadataInfoMinimalMatch( + pdpFMetadataInfoA: ProjectDataProviderFactoryMetadataInfo | undefined, + pdpFMetadataInfoB: ProjectDataProviderFactoryMetadataInfo | undefined, +): -1 | 0 | 1 { + if (!pdpFMetadataInfoA) { + if (!pdpFMetadataInfoB) return 0; + return 1; + } + if (!pdpFMetadataInfoB) { + return -1; + } + // Note: we could convert these arrays to sets first to ensure no duplicates to make sure + // these comparisons are accurate, but let's just say extension developers should write them + // with no duplicates until we have a reason to say something else + const lengthA = pdpFMetadataInfoA.projectInterfaces.length; + const lengthB = pdpFMetadataInfoB.projectInterfaces.length; + + // If one only has the original interface or is smaller than the other, it should be first + if (lengthA === 1 || lengthA < lengthB) return -1; + if (lengthB === 1 || lengthB < lengthA) return 1; + // Otherwise they are pretty much the same as far as we can tell + return 0; +} + +// #endregion + +// #region testing + +/** This is an internal-only export for testing purposes and should not be used in development */ +export const testingProjectLookupService = { + internalGetMetadata, + compareProjectDataProviderFactoryMetadataInfoMinimalMatch, + transformGetMetadataForProjectParametersToFilter, +}; + +// #endregion diff --git a/src/shared/models/project-metadata.model.ts b/src/shared/models/project-metadata.model.ts index 04ac575ab7..64779ff7dc 100644 --- a/src/shared/models/project-metadata.model.ts +++ b/src/shared/models/project-metadata.model.ts @@ -10,8 +10,6 @@ import { ProjectInterfaces } from 'papi-shared-types'; export type ProjectMetadataWithoutFactoryInfo = { /** ID of the project (must be unique and case insensitive) */ id: string; - /** Short name of the project (not necessarily unique) */ - name: string; /** * All `projectInterface`s (aka standardized sets of methods on a PDP) that Project Data Providers * for this project support. Indicates what sort of project data should be available on this diff --git a/src/shared/services/network.service.ts b/src/shared/services/network.service.ts index 08a71f318b..efd90bfde1 100644 --- a/src/shared/services/network.service.ts +++ b/src/shared/services/network.service.ts @@ -7,18 +7,17 @@ import { ClientConnectEvent, ClientDisconnectEvent, - CLIENT_ID_SERVER, NetworkEventHandler, RequestHandler, RequestRouter, CATEGORY_COMMAND, + CLIENT_ID_UNKNOWN, } from '@shared/data/internal-connection.model'; import { aggregateUnsubscriberAsyncs, stringLength, UnsubscriberAsync, getErrorMessage, - wait, PlatformEventEmitter, PlatformEvent, indexOf, @@ -187,24 +186,7 @@ const requestRawUnsafe = async ( `Cannot perform raw request ${requestType} as the NetworkService is not initialized`, ); - // https://github.com/paranext/paranext-core/issues/51 - // If the request type doesn't have a registered handler yet, retry a few times to help with race - // conditions. This approach is hacky but works well enough for now. - const expectedErrorMsg: string = `No handler was found to process the request of type ${requestType}`; - const maxAttempts: number = 10; - for (let attemptsRemaining = maxAttempts; attemptsRemaining > 0; attemptsRemaining--) { - // eslint-disable-next-line no-await-in-loop - const response = await connectionService.request(requestType, contents); - if (response.success || attemptsRemaining === 1 || response.errorMessage !== expectedErrorMsg) - return response; - - // eslint-disable-next-line no-await-in-loop - await wait(1000); - - logger.debug(`Retrying network service request of type ${requestType}`); - } - - throw new Error(`Raw request ${requestType} failed`); + return connectionService.request(requestType, contents); }; /** @@ -652,8 +634,8 @@ const routeRequest: RequestRouter = (requestType: string): number => { const registration = requestRegistrations.get(requestType); if (!registration) // We are the client and we need to send the request to the server or we are the server and we - // need to return an error - return CLIENT_ID_SERVER; + // need to try to figure out the recipient or return an error + return CLIENT_ID_UNKNOWN; if (registration.registrationType === 'local') // We will handle this request here return connectionService.getClientId(); diff --git a/src/shared/services/papi-core.service.ts b/src/shared/services/papi-core.service.ts index c7ea56fd3e..a2eeb6d0a0 100644 --- a/src/shared/services/papi-core.service.ts +++ b/src/shared/services/papi-core.service.ts @@ -26,6 +26,7 @@ export type { IProjectDataProviderEngine, IProjectDataProviderEngineFactory, } from '@shared/models/project-data-provider-engine.model'; +export type { IBaseProjectDataProviderEngine } from '@shared/models/base-project-data-provider-engine.model'; export type { default as IProjectDataProviderFactory } from '@shared/models/project-data-provider-factory.interface'; export type { ProjectDataProviderFactoryMetadataInfo, diff --git a/src/shared/services/project-data-provider.service.ts b/src/shared/services/project-data-provider.service.ts index 9c2328a5b0..f8cdaf6bda 100644 --- a/src/shared/services/project-data-provider.service.ts +++ b/src/shared/services/project-data-provider.service.ts @@ -15,15 +15,14 @@ import { MutexMap, UnionToIntersection, UnsubscriberAsyncList, - newGuid, } from 'platform-bible-utils'; import IProjectDataProviderFactory, { PDP_FACTORY_OBJECT_TYPE, } from '@shared/models/project-data-provider-factory.interface'; -import projectLookupService, { - getMinimalMatchPdpFactoryId, -} from '@shared/services/project-lookup.service'; +import projectLookupService from '@shared/services/project-lookup.service'; import { ProjectMetadataWithoutFactoryInfo } from '@shared/models/project-metadata.model'; +import { PROJECT_INTERFACE_PLATFORM_BASE } from '@shared/models/project-data-provider.model'; +import { getPDPFactoryNetworkObjectNameFromId } from '@shared/models/project-lookup.service-model'; /** * Class that creates Project Data Providers of a specific set of `projectInterface`s. Layers over @@ -34,6 +33,7 @@ class ProjectDataProviderFactory = new Map(); + private readonly pdpFactoryId: string; private readonly projectInterfaces: SupportedProjectInterfaces; private readonly pdpCleanupList: UnsubscriberAsyncList; private readonly pdpEngineFactory: IProjectDataProviderEngineFactory; @@ -45,9 +45,11 @@ class ProjectDataProviderFactory, ) { + this.pdpFactoryId = pdpFactoryId; this.projectInterfaces = projectInterfaces; this.pdpCleanupList = new UnsubscriberAsyncList(`PDP Factory for ${projectInterfaces}`); this.pdpEngineFactory = pdpEngineFactory; @@ -92,16 +94,21 @@ class ProjectDataProviderFactory, projectId: string, ): Promise { - // Add a check for new PDPs to make sure they fulfill `MandatoryProjectDataTypes` + // Check to make sure new Base PDPs fulfill the requirements of the `platform.base` `projectInterface` if ( - !('getExtensionData' in projectDataProviderEngine) || - !('getSetting' in projectDataProviderEngine) + this.projectInterfaces.includes(PROJECT_INTERFACE_PLATFORM_BASE) && + (!('getExtensionData' in projectDataProviderEngine) || + !('getSetting' in projectDataProviderEngine)) ) - throw new Error('projectDataProviderEngine must implement "MandatoryProjectDataTypes"'); + throw new Error( + `\`BaseProjectDataProviderEngine\` with project id ${projectId} created by PDP Factory with id ${this.pdpFactoryId} must implement \`${PROJECT_INTERFACE_PLATFORM_BASE}\` \`projectInterface\`. See \`IBaseProjectDataProvider\` for more information`, + ); + // ENHANCEMENT: Re-add a check for new PDPs to make sure there is some PDP somewhere that + // fulfills `platform.base` + const pdpId: string = `${newNonce()}-pdp`; const pdp = await registerEngineByType< - // @ts-ignore TypeScript thinks there is some unknown data type getting in, but there is not - UnionToIntersection + UnionToIntersection & {} >(pdpId, projectDataProviderEngine, 'pdp', { projectId, projectInterfaces: this.projectInterfaces, @@ -114,6 +121,7 @@ class ProjectDataProviderFactory( + pdpFactoryId: string, projectInterfaces: SupportedProjectInterfaces, pdpEngineFactory: IProjectDataProviderEngineFactory, ): Promise { - const factoryId = newGuid(); - const factory = new ProjectDataProviderFactory(projectInterfaces, pdpEngineFactory); + const factoryNetworkObjectId = getPDPFactoryNetworkObjectNameFromId(pdpFactoryId); + const factory = new ProjectDataProviderFactory(pdpFactoryId, projectInterfaces, pdpEngineFactory); return networkObjectService.set>( - factoryId, + factoryNetworkObjectId, factory, PDP_FACTORY_OBJECT_TYPE, { projectInterfaces }, @@ -142,7 +151,7 @@ export async function registerProjectDataProviderEngineFactory< * @example * * ```typescript - * const pdp = await get('ParatextStandard', 'ProjectID12345'); + * const pdp = await get('platformScripture.USFM_BookChapterVerse', 'ProjectID12345'); * pdp.getVerse(new VerseRef('JHN', '1', '1')); * ``` * @@ -169,17 +178,19 @@ export async function get( pdpFactoryId, ); - const minimalMatchPdpFactoryId = getMinimalMatchPdpFactoryId(metadata, projectInterface); + const minimalMatchPdpFactoryId = projectLookupService.getMinimalMatchPdpFactoryId( + metadata, + projectInterface, + ); if (!minimalMatchPdpFactoryId) throw new Error( `pdpService.get(${projectInterface}, ${projectId}, ${pdpFactoryId}): Somehow there was a project with the id and provided projectInterface, but could not find a PDPF that provided the projectInterface. This should not happen.`, ); - const pdpFactory = - await networkObjectService.get>( - minimalMatchPdpFactoryId, - ); + const pdpFactory = await networkObjectService.get>( + getPDPFactoryNetworkObjectNameFromId(minimalMatchPdpFactoryId), + ); if (!pdpFactory) throw new Error( `pdpService.get(${projectInterface}, ${projectId}, ${pdpFactoryId}): Cannot get project data providers with projectInterface ${projectInterface}: Could not get pdpf with id ${minimalMatchPdpFactoryId}`, diff --git a/src/shared/services/project-lookup.service.test.ts b/src/shared/services/project-lookup.service.test.ts index 026bad866b..e5047fa3e7 100644 --- a/src/shared/services/project-lookup.service.test.ts +++ b/src/shared/services/project-lookup.service.test.ts @@ -5,11 +5,10 @@ import { ProjectMetadataWithoutFactoryInfo, } from '@shared/models/project-metadata.model'; import { - filterProjectsMetadata, - getMinimalMatchPdpFactoryId, + ProjectMetadataFilterOptions, + getPDPFactoryNetworkObjectNameFromId, testingProjectLookupService, -} from '@shared/services/project-lookup.service'; -import { ProjectMetadataFilterOptions } from '@shared/models/project-lookup.service-model'; +} from '@shared/models/project-lookup.service-model'; import networkObjectService from '@shared/services/network-object.service'; import networkObjectStatusService from '@shared/services/network-object-status.service'; import IProjectDataProviderFactory, { @@ -17,6 +16,7 @@ import IProjectDataProviderFactory, { } from '@shared/models/project-data-provider-factory.interface'; import { NetworkObjectDetails } from '@shared/models/network-object.model'; import { ProjectInterfaces } from 'papi-shared-types'; +import projectLookupService from '@shared/services/project-lookup.service'; jest.mock('@shared/services/network-object.service', () => ({ __esModule: true, @@ -29,6 +29,8 @@ jest.mock('@shared/services/network-object-status.service', () => ({ __esModule: true, default: { getAllNetworkObjectDetails: jest.fn(), + // Not a full implementation - we just don't need whatever is returned here + waitForNetworkObject: jest.fn(async () => {}), }, })); @@ -50,80 +52,83 @@ describe('Metadata generation:', () => { const expectedTestProjectInterfaces: ProjectInterfaces[] = [ 'platform.placeholder', 'platform.notesOnly', - 'ParatextStandard', + 'platformScripture.USFM_BookChapterVerse', 'helloWorld', ]; const expectedTest2ProjectInterfaces: ProjectInterfaces[] = [ - 'ParatextStandard', + 'platformScripture.USFM_BookChapterVerse', 'platform.notesOnly', ]; const test2ProjectId = 'test-2-project'; const testPDPFInfo: Record> = { - 'test-0-pdpf': { + [getPDPFactoryNetworkObjectNameFromId('test-0')]: { objectType: PDP_FACTORY_OBJECT_TYPE, }, - 'test-1-pdpf': { + [getPDPFactoryNetworkObjectNameFromId('test-1')]: { objectType: PDP_FACTORY_OBJECT_TYPE, }, - 'test-2-pdpf': { + [getPDPFactoryNetworkObjectNameFromId('test-2')]: { objectType: PDP_FACTORY_OBJECT_TYPE, }, - 'test-3-pdpf': { + [getPDPFactoryNetworkObjectNameFromId('test-3')]: { objectType: PDP_FACTORY_OBJECT_TYPE, }, - 'test-4-pdpf': { + [getPDPFactoryNetworkObjectNameFromId('test-4')]: { objectType: PDP_FACTORY_OBJECT_TYPE, }, + extraneous: { + objectType: 'not-a-pdpf', + }, }; const testPDPFs: Record> = { - 'test-2-pdpf': { + [getPDPFactoryNetworkObjectNameFromId('test-2')]: { async getAvailableProjects(): Promise { return [ { id: testProjectId, - name: 'Test', projectInterfaces: ['platform.placeholder', 'platform.notesOnly', 'helloWorld'], }, ]; }, }, - 'test-1-pdpf': { + [getPDPFactoryNetworkObjectNameFromId('test-1')]: { async getAvailableProjects(): Promise { return [ { id: testProjectId, - name: 'Test', projectInterfaces: ['platform.placeholder', 'platform.notesOnly'], }, { id: test2ProjectId, - name: 'Test2', - projectInterfaces: ['ParatextStandard', 'platform.notesOnly'], + projectInterfaces: ['platformScripture.USFM_BookChapterVerse', 'platform.notesOnly'], }, ]; }, }, - 'test-3-pdpf': { + [getPDPFactoryNetworkObjectNameFromId('test-3')]: { async getAvailableProjects(): Promise { return [ { id: testProjectId, - name: 'Test', - projectInterfaces: ['platform.placeholder', 'platform.notesOnly', 'ParatextStandard'], + projectInterfaces: [ + 'platform.placeholder', + 'platform.notesOnly', + 'platformScripture.USFM_BookChapterVerse', + ], }, ]; }, }, - 'test-0-pdpf': { + [getPDPFactoryNetworkObjectNameFromId('test-0')]: { async getAvailableProjects(): Promise { - return [{ id: testProjectId, name: 'Test', projectInterfaces: ['platform.placeholder'] }]; + return [{ id: testProjectId, projectInterfaces: ['platform.placeholder'] }]; }, }, - 'test-4-pdpf': { + [getPDPFactoryNetworkObjectNameFromId('test-4')]: { async getAvailableProjects(): Promise { return [ - { id: testProjectId, name: 'Test', projectInterfaces: ['platform.notesOnly'] }, - { id: test2ProjectId, name: 'Test2', projectInterfaces: ['platform.notesOnly'] }, + { id: testProjectId, projectInterfaces: ['platform.notesOnly'] }, + { id: test2ProjectId, projectInterfaces: ['platform.notesOnly'] }, ]; }, }, @@ -142,8 +147,10 @@ describe('Metadata generation:', () => { describe('compareProjectDataProviderFactoryMetadataInfoMinimalMatch', () => { test('gets the PDP Factory Id that implements projectInterface and as few others as possible', async () => { const projectsMetadata = await testingProjectLookupService.internalGetMetadata( - undefined, - 'platform.placeholder', + testingProjectLookupService.transformGetMetadataForProjectParametersToFilter( + undefined, + 'platform.placeholder', + ), ); // Check project 1 @@ -175,7 +182,7 @@ describe('Metadata generation:', () => { expect(pdpfInfoValuesSorted[3].projectInterfaces).toEqual([ 'platform.placeholder', 'platform.notesOnly', - 'ParatextStandard', + 'platformScripture.USFM_BookChapterVerse', ]); }); }); @@ -183,8 +190,10 @@ describe('Metadata generation:', () => { describe('getMinimalMatchPdpFactoryInfo', () => { test('returns sort values to order by minimally matching to the projectInterface', async () => { const projectsMetadata = await testingProjectLookupService.internalGetMetadata( - undefined, - 'platform.placeholder', + testingProjectLookupService.transformGetMetadataForProjectParametersToFilter( + undefined, + 'platform.placeholder', + ), ); // Check project 1 @@ -194,14 +203,14 @@ describe('Metadata generation:', () => { expect(testProjectMetadataPossiblyUndefined).toBeDefined(); const testProjectMetadata = testProjectMetadataPossiblyUndefined as ProjectMetadata; - const minimalMatchIdPossiblyUndefined = getMinimalMatchPdpFactoryId( + const minimalMatchIdPossiblyUndefined = projectLookupService.getMinimalMatchPdpFactoryId( testProjectMetadata, 'platform.placeholder', ); expect(minimalMatchIdPossiblyUndefined).toBeDefined(); const minimalMatchId = minimalMatchIdPossiblyUndefined as string; - expect(minimalMatchId).toBe('test-0-pdpf'); + expect(minimalMatchId).toBe('test-0'); expect(testProjectMetadata.pdpFactoryInfo[minimalMatchId]?.projectInterfaces).toEqual([ 'platform.placeholder', ]); @@ -252,7 +261,9 @@ describe('Metadata generation:', () => { }); test('deep clones resulting ProjectMetadata so future calls are not affected', async () => { - const projectsMetadata = await testingProjectLookupService.internalGetMetadata(testProjectId); + const projectsMetadata = await testingProjectLookupService.internalGetMetadata( + testingProjectLookupService.transformGetMetadataForProjectParametersToFilter(testProjectId), + ); // First time checking to make sure everything is consistent for later expect(projectsMetadata.length).toBe(1); @@ -265,8 +276,9 @@ describe('Metadata generation:', () => { projectsMetadata[0].projectInterfaces.push('FAKE PROJECT INTERFACE' as ProjectInterfaces); // Get the same ProjectMetadata again - const newProjectsMetadata = - await testingProjectLookupService.internalGetMetadata(testProjectId); + const newProjectsMetadata = await testingProjectLookupService.internalGetMetadata( + testingProjectLookupService.transformGetMetadataForProjectParametersToFilter(testProjectId), + ); // Check the same conditions to make sure nothing changed expect(newProjectsMetadata.length).toBe(1); @@ -276,7 +288,9 @@ describe('Metadata generation:', () => { }); test('gets just one ProjectMetadata when filtered by projectId', async () => { - const projectsMetadata = await testingProjectLookupService.internalGetMetadata(testProjectId); + const projectsMetadata = await testingProjectLookupService.internalGetMetadata( + testingProjectLookupService.transformGetMetadataForProjectParametersToFilter(testProjectId), + ); expect(projectsMetadata.length).toBe(1); @@ -292,8 +306,10 @@ describe('Metadata generation:', () => { test('gets the whole ProjectMetadata when filtered by projectInterface', async () => { const projectsMetadata = await testingProjectLookupService.internalGetMetadata( - undefined, - 'platform.placeholder', + testingProjectLookupService.transformGetMetadataForProjectParametersToFilter( + undefined, + 'platform.placeholder', + ), ); expect(projectsMetadata.length).toBe(1); @@ -313,27 +329,44 @@ describe('Metadata generation:', () => { ); // Each entry in pdpfInfo should have the `projectInterface`s provided by that pdpf - // Notably, test-4-pdpf doesn't have platform.placeholder, but it is still there because the + // Notably, test-4 doesn't have platform.placeholder, but it is still there because the // project has `platform.placeholder` and we're looking for all info about all projects that // match expect(testProjectMetadata.pdpFactoryInfo).toEqual({ - 'test-2-pdpf': { + 'test-2': { projectInterfaces: ['platform.placeholder', 'platform.notesOnly', 'helloWorld'], }, - 'test-1-pdpf': { projectInterfaces: ['platform.placeholder', 'platform.notesOnly'] }, - 'test-3-pdpf': { - projectInterfaces: ['platform.placeholder', 'platform.notesOnly', 'ParatextStandard'], + 'test-1': { projectInterfaces: ['platform.placeholder', 'platform.notesOnly'] }, + 'test-3': { + projectInterfaces: [ + 'platform.placeholder', + 'platform.notesOnly', + 'platformScripture.USFM_BookChapterVerse', + ], }, - 'test-0-pdpf': { projectInterfaces: ['platform.placeholder'] }, - 'test-4-pdpf': { projectInterfaces: ['platform.notesOnly'] }, + 'test-0': { projectInterfaces: ['platform.placeholder'] }, + 'test-4': { projectInterfaces: ['platform.notesOnly'] }, }); }); + test('does not use regex for projectInterface when transforming parameters', async () => { + const projectsMetadata = await testingProjectLookupService.internalGetMetadata( + testingProjectLookupService.transformGetMetadataForProjectParametersToFilter( + undefined, + 'platform.*' as ProjectInterfaces, + ), + ); + + expect(projectsMetadata.length).toBe(0); + }); + test('gets partial ProjectMetadata when filtered by pdpFactoryId', async () => { const projectsMetadata = await testingProjectLookupService.internalGetMetadata( - undefined, - undefined, - 'test-1-pdpf', + testingProjectLookupService.transformGetMetadataForProjectParametersToFilter( + undefined, + undefined, + 'test-1', + ), ); expect(projectsMetadata.length).toBe(2); @@ -356,7 +389,7 @@ describe('Metadata generation:', () => { // Each entry in pdpfInfo should have only the `projectInterface`s provided by this pdpf expect(testProjectMetadata.pdpFactoryInfo).toEqual({ - 'test-1-pdpf': { projectInterfaces: ['platform.placeholder', 'platform.notesOnly'] }, + 'test-1': { projectInterfaces: ['platform.placeholder', 'platform.notesOnly'] }, }); // Check project 2 @@ -367,7 +400,10 @@ describe('Metadata generation:', () => { const test2ProjectMetadata = test2ProjectMetadataPossiblyUndefined as ProjectMetadata; // `projectInterface`s should be just the `projectInterface`s from this pdpf - const expectedTest2ProjectInterfacesOnePDPF = ['ParatextStandard', 'platform.notesOnly']; + const expectedTest2ProjectInterfacesOnePDPF = [ + 'platformScripture.USFM_BookChapterVerse', + 'platform.notesOnly', + ]; expect(test2ProjectMetadata.projectInterfaces.length).toEqual( expectedTest2ProjectInterfacesOnePDPF.length, ); @@ -377,16 +413,67 @@ describe('Metadata generation:', () => { // Each entry in pdpfInfo should have only the `projectInterface`s provided by this pdpf expect(test2ProjectMetadata.pdpFactoryInfo).toEqual({ - 'test-1-pdpf': { projectInterfaces: ['ParatextStandard', 'platform.notesOnly'] }, + 'test-1': { + projectInterfaces: ['platformScripture.USFM_BookChapterVerse', 'platform.notesOnly'], + }, }); }); + test('does not use regex for pdpFactoryId when transforming parameters', async () => { + const projectsMetadata = await testingProjectLookupService.internalGetMetadata( + testingProjectLookupService.transformGetMetadataForProjectParametersToFilter( + undefined, + undefined, + 'test-.', + ), + ); + + expect(projectsMetadata.length).toBe(0); + }); + test('only calls the specific pdpFactory when filtered by pdpFactoryId', async () => { - const pdpfId = 'test-1-pdpf'; - await testingProjectLookupService.internalGetMetadata(undefined, undefined, pdpfId); + const pdpfId = 'test-1'; + await testingProjectLookupService.internalGetMetadata( + testingProjectLookupService.transformGetMetadataForProjectParametersToFilter( + undefined, + undefined, + pdpfId, + ), + ); expect(networkObjectService.get).toHaveBeenCalledTimes(1); - expect(networkObjectService.get).toHaveBeenCalledWith('test-1-pdpf'); + expect(networkObjectService.get).toHaveBeenCalledWith( + getPDPFactoryNetworkObjectNameFromId('test-1'), + ); + }); + + test('calls all but the specific pdpFactory when excluding one pdpFactoryId', async () => { + const pdpfId = 'test-1'; + await testingProjectLookupService.internalGetMetadata({ excludePdpFactoryIds: pdpfId }); + + expect(networkObjectService.get).toHaveBeenCalledTimes(Object.keys(testPDPFInfo).length - 2); + expect(networkObjectService.get).not.toHaveBeenCalledWith( + getPDPFactoryNetworkObjectNameFromId('test-1'), + ); + }); + }); + + describe('getMetadataForProject', () => { + test('gets the matching project metadata when providing just projectId', async () => { + const testProjectMetadata = await projectLookupService.getMetadataForProject(testProjectId); + + expect(testProjectMetadata.id).toBe(testProjectId); + + // `projectInterface`s should be a combination of all available `projectInterface`s + expect(testProjectMetadata.projectInterfaces.length).toEqual( + expectedTestProjectInterfaces.length, + ); + expectedTestProjectInterfaces.forEach((projectInterface) => + expect(testProjectMetadata.projectInterfaces).toContain(projectInterface), + ); + + // Should be provided by the right number of pdpfs + expect(Object.entries(testProjectMetadata.pdpFactoryInfo).length).toBe(5); }); }); }); @@ -395,7 +482,6 @@ describe('filterProjectsMetadata', () => { const projectsMetadata: ProjectMetadata[] = [ { id: 'asdf', - name: 'fdsa', projectInterfaces: ['helloWorld', 'platform.notesOnly'], pdpFactoryInfo: { test1: { projectInterfaces: ['platform.notesOnly'] }, @@ -404,16 +490,16 @@ describe('filterProjectsMetadata', () => { }, { id: 'asdfg', - name: 'fdsag', - projectInterfaces: ['ParatextStandard', 'platform.notesOnly'], + projectInterfaces: ['platformScripture.USFM_BookChapterVerse', 'platform.notesOnly'], pdpFactoryInfo: { - test2: { projectInterfaces: ['ParatextStandard', 'platform.notesOnly'] }, - test4: { projectInterfaces: ['ParatextStandard'] }, + test2: { + projectInterfaces: ['platformScripture.USFM_BookChapterVerse', 'platform.notesOnly'], + }, + test4: { projectInterfaces: ['platformScripture.USFM_BookChapterVerse'] }, }, }, { id: 'asdfgh', - name: 'fdsagh', projectInterfaces: ['platform.placeholder'], pdpFactoryInfo: { test3: { projectInterfaces: ['platform.placeholder'] } }, }, @@ -422,7 +508,7 @@ describe('filterProjectsMetadata', () => { test('should return a shallow clone if there are no filters', () => { const options: ProjectMetadataFilterOptions = {}; - const filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + const filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata).not.toBe(projectsMetadata); expect(filteredMetadata).toEqual(projectsMetadata); @@ -435,7 +521,7 @@ describe('filterProjectsMetadata', () => { excludeProjectIds: 'asdf', }; - let filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + let filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(2); @@ -445,7 +531,7 @@ describe('filterProjectsMetadata', () => { excludeProjectIds: ['asdf', 'asdfg'], }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(1); @@ -453,10 +539,13 @@ describe('filterProjectsMetadata', () => { options = { excludeProjectIds: ['asdf', 'asdfg'], - includeProjectInterfaces: ['^ParatextStandard$', '^platform\\.placeholder$'], + includeProjectInterfaces: [ + '^platformScripture\\.USFM_BookChapterVerse$', + '^platform\\.placeholder$', + ], }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(1); }); @@ -468,7 +557,7 @@ describe('filterProjectsMetadata', () => { includeProjectIds: 'asdf', }; - let filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + let filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(1); @@ -478,7 +567,7 @@ describe('filterProjectsMetadata', () => { includeProjectIds: ['asdf', 'asdfg'], }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(2); @@ -487,10 +576,13 @@ describe('filterProjectsMetadata', () => { options = { includeProjectIds: ['asdf', 'asdfg', 'asdfgh'], excludeProjectIds: 'asdfg', - includeProjectInterfaces: ['^ParatextStandard$', '^platform\\.placeholder$'], + includeProjectInterfaces: [ + '^platformScripture\\.USFM_BookChapterVerse$', + '^platform\\.placeholder$', + ], }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(1); }); @@ -502,27 +594,27 @@ describe('filterProjectsMetadata', () => { excludeProjectInterfaces: '^helloWorld$', }; - let filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + let filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(2); // Single RegExp that loosely matches all projects options = { - excludeProjectInterfaces: 'd', + excludeProjectInterfaces: 'o', }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(0); // Multiple OR'ed RegExps that match two project interfaces options = { - excludeProjectInterfaces: ['^helloWorld$', '^ParatextStandard$'], + excludeProjectInterfaces: ['^helloWorld$', '^platformScripture\\.USFM_BookChapterVerse$'], }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(1); @@ -532,7 +624,7 @@ describe('filterProjectsMetadata', () => { excludeProjectInterfaces: [['platform.notesOnly']], }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(1); @@ -542,7 +634,7 @@ describe('filterProjectsMetadata', () => { excludeProjectInterfaces: [['helloWorld', 'platform.notesOnly']], }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(2); @@ -552,18 +644,21 @@ describe('filterProjectsMetadata', () => { excludeProjectInterfaces: [['helloWorld', 'platform.notesOnly'], 'platform.placeholder'], }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(1); // Still excludes even if includeProjectInterfaces includes options = { - excludeProjectInterfaces: 'Paratext', - includeProjectInterfaces: ['^ParatextStandard$', '^platform\\.placeholder$'], + excludeProjectInterfaces: 'USFM', + includeProjectInterfaces: [ + '^platformScripture\\.USFM_BookChapterVerse$', + '^platform\\.placeholder$', + ], }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(1); }); @@ -575,27 +670,27 @@ describe('filterProjectsMetadata', () => { includeProjectInterfaces: '^helloWorld$', }; - let filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + let filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(1); // Single RegExp that loosely matches all projects options = { - includeProjectInterfaces: 'd', + includeProjectInterfaces: 'o', }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata).toEqual(projectsMetadata); // Multiple RegExps that match two project interfaces options = { - includeProjectInterfaces: ['^helloWorld$', '^ParatextStandard$'], + includeProjectInterfaces: ['^helloWorld$', '^platformScripture\\.USFM_BookChapterVerse$'], }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(2); @@ -605,7 +700,7 @@ describe('filterProjectsMetadata', () => { includeProjectInterfaces: [['platform.notesOnly']], }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(2); @@ -615,7 +710,7 @@ describe('filterProjectsMetadata', () => { includeProjectInterfaces: [['helloWorld', 'platform.notesOnly']], }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(1); @@ -625,18 +720,21 @@ describe('filterProjectsMetadata', () => { includeProjectInterfaces: [['helloWorld', 'platform.notesOnly'], 'platform.placeholder'], }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(2); // excludeProjectInterfaces excludes even if this includes options = { - excludeProjectInterfaces: 'Paratext', - includeProjectInterfaces: ['^ParatextStandard$', '^platform\\.placeholder$'], + excludeProjectInterfaces: 'USFM', + includeProjectInterfaces: [ + '^platformScripture\\.USFM_BookChapterVerse$', + '^platform\\.placeholder$', + ], }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(1); }); @@ -648,7 +746,7 @@ describe('filterProjectsMetadata', () => { excludePdpFactoryIds: '.est1', }; - let filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + let filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(2); @@ -658,7 +756,7 @@ describe('filterProjectsMetadata', () => { excludePdpFactoryIds: 'test[34]', }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(0); @@ -668,7 +766,7 @@ describe('filterProjectsMetadata', () => { excludePdpFactoryIds: ['test1', '^test2$'], }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(1); @@ -679,7 +777,7 @@ describe('filterProjectsMetadata', () => { includePdpFactoryIds: 'test', }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(1); }); @@ -691,7 +789,7 @@ describe('filterProjectsMetadata', () => { includePdpFactoryIds: 'test[^234]', }; - let filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + let filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(1); @@ -701,7 +799,7 @@ describe('filterProjectsMetadata', () => { includePdpFactoryIds: '\\d', }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata).toEqual(projectsMetadata); @@ -711,7 +809,7 @@ describe('filterProjectsMetadata', () => { includePdpFactoryIds: ['test1', '2$'], }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(2); @@ -722,7 +820,7 @@ describe('filterProjectsMetadata', () => { includePdpFactoryIds: ['test1', 'test2'], }; - filteredMetadata = filterProjectsMetadata(projectsMetadata, options); + filteredMetadata = projectLookupService.filterProjectsMetadata(projectsMetadata, options); expect(filteredMetadata.length).toEqual(1); }); diff --git a/src/shared/services/project-lookup.service.ts b/src/shared/services/project-lookup.service.ts index 9e8dc3d371..1115fc5224 100644 --- a/src/shared/services/project-lookup.service.ts +++ b/src/shared/services/project-lookup.service.ts @@ -1,380 +1,5 @@ -import { - ProjectLookupServiceType, - ProjectMetadataFilterOptions, -} from '@shared/models/project-lookup.service-model'; -import networkObjectService from '@shared/services/network-object.service'; -import { - ProjectDataProviderFactoryMetadataInfo, - ProjectMetadata, -} from '@shared/models/project-metadata.model'; -import logger from '@shared/services/logger.service'; -import networkObjectStatusService from '@shared/services/network-object-status.service'; -import { IProjectDataProviderFactory } from '@shared/services/papi-core.service'; -import { ProjectInterfaces } from 'papi-shared-types'; -import { PDP_FACTORY_OBJECT_TYPE } from '@shared/models/project-data-provider-factory.interface'; -import { deepClone } from 'platform-bible-utils'; +import { projectLookupServiceBase } from '@shared/models/project-lookup.service-model'; -/** - * Note: If there are multiple PDPs available whose metadata matches the conditions provided by the - * parameters, their project metadata will all be combined, so all available `projectInterface`s - * provided by the PDP Factory with the matching id (or all PDP Factories if no id is specified) for - * the project will be returned. If you need `projectInterface`s supported by specific PDP - * Factories, you can access it at {@link ProjectMetadata.pdpFactoryInfo}. - */ -async function internalGetMetadata( - onlyProjectId?: string, - onlyProjectInterface?: string, - onlyPdpFactoryId?: string, -): Promise { - // Get all registered PDP factories - const networkObjects = await networkObjectStatusService.getAllNetworkObjectDetails(); - const pdpFactoryNames = Object.keys(networkObjects).filter((networkObjectName) => { - const details = networkObjects[networkObjectName]; - if ( - details.objectType === PDP_FACTORY_OBJECT_TYPE && - // If a pdp factory id was specified, only get metadata from that pdp factory id. - // This means the ProjectMetadata could be partial in some sense because not all projectInterfaces - // available for that project will be in the ProjectMetadata - (!onlyPdpFactoryId || onlyPdpFactoryId === networkObjectName) - ) - return true; - return false; - }); - - // For each PDP factory, get all available projects - const allProjectsMetadata = new Map(); - await Promise.all( - pdpFactoryNames.map(async (pdpFactoryId) => { - const pdpFactory = await networkObjectService.get(pdpFactoryId); - const projectsMetadata = await pdpFactory?.getAvailableProjects(); - if (projectsMetadata) { - const clonedProjectsMetadata = deepClone(projectsMetadata); - clonedProjectsMetadata.forEach((md) => { - // Type assert to add the factory info to the object - // eslint-disable-next-line no-type-assertion/no-type-assertion - const enrichedMd = allProjectsMetadata.get(md.id) ?? (md as ProjectMetadata); - if (!enrichedMd.pdpFactoryInfo) enrichedMd.pdpFactoryInfo = {}; - - if (pdpFactoryId in enrichedMd.pdpFactoryInfo) { - logger.warn( - `Project ${md.id} already has metadata from pdp factory ${pdpFactoryId}. Skipping additional metadata: ${JSON.stringify(md)}`, - ); - return; - } - - // Filter out metadata with the wrong project id - if ( - onlyProjectId && - // We're treating project IDs as case insensitive strings - // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator/Collator: - // Only strings that differ in base letters or accents and other diacritic marks compare as unequal. Examples: a ≠ b, a ≠ á, a = A. - onlyProjectId.localeCompare(md.id, undefined, { sensitivity: 'accent' }) !== 0 - ) - return; - - // Wait to filter metadata by `projectInterface` because we want to return ProjectMetadata - // for a project including all available `projectInterface`s, not just `projectInterface`s - // provided by PDPFs that provide that `projectInterface` - - // This project metadata passes project id and pdpf id! Merge it into the existing metadata - // Put the factory info on - enrichedMd.pdpFactoryInfo[pdpFactoryId] = { - projectInterfaces: [...md.projectInterfaces], - }; - // If there is metadata already in the map, add the new `projectInterface`s - if (allProjectsMetadata.has(md.id)) { - md.projectInterfaces.forEach((newProjectInterface) => { - if (!enrichedMd.projectInterfaces.includes(newProjectInterface)) - enrichedMd.projectInterfaces.push(newProjectInterface); - }); - } else allProjectsMetadata.set(md.id, enrichedMd); - }); - } - }), - ); - - let allProjectsMetadataArray = Array.from(allProjectsMetadata.values()); - - // Filter out metadata without the right `projectInterface` - if (onlyProjectInterface) { - allProjectsMetadataArray = allProjectsMetadataArray.filter((projectMetadata) => - projectMetadata.projectInterfaces.some( - (projectInterface) => onlyProjectInterface === projectInterface, - ), - ); - } - - return allProjectsMetadataArray; -} - -async function getMetadataForAllProjects(): Promise { - return internalGetMetadata(); -} - -async function getMetadataForProject( - projectId: string, - projectInterface?: ProjectInterfaces, - pdpFactoryId?: string, -): Promise { - // Wait for an appropriate PDP factory to be registered - const timeoutInMS = 20 * 1000; - if (pdpFactoryId) { - await networkObjectStatusService.waitForNetworkObject( - { objectType: PDP_FACTORY_OBJECT_TYPE, id: pdpFactoryId }, - timeoutInMS, - ); - } else if (projectInterface) { - await networkObjectStatusService.waitForNetworkObject( - { - objectType: PDP_FACTORY_OBJECT_TYPE, - attributes: { projectInterfaces: [projectInterface] }, - }, - timeoutInMS, - ); - } else { - await networkObjectStatusService.waitForNetworkObject( - { objectType: PDP_FACTORY_OBJECT_TYPE }, - timeoutInMS, - ); - } - - const metadata = await internalGetMetadata(projectId, projectInterface, pdpFactoryId); - // Get the most minimal match to the projectInterface in question. Hopefully this will give us the - // PDP that most closely matches the projectInterfaces to avoid unnecessary redirects through - // layered PDPs - if (metadata && metadata.length > 0) return metadata[0]; - throw new Error( - `No project found with ID ${projectId}${projectInterface ? ` and project interface ${projectInterface}` : ''}${pdpFactoryId ? ` from ${pdpFactoryId}` : ''}`, - ); -} - -// #region Project utilities - -function ensureArray(maybeArray: T | T[] | undefined): T[] { - if (!maybeArray) return []; - - return Array.isArray(maybeArray) ? maybeArray : [maybeArray]; -} - -function transformAndEnsureRegExpArray(stringMaybeArray: string | string[] | undefined): RegExp[] { - if (!stringMaybeArray) return []; - - const stringArray = ensureArray(stringMaybeArray); - - const regExpArray = stringArray.map((str) => new RegExp(str)); - - return regExpArray; -} - -function transformAndEnsureRegExpRegExpArray( - stringStringMaybeArray: string | (string | string[])[] | undefined, -): (RegExp | RegExp[])[] { - if (!stringStringMaybeArray) return []; - - const stringStringArray = ensureArray(stringStringMaybeArray); - - const regExpRegExpArray = stringStringArray.map((stringMaybeStringArray) => - Array.isArray(stringMaybeStringArray) - ? stringMaybeStringArray.map((str) => new RegExp(str)) - : new RegExp(stringMaybeStringArray), - ); - - return regExpRegExpArray; -} - -export function filterProjectsMetadata( - projectsMetadata: ProjectMetadata[], - options: ProjectMetadataFilterOptions, -): ProjectMetadata[] { - if (!options) return [...projectsMetadata]; - - const { - excludeProjectIds, - includeProjectIds, - includeProjectInterfaces, - excludeProjectInterfaces, - includePdpFactoryIds, - excludePdpFactoryIds, - } = options; - - if ( - !excludeProjectIds && - !includeProjectIds && - !includeProjectInterfaces && - !excludeProjectInterfaces && - !includePdpFactoryIds && - !excludePdpFactoryIds - ) - return [...projectsMetadata]; - - // Get array of excludeProjectIds - const excludeProjectIdsArray = ensureArray(excludeProjectIds); - - // Get array of includeProjectIds - const includeProjectIdsArray = ensureArray(includeProjectIds); - - // Get array of excludeProjectInterfaces RegExps - const excludeProjectInterfacesRegExps = - transformAndEnsureRegExpRegExpArray(excludeProjectInterfaces); - - // Get array of includeProjectInterfaces RegExps - const includeProjectInterfacesRegExps = - transformAndEnsureRegExpRegExpArray(includeProjectInterfaces); - - // Get array of includePdpFactoryIds RegExps - const includePdpFactoryIdsRegExps = transformAndEnsureRegExpArray(includePdpFactoryIds); - - // Get array of excludePdpFactoryIds RegExps - const excludePdpFactoryIdsRegExps = transformAndEnsureRegExpArray(excludePdpFactoryIds); - - return projectsMetadata.filter((projectMetadata) => { - // If the project ID is excluded, it's out - if (excludeProjectIdsArray.length > 0 && excludeProjectIdsArray.includes(projectMetadata.id)) - return false; - - // If the project ID is not included, it's out - if (includeProjectIdsArray.length > 0 && !includeProjectIdsArray.includes(projectMetadata.id)) - return false; - - // If the project interface is excluded, it's out - if ( - excludeProjectInterfacesRegExps.length > 0 && - excludeProjectInterfacesRegExps.some((excludeRegExp) => - Array.isArray(excludeRegExp) - ? excludeRegExp.every((subExcludeRegExp) => - projectMetadata.projectInterfaces.some((projectInterface) => - subExcludeRegExp.test(projectInterface), - ), - ) - : projectMetadata.projectInterfaces.some((projectInterface) => - excludeRegExp.test(projectInterface), - ), - ) - ) - return false; - - // If the project interface isn't included, it's out - if ( - includeProjectInterfacesRegExps.length > 0 && - !includeProjectInterfacesRegExps.some((includeRegExp) => - Array.isArray(includeRegExp) - ? includeRegExp.every((subIncludeRegExp) => - projectMetadata.projectInterfaces.some((projectInterface) => - subIncludeRegExp.test(projectInterface), - ), - ) - : projectMetadata.projectInterfaces.some((projectInterface) => - includeRegExp.test(projectInterface), - ), - ) - ) - return false; - - const pdpFactoryIds = Object.keys(projectMetadata.pdpFactoryInfo); - // If the PDP Factory Id is excluded, it's out - if ( - excludePdpFactoryIdsRegExps.length > 0 && - excludePdpFactoryIdsRegExps.some((excludeRegExp) => - pdpFactoryIds.some((pdpFactoryId) => excludeRegExp.test(pdpFactoryId)), - ) - ) - return false; - - // If the PDP Factory Id isn't included, it's out - if ( - includePdpFactoryIdsRegExps.length > 0 && - (pdpFactoryIds.length === 0 || - !includePdpFactoryIdsRegExps.some((includeRegExp) => - pdpFactoryIds.some((pdpFactoryId) => includeRegExp.test(pdpFactoryId)), - )) - ) - return false; - - return true; - }); -} - -/** - * Compare function (for array sorting and such) that compares two PDPF Metadata infos by most - * minimal match to the `projectInterface` in question. - * - * Hopefully this will allow us to get the PDP that most closely matches the `projectInterface`s to - * avoid unnecessary redirects through layered PDPs - * - * @param pdpFMetadataInfoA First ProjectDataProviderFactoryMetadataInfo to compare - * @param pdpFMetadataInfoB Second ProjectDataProviderFactoryMetadataInfo to compare - * @returns -1 if a is less than b, 0 if equal, and 1 otherwise - */ -function compareProjectDataProviderFactoryMetadataInfoMinimalMatch( - pdpFMetadataInfoA: ProjectDataProviderFactoryMetadataInfo | undefined, - pdpFMetadataInfoB: ProjectDataProviderFactoryMetadataInfo | undefined, -): -1 | 0 | 1 { - if (!pdpFMetadataInfoA) { - if (!pdpFMetadataInfoB) return 0; - return 1; - } - if (!pdpFMetadataInfoB) { - return -1; - } - // Note: we could convert these arrays to sets first to ensure no duplicates to make sure - // these comparisons are accurate, but let's just say extension developers should write them - // with no duplicates until we have a reason to say something else - const lengthA = pdpFMetadataInfoA.projectInterfaces.length; - const lengthB = pdpFMetadataInfoB.projectInterfaces.length; - - // If one only has the original interface or is smaller than the other, it should be first - if (lengthA === 1 || lengthA < lengthB) return -1; - if (lengthB === 1 || lengthB < lengthA) return 1; - // Otherwise they are pretty much the same as far as we can tell - return 0; -} - -/** - * Get the PDP Factory info whose `projectInterface`s are most minimally matching to the provided - * `projectInterface` - * - * Hopefully this will allow us to get the PDP that most closely matches the `projectInterface`s to - * avoid unnecessary redirects through layered PDPs - * - * @param projectMetadata Metadata for project for which to get minimally matching PDPF - * @param projectInterface Which `projectInterface` to minimally match for - * @returns PDP Factory id whose `projectInterface`s minimally match the provided `projectInterface` - * if at least one PDP Factory was found that supports the `projectInterface` provided - */ -export function getMinimalMatchPdpFactoryId( - projectMetadata: ProjectMetadata, - projectInterface: ProjectInterfaces, -): string | undefined { - const minimalMatch = Object.entries(projectMetadata.pdpFactoryInfo).reduce( - (previousPdpfInfoEntry, nextPdpfInfoEntry) => - nextPdpfInfoEntry[1]?.projectInterfaces.includes(projectInterface) && - compareProjectDataProviderFactoryMetadataInfoMinimalMatch( - previousPdpfInfoEntry[1], - nextPdpfInfoEntry[1], - ) > 0 - ? nextPdpfInfoEntry - : previousPdpfInfoEntry, - ['', undefined], - ); - - return minimalMatch[0] && minimalMatch[1] ? minimalMatch[0] : undefined; -} - -// #endregion - -// #region testing - -/** This is an internal-only export for testing purposes and should not be used in development */ -export const testingProjectLookupService = { - internalGetMetadata, - compareProjectDataProviderFactoryMetadataInfoMinimalMatch, -}; - -// #endregion - -const projectLookupService: ProjectLookupServiceType = { - getMetadataForAllProjects, - getMetadataForProject, -}; +const projectLookupService = projectLookupServiceBase; export default projectLookupService; diff --git a/src/shared/services/project-settings.service-model.ts b/src/shared/services/project-settings.service-model.ts index 67b622071b..24e6df08f2 100644 --- a/src/shared/services/project-settings.service-model.ts +++ b/src/shared/services/project-settings.service-model.ts @@ -56,8 +56,6 @@ export interface IProjectSettingsService { * throw. * * @param key The project setting key for which to get the default value - * @param projectInterfaces The `projectInterface`s supported by the calling PDP for the project - * for which to get the default setting value * @returns The default value for the setting if a default value is registered * @throws If a default value is not registered for the setting */ @@ -99,8 +97,6 @@ export type SimultaneousProjectSettingsChanges = { * @param newValue The new value requested to set the project setting value to * @param currentValue The current project setting value * @param allChanges All project settings changes being set in one batch - * @param projectInterfaces The `projectInterface`s supported by the calling PDP for the project - * whose setting is being changed */ export type ProjectSettingValidator = ( newValue: ProjectSettingTypes[ProjectSettingName], diff --git a/src/shared/services/web-view.service-model.ts b/src/shared/services/web-view.service-model.ts index 5ee3d37531..673da7fea6 100644 --- a/src/shared/services/web-view.service-model.ts +++ b/src/shared/services/web-view.service-model.ts @@ -40,6 +40,12 @@ export interface WebViewServiceType { /** * Gets the saved properties on the WebView definition with the specified ID * + * Note: this only returns a representation of the current web view definition, not the actual web + * view definition itself. Changing properties on the returned definition does not affect the + * actual web view definition. You can possibly change the actual web view definition by calling + * {@link WebViewServiceType.getWebView} with certain `options`, depending on what options the web + * view provider has made available. + * * @param webViewId The ID of the WebView whose saved properties to get * @returns Saved properties of the WebView definition with the specified ID or undefined if not * found diff --git a/src/shared/utils/project-settings-document-combiner.test.ts b/src/shared/utils/project-settings-document-combiner.test.ts index 53719382b8..459091a652 100644 --- a/src/shared/utils/project-settings-document-combiner.test.ts +++ b/src/shared/utils/project-settings-document-combiner.test.ts @@ -38,11 +38,11 @@ const platformSettingsLocalized: Localized = { properties: { 'platform.fullName': { label: 'project_settings_platform_fullName_label', - default: '%project_full_name_missing%', + default: 'project_full_name_missing', }, 'platform.language': { label: 'project_settings_platform_language_label', - default: '%project_language_missing%', + default: 'project_language_missing', }, }, }; diff --git a/src/shared/utils/settings-document-combiner-base.ts b/src/shared/utils/settings-document-combiner-base.ts index 37803dcd6e..b00dc49a4e 100644 --- a/src/shared/utils/settings-document-combiner-base.ts +++ b/src/shared/utils/settings-document-combiner-base.ts @@ -12,6 +12,8 @@ import { SettingsContribution, SettingsGroup, deepClone, + isLocalizeKey, + isString, startsWith, } from 'platform-bible-utils'; @@ -80,6 +82,8 @@ async function localizeSettingsContributionInfo( Object.values(settingsGroup.properties).forEach((setting: Setting) => { localizedStringKeys.add(setting.label); if (setting.description) localizedStringKeys.add(setting.description); + if (isString(setting.default) && isLocalizeKey(setting.default)) + localizedStringKeys.add(setting.default); }); }), ); @@ -112,6 +116,8 @@ async function localizeSettingsContributionInfo( // We are changing the type from LocalizeKey to the localized string here // eslint-disable-next-line no-type-assertion/no-type-assertion setting.description = localizedStrings[setting.description as LocalizeKey]; + if (isString(setting.default) && isLocalizeKey(setting.default)) + setting.default = localizedStrings[setting.default]; }); }), );