Skip to content

Commit

Permalink
Twitter Metadata field support (#234)
Browse files Browse the repository at this point in the history
* 🚧 additional opengraph/twitter fields

* addresses #191 by adding support for Twitter-specific metadata and OpenGraph Url

* added Facebook app_Id and separated twitter fields into their own group

* reconsolidated all social media fields under "Social Media"
  • Loading branch information
robertjf authored Sep 23, 2023
1 parent 8ee64df commit ad07e56
Show file tree
Hide file tree
Showing 20 changed files with 299 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Collections.Generic;
using SeoToolkit.Umbraco.MetaFields.Core.Common.Converters.EditorConverters;
using SeoToolkit.Umbraco.MetaFields.Core.Interfaces.Converters;
using SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField;
using SeoToolkit.Umbraco.MetaFields.Core.Models.Converters;

namespace SeoToolkit.Umbraco.MetaFields.Core.Common.SeoFieldEditEditors
{
public class SeoDropdownEditEditor : ISeoFieldEditEditor
{
public string View => "/App_Plugins/SeoToolkit/MetaFields/Interface/SeoFieldEditors/PropertyEditor/dropdownList.html";
public Dictionary<string, object> Config { get; }
public IEditorValueConverter ValueConverter { get; }

public SeoDropdownEditEditor(string[] items)
{
ValueConverter = new TextValueConverter();
Config = new Dictionary<string, object>
{
{"items", items}
};
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using SeoToolkit.Umbraco.MetaFields.Core.Common.Converters.EditorConverters;
using SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField;

namespace SeoToolkit.Umbraco.MetaFields.Core.Common.SeoFieldEditors
{
public class DropdownFieldPropertyEditor : SeoFieldPropertyEditor, ISeoFieldEditorProcessor
{

public DropdownFieldPropertyEditor(string[] items) : base("/App_Plugins/SeoToolkit/MetaFields/Interface/SeoFieldEditors/PropertyEditor/dropdownList.html", new TextValueConverter())
{
IsPreValue = true;
Config.Add("items", items);
}

public object HandleValue(object value)
{
return value;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@

namespace SeoToolkit.Umbraco.MetaFields.Core.Common.SeoFieldGroups
{
public class OpenGraphFieldsGroup : ISeoFieldGroup
public class SocialMediaFieldsGroup : ISeoFieldGroup
{
public string Alias => SeoFieldGroupConstants.OpenGraphGroup;
public string Name => "Open Graph";
public string Alias => SeoFieldGroupConstants.SocialMediaGroup;
public string Name => "Social Media";
public string Description => "Fields that indicate how your page will look on social media platforms";
public ISeoFieldPreviewer Previewer { get; }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@

namespace SeoToolkit.Umbraco.MetaFields.Core.Common.SeoFieldPreviewers
{
public class OpenGraphPreviewer : ISeoFieldPreviewer
public class SocialMediaPreviewer : ISeoFieldPreviewer
{
public string Group => SeoFieldGroupConstants.OpenGraphGroup;
public string Group => SeoFieldGroupConstants.SocialMediaGroup;
public string View => "/App_Plugins/SeoToolkit/MetaFields/Interface/Previewers/OpenGraph/openGraphPreviewer.html";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,14 @@ public void Compose(IUmbracoBuilder builder)
.Add<OpenGraphTitleField>()
.Add<OpenGraphDescriptionField>()
.Add<OpenGraphImageField>()
.Add<OpenGraphUrlField>()
.Add<CanonicalUrlField>()
.Add<RobotsField>()
.Add<SeoSchemaField>();
.Add<SeoSchemaField>()
.Add<TwitterCardTypeField>()
.Add<TwitterSiteField>()
.Add<TwitterCreatorField>()
.Add<FacebookIdField>();

if (settings?.ShowKeywordsField is true)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,12 @@ public static class SeoFieldAliasConstants
public const string OpenGraphImage = "openGraphImage";
public const string CanonicalUrl = "canonicalUrl";
public const string Robots = "robots";

public const string OpenGraphUrl = "openGraphUrl";
public const string TwitterCardType = "twitterCardType";
public const string TwitterSite = "twitterSite";
public const string TwitterCreator = "twitterCreator";

public const string FacebookId = "facebookId";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
public static class SeoFieldGroupConstants
{
public const string MetaFieldsGroup = "metaFields";
public const string OpenGraphGroup = "openGraph";
public const string SocialMediaGroup = "socialMedia";
// public const string TwitterGroup = "twitter";
// public const string FacebookGroup = "facebook";
public const string Others = "others";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ public IActionResult Get(int nodeId, string culture)
EditConfig = key.EditEditor.Config
};
}).ToArray(),
Previewers = new[] { new FieldPreviewerViewModel(new MetaFieldsPreviewer()), new FieldPreviewerViewModel(new OpenGraphPreviewer()) }
Previewers = new[] { new FieldPreviewerViewModel(new MetaFieldsPreviewer()), new FieldPreviewerViewModel(new SocialMediaPreviewer()) }
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Html;
using Umbraco.Cms.Core.Composing;
using SeoToolkit.Umbraco.MetaFields.Core.Common.SeoFieldEditEditors;
using SeoToolkit.Umbraco.MetaFields.Core.Common.SeoFieldEditors;
using SeoToolkit.Umbraco.MetaFields.Core.Constants;
using SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField;
using Umbraco.Extensions;

namespace SeoToolkit.Umbraco.MetaFields.Core.Models.SeoField;

[Weight(701)]
public class FacebookIdField : SeoField<string>
{
public override string Title => "App Id";
public override string Alias => SeoFieldAliasConstants.FacebookId;
public override string Description => "Facebook app_id for the content";
public override string GroupAlias => SeoFieldGroupConstants.SocialMediaGroup;
public override ISeoFieldEditor Editor { get; }
public override ISeoFieldEditEditor EditEditor => new SeoTextBoxEditEditor();

public FacebookIdField()
{
var propertyEditor = new SeoFieldPropertyEditor("textbox");
propertyEditor.SetExtraInformation("Provide the Facebook app_id associated with this content");

Editor = propertyEditor;
}

protected override HtmlString Render(string value)
{
return new HtmlString(value.IsNullOrWhiteSpace() ? string.Empty : $"<meta name=\"fb:app_id\" content=\"{value}\"/>");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class OpenGraphDescriptionField : ISeoField
public string Title => "Open Graph Description";
public string Alias => SeoFieldAliasConstants.OpenGraphDescription;
public string Description => "Description for Open Graph";
public string GroupAlias => SeoFieldGroupConstants.OpenGraphGroup;
public string GroupAlias => SeoFieldGroupConstants.SocialMediaGroup;
public Type FieldType => typeof(string);

public ISeoFieldEditor Editor => new SeoFieldFieldsEditor(new[] { "Umbraco.TextBox", "Umbraco.TextArea", "Umbraco.TinyMCE" });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public class OpenGraphImageField : ISeoField
public string Title => "Open Graph Image";
public string Alias => SeoFieldAliasConstants.OpenGraphImage;
public string Description => "Image for Open Graph";
public string GroupAlias => SeoFieldGroupConstants.OpenGraphGroup;
public string GroupAlias => SeoFieldGroupConstants.SocialMediaGroup;
public Type FieldType => typeof(string);

public ISeoFieldEditor Editor => new SeoFieldFieldsEditor(new[] { "Umbraco.MediaPicker", "Umbraco.MediaPicker3" });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class OpenGraphTitleField : ISeoField
public string Title => "Open Graph Title";
public string Alias => SeoFieldAliasConstants.OpenGraphTitle;
public string Description => "Title for open graph";
public string GroupAlias => SeoFieldGroupConstants.OpenGraphGroup;
public string GroupAlias => SeoFieldGroupConstants.SocialMediaGroup;
public Type FieldType => typeof(string);

public ISeoFieldEditor Editor => new SeoFieldFieldsEditor(new[] { "Umbraco.TextBox", "Umbraco.TextArea", "Umbraco.TinyMCE" });
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Microsoft.AspNetCore.Html;
using Umbraco.Cms.Core.Composing;
using SeoToolkit.Umbraco.MetaFields.Core.Common.SeoFieldEditEditors;
using SeoToolkit.Umbraco.MetaFields.Core.Common.SeoFieldEditors;
using SeoToolkit.Umbraco.MetaFields.Core.Constants;
using SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField;
using Umbraco.Extensions;

namespace SeoToolkit.Umbraco.MetaFields.Core.Models.SeoField;

[Weight(600)]
public class OpenGraphUrlField : SeoField<string>
{
public override string Title => "Open Graph Url";
public override string Alias => SeoFieldAliasConstants.OpenGraphUrl;
public override string Description => "Open Graph Url for the content";
public override string GroupAlias => SeoFieldGroupConstants.SocialMediaGroup;
public override ISeoFieldEditor Editor { get; }
public override ISeoFieldEditEditor EditEditor => new SeoTextBoxEditEditor();

public OpenGraphUrlField()
{
var propertyEditor = new SeoFieldPropertyEditor("textbox");
propertyEditor.SetExtraInformation("You can use %CurrentUrl% to display the URL of the current item");
propertyEditor.SetDefaultValue("%CurrentUrl%");

Editor = propertyEditor;
}

protected override HtmlString Render(string value)
{
return new HtmlString(value.IsNullOrWhiteSpace() ? null : $"<meta name=\"og:url\" content=\"{value}\"/>");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using Microsoft.AspNetCore.Html;
using Umbraco.Cms.Core.Composing;
using SeoToolkit.Umbraco.MetaFields.Core.Common.SeoFieldEditEditors;
using SeoToolkit.Umbraco.MetaFields.Core.Common.SeoFieldEditors;
using SeoToolkit.Umbraco.MetaFields.Core.Constants;
using SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField;
using SeoToolkit.Umbraco.MetaFields.Core.Models.Converters;
using Umbraco.Extensions;
using System.Collections.Generic;
using System.Linq;

namespace SeoToolkit.Umbraco.MetaFields.Core.Models.SeoField;

[Weight(601)]
public class TwitterCardTypeField : SeoField<string>
{
public override string Title => "Twitter Card Type";
public override string Alias => SeoFieldAliasConstants.TwitterCardType;
public override string Description => "Twitter Card Type for your page";
public override string GroupAlias => SeoFieldGroupConstants.SocialMediaGroup;
public override ISeoFieldEditor Editor { get; }
public override ISeoFieldEditEditor EditEditor { get; }

public TwitterCardTypeField()
{
// App is relevant to direct download links to mobile apps; Player is relevant to video/audio media.
var items = new string[] {
"summary",
"summary_large_image",
"app",
"player"
};

var propertyEditor = new SeoDropdownEditEditor(items);
// propertyEditor.SetExtraInformation("Use one of 'summary', 'summary_large_image', 'app', 'player' or 'product'");
// propertyEditor.SetDefaultValue("summary");

Editor = new DropdownFieldPropertyEditor(items);

EditEditor = propertyEditor;
}

protected override HtmlString Render(string value)
{
if (value is null) {
return HtmlString.Empty;
}
return new HtmlString(value.IsNullOrWhiteSpace() ? null : $"<meta name=\"twitter:card\" content=\"{value}\"/>");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Html;
using Umbraco.Cms.Core.Composing;
using SeoToolkit.Umbraco.MetaFields.Core.Common.SeoFieldEditEditors;
using SeoToolkit.Umbraco.MetaFields.Core.Common.SeoFieldEditors;
using SeoToolkit.Umbraco.MetaFields.Core.Constants;
using SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField;
using Umbraco.Extensions;

namespace SeoToolkit.Umbraco.MetaFields.Core.Models.SeoField;

[Weight(603)]
public class TwitterCreatorField : SeoField<string>
{
public override string Title => "Twitter Creator";
public override string Alias => SeoFieldAliasConstants.TwitterCreator;
public override string Description => "Twitter Creator for the content";
public override string GroupAlias => SeoFieldGroupConstants.SocialMediaGroup;
public override ISeoFieldEditor Editor { get; }
public override ISeoFieldEditEditor EditEditor => new SeoTextBoxEditEditor();

public TwitterCreatorField()
{
var propertyEditor = new SeoFieldPropertyEditor("textbox");
propertyEditor.SetExtraInformation("Provide the Twitter username of the content creator");

Editor = propertyEditor;
}

protected override HtmlString Render(string value)
{
// Check for the "@" at the start of the value and remove if present - we'll add it in.
if (value.StartsWith("@")) {
value = value[1..];
}
return new HtmlString(value.IsNullOrWhiteSpace() ? null : $"<meta name=\"twitter:creator\" content=\"@{value}\"/>");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Html;
using Umbraco.Cms.Core.Composing;
using SeoToolkit.Umbraco.MetaFields.Core.Common.SeoFieldEditEditors;
using SeoToolkit.Umbraco.MetaFields.Core.Common.SeoFieldEditors;
using SeoToolkit.Umbraco.MetaFields.Core.Constants;
using SeoToolkit.Umbraco.MetaFields.Core.Interfaces.SeoField;
using Umbraco.Extensions;

namespace SeoToolkit.Umbraco.MetaFields.Core.Models.SeoField;

[Weight(602)]
public class TwitterSiteField : SeoField<string>
{
public override string Title => "Twitter Site";
public override string Alias => SeoFieldAliasConstants.TwitterSite;
public override string Description => "Twitter Site for the content";
public override string GroupAlias => SeoFieldGroupConstants.SocialMediaGroup;
public override ISeoFieldEditor Editor { get; }
public override ISeoFieldEditEditor EditEditor => new SeoTextBoxEditEditor();

public TwitterSiteField()
{
var propertyEditor = new SeoFieldPropertyEditor("textbox");
propertyEditor.SetExtraInformation("Provide the Twitter username of the website");

Editor = propertyEditor;
}

protected override HtmlString Render(string value)
{
// Check for the "@" at the start of the value and remove if present - we'll add it in.
if (value.StartsWith("@")) {
value = value[1..];
}
return new HtmlString(value.IsNullOrWhiteSpace() ? string.Empty : $"<meta name=\"twitter:site\" content=\"@{value}\"/>");
}
}
2 changes: 1 addition & 1 deletion src/SeoToolkit.Umbraco.MetaFields/Composer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void Compose(IUmbracoBuilder builder)

builder.WithCollectionBuilder<SeoGroupCollectionBuilder>()
.Append<MetaFieldsGroup>()
.Append<OpenGraphFieldsGroup>()
.Append<SocialMediaFieldsGroup>()
.Append<OthersFieldGroup>();
}
}
Expand Down
1 change: 1 addition & 0 deletions src/SeoToolkit.Umbraco.MetaFields/ManifestLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public void Filter(List<PackageManifest> manifests)
"/App_Plugins/SeoToolkit/MetaFields/Interface/SeoFieldEditors/FieldsEditor/fieldsEditor.controller.js",
"/App_Plugins/SeoToolkit/MetaFields/Interface/SeoFieldEditors/PropertyEditor/propertyEditor.controller.js",
"/App_Plugins/SeoToolkit/MetaFields/Interface/SeoFieldEditors/PropertyEditor/noSelectCheckboxList.controller.js",
"/App_Plugins/SeoToolkit/MetaFields/Interface/SeoFieldEditors/PropertyEditor/dropdownList.controller.js",
"/App_Plugins/SeoToolkit/MetaFields/Interface/Components/ItemGroupPicker/itemGroupPicker.controller.js",
"/App_Plugins/SeoToolkit/MetaFields/Interface/SeoFieldEditors/seoFieldEditor.directive.js",
"/App_Plugins/SeoToolkit/MetaFields/Interface/Previewers/previewer.directive.js",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
angular.module("umbraco").controller("SeoToolkit.SeoFieldEditors.DropdownListController",
function ($scope) {

//ensure when form is saved that we don't store [] or [null] as string values in the database when no items are selected
$scope.$on("formSubmitting", function () {
if ($scope.model.value !== null && ($scope.model.value.length === 0 || $scope.model.value[0] === null)) {
$scope.model.value = null;
}
});

$scope.updateSingleDropdownValue = function() {
$scope.model.value = $scope.model.singleDropdownValue;
}

//now we need to check if the value is null/undefined, if it is we need to set it to "" so that any value that is set
// to "" gets selected by default
if ($scope.model.value === null || $scope.model.value === undefined) {
$scope.model.value = "";
}

// if we run in single mode we'll store the value in a local variable
// so we can pass an array as the model as our PropertyValueEditor expects that
$scope.model.singleDropdownValue = "";
if ($scope.model.value) {
$scope.model.singleDropdownValue = $scope.model.value;
}
});
Loading

0 comments on commit ad07e56

Please sign in to comment.