diff --git a/.github/README.md b/.github/README.md index 4a70453..a12c322 100644 --- a/.github/README.md +++ b/.github/README.md @@ -1,3 +1,94 @@ # Umbraco.Community.SimpleTrees +[![Umbraco Marketplace](https://img.shields.io/badge/Umbraco-Marketplace-%233544B1?style=flat&logo=umbraco)](https://marketplace.umbraco.com/package/Umbraco.Community.SimpleTrees) +[![License](https://img.shields.io/github/license/jcdcdev/Umbraco.Community.SimpleTrees?color=8AB803&label=License&logo=github)](https://github.com/jcdcdev/Umbraco.Community.SimpleTrees?tab=MIT-1-ov-file) +[![NuGet Downloads](https://img.shields.io/nuget/dt/Umbraco.Community.SimpleTrees?color=cc9900&label=Downloads&logo=nuget)](https://www.nuget.org/packages/Umbraco.Community.SimpleTrees) +[![Project Website](https://img.shields.io/badge/Project%20Website-jcdc.dev-jcdcdev?style=flat&color=3c4834&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0id2hpdGUiIGNsYXNzPSJiaSBiaS1wYy1kaXNwbGF5IiB2aWV3Qm94PSIwIDAgMTYgMTYiPgogIDxwYXRoIGQ9Ik04IDFhMSAxIDAgMCAxIDEtMWg2YTEgMSAwIDAgMSAxIDF2MTRhMSAxIDAgMCAxLTEgMUg5YTEgMSAwIDAgMS0xLTF6bTEgMTMuNWEuNS41IDAgMSAwIDEgMCAuNS41IDAgMCAwLTEgMG0yIDBhLjUuNSAwIDEgMCAxIDAgLjUuNSAwIDAgMC0xIDBNOS41IDFhLjUuNSAwIDAgMCAwIDFoNWEuNS41IDAgMCAwIDAtMXpNOSAzLjVhLjUuNSAwIDAgMCAuNS41aDVhLjUuNSAwIDAgMCAwLTFoLTVhLjUuNSAwIDAgMC0uNS41TTEuNSAyQTEuNSAxLjUgMCAwIDAgMCAzLjV2N0ExLjUgMS41IDAgMCAwIDEuNSAxMkg2djJoLS41YS41LjUgMCAwIDAgMCAxSDd2LTRIMS41YS41LjUgMCAwIDEtLjUtLjV2LTdhLjUuNSAwIDAgMSAuNS0uNUg3VjJ6Ii8+Cjwvc3ZnPg==)](https://jcdc.dev/umbraco-packages/simple-trees) + + +This packages aims to help developers quickly put together Umbraco Trees using C#. + +## Features + +- C# custom tree creation +- No javascript or umbraco-package.json files required +- Supports both Views & View Components +- Easy to define section permissions + +## Quick Start + +### Install Package + +```csharp +dotnet add package Umbraco.Community.SimpleTrees +``` + +### Register Tree + +By default, this will display in the content section. + +```csharp title="ExampleTree.cs" +using Umbraco.Cms.Core.Models; +using Umbraco.Community.SimpleTrees.Models; + +namespace Umbraco.Community.SimpleTrees.TestSite.Trees; + +public class MyTree : SimpleTree +{ + public override Task> GetTreeRootAsync(int skip, int take, bool foldersOnly) + { + var data = new List + { + CreateRootItem("James", Guid.NewGuid().ToString(), "icon-user"), + CreateRootItem("Tim", Guid.NewGuid().ToString(), "icon-user"), + }; + + return Task.FromResult(new PagedModel(data.Count, data)); + } + + public override Task> GetTreeChildrenAsync(string entityType, string parentUnique, int skip, int take, bool foldersOnly) => Task.FromResult(EmptyResult()); + + public override string Name => "My Tree"; +} +``` + +### Create Views + +- Your views **must** go in `/Views/Trees` +- You views **must** be the name of your tree entities + - For example: `MyTree.cs` => `/Views/Trees/MyItem.cshtml` & `/Views/Trees/MyRoot.cshtml` + +```csharp title="Views/Trees/MyItem.cshtml" +@inherits Umbraco.Community.SimpleTrees.Web.SimpleTreeViewPage + + +
+ + + + + + + + + + + + +
Entity TypeUnique
@Model.EntityType@Model.Unique
+
+
+``` + + + +## Contributing + +Contributions to this package are most welcome! Please visit the [Contributing](https://github.com/jcdcdev/Umbraco.Community.SimpleTrees/contribute) page. + +## Acknowledgements (Thanks) + +- LottePitcher - [opinionated-package-starter](https://github.com/LottePitcher/opinionated-package-starter) + + diff --git a/.github/workflows/update-readme.yml b/.github/workflows/update-readme.yml index 1ba53b8..7c07762 100644 --- a/.github/workflows/update-readme.yml +++ b/.github/workflows/update-readme.yml @@ -30,7 +30,7 @@ jobs: needs: get-refs env: DRY_RUN: ${{ github.event.inputs.dry-run }} - PROJECT_NAME: jcdcdev.Umbraco.RelationsManager + PROJECT_NAME: Umbraco.Community.SimpleTrees README_FILEPATH: ./.github/README.md NUGET_README_FILEPATH: ./docs/README_nuget.md strategy: diff --git a/.github/workflows/update-security-policy.yml b/.github/workflows/update-security-policy.yml index cfc967c..0e97f97 100644 --- a/.github/workflows/update-security-policy.yml +++ b/.github/workflows/update-security-policy.yml @@ -30,7 +30,7 @@ jobs: needs: get-refs env: DRY_RUN: ${{ github.event.inputs.dry-run }} - PROJECT_NAME: jcdcdev.Umbraco.RelationsManager + PROJECT_NAME: Umbraco.Community.SimpleTrees SECURITY_POLICY_FILEPATH: ./SECURITY.md strategy: max-parallel: 1 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..fd3030f --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,55 @@ +# Security Policy + +## Supported Versions + +The following table outlines the versions of the project that are currently supported with security & feature updates: + +> [!NOTE] +> Once a version starts its security phase, it will no longer receive feature updates. Only critical bug fixes and security updates will be provided. + +| Package Version | Umbraco Version | Security Phase Start | End of Life | +| ---------------------------------------------------------------------------------- | --------------- | -------------------- | ----------- | +| [15.x](https://github.com/jcdcdev/Umbraco.Community.SimpleTrees/tree/v15) | 15 | 2025-08-14 | 2025-11-14 | +| [16.x](https://github.com/jcdcdev/Umbraco.Community.SimpleTrees/tree/v16) | 16 | 2026-03-12 | 2026-06-12 | + + +## Future Support + +Project maintainers plan to support all STS (Short-Term Support) and LTS (Long-Term Support) versions of Umbraco. However, exact release dates cannot be guaranteed. + +> [!NOTE] +> Visit [jcdc.dev/blog/umbraco-version-information](https://jcdc.dev/blog/umbraco-version-information) for more information on Umbraco versions. + +## Reporting a Vulnerability + +If you discover a vulnerability in this project, please follow one of these steps to report it: + +- Create an [issue](https://github.com/jcdcdev/Umbraco.Community.SimpleTrees/security/advisories/new) +- Contact the project author privately at [jcdc.dev/contact](https://jcdc.dev/contact) + +### Details + +Include as much information as possible about the vulnerability, including: + +- Steps to reproduce +- Potential impact +- Any suggested fixes + +### Acknowledgment + +You will receive an acknowledgment of your report as soon as possible. + +> [!NOTE] +> Response times may vary depending on other commitments. + +### Resolution + +Once the vulnerability is confirmed, project maintainers will work to resolve it as quickly as possible. + +You will be notified once the issue has been resolved or rejected. + +> [!TIP] +> If the vulnerability is accepted, you will receive credit in the release notes. + +Thank you for helping to keep this project secure! + diff --git a/docs/README_nuget.md b/docs/README_nuget.md index 5aceefa..28029d2 100644 --- a/docs/README_nuget.md +++ b/docs/README_nuget.md @@ -1,4 +1,94 @@ # Umbraco.Community.SimpleTrees +[![Documentation](https://img.shields.io/badge/Docs-Quickstart-394933?style=flat&logo=github)](https://github.com/jcdcdev/Umbraco.Community.SimpleTrees#quick-start) +[![Umbraco Marketplace](https://img.shields.io/badge/Umbraco-Marketplace-%233544B1?style=flat&logo=umbraco)](https://marketplace.umbraco.com/package/Umbraco.Community.SimpleTrees) +[![License](https://img.shields.io/github/license/jcdcdev/Umbraco.Community.SimpleTrees?color=8AB803&label=License&logo=github)](https://github.com/jcdcdev/Umbraco.Community.SimpleTrees?tab=MIT-1-ov-file) +[![NuGet Downloads](https://img.shields.io/nuget/dt/Umbraco.Community.SimpleTrees?color=cc9900&label=Downloads&logo=nuget)](https://www.nuget.org/packages/Umbraco.Community.SimpleTrees) +[![Project Website](https://img.shields.io/badge/Project%20Website-jcdc.dev-jcdcdev?style=flat&color=3c4834&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0id2hpdGUiIGNsYXNzPSJiaSBiaS1wYy1kaXNwbGF5IiB2aWV3Qm94PSIwIDAgMTYgMTYiPgogIDxwYXRoIGQ9Ik04IDFhMSAxIDAgMCAxIDEtMWg2YTEgMSAwIDAgMSAxIDF2MTRhMSAxIDAgMCAxLTEgMUg5YTEgMSAwIDAgMS0xLTF6bTEgMTMuNWEuNS41IDAgMSAwIDEgMCAuNS41IDAgMCAwLTEgMG0yIDBhLjUuNSAwIDEgMCAxIDAgLjUuNSAwIDAgMC0xIDBNOS41IDFhLjUuNSAwIDAgMCAwIDFoNWEuNS41IDAgMCAwIDAtMXpNOSAzLjVhLjUuNSAwIDAgMCAuNS41aDVhLjUuNSAwIDAgMCAwLTFoLTVhLjUuNSAwIDAgMC0uNS41TTEuNSAyQTEuNSAxLjUgMCAwIDAgMCAzLjV2N0ExLjUgMS41IDAgMCAwIDEuNSAxMkg2djJoLS41YS41LjUgMCAwIDAgMCAxSDd2LTRIMS41YS41LjUgMCAwIDEtLjUtLjV2LTdhLjUuNSAwIDAgMSAuNS0uNUg3VjJ6Ii8+Cjwvc3ZnPg==)](https://jcdc.dev/umbraco-packages/simple-trees) + + +This packages aims to help developers quickly put together Umbraco Trees using C#. + +## Features + +- C# custom tree creation +- No javascript or umbraco-package.json files required +- Supports both Views & View Components +- Easy to define section permissions + +## Quick Start + +### Install Package + +```csharp +dotnet add package Umbraco.Community.SimpleTrees +``` + +### Register Tree + +By default, this will display in the content section. + +```csharp title="ExampleTree.cs" +using Umbraco.Cms.Core.Models; +using Umbraco.Community.SimpleTrees.Models; + +namespace Umbraco.Community.SimpleTrees.TestSite.Trees; + +public class MyTree : SimpleTree +{ + public override Task> GetTreeRootAsync(int skip, int take, bool foldersOnly) + { + var data = new List + { + CreateRootItem("James", Guid.NewGuid().ToString(), "icon-user"), + CreateRootItem("Tim", Guid.NewGuid().ToString(), "icon-user"), + }; + + return Task.FromResult(new PagedModel(data.Count, data)); + } + + public override Task> GetTreeChildrenAsync(string entityType, string parentUnique, int skip, int take, bool foldersOnly) => Task.FromResult(EmptyResult()); + + public override string Name => "My Tree"; +} +``` + +### Create Views + +- Your views **must** go in `/Views/Trees` +- You views **must** be the name of your tree entities + - For example: `MyTree.cs` => `/Views/Trees/MyItem.cshtml` & `/Views/Trees/MyRoot.cshtml` + +```csharp title="Views/Trees/MyItem.cshtml" +@inherits Umbraco.Community.SimpleTrees.Web.SimpleTreeViewPage + + +
+ + + + + + + + + + + + +
Entity TypeUnique
@Model.EntityType@Model.Unique
+
+
+``` + + +## Contributing + +Contributions to this package are most welcome! Please visit the [Contributing](https://github.com/jcdcdev/Umbraco.Community.SimpleTrees/contribute) page. + +## Acknowledgements (Thanks) + +- LottePitcher - [opinionated-package-starter](https://github.com/LottePitcher/opinionated-package-starter) + diff --git a/docs/screenshots/custom-nuget-tree-item.png b/docs/screenshots/custom-nuget-tree-item.png new file mode 100644 index 0000000..a09a3f2 Binary files /dev/null and b/docs/screenshots/custom-nuget-tree-item.png differ diff --git a/docs/screenshots/custom-tree-item-0.png b/docs/screenshots/custom-tree-item-0.png new file mode 100644 index 0000000..cd01406 Binary files /dev/null and b/docs/screenshots/custom-tree-item-0.png differ diff --git a/docs/screenshots/custom-tree-item-1.png b/docs/screenshots/custom-tree-item-1.png new file mode 100644 index 0000000..aa8393e Binary files /dev/null and b/docs/screenshots/custom-tree-item-1.png differ diff --git a/docs/screenshots/custom-tree-item-2.png b/docs/screenshots/custom-tree-item-2.png new file mode 100644 index 0000000..f528ecd Binary files /dev/null and b/docs/screenshots/custom-tree-item-2.png differ diff --git a/docs/screenshots/tree-not-found.png b/docs/screenshots/tree-not-found.png new file mode 100644 index 0000000..1e8f586 Binary files /dev/null and b/docs/screenshots/tree-not-found.png differ diff --git a/src/Umbraco.Community.SimpleTrees.TestSite/NuGetService.cs b/src/Umbraco.Community.SimpleTrees.TestSite/NuGetService.cs new file mode 100644 index 0000000..289bfc3 --- /dev/null +++ b/src/Umbraco.Community.SimpleTrees.TestSite/NuGetService.cs @@ -0,0 +1,48 @@ +using NuGet.Common; +using NuGet.Protocol; +using NuGet.Protocol.Core.Types; + +namespace Umbraco.Community.SimpleTrees.TestSite; + +public class NuGetService +{ + public async Task> GetPackageMetadata(string packageId) + { + var logger = NullLogger.Instance; + var cancellationToken = CancellationToken.None; + var cache = new SourceCacheContext(); + + var repository = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json"); + var resource = await repository.GetResourceAsync(cancellationToken); + + var results = await resource.GetMetadataAsync( + packageId, + includePrerelease: true, + includeUnlisted: false, + cache, + logger, + cancellationToken); + + return results; + } + + public async Task> GetPackages(string owner, int skip, int take) + { + var logger = NullLogger.Instance; + var cancellationToken = CancellationToken.None; + + var repository = Repository.Factory.GetCoreV3("https://api.nuget.org/v3/index.json"); + var resource = await repository.GetResourceAsync(cancellationToken); + var searchFilter = new SearchFilter(includePrerelease: true); + + var results = await resource.SearchAsync( + $"owner:{owner}", + searchFilter, + skip: skip, + take: take, + logger, + cancellationToken); + + return results; + } +} \ No newline at end of file diff --git a/src/Umbraco.Community.SimpleTrees.TestSite/Trees/ExampleTree.cs b/src/Umbraco.Community.SimpleTrees.TestSite/Trees/ExampleTree.cs new file mode 100644 index 0000000..c14742d --- /dev/null +++ b/src/Umbraco.Community.SimpleTrees.TestSite/Trees/ExampleTree.cs @@ -0,0 +1,19 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Community.SimpleTrees.Models; + +namespace Umbraco.Community.SimpleTrees.TestSite.Trees; + +public class ExampleTree : SimpleTree +{ + public override Task> GetTreeRootAsync(int skip, int take, bool foldersOnly) + { + return Task.FromResult(new PagedModel(0, [])); + } + + public override Task> GetTreeChildrenAsync(string entityType, string parentUnique, int skip, int take, bool foldersOnly) + { + return Task.FromResult(new PagedModel(0, [])); + } + + public override string Name => "Example Tree"; +} \ No newline at end of file diff --git a/src/Umbraco.Community.SimpleTrees.TestSite/Trees/MyMenu.cs b/src/Umbraco.Community.SimpleTrees.TestSite/Trees/MyMenu.cs new file mode 100644 index 0000000..2114c43 --- /dev/null +++ b/src/Umbraco.Community.SimpleTrees.TestSite/Trees/MyMenu.cs @@ -0,0 +1,9 @@ +using Umbraco.Community.SimpleTrees.Models; + +namespace Umbraco.Community.SimpleTrees.TestSite.Trees; + +public class MyMenu : SimpleMenu +{ + public override string Name => "My Tree Menu"; + public override string[] Sections => [jcdcdev.Umbraco.Core.Constants.Sections.Members, jcdcdev.Umbraco.Core.Constants.Sections.Media]; +} \ No newline at end of file diff --git a/src/Umbraco.Community.SimpleTrees.TestSite/Trees/MyTree.cs b/src/Umbraco.Community.SimpleTrees.TestSite/Trees/MyTree.cs new file mode 100644 index 0000000..47cc367 --- /dev/null +++ b/src/Umbraco.Community.SimpleTrees.TestSite/Trees/MyTree.cs @@ -0,0 +1,24 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Community.SimpleTrees.Models; + +namespace Umbraco.Community.SimpleTrees.TestSite.Trees; + +public class MyTree : SimpleTree +{ + public override Task> GetTreeRootAsync(int skip, int take, bool foldersOnly) + { + var data = new List + { + CreateRootItem("James", Guid.NewGuid().ToString(), "icon-user"), + CreateRootItem("Tim", Guid.NewGuid().ToString(), "icon-user"), + }; + + return Task.FromResult(new PagedModel(data.Count, data)); + } + + public override Task> GetTreeChildrenAsync(string entityType, string parentUnique, int skip, int take, bool foldersOnly) => Task.FromResult(EmptyResult()); + + public override string[] Menus => [nameof(MyMenu)]; + + public override string Name => "My Tree"; +} \ No newline at end of file diff --git a/src/Umbraco.Community.SimpleTrees.TestSite/Trees/NuGetMenu.cs b/src/Umbraco.Community.SimpleTrees.TestSite/Trees/NuGetMenu.cs new file mode 100644 index 0000000..ee8706a --- /dev/null +++ b/src/Umbraco.Community.SimpleTrees.TestSite/Trees/NuGetMenu.cs @@ -0,0 +1,9 @@ +using Umbraco.Community.SimpleTrees.Models; + +namespace Umbraco.Community.SimpleTrees.TestSite.Trees; + +public class NuGetMenu : SimpleMenu +{ + public override string Name => "NuGet Packages"; + public override string[] Sections => [jcdcdev.Umbraco.Core.Constants.Sections.Content]; +} \ No newline at end of file diff --git a/src/Umbraco.Community.SimpleTrees.TestSite/Trees/NuGetPackageTree.cs b/src/Umbraco.Community.SimpleTrees.TestSite/Trees/NuGetPackageTree.cs new file mode 100644 index 0000000..c2cb6ef --- /dev/null +++ b/src/Umbraco.Community.SimpleTrees.TestSite/Trees/NuGetPackageTree.cs @@ -0,0 +1,53 @@ +using Umbraco.Cms.Core.Models; +using Umbraco.Community.SimpleTrees.Models; + +namespace Umbraco.Community.SimpleTrees.TestSite.Trees; + +public class NuGetPackageTree : SimpleTree +{ + private static readonly string[] Authors = ["jcdcdev", "umbraco"]; + public override string[] Menus => [nameof(NuGetMenu)]; + + public override Task> GetTreeRootAsync(int skip, int take, bool foldersOnly) + { + var items = Authors.Select(x => CreateRootItem(x, x, "icon-user", true, true)); + return Task.FromResult>(new(Authors.Length, items)); + } + + public override async Task> GetTreeChildrenAsync(string entityType, string parentUnique, int skip, int take, bool foldersOnly) + { + var service = new NuGetService(); + if (entityType == DefaultRootEntityType) + { + var results = await service.GetPackages(parentUnique, skip, take); + + var items = new List(); + foreach (var result in results) + { + var item = CreateItem(result.Title, result.Identity.Id, parentUnique, "icon-document", hasChildren: true); + item.IsFolder = true; + items.Add(item); + } + + return new PagedModel(items.Count, items); + } + + var versions = await service.GetPackageMetadata(parentUnique); + + var versionItems = new List(); + foreach (var version in versions.Reverse()) + { + var item = CreateItem( + version.Identity.Version.ToString(), + $"{version.Identity.Id}_{version.Identity.Version}", + parentUnique, + "icon-box"); + + versionItems.Add(item); + } + + return new PagedModel(versionItems.Count, versionItems); + } + + public override string Name => "NuGet Packages"; +} \ No newline at end of file diff --git a/src/Umbraco.Community.SimpleTrees.TestSite/ViewComponents/ExampleItemViewComponent.cs b/src/Umbraco.Community.SimpleTrees.TestSite/ViewComponents/ExampleItemViewComponent.cs new file mode 100644 index 0000000..8639a9a --- /dev/null +++ b/src/Umbraco.Community.SimpleTrees.TestSite/ViewComponents/ExampleItemViewComponent.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc; +using Umbraco.Community.SimpleTrees.Web; +using Umbraco.Community.SimpleTrees.Web.Models; + +namespace Umbraco.Community.SimpleTrees.TestSite.ViewComponents; + +public class ExampleItemViewComponent : SimpleTreeViewComponent +{ + public override IViewComponentResult Invoke(SimpleTreeViewModel model) + { + return Content($""" + + Hello {model.Unique} {model.EntityType} {DateTime.UtcNow:HH:mm:ss} + + """ + ); + } +} \ No newline at end of file diff --git a/src/Umbraco.Community.SimpleTrees.TestSite/ViewComponents/ExampleRootViewComponent.cs b/src/Umbraco.Community.SimpleTrees.TestSite/ViewComponents/ExampleRootViewComponent.cs new file mode 100644 index 0000000..37d044b --- /dev/null +++ b/src/Umbraco.Community.SimpleTrees.TestSite/ViewComponents/ExampleRootViewComponent.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc; +using Umbraco.Community.SimpleTrees.Web; +using Umbraco.Community.SimpleTrees.Web.Models; + +namespace Umbraco.Community.SimpleTrees.TestSite.ViewComponents; + +public class ExampleRootViewComponent : SimpleTreeViewComponent +{ + public override IViewComponentResult Invoke(SimpleTreeViewModel model) + { + return Content($""" + + Hello {model.Unique} {model.EntityType} {DateTime.UtcNow:HH:mm:ss} + + """ + ); + } +} \ No newline at end of file diff --git a/src/Umbraco.Community.SimpleTrees.TestSite/Views/Trees/MyItem.cshtml b/src/Umbraco.Community.SimpleTrees.TestSite/Views/Trees/MyItem.cshtml new file mode 100644 index 0000000..a0ccc02 --- /dev/null +++ b/src/Umbraco.Community.SimpleTrees.TestSite/Views/Trees/MyItem.cshtml @@ -0,0 +1,19 @@ +@inherits Umbraco.Community.SimpleTrees.Web.SimpleTreeViewPage + + +
+ + + + + + + + + + + + +
Entity TypeUnique
@Model.EntityType@Model.Unique
+
+
\ No newline at end of file diff --git a/src/Umbraco.Community.SimpleTrees.TestSite/Views/Trees/MyRoot.cshtml b/src/Umbraco.Community.SimpleTrees.TestSite/Views/Trees/MyRoot.cshtml new file mode 100644 index 0000000..34f343b --- /dev/null +++ b/src/Umbraco.Community.SimpleTrees.TestSite/Views/Trees/MyRoot.cshtml @@ -0,0 +1,19 @@ +@inherits Umbraco.Community.SimpleTrees.Web.SimpleTreeViewPage + + +
+ + + + + + + + + + + + +
Entity TypeUnique
@Model.EntityType@Model.Unique
+
+
diff --git a/src/Umbraco.Community.SimpleTrees.TestSite/Views/Trees/NuGetPackageItem.cshtml b/src/Umbraco.Community.SimpleTrees.TestSite/Views/Trees/NuGetPackageItem.cshtml new file mode 100644 index 0000000..ef1f481 --- /dev/null +++ b/src/Umbraco.Community.SimpleTrees.TestSite/Views/Trees/NuGetPackageItem.cshtml @@ -0,0 +1,64 @@ +@using Umbraco.Community.SimpleTrees.TestSite +@inherits Umbraco.Community.SimpleTrees.Web.SimpleTreeViewPage +@{ + var split = Model.Unique.Split('_'); + var packageId = split[0]; + var version = split.Length > 1 ? split[1] : null; + var service = new NuGetService(); + var versions = (await service.GetPackageMetadata(packageId)).Reverse().ToList(); + if (versions.Count == 0) + { + +
+ The package with ID @packageId was not found. +
+
+ return; + } + + var package = versions.First(); + if (version.IsNullOrWhiteSpace()) + { + + Icon for @package.Title +
+ @package.Description +
+ + Project URL + + @if (package.ReadmeUrl != null) + { + + Readme URL + + } +
+ @versions.Count versions +
+
+ return; + } + + package = versions.First(x => x.Identity.Version.OriginalVersion?.InvariantStartsWith(version) ?? false); +} + + +
Published @package.Published
+
+ + + @foreach (var set in package.DependencySets) + { +
+
Target Framework: @set.TargetFramework
+ @foreach (var dependency in set.Packages) + { +
+ @dependency.Id @dependency.VersionRange +
+ } +
+ } +
+ diff --git a/src/Umbraco.Community.SimpleTrees.TestSite/Views/Trees/NuGetPackageRoot.cshtml b/src/Umbraco.Community.SimpleTrees.TestSite/Views/Trees/NuGetPackageRoot.cshtml new file mode 100644 index 0000000..591b8dc --- /dev/null +++ b/src/Umbraco.Community.SimpleTrees.TestSite/Views/Trees/NuGetPackageRoot.cshtml @@ -0,0 +1,17 @@ +@inherits Umbraco.Community.SimpleTrees.Web.SimpleTreeViewPage + +@if (Model.IsRoot) +{ + +
+ Click on an author to see their packages. +
+
+ return; +} + + +
+ Showing packages published by @Model.Unique +
+
\ No newline at end of file diff --git a/src/Umbraco.Community.SimpleTrees.TestSite/packages.lock.json b/src/Umbraco.Community.SimpleTrees.TestSite/packages.lock.json index a0fc780..6a042c8 100644 --- a/src/Umbraco.Community.SimpleTrees.TestSite/packages.lock.json +++ b/src/Umbraco.Community.SimpleTrees.TestSite/packages.lock.json @@ -181,8 +181,8 @@ }, "jcdcdev.Umbraco.Core": { "type": "Transitive", - "resolved": "16.0.1", - "contentHash": "Bw1MR604hi623BdRsoVnd6SE6d886WNSnHy5Yb8TlLyXj5PumAclJCdOD7054OA6vQGKVYK7LhiERSFIDHm6yQ==", + "resolved": "16.1.0", + "contentHash": "zGLyOd4mMnVi9xfvzH4ITqqmbkc8wvdfiMp8hxPgkI8qLbIXkjaqApCi0yZXDeZg65YNiF12GeHnvrHbdihadQ==", "dependencies": { "Umbraco.Cms.Web.Common": "[16.0.0, 17.0.0)" } @@ -2968,7 +2968,7 @@ "Umbraco.Cms.Api.Common": "[16.0.0, 17.0.0)", "Umbraco.Cms.Api.Management": "[16.0.0, 17.0.0)", "Umbraco.Cms.Web.Website": "[16.0.0, 17.0.0)", - "jcdcdev.Umbraco.Core": "[16.0.1, 17.0.0)" + "jcdcdev.Umbraco.Core": "[16.1.0, 17.0.0)" } } } diff --git a/src/Umbraco.Community.SimpleTrees/Umbraco.Community.SimpleTrees.csproj b/src/Umbraco.Community.SimpleTrees/Umbraco.Community.SimpleTrees.csproj index cd8a15c..e65c819 100644 --- a/src/Umbraco.Community.SimpleTrees/Umbraco.Community.SimpleTrees.csproj +++ b/src/Umbraco.Community.SimpleTrees/Umbraco.Community.SimpleTrees.csproj @@ -26,7 +26,7 @@ - + diff --git a/src/Umbraco.Community.SimpleTrees/Web/Controllers/SimpleTreesController.cs b/src/Umbraco.Community.SimpleTrees/Web/Controllers/SimpleTreesController.cs deleted file mode 100644 index 7884425..0000000 --- a/src/Umbraco.Community.SimpleTrees/Web/Controllers/SimpleTreesController.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System.Text.Encodings.Web; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.ViewComponents; -using Microsoft.AspNetCore.Mvc.ViewEngines; -using Microsoft.AspNetCore.Mvc.ViewFeatures; -using Microsoft.AspNetCore.Mvc.ViewFeatures.Buffers; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; -using Umbraco.Cms.Api.Common.Attributes; -using Umbraco.Cms.Api.Common.Filters; -using Umbraco.Cms.Api.Management.Filters; -using Umbraco.Cms.Core.Cache; -using Umbraco.Cms.Web.Common.Authorization; -using Umbraco.Community.SimpleTrees.Web.Models; -using Umbraco.Extensions; - -namespace Umbraco.Community.SimpleTrees.Web.Controllers; - -[ApiExplorerSettings(GroupName = Constants.Api.GroupName)] -[SimpleTreesVersionedRoute("tree")] -[MapToApi(Constants.Api.ApiName)] -[JsonOptionsName(Cms.Core.Constants.JsonOptionsNames.BackOffice)] -[ApiController] -[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] -[AppendEventMessages] -[Produces("application/json")] -public class SimpleTreesController( - ICompositeViewEngine viewEngine, - ILogger logger, - IViewComponentDescriptorProvider viewComponentDescriptorProvider, - AppCaches appCaches) - : Controller -{ - private readonly ILogger _logger = logger; - private readonly IAppPolicyCache _runtimeCache = appCaches.RuntimeCache; - - [HttpGet("render")] - [Produces] - public async Task Render(string unique, string entityType) - { - var model = new SimpleTreeViewModel(unique, entityType); - var path = model.ViewPath; - var result = viewEngine.GetView(null, path, false); - if (result.Success) - { - var body = await RenderAsync(result, model); - return Ok(body); - } - - var viewComponentName = model.ViewComponent; - if (ViewComponentExists(viewComponentName)) - { - var body = await RenderAsync(viewComponentName, model); - return Ok(body); - } - - return await ReturnError(model); - } - - private async Task ReturnError(SimpleTreeViewModel viewModel) - { - var result = viewEngine.GetView(null, Constants.ErrorViewPath, false); - var body = await RenderAsync(result, viewModel); - return Ok(body); - } - - private async Task RenderAsync(string viewComponentName, SimpleTreeViewModel viewModel) - { - var sp = HttpContext.RequestServices; - - var helper = new DefaultViewComponentHelper( - sp.GetRequiredService(), - HtmlEncoder.Default, - sp.GetRequiredService(), - sp.GetRequiredService(), - sp.GetRequiredService()); - await using var writer = new StringWriter(); - var context = new ViewContext(ControllerContext, NullView.Instance, ViewData, TempData, writer, new HtmlHelperOptions()); - helper.Contextualize(context); - var vcResult = await helper.InvokeAsync(viewComponentName, new { Model = viewModel }); - vcResult.WriteTo(writer, HtmlEncoder.Default); - await writer.FlushAsync(); - var body = writer.ToString(); - return new SimpleTreeRenderModel - { - Body = body - }; - } - - private async Task RenderAsync(ViewEngineResult result, object? model) - { - if (result.View == null) - { - return SimpleTreeRenderModel.Error; - } - - var writer = new StringWriter(); - var viewContext = new ViewContext(new ActionContext(HttpContext, RouteData, ControllerContext.ActionDescriptor, ModelState), result.View, ViewData, TempData, writer, new HtmlHelperOptions()) - { - ViewData = - { - Model = model - } - }; - - await result.View.RenderAsync(viewContext); - var body = writer.ToString(); - return new SimpleTreeRenderModel - { - Body = body - }; - } - - private bool ViewComponentExists(string viewComponentName) - { - return _runtimeCache.GetCacheItem(viewComponentName, () => - { - var viewComponentDescriptors = viewComponentDescriptorProvider.GetViewComponents(); - return viewComponentDescriptors.Any(vc => vc.ShortName == viewComponentName); - }); - } -} \ No newline at end of file diff --git a/src/Umbraco.Community.SimpleTrees/Web/Controllers/SimpleTreesRenderController.cs b/src/Umbraco.Community.SimpleTrees/Web/Controllers/SimpleTreesRenderController.cs new file mode 100644 index 0000000..9981e5a --- /dev/null +++ b/src/Umbraco.Community.SimpleTrees/Web/Controllers/SimpleTreesRenderController.cs @@ -0,0 +1,61 @@ +using jcdcdev.Umbraco.Core.Extensions; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.Extensions.Logging; +using Umbraco.Cms.Api.Common.Attributes; +using Umbraco.Cms.Api.Common.Filters; +using Umbraco.Cms.Api.Management.Filters; +using Umbraco.Cms.Web.Common.Authorization; +using Umbraco.Community.SimpleTrees.Web.Models; + +namespace Umbraco.Community.SimpleTrees.Web.Controllers; + +[ApiExplorerSettings(GroupName = Constants.Api.GroupName)] +[SimpleTreesVersionedRoute("tree")] +[MapToApi(Constants.Api.ApiName)] +[JsonOptionsName(Cms.Core.Constants.JsonOptionsNames.BackOffice)] +[ApiController] +[Authorize(Policy = AuthorizationPolicies.BackOfficeAccess)] +[AppendEventMessages] +[Produces("application/json")] +public class SimpleTreesRenderController(ICompositeViewEngine viewEngine, ILogger logger) : Controller +{ + [HttpGet("render")] + [Produces] + public async Task Render(string unique, string entityType) + { + var model = new SimpleTreeViewModel(unique, entityType); + var path = model.ViewPath; + var result = viewEngine.GetView(null, path, false); + if (result.Success) + { + logger.LogDebug("Rendering view {ViewPath} for unique {Unique} and entity type {EntityType}", path, unique, entityType); + var partialViewResult = await this.RenderViewResultToStringAsync(result, model); + return Ok(SimpleTreeRenderModel.Create(partialViewResult)); + } + + var viewComponentName = model.ViewComponent; + if (!this.ViewComponentExists(viewComponentName)) + { + logger.LogDebug("ViewComponent {ViewComponent} not found", viewComponentName); + return await ReturnError(model); + } + + var viewComponentResult = await this.RenderViewComponentToStringAsync(viewComponentName, model); + logger.LogDebug("ViewComponent {ViewComponent} result for unique {Unique} and entity type {EntityType} {Body}", viewComponentName, unique, entityType, viewComponentResult); + return Ok(SimpleTreeRenderModel.Create(viewComponentResult)); + } + + private async Task ReturnError(SimpleTreeViewModel viewModel) + { + var errorViewResult = viewEngine.GetView(null, Constants.ErrorViewPath, false); + if (errorViewResult.Success) + { + var body = await this.RenderViewResultToStringAsync(errorViewResult, viewModel); + return Ok(SimpleTreeRenderModel.Create(body)); + } + + return Ok(SimpleTreeRenderModel.Error); + } +} \ No newline at end of file diff --git a/src/Umbraco.Community.SimpleTrees/Web/Models/SimpleTreeRenderModel.cs b/src/Umbraco.Community.SimpleTrees/Web/Models/SimpleTreeRenderModel.cs index 1d91fdd..6285107 100644 --- a/src/Umbraco.Community.SimpleTrees/Web/Models/SimpleTreeRenderModel.cs +++ b/src/Umbraco.Community.SimpleTrees/Web/Models/SimpleTreeRenderModel.cs @@ -4,4 +4,14 @@ public class SimpleTreeRenderModel { public required string Body { get; set; } public static SimpleTreeRenderModel Error => new() { Body = Constants.ErrorView }; + + public static SimpleTreeRenderModel Create(string body) + { + if (string.IsNullOrWhiteSpace(body)) + { + throw new ArgumentException("Body cannot be null or whitespace.", nameof(body)); + } + + return new SimpleTreeRenderModel { Body = body }; + } } \ No newline at end of file diff --git a/src/Umbraco.Community.SimpleTrees/Web/NullView.cs b/src/Umbraco.Community.SimpleTrees/Web/NullView.cs deleted file mode 100644 index 66328c7..0000000 --- a/src/Umbraco.Community.SimpleTrees/Web/NullView.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.ViewEngines; - -namespace Umbraco.Community.SimpleTrees.Web; - -internal sealed class NullView : IView -{ - public static readonly NullView Instance = new(); - - public string Path => string.Empty; - - public Task RenderAsync(ViewContext context) - { - ArgumentNullException.ThrowIfNull(context); - - return Task.CompletedTask; - } -} diff --git a/src/Umbraco.Community.SimpleTrees/packages.lock.json b/src/Umbraco.Community.SimpleTrees/packages.lock.json index 24a5158..835a832 100644 --- a/src/Umbraco.Community.SimpleTrees/packages.lock.json +++ b/src/Umbraco.Community.SimpleTrees/packages.lock.json @@ -4,9 +4,9 @@ "net9.0": { "jcdcdev.Umbraco.Core": { "type": "Direct", - "requested": "[16.0.1, 17.0.0)", - "resolved": "16.0.1", - "contentHash": "Bw1MR604hi623BdRsoVnd6SE6d886WNSnHy5Yb8TlLyXj5PumAclJCdOD7054OA6vQGKVYK7LhiERSFIDHm6yQ==", + "requested": "[16.1.0, 17.0.0)", + "resolved": "16.1.0", + "contentHash": "zGLyOd4mMnVi9xfvzH4ITqqmbkc8wvdfiMp8hxPgkI8qLbIXkjaqApCi0yZXDeZg65YNiF12GeHnvrHbdihadQ==", "dependencies": { "Umbraco.Cms.Web.Common": "[16.0.0, 17.0.0)" }