Skip to content

Commit

Permalink
Misc improvements to outbound redirects
Browse files Browse the repository at this point in the history
  • Loading branch information
abjerner committed Nov 10, 2022
1 parent 6b91a20 commit e999162
Show file tree
Hide file tree
Showing 11 changed files with 390 additions and 44 deletions.
3 changes: 3 additions & 0 deletions src/Skybrud.Umbraco.Redirects/Composers/RedirectsComposer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.Extensions.DependencyInjection;
using Skybrud.Umbraco.Redirects.Config;
using Skybrud.Umbraco.Redirects.ContentApps;
using Skybrud.Umbraco.Redirects.Factories;
using Skybrud.Umbraco.Redirects.Factories.References;
using Skybrud.Umbraco.Redirects.Helpers;
using Skybrud.Umbraco.Redirects.Manifests;
Expand Down Expand Up @@ -32,6 +33,8 @@ public class RedirectsComposer : IComposer {
builder.Services.AddUnique<IRedirectsService, RedirectsService>();
builder.Services.AddSingleton<RedirectsBackOfficeHelper>();

builder.Services.AddSingleton<RedirectsModelsFactory>();

builder.ManifestFilters().Append<RedirectsManifestFilter>();

builder.ContentApps()?.Append<RedirectsContentAppFactory>();
Expand Down
51 changes: 49 additions & 2 deletions src/Skybrud.Umbraco.Redirects/Extensions/RedirectsExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ public static class RedirectsExtensions {
/// <returns>An instance of <see cref="IOutboundRedirect"/>.</returns>
public static IOutboundRedirect GetOutboundRedirect(this IPublishedContent content, string propertyAlias) {
if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentNullException(nameof(propertyAlias));
OutboundRedirect redirect = content.Value(propertyAlias) as OutboundRedirect;
IOutboundRedirect redirect = content.Value(propertyAlias) as IOutboundRedirect;
return redirect ?? new OutboundRedirect();
}

Expand All @@ -173,13 +173,60 @@ public static class RedirectsExtensions {
if (propertyAliases == null) throw new ArgumentNullException(nameof(propertyAliases));

foreach (string alias in propertyAliases) {
if (content.Value(alias) is OutboundRedirect redirect) return redirect;
if (content.Value(alias) is IOutboundRedirect redirect) return redirect;
}

return new OutboundRedirect();

}

/// <summary>
/// Returns an instance of <see cref="IOutboundRedirect"/> representing the outbound redirect from either the
/// <c>skyRedirect</c> or <c>outboundRedirect</c> properties.
///
/// If the property doesn't hold a <see cref="IOutboundRedirect"/> value, <see langword="null"/> will be returned instead.
/// </summary>
/// <param name="content">The content item holding the outbound rediderect.</param>
/// <returns>An instance of <see cref="IOutboundRedirect"/> if successful; otherwise, <see langword="null"/>.</returns>
public static IOutboundRedirect GetOutboundRedirectOrDefault(this IPublishedContent content) {
return GetOutboundRedirectOrDefault(content, OutboundPropertyAliases);
}

/// <summary>
/// Returns an instance of <see cref="IOutboundRedirect"/> representing the outbound redirect from the property
/// with specified alias <paramref name="propertyAlias"/>.
///
/// If the property doesn't hold a <see cref="IOutboundRedirect"/> value, <see langword="null"/> will be returned instead.
/// </summary>
/// <param name="content">The content item holding the outbound rediderect.</param>
/// <param name="propertyAlias">The alias of the property.</param>
/// <returns>An instance of <see cref="IOutboundRedirect"/> if successful; otherwise, <see langword="null"/>.</returns>
public static IOutboundRedirect GetOutboundRedirectOrDefault(this IPublishedContent content, string propertyAlias) {
if (string.IsNullOrWhiteSpace(propertyAlias)) throw new ArgumentNullException(nameof(propertyAlias));
return content.Value(propertyAlias) as IOutboundRedirect;
}

/// <summary>
/// Returns an instance of <see cref="IOutboundRedirect"/> representing the outbound redirect from the first property
/// matching the specified <paramref name="propertyAliases"/> where the value is an <see cref="IOutboundRedirect"/>.
///
/// If the property doesn't hold a <see cref="IOutboundRedirect"/> value, <see langword="null"/> will be returned instead.
/// </summary>
/// <param name="content">The content item holding the outbound rediderect.</param>
/// <param name="propertyAliases">The aliases of the properties.</param>
/// <returns>An instance of <see cref="IOutboundRedirect"/> if successful; otherwise, <see langword="null"/>.</returns>
public static IOutboundRedirect GetOutboundRedirectOrDefault(this IPublishedContent content, params string[] propertyAliases) {

if (propertyAliases == null) throw new ArgumentNullException(nameof(propertyAliases));

foreach (string alias in propertyAliases) {
if (content.Value(alias) is IOutboundRedirect redirect) return redirect;
}

return null;

}

}

}
86 changes: 86 additions & 0 deletions src/Skybrud.Umbraco.Redirects/Factories/RedirectsModelsFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using Newtonsoft.Json.Linq;
using Skybrud.Essentials.Json.Extensions;
using Skybrud.Umbraco.Redirects.Models;
using Skybrud.Umbraco.Redirects.Models.Outbound;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.Web;
using Umbraco.Extensions;

// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault

namespace Skybrud.Umbraco.Redirects.Factories {

/// <summary>
/// Factory class for various models used within the redirects package.
/// </summary>
public class RedirectsModelsFactory {

private readonly IUmbracoContextAccessor _umbracoContextAccessor;

#region Properties

/// <summary>
/// Gets a reference to the current <see cref="IUmbracoContext"/>, if any.
/// </summary>
protected IUmbracoContext UmbracoContext => _umbracoContextAccessor.GetRequiredUmbracoContext();

#endregion

#region Constructors

/// <summary>
/// Initializes a new instance based on the specified <see cref="IUmbracoContextAccessor"/>.
/// </summary>
/// <param name="umbracoContextAccessor">An instance of <see cref="IUmbracoContextAccessor"/>.</param>
public RedirectsModelsFactory(IUmbracoContextAccessor umbracoContextAccessor) {
_umbracoContextAccessor = umbracoContextAccessor;
}

#endregion

#region Member methods

/// <summary>
/// Creates and returns a new <see cref="OutboundRedirect"/> instance.
/// </summary>
/// <param name="json">A <see cref="JObject"/> instance representing the outbound redirect.</param>
/// <returns>An instance of <see cref="OutboundRedirect"/>.</returns>
public virtual IOutboundRedirect CreateOutboundRedirect(JObject json) {

if (json == null) return null;

// Gets the type of the redirect
RedirectType type = json.GetBoolean("permanent") ? RedirectType.Permanent : RedirectType.Temporary;

// Get whether query string forwarding should be enabled
bool forward = json.GetBoolean("forward");

// Parse the destination
RedirectDestination destination = json.GetObject("destination", RedirectDestination.Parse);
if (destination is not { IsValid: true }) return null;

// Look up the current URL for content and media
switch (destination.Type) {

case RedirectDestinationType.Content:
IPublishedContent content = UmbracoContext?.Content?.GetById(destination.Key);
if (content != null) destination.Url = content.Url();
break;

case RedirectDestinationType.Media:
IPublishedContent media = UmbracoContext?.Media?.GetById(destination.Key);
if (media != null) destination.Url = media.Url();
break;

}

// Initialize and return a new outbound redirect
return new OutboundRedirect(type, forward, destination, json);

}

#endregion

}

}
25 changes: 25 additions & 0 deletions src/Skybrud.Umbraco.Redirects/Models/Outbound/OutboundRedirect.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,31 @@ public class OutboundRedirect : JsonObjectBase, IOutboundRedirect {
Destination = obj.GetObject("destination", RedirectDestination.Parse);
}

/// <summary>
/// Initializes a new instance based on the specified <paramref name="type"/>, <paramref name="forward"/> and <paramref name="destination"/>.
/// </summary>
/// <param name="type">The type of the redirect.</param>
/// <param name="forward">Whether query string forwarding should be enabled.</param>
/// <param name="destination">The destination of the redirect.</param>
public OutboundRedirect(RedirectType type, bool forward, IRedirectDestination destination) : base(null) {
Type = type;
ForwardQueryString = forward;
Destination = destination;
}

/// <summary>
/// Initializes a new instance based on the specified <paramref name="type"/>, <paramref name="forward"/>, <paramref name="destination"/> and <paramref name="json"/>.
/// </summary>
/// <param name="type">The type of the redirect.</param>
/// <param name="forward">Whether query string forwarding should be enabled.</param>
/// <param name="destination">The destination of the redirect.</param>
/// <param name="json">A JSON object representing the redirect.</param>
public OutboundRedirect(RedirectType type, bool forward, IRedirectDestination destination, JObject json) : base(json) {
Type = type;
ForwardQueryString = forward;
Destination = destination;
}

#endregion

#region Static methods
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,53 @@
using Skybrud.Umbraco.Redirects.Models.Outbound;
using Newtonsoft.Json.Linq;
using Skybrud.Essentials.Json.Newtonsoft;
using Skybrud.Umbraco.Redirects.Factories;
using Skybrud.Umbraco.Redirects.Models.Outbound;
using Umbraco.Cms.Core.Models.PublishedContent;
using Umbraco.Cms.Core.PropertyEditors;
using Umbraco.Extensions;

namespace Skybrud.Umbraco.Redirects.PropertyEditors {

internal class OutboundRedirectValueConverter : PropertyValueConverterBase {

private readonly RedirectsModelsFactory _modelsFactory;

#region Constructors

public OutboundRedirectValueConverter(RedirectsModelsFactory modelsFactory) {
_modelsFactory = modelsFactory;
}

#endregion

#region Member methods

public override bool IsConverter(IPublishedPropertyType propertyType) {
return propertyType.EditorAlias == OutboundRedirectEditor.EditorAlias;
}

public override object ConvertSourceToIntermediate(IPublishedElement owner, IPublishedPropertyType propertyType, object source, bool preview) {
return OutboundRedirect.Deserialize(source as string);
return source is string json && json.DetectIsJson() ? JsonUtils.ParseJsonObject(json) : null;
}

public override object ConvertIntermediateToObject(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) {
return inter as OutboundRedirect;
return _modelsFactory.CreateOutboundRedirect(inter as JObject);
}

public override object ConvertIntermediateToXPath(IPublishedElement owner, IPublishedPropertyType propertyType, PropertyCacheLevel referenceCacheLevel, object inter, bool preview) {
return null;
}

public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) => PropertyCacheLevel.None;
public override PropertyCacheLevel GetPropertyCacheLevel(IPublishedPropertyType propertyType) {
return PropertyCacheLevel.None;
}

public override System.Type GetPropertyValueType(IPublishedPropertyType propertyType) {
return typeof(IOutboundRedirect);
}

#endregion

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@

const vm = this;

if (!$scope.model.value) $scope.model.value = {};
if ($scope.model.value.permanent === undefined) $scope.model.value.permanent = true;

// Parse the redirect type
vm.type = $scope.model.value.permanent ? "permanent" : "temporary";

function editLink(link) {
skybrudRedirectsService.editLink(link, function(model) {
$scope.model.value.destination = model;
skybrudRedirectsService.editLink(link, function (model) {
$scope.model.value = {
permanent: vm.redirectType.value === "true",
forward: vm.forward.value === "true",
destination: model
};
editorService.close();
});
}
Expand All @@ -24,12 +22,49 @@
};

vm.removeLink = function () {
$scope.model.value.destination = null;
$scope.model.value = "";
};

vm.updated = function () {
if (!$scope.model.value) return;
$scope.model.value.permanent = vm.redirectType.value === "true";
$scope.model.value.forward = vm.forward.value === "true";
};

vm.redirectType = {
uniqueId: `_${Math.random().toString(36).substr(2, 12)}`,
value: $scope.model.value?.permanent === true ? "true" : "false",
options: [
{
label: "Permanent",
labelKey: "redirects_labelPermanent",
value: "true"
},
{
label: "Temporary",
labelKey: "redirects_labelTemporary",
value: "false"
}
]
};

vm.changed = function() {
$scope.model.value.permanent = vm.type === "permanent";
vm.forward = {
uniqueId: `_${Math.random().toString(36).substr(2, 12)}`,
value: $scope.model.value?.forward === true ? "true" : "false",
options: [
{
label: "Enabled",
labelKey: "redirects_labelEnabled",
value: "true"
},
{
label: "Disabled",
labelKey: "redirects_labelDisabled",
value: "false"
}
]
};

if ($scope.model.value && !$scope.model.value.destination) $scope.model.value = "";

});
42 changes: 42 additions & 0 deletions src/Skybrud.Umbraco.Redirects/wwwroot/Styles/Styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,48 @@
max-width: initial;
width: 100%;
}
.skybrud-outbound-redirect {
border: 1px solid #E9E9EB;
border-radius: 3px;
padding: 12px 15px;
max-width: 1100px;
box-sizing: border-box;
}
.skybrud-outbound-redirect-properties {
display: flex;
flex-direction: column;
gap: 20px 0;
flex: 1;
}
.skybrud-outbound-redirect-property {
display: flex;
flex-wrap: wrap;
gap: 3px 20px;
}
.skybrud-outbound-redirect-label {
flex: 0;
font-size: 14px;
min-width: 250px;
font-weight: bold;
}
.skybrud-outbound-redirect-label small {
font-weight: normal;
}
.skybrud-outbound-redirect-value {
flex: 1;
min-width: 60%;
}
.skybrud-outbound-redirect-radio-list {
display: flex;
}
.skybrud-outbound-redirect-radio-list > div + div {
margin-left: 15px;
}
.skybrud-outbound-redirect .umb-node-preview[single] {
border: 1px solid #E9E9EB;
border-radius: 3px;
padding: 12px 15px;
}
/*
New styling lives here while still "work in progress"
*/
Expand Down
Loading

0 comments on commit e999162

Please sign in to comment.