diff --git a/.github/README.md b/.github/README.md index 3e6ec3c7..227c46db 100644 --- a/.github/README.md +++ b/.github/README.md @@ -24,6 +24,7 @@ Let's take a look inside... ##### Property Editors - [Bytes](../docs/editors/bytes.md) - a read-only label to display file sizes in relative bytes. +- [Code Editor](../docs/editors/code-editor.md) - a code snippet editor, _(using the ACE library that is bundled with Umbraco)._ - [Content Blocks](../docs/editors/content-blocks.md) - a block editor, configurable using element types. - [Data List](../docs/editors/data-list.md) - an editor that combines a custom data source with a custom list editor. - [Icon Picker](../docs/editors/icon-picker.md) - an editor to select an icon (from the Umbraco icon library). diff --git a/.github/ROADMAP.md b/.github/ROADMAP.md index 386e080d..7ae3b38e 100644 --- a/.github/ROADMAP.md +++ b/.github/ROADMAP.md @@ -35,10 +35,15 @@ Property Editors are: ### v1.3 -- Code Editor _(using ACE bundled with Umbraco)_ +- [Code Editor](../docs/editors/code-editor.md) _(using ACE bundled with Umbraco)_ - Data List: Preview _(a real time preview of the configured Data Source and List Editor)_ +- Data List: Buttons _(list editor, similar to what folk see used in Umbraco Uno)_ - Data List: Tags _(list editor, visually similar to Umbraco Tags editor)_ +### v1.4 + +- 🤫 + ## v2 diff --git a/VERSION b/VERSION index cb174d58..589268e6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.1 \ No newline at end of file +1.3.0 \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 259cb34e..8c44072d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,6 +13,7 @@ Here is the documentation for the Contentment property-editors... - [Bytes](../docs/editors/bytes.md) - a read-only label to display file sizes in relative bytes. +- [Code Editor](../docs/editors/code-editor.md) - a code snippet editor, _(using the ACE library that is bundled with Umbraco)._ - [Content Blocks](../docs/editors/content-blocks.md) - a block editor, configurable using element types. - [Data List](../docs/editors/data-list.md) - an editor that combines a custom data source with a custom list editor. - [Icon Picker](../docs/editors/icon-picker.md) - an editor to select an icon (from the Umbraco icon library). diff --git a/docs/editors/code-editor--configuration-editor.png b/docs/editors/code-editor--configuration-editor.png new file mode 100644 index 00000000..57d3c351 Binary files /dev/null and b/docs/editors/code-editor--configuration-editor.png differ diff --git a/docs/editors/code-editor--property-editor-01.png b/docs/editors/code-editor--property-editor-01.png new file mode 100644 index 00000000..57d3c351 Binary files /dev/null and b/docs/editors/code-editor--property-editor-01.png differ diff --git a/docs/editors/code-editor.md b/docs/editors/code-editor.md new file mode 100644 index 00000000..8f8a6f9c --- /dev/null +++ b/docs/editors/code-editor.md @@ -0,0 +1,72 @@ +Contentment for Umbraco logo + +## Contentment for Umbraco + +### Code Editor + +Code Editor is a property-editor that is used to enter code snippets (as content), makes use of [AWS Cloud 9's Ace editor](https://ace.c9.io/) library that is distributed with Umbraco. + + +### How to configure the editor? + +In your new Data Type, selected the "[Contentment] Code Editor" option. You will see the following configuration fields. + +![Configuration Editor for Code Editor](code-editor--configuration-editor.png) + +The first field is **Language mode**, this is used to select the programming language mode, for code syntax highlighting. The default mode is "Razor", meaning that you can use a combination of HTML, CSS, JavaScript and Razor syntax. + +The next field is **Theme**, which is used to set the visual appearance of the code editor. The default _(and only)_ theme is "Chrome". + +> **Please note,** by default, Umbraco ships a streamlined set of programming language modes and themes. +> +> If you would like to add more modes and themes, you can do this by [downloading the latest pre-packaged version of the Ace editor](https://github.com/ajaxorg/ace-builds/releases) and copy any of the `mode-*` or `theme-*` files from the `src-min-noconflict` folder over to the `~/umbraco/lib/ace-builds/src-min-noconflict/` folder in you Umbraco installation. +> +> Once you've done this, you can reload the Data Type screen, and the new programming language modes and themes will appear in the dropdown options for the fields above. + + +The **Font size** field is used to set the font size, the value must be a [valid CSS font-size value](https://developer.mozilla.org/en-US/docs/Web/CSS/font-size), e.g. `14px`, `80%`, `0.8em`, etc. The default size is "`small`". + +The **Word wrapping** option can enable the code editor to wrap the text around to the following line. + +The next two fields, **Minimum lines** and **Maximum lines**, are used to set the default height size of the code editor. If you would like the height to auto-scale forever, then set the maximum number to something ridiculously high. If left empty, the height of the code editor will remain at a fixed height and not auto-scale. + + +### How to use the editor? + +Once you have added the configured Data Type to your Document Type, the Code Editor editor will be displayed on the content page's property panel. + +![Code Editor property-editor](code-editor--property-editor-01.png) + + +### How to get the value? + +The value for the Code Editor is a `string`. + +Programmatically, you would access the value exactly the same as Umbraco's Textarea editor, [see Umbraco's documentation for code snippet examples](https://our.umbraco.com/Documentation/Getting-Started/Backoffice/Property-Editors/Built-in-Property-Editors/Textarea/#mvc-view-example). + +If you are wanting to display the code content as a pre-formatted code snippet, I would recommend using the `
` and `` tags.
+
+Using Umbraco's Models Builder...
+
+```cshtml
+
@Html.Raw(Model.CodeEditor)
+``` + +Without ModelsBuilder... + +Weakly-typed... + +```cshtml +
@Html.Raw(Model.Value("codeEditor"))
+``` + +Strongly-typed... + +```cshtml +
@Html.Raw(Model.Value("codeEditor"))
+``` + +For code syntax highlighting, the following JavaScript libraries are quite popular: + +- [Prism.js](https://prismjs.com/) +- [highlight.js](https://highlightjs.org/) diff --git a/docs/telemetry.md b/docs/telemetry.md index 26f996e3..220cfdd4 100644 --- a/docs/telemetry.md +++ b/docs/telemetry.md @@ -15,8 +15,12 @@ Here is an example of the JSON data that is sent. "dataType": "4E7D6B3A-F959-42E4-921E-081BC0E9E7EE", "editorAlias": "DataList", "umbracoId": "0403E47E-EFE7-4CF2-8E97-148681DAFC10", - "umbracoVersion": "8.6.6", - "contentmentVersion": "1.2.0", + "umbracoVersion": "8.6.8", + "contentmentVersion": "1.3.0", + "dataTypeConfig": { + "dataSource": "EnumDataListSource", + "listEditor": "CheckboxListDataListEditor", + } } ``` diff --git a/src/Umbraco.Community.Contentment.sln b/src/Umbraco.Community.Contentment.sln index b485ab86..d03ab422 100644 --- a/src/Umbraco.Community.Contentment.sln +++ b/src/Umbraco.Community.Contentment.sln @@ -41,6 +41,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Editors", "Editors", "{A5294B30-2ED5-4BBA-A2DE-A07103DAE78F}" ProjectSection(SolutionItems) = preProject ..\docs\editors\bytes.md = ..\docs\editors\bytes.md + ..\docs\editors\code-editor.md = ..\docs\editors\code-editor.md ..\docs\editors\content-blocks.md = ..\docs\editors\content-blocks.md ..\docs\editors\data-list.md = ..\docs\editors\data-list.md ..\docs\editors\icon-picker.md = ..\docs\editors\icon-picker.md diff --git a/src/Umbraco.Community.Contentment/Core/DictionaryExtensions.cs b/src/Umbraco.Community.Contentment/Core/DictionaryExtensions.cs index ca9589fa..e9ffae3f 100644 --- a/src/Umbraco.Community.Contentment/Core/DictionaryExtensions.cs +++ b/src/Umbraco.Community.Contentment/Core/DictionaryExtensions.cs @@ -11,7 +11,7 @@ internal static class DictionaryExtensions { public static TValueOut GetValueAs(this IDictionary config, TKey key, TValueOut defaultValue = default) { - if (config.TryGetValue(key, out var tmp)) + if (config.TryGetValue(key, out var tmp) == true) { if (tmp is TValueOut value) { @@ -19,7 +19,7 @@ public static TValueOut GetValueAs(this IDictionary(); - if (attempt.Success) + if (attempt.Success == true) { return attempt.Result; } @@ -30,7 +30,7 @@ public static TValueOut GetValueAs(this IDictionary(this IDictionary config, TKey key, out TValueOut value) { - if (config.TryGetValue(key, out var tmp1)) + if (config.TryGetValue(key, out var tmp1) == true) { if (tmp1 is TValueOut tmp2) { @@ -39,7 +39,7 @@ public static bool TryGetValueAs(this IDictionary(); - if (attempt.Success) + if (attempt.Success == true) { value = attempt.Result; return attempt.Success; diff --git a/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs new file mode 100644 index 00000000..4fe0c840 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/Buttons/ButtonsDataListEditor.cs @@ -0,0 +1,95 @@ +/* Copyright © 2020 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System.Collections.Generic; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Community.Contentment.DataEditors +{ + public sealed class ButtonsDataListEditor : IDataListEditor + { + internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "buttons.html"; + + public string Name => "Buttons"; + + public string Description => "Select multiple values from a group of buttons."; + + public string Icon => "icon-tab"; + + public IEnumerable Fields => new ConfigurationField[] + { + new ConfigurationField + { + Key = "defaultIcon", + Name = "Default icon", + Description = "Select an icon to be displayed as the default icon, (for when no icon is available).", + View = IOHelper.ResolveUrl("~/umbraco/views/propertyeditors/listview/icon.prevalues.html"), + }, + new ConfigurationField + { + Key = "size", + Name = "Size", + Description = "Select the button size. By default this is set to 'medium'.", + View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + Config = new Dictionary + { + { Constants.Conventions.ConfigurationFieldAliases.Items, new[] + { + new DataListItem { Name = "Small", Value = "s" }, + new DataListItem { Name = "Medium", Value = "m" }, + new DataListItem { Name = "Large", Value = "l" }, + } + }, + { Constants.Conventions.ConfigurationFieldAliases.DefaultValue, "m" } + } + }, + new ConfigurationField + { + Key = "labelStyle", + Name = "Label style", + Description = "Select the style of the button's label.", + View = IOHelper.ResolveUrl(RadioButtonListDataListEditor.DataEditorViewPath), + Config = new Dictionary + { + { Constants.Conventions.ConfigurationFieldAliases.Items, new[] + { + new DataListItem { Name = "Icon and Text", Value = "both", Description = "Displays both the item's icon and name." }, + new DataListItem { Name = "Icon only", Value = "icon", Description = "Hides the item's name and only displays the icon." }, + new DataListItem { Name = "Text only", Value = "text", Description = "Hides the item's icon and only displays the name." }, + } + }, + { Constants.Conventions.ConfigurationFieldAliases.DefaultValue, "both" }, + { ShowDescriptionsConfigurationField.ShowDescriptions, Constants.Values.True }, + } + }, + new ConfigurationField + { + Key = "enableMultiple", + Name = "Multiple selection?", + Description = "Select to enable picking multiple items.", + View = "boolean", + }, + }; + + public Dictionary DefaultValues => new Dictionary + { + { "defaultIcon", Core.Constants.Icons.DefaultIcon }, + { "labelStyle", "both" }, + }; + + public Dictionary DefaultConfig => default; + + public bool HasMultipleValues(Dictionary config) + { + return config.TryGetValue("enableMultiple", out var tmp) && tmp.TryConvertTo().Result; + } + + public OverlaySize OverlaySize => OverlaySize.Small; + + public string View => DataEditorViewPath; + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/Buttons/buttons.css b/src/Umbraco.Community.Contentment/DataEditors/Buttons/buttons.css new file mode 100644 index 00000000..550d4892 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/Buttons/buttons.css @@ -0,0 +1,21 @@ +/* Copyright © 2020 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +.contentment.lk-buttons .umb-button { + margin-bottom: 5px; +} + +.contentment.lk-buttons .umb-button--selected .umb-button__button { + background-color: #f5c1bc; +} + +.contentment.lk-buttons .umb-button__button { + padding-left: 20px; + padding-right: 20px; +} + + .contentment.lk-buttons .umb-button__button[disabled] { + opacity: 0.5; + } diff --git a/src/Umbraco.Community.Contentment/DataEditors/Buttons/buttons.html b/src/Umbraco.Community.Contentment/DataEditors/Buttons/buttons.html new file mode 100644 index 00000000..122b583d --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/Buttons/buttons.html @@ -0,0 +1,17 @@ + + +
+ + +
diff --git a/src/Umbraco.Community.Contentment/DataEditors/Buttons/buttons.js b/src/Umbraco.Community.Contentment/DataEditors/Buttons/buttons.js new file mode 100644 index 00000000..7c49d787 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/Buttons/buttons.js @@ -0,0 +1,92 @@ +/* Copyright © 2020 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors.Buttons.Controller", [ + "$scope", + function ($scope) { + + // console.log("buttons.model", $scope.model); + + var defaultConfig = { + defaultIcon: "icon-science", + defaultValue: [], + items: [], + enableMultiple: 0, + labelStyle: "both", + size: "m", + }; + var config = Object.assign({}, defaultConfig, $scope.model.config); + + var vm = this; + + function init() { + $scope.model.value = $scope.model.value || config.defaultValue; + + if (Array.isArray($scope.model.value) === false) { + $scope.model.value = [$scope.model.value]; + } + + vm.multiple = Object.toBoolean(config.enableMultiple); + + if (vm.multiple === false && $scope.model.value.length > 0) { + $scope.model.value.splice(1); + } + + vm.items = config.items.slice(); + + vm.hideIcon = config.labelStyle === 'text'; + vm.hideName = config.labelStyle === 'icon'; + + vm.uniqueId = $scope.model.hasOwnProperty("dataTypeKey") + ? [$scope.model.alias, $scope.model.dataTypeKey.substring(0, 8)].join("-") + : $scope.model.alias; + + var sizes = { + "s": "small", + "m": "medium", + "l": "large", + }; + + vm.size = config.size; + + vm.defaultIcon = config.defaultIcon; + vm.iconExtras = sizes[config.size] + (vm.hideName === false ? " mr2 " : " mr0 "); + + vm.items.forEach(function (item) { + item.selected = $scope.model.value.indexOf(item.value) > -1; + }); + + vm.select = select; + }; + + function select(item) { + + item.selected = item.selected === false; + $scope.model.value = []; + + vm.items.forEach(function (x) { + + if (vm.multiple === false) { + x.selected = x.value === item.value; + } + + if (x.selected) { + $scope.model.value.push(x.value); + } + + }); + + setDirty(); + }; + + function setDirty() { + if ($scope.propertyForm) { + $scope.propertyForm.$setDirty(); + } + }; + + init(); + } +]); diff --git a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesConfigurationEditor.cs index b561766c..8af6f7fa 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Bytes/BytesConfigurationEditor.cs @@ -57,7 +57,7 @@ public override IDictionary ToValueEditor(object configuration) { config.Add(Filter, "formatBytes"); - if (config.ContainsKey(Format) == false && config.ContainsKey(Kilo) && config.ContainsKey(Decimals)) + if (config.ContainsKey(Format) == false && config.ContainsKey(Kilo) == true && config.ContainsKey(Decimals) == true) { config.Add(Format, new { diff --git a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs index 77e85714..3885be21 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorConfigurationEditor.cs @@ -3,13 +3,139 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +using System.Collections.Generic; +using System.IO; +using Umbraco.Core; +using Umbraco.Core.IO; +using Umbraco.Core.PropertyEditors; + namespace Umbraco.Community.Contentment.DataEditors { - internal sealed class CodeEditorConfigurationEditor + internal sealed class CodeEditorConfigurationEditor : ConfigurationEditor { internal const string FontSize = "fontSize"; internal const string Mode = "mode"; internal const string Theme = "theme"; internal const string UseWrapMode = "useWrapMode"; + + public CodeEditorConfigurationEditor() + : base() + { + var targetPath = "~/umbraco/lib/ace-builds/src-min-noconflict/"; + var aceEditorPath = IOHelper.MapPath(targetPath); + + if (Directory.Exists(aceEditorPath) == true) + { + var aceEditorFiles = Directory.GetFiles(aceEditorPath, "*.js"); + if (aceEditorFiles != null && aceEditorFiles.Length > 0) + { + var modes = new List(); + var themes = new List(); + + foreach (var file in aceEditorFiles) + { + var filename = Path.GetFileNameWithoutExtension(file); + if (filename.StartsWith("mode-") == true) + { + var mode = filename.Replace("mode-", string.Empty).ToLower(); + modes.Add(new DataListItem { Name = mode.ToFirstUpperInvariant(), Value = mode }); + } + + if (filename.StartsWith("theme-") == true) + { + var theme = filename.Replace("theme-", string.Empty).ToLower(); + themes.Add(new DataListItem { Name = theme.ToFirstUpperInvariant(), Value = theme }); + } + } + + if (modes.Count > 0) + { + DefaultConfiguration.Add(Mode, "razor"); + Fields.Add( + Mode, + "Language mode", + "Select the programming language mode. The default mode is 'Razor'.", + IOHelper.ResolveUrl(DropdownListDataListEditor.DataEditorViewPath), + new Dictionary + { + { DropdownListDataListEditor.AllowEmpty, Constants.Values.False }, + { Constants.Conventions.ConfigurationFieldAliases.Items, modes }, + }); + } + + if (themes.Count > 0) + { + DefaultConfiguration.Add(Theme, "chrome"); + Fields.Add( + Theme, + nameof(Theme), + "Set the theme for the code editor. The default theme is 'Chrome'.", + IOHelper.ResolveUrl(DropdownListDataListEditor.DataEditorViewPath), + new Dictionary + { + { DropdownListDataListEditor.AllowEmpty, Constants.Values.False }, + { Constants.Conventions.ConfigurationFieldAliases.Items, themes }, + }); + } + + if (modes.Count > 0 || themes.Count > 0) + { + Fields.Add(new NotesConfigurationField($@"
+Would you like to add more language modes and themes? +
+

This property editor makes use of AWS Cloud 9's Ace editor library that is distributed with Umbraco. By default, Umbraco ships a streamlined set of programming language modes and themes.

+

If you would like to add more modes and themes, you can do this by downloading the latest pre-packaged version of the Ace editor and copy any of the mode-* or theme-* files from the src-min-noconflict folder over to the {targetPath} folder in this Umbraco installation.

+

When you reload this screen, the new programming language modes and themes will appear in the dropdown options above.

+
+
", true)); + } + } + } + + DefaultConfiguration.Add(FontSize, "small"); + Fields.Add( + FontSize, + "Font size", + @"Set the font size. The value must be a valid CSS font-size value. The default size is 'small'.", + IOHelper.ResolveUrl(TextInputDataEditor.DataEditorViewPath), + new Dictionary + { + { Constants.Conventions.ConfigurationFieldAliases.Items, new[] { + new DataListItem { Name = "Extra extra small", Value = "xx-small" }, + new DataListItem { Name = "Extra small", Value = "x-small" }, + new DataListItem { Name = "Small", Value = "small" }, + new DataListItem { Name = "Medium", Value = "medium" }, + new DataListItem { Name = "Large", Value = "large" }, + new DataListItem { Name = "Extra large", Value = "x-large" }, + new DataListItem { Name = "Extra extra large", Value = "xx-large" }, + new DataListItem { Name = "Extra extra extra large", Value = "xxx-large" }, + new DataListItem { Name = "Use pixels?", Value = "14px" }, + new DataListItem { Name = "Use percentage?", Value = "80%" }, + new DataListItem { Name = "Use ems?", Value = "0.8em" }, + new DataListItem { Name = "Use rems?", Value = "1.2rem" }, + } }, + }); + + Fields.Add(UseWrapMode, "Word wrapping", "Select to enable word wrapping.", "boolean"); + + // NOTE: [LK:2019-06-07] Hidden the advanced options (for now), need to review. + //Fields.Add("showGutter", "Show gutter?", "Select to show the left-hand side gutter in the code editor.", "boolean"); + //Fields.Add("firstLineNumber", "First Line Number", "[A friendly description]", "number"); + //Fields.Add("showInvisibles", "showInvisibles", "[A friendly description]", "boolean");// showInvisibles: 0, + //Fields.Add("showIndentGuides", "showIndentGuides", "[A friendly description]", "boolean");// showIndentGuides: 0, + //Fields.Add("useSoftTabs", "useSoftTabs", "[A friendly description]", "boolean");// useSoftTabs: 1, + //Fields.Add("showPrintMargin", "showPrintMargin", "[A friendly description]", "boolean");// showPrintMargin: 0, + //Fields.Add("disableSearch", "disableSearch", "[A friendly description]", "boolean");// disableSearch: 0, + //Fields.Add("enableSnippets", "enableSnippets", "[A friendly description]", "boolean");// enableSnippets: 0, + //Fields.Add("enableBasicAutocompletion", "enableBasicAutocompletion", "[A friendly description]", "boolean");// enableBasicAutocompletion: 0, + //Fields.Add("enableLiveAutocompletion", "enableLiveAutocompletion", "[A friendly description]", "boolean");// enableLiveAutocompletion: 0, + //Fields.Add("readonly", "readonly", "[A friendly description]", "boolean");// readonly: 0, + + DefaultConfiguration.Add("minLines", 12); + Fields.Add("minLines", "Minimum lines", "Set the minimum number of lines that the editor will be. The default is 12 lines.", IOHelper.ResolveUrl(NumberInputDataEditor.DataEditorViewPath)); + + DefaultConfiguration.Add("maxLines", 30); + Fields.Add("maxLines", "Maximum lines", "Set the maximum number of lines that the editor can be. If left empty, the editor will not auto-scale.", IOHelper.ResolveUrl(NumberInputDataEditor.DataEditorViewPath)); + } } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs index 43c3822e..bc0331de 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorDataEditor.cs @@ -3,13 +3,33 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +using Umbraco.Core.Logging; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.PropertyEditors; + namespace Umbraco.Community.Contentment.DataEditors { - internal sealed class CodeEditorDataEditor + [DataEditor( + DataEditorAlias, + EditorType.PropertyValue, + DataEditorName, + DataEditorViewPath, + ValueType = ValueTypes.Text, + Group = Constants.Conventions.PropertyGroups.Code, + Icon = DataEditorIcon)] + internal sealed class CodeEditorDataEditor : DataEditor { internal const string DataEditorAlias = Constants.Internals.DataEditorAliasPrefix + "CodeEditor"; internal const string DataEditorName = Constants.Internals.DataEditorNamePrefix + "Code Editor"; internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "code-editor.html"; - internal const string DataEditorIcon = "icon-code"; + internal const string DataEditorIcon = "icon-fa fa-code"; + + public CodeEditorDataEditor(ILogger logger) + : base(logger) + { } + + protected override IConfigurationEditor CreateConfigurationEditor() => new CodeEditorConfigurationEditor(); + + protected override IDataValueEditor CreateValueEditor() => new TextOnlyValueEditor(Attribute); } } diff --git a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorValueConverter.cs new file mode 100644 index 00000000..2c331f35 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/CodeEditorValueConverter.cs @@ -0,0 +1,19 @@ +/* Copyright © 2020 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System; +using Umbraco.Core; +using Umbraco.Core.Models.PublishedContent; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Community.Contentment.DataEditors +{ + internal sealed class CodeEditorValueConverter : PropertyValueConverterBase + { + public override bool IsConverter(IPublishedPropertyType propertyType) => propertyType.EditorAlias.InvariantEquals(CodeEditorDataEditor.DataEditorAlias); + + public override Type GetPropertyValueType(IPublishedPropertyType propertyType) => typeof(string); + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/README.md b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/README.md index 9844960d..730e2bf7 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/README.md +++ b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/README.md @@ -1,10 +1,7 @@ ## Code Editor +Code Editor is a property-editor that uses Umbraco's ACE editor implementation as a input for code (as content). + ### Used interally by Code Editor is used in the configuration editors of a couple of Data List providers, namely SQL data source and Templated List editor. - - -### Future scope - -It has future scope to be a standalone property-editor. diff --git a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/code-editor.html b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/code-editor.html index eb2d54d8..6b86223a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/code-editor.html +++ b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/code-editor.html @@ -3,6 +3,6 @@ - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at https://mozilla.org/MPL/2.0/. --> -
+
diff --git a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/code-editor.js b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/code-editor.js index 2da7a0d1..406cb867 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/code-editor.js +++ b/src/Umbraco.Community.Contentment/DataEditors/CodeEditor/code-editor.js @@ -20,7 +20,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. theme: "chrome", mode: "javascript", firstLineNumber: 1, - fontSize: "14px", + fontSize: "small", enableSnippets: 0, enableBasicAutocompletion: 0, enableLiveAutocompletion: 0, @@ -35,6 +35,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. function init() { vm.readonly = Object.toBoolean(config.readonly); + vm.options = { autoFocus: false, showGutter: Object.toBoolean(config.showGutter), diff --git a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.js b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.js index f98dbda0..61160f8d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.js +++ b/src/Umbraco.Community.Contentment/DataEditors/ConfigurationEditor/configuration-editor.js @@ -6,11 +6,13 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors.ConfigurationEditor.Controller", [ "$scope", "$interpolate", + "$timeout", "editorService", + "eventsService", "localizationService", "overlayService", "Umbraco.Community.Contentment.Services.DevMode", - function ($scope, $interpolate, editorService, localizationService, overlayService, devModeService) { + function ($scope, $interpolate, $timeout, editorService, eventsService, localizationService, overlayService, devModeService) { // console.log("config-editor.model", $scope.model); @@ -102,6 +104,8 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. }); } + // NOTE: Must wait, otherwise the preview may not be ready to receive the event. + $timeout(function () { emit(); }, 100); }; function add() { @@ -167,6 +171,10 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. }); }; + function emit() { + eventsService.emit("contentment.config-editor.model", $scope.model); + }; + function populate(item, $index, propertyName) { var label = ""; @@ -232,6 +240,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. if ($scope.propertyForm) { $scope.propertyForm.$setDirty(); } + emit(); }; init(); diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs index 459afab7..516564d2 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlockPreviewView.cs @@ -24,7 +24,7 @@ protected override void SetViewData(ViewDataDictionary viewData) { void setProperty(string key, Action action) { - if (viewData.TryGetValueAs(key, out T tmp)) + if (viewData.TryGetValueAs(key, out T tmp) == true) { action(tmp); diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs index 52d7b46a..b6da9cd4 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksConfigurationEditor.cs @@ -127,13 +127,13 @@ public override IDictionary ToValueEditor(object configuration) var item = (JObject)array2[i]; // NOTE: Patches a breaking-change. I'd renamed `type` to become `key`. [LK:2020-04-03] - if (item.ContainsKey("key") == false && item.ContainsKey("type")) + if (item.ContainsKey("key") == false && item.ContainsKey("type") == true) { item.Add("key", item["type"]); item.Remove("type"); } - if (Guid.TryParse(item.Value("key"), out var guid) && _elementTypes.ContainsKey(guid)) + if (Guid.TryParse(item.Value("key"), out var guid) && _elementTypes.ContainsKey(guid) == true) { var elementType = _elementTypes[guid]; diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs index a297f55b..643969b4 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksDataValueEditor.cs @@ -43,7 +43,7 @@ public ContentBlocksDataValueEditor( public override object ToEditor(Property property, IDataTypeService dataTypeService, string culture = null, string segment = null) { var value = property.GetValue(culture, segment)?.ToString(); - if (string.IsNullOrWhiteSpace(value)) + if (string.IsNullOrWhiteSpace(value) == true) { return base.ToEditor(property, dataTypeService, culture, segment); } @@ -105,7 +105,7 @@ public override object ToEditor(Property property, IDataTypeService dataTypeServ public override object FromEditor(ContentPropertyData editorValue, object currentValue) { var value = editorValue?.Value?.ToString(); - if (string.IsNullOrWhiteSpace(value)) + if (string.IsNullOrWhiteSpace(value) == true) { return base.FromEditor(editorValue, currentValue); } diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs index faba7bc0..bb093bb3 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentBlocksValueConverter.cs @@ -54,7 +54,7 @@ public override object ConvertIntermediateToObject(IPublishedElement owner, IPub foreach (var item in items) { - if (item == null || item.ElementType.Equals(Guid.Empty)) + if (item == null || item.ElementType == Guid.Empty) continue; // NOTE: [LK:2019-09-03] Why `IPublishedCache` doesn't support Guids or UDIs, I do not know!? diff --git a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs index 557c413d..baddc58f 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/ContentBlocks/ContentTypeCacheHelper.cs @@ -42,7 +42,7 @@ public static void TryAdd(Guid guid, string alias, string icon = null) public static bool TryGetAlias(Guid key, out string alias, IContentTypeService contentTypeService = null) { - if (_forward.TryGetValue(key, out alias)) + if (_forward.TryGetValue(key, out alias) == true) return true; // The alias isn't cached, we can attempt to get it via the content-type service, using the GUID. @@ -62,7 +62,7 @@ public static bool TryGetAlias(Guid key, out string alias, IContentTypeService c public static bool TryGetIcon(string alias, out string icon, IContentTypeService contentTypeService = null) { - if (_icons.TryGetValue(alias, out icon)) + if (_icons.TryGetValue(alias, out icon) == true) return true; // The icon isn't cached, we can attempt to get it via the content-type service, using the alias. @@ -82,7 +82,7 @@ public static bool TryGetIcon(string alias, out string icon, IContentTypeService public static bool TryGetGuid(string alias, out Guid key, IContentTypeService contentTypeService = null) { - if (_reverse.TryGetValue(alias, out key)) + if (_reverse.TryGetValue(alias, out key) == true) return true; // The GUID isn't cached, we can attempt to get it via the content-type service, using the alias. diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs new file mode 100644 index 00000000..36d4b406 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListApiController.cs @@ -0,0 +1,47 @@ +/* Copyright © 2020 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Web.Http; +using Newtonsoft.Json.Linq; +using Umbraco.Core; +using Umbraco.Core.PropertyEditors; +using Umbraco.Web.Editors; +using Umbraco.Web.Mvc; +using Umbraco.Web.WebApi; + +namespace Umbraco.Community.Contentment.DataEditors +{ + [PluginController(Constants.Internals.PluginControllerName), IsBackOffice] + public sealed class DataListApiController : UmbracoAuthorizedJsonController + { + private readonly PropertyEditorCollection _propertyEditors; + + public DataListApiController(PropertyEditorCollection propertyEditors) + { + _propertyEditors = propertyEditors; + } + + [HttpPost] + public HttpResponseMessage GetPreview([FromBody] JObject data) + { + var config = data.ToObject>(); + + if (_propertyEditors.TryGet(DataListDataEditor.DataEditorAlias, out var propertyEditor) == true) + { + var alias = config.GetValueAs("alias", "preview"); + var configurationEditor = propertyEditor.GetConfigurationEditor(); + var valueEditorConfig = configurationEditor.ToValueEditor(config); + var valueEditor = propertyEditor.GetValueEditor(config); + + return Request.CreateResponse(HttpStatusCode.OK, new { config = valueEditorConfig, view = valueEditor.View, alias }); + } + + return Request.CreateResponse(HttpStatusCode.NotFound); + } + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs index beaec048..809ac1c2 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListConfigurationEditor.cs @@ -36,8 +36,8 @@ public DataListConfigurationEditor(ConfigurationEditorUtility utility) { EnableDevModeConfigurationField.EnableDevMode, Constants.Values.True }, }; - var dataSources = utility.GetConfigurationEditorModels(); - var listEditors = utility.GetConfigurationEditorModels(); + var dataSources = utility.GetConfigurationEditorModels().ToList(); + var listEditors = utility.GetConfigurationEditorModels().ToList(); Fields.Add( DataSource, @@ -47,7 +47,8 @@ public DataListConfigurationEditor(ConfigurationEditorUtility utility) new Dictionary(defaultConfigEditorConfig) { { Constants.Conventions.ConfigurationFieldAliases.AddButtonLabelKey, "contentment_configureDataSource" }, - { Constants.Conventions.ConfigurationFieldAliases.Items, dataSources } + { EnableFilterConfigurationField.EnableFilter, dataSources.Count > 10 ? Constants.Values.True : Constants.Values.False }, + { Constants.Conventions.ConfigurationFieldAliases.Items, dataSources }, }); Fields.Add( @@ -58,8 +59,15 @@ public DataListConfigurationEditor(ConfigurationEditorUtility utility) new Dictionary(defaultConfigEditorConfig) { { Constants.Conventions.ConfigurationFieldAliases.AddButtonLabelKey, "contentment_configureListEditor" }, - { Constants.Conventions.ConfigurationFieldAliases.Items, listEditors } + { EnableFilterConfigurationField.EnableFilter, dataSources.Count > 10 ? Constants.Values.True : Constants.Values.False }, + { Constants.Conventions.ConfigurationFieldAliases.Items, listEditors }, }); + + Fields.Add( + "preview", + "Preview", + null, + IOHelper.ResolveUrl(DataListDataEditor.DataEditorPreviewViewPath)); } public override IDictionary ToValueEditor(object configuration) @@ -68,10 +76,10 @@ public override IDictionary ToValueEditor(object configuration) var toValueEditor = new Dictionary(); - if (config.TryGetValueAs(DataSource, out JArray array1) && array1.Count > 0 && array1[0] is JObject item1) + if (config.TryGetValueAs(DataSource, out JArray array1) == true && array1.Count > 0 && array1[0] is JObject item1) { // NOTE: Patches a breaking-change. I'd renamed `type` to become `key`. [LK:2020-04-03] - if (item1.ContainsKey("key") == false && item1.ContainsKey("type")) + if (item1.ContainsKey("key") == false && item1.ContainsKey("type") == true) { item1.Add("key", item1["type"]); item1.Remove("type"); @@ -87,10 +95,10 @@ public override IDictionary ToValueEditor(object configuration) } } - if (config.TryGetValueAs(ListEditor, out JArray array2) && array2.Count > 0 && array2[0] is JObject item2) + if (config.TryGetValueAs(ListEditor, out JArray array2) == true && array2.Count > 0 && array2[0] is JObject item2) { // NOTE: Patches a breaking-change. I'd renamed `type` to become `key`. [LK:2020-04-03] - if (item2.ContainsKey("key") == false && item2.ContainsKey("type")) + if (item2.ContainsKey("key") == false && item2.ContainsKey("type") == true) { item2.Add("key", item2["type"]); item2.Remove("type"); diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs index 22137f86..3de04412 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListDataEditor.cs @@ -15,6 +15,7 @@ public sealed class DataListDataEditor : IDataEditor internal const string DataEditorAlias = Constants.Internals.DataEditorAliasPrefix + "DataList"; internal const string DataEditorName = Constants.Internals.DataEditorNamePrefix + "Data List"; internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "_empty.html"; + internal const string DataEditorPreviewViewPath = Constants.Internals.EditorsPathRoot + "data-list.preview.html"; internal const string DataEditorIcon = "icon-fa fa-list-ul"; private readonly ConfigurationEditorUtility _utility; @@ -53,12 +54,12 @@ public IDataValueEditor GetValueEditor(object configuration) var view = default(string); if (configuration is Dictionary config && - config.TryGetValueAs(DataListConfigurationEditor.ListEditor, out JArray array) && + config.TryGetValueAs(DataListConfigurationEditor.ListEditor, out JArray array) == true && array.Count > 0 && array[0] is JObject item) { // NOTE: Patches a breaking-change. I'd renamed `type` to become `key`. [LK:2020-04-03] - if (item.ContainsKey("key") == false && item.ContainsKey("type")) + if (item.ContainsKey("key") == false && item.ContainsKey("type") == true) { item.Add("key", item["type"]); item.Remove("type"); diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListValueConverter.cs index 119d12b5..03956a4d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataListValueConverter.cs @@ -125,13 +125,13 @@ private void TryGetPropertyTypeConfiguration(IPublishedPropertyType propertyType converter = default; if (propertyType.DataType.Configuration is Dictionary configuration && - configuration.TryGetValue(DataListConfigurationEditor.DataSource, out var tmp1) && + configuration.TryGetValue(DataListConfigurationEditor.DataSource, out var tmp1) == true && tmp1 is JArray array1 && array1.Count > 0 && array1[0] is JObject obj1 && - configuration.TryGetValue(DataListConfigurationEditor.ListEditor, out var tmp2) && + configuration.TryGetValue(DataListConfigurationEditor.ListEditor, out var tmp2) == true && tmp2 is JArray array2 && array2.Count > 0 && array2[0] is JObject obj2) { // NOTE: Patches a breaking-change. I'd renamed `type` to become `key`. [LK:2020-04-03] - if (obj1.ContainsKey("key") == false && obj1.ContainsKey("type")) + if (obj1.ContainsKey("key") == false && obj1.ContainsKey("type") == true) { obj1.Add("key", obj1["type"]); obj1.Remove("type"); @@ -146,7 +146,7 @@ tmp1 is JArray array1 && array1.Count > 0 && array1[0] is JObject obj1 && } // NOTE: Patches a breaking-change. I'd renamed `type` to become `key`. [LK:2020-04-03] - if (obj2.ContainsKey("key") == false && obj2.ContainsKey("type")) + if (obj2.ContainsKey("key") == false && obj2.ContainsKey("type") == true) { obj2.Add("key", obj2["type"]); obj2.Remove("type"); diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs index 58e50bd8..614d2f3f 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/EnumDataListSource.cs @@ -93,7 +93,7 @@ public IEnumerable GetItems(Dictionary config) }); } - if (config.TryGetValueAs("sortAlphabetically", out string boolean) && boolean.Equals("1")) + if (config.TryGetValueAs("sortAlphabetically", out string boolean) == true && boolean == "1") { return items.OrderBy(x => x.Name, StringComparer.InvariantCultureIgnoreCase); } @@ -103,7 +103,7 @@ public IEnumerable GetItems(Dictionary config) public Type GetValueType(Dictionary config) { - if (config.TryGetValueAs("enumType", out JArray array)) + if (config.TryGetValueAs("enumType", out JArray array) == true) { var enumType = array.ToObject(); if (enumType?.Length > 1) @@ -114,7 +114,7 @@ public Type GetValueType(Dictionary config) { var type = default(Type); try { type = assembly.GetType(enumType[1]); } catch (Exception ex) { _logger.Error(ex); } - if (type != null && type.IsEnum) + if (type != null && type.IsEnum == true) { return type; } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs index ffedc9d7..4d38a1ae 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/JsonDataListSource.cs @@ -104,7 +104,7 @@ public IEnumerable GetItems(Dictionary config) var url = config.GetValueAs("url", string.Empty); - if (string.IsNullOrWhiteSpace(url)) + if (string.IsNullOrWhiteSpace(url) == true) { return items; } @@ -118,7 +118,7 @@ public IEnumerable GetItems(Dictionary config) var itemsJsonPath = config.GetValueAs("itemsJsonPath", string.Empty); - if (string.IsNullOrWhiteSpace(itemsJsonPath)) + if (string.IsNullOrWhiteSpace(itemsJsonPath) == true) { return items; } @@ -189,7 +189,7 @@ private JToken GetJson(string url) { var content = string.Empty; - if (url.StartsWith("http", StringComparison.InvariantCultureIgnoreCase)) + if (url.StartsWith("http", StringComparison.InvariantCultureIgnoreCase) == true) { try { @@ -207,7 +207,7 @@ private JToken GetJson(string url) { // assume local file var path = IOHelper.MapPath(url); - if (File.Exists(path)) + if (File.Exists(path) == true) { content = File.ReadAllText(path); } @@ -218,7 +218,7 @@ private JToken GetJson(string url) } } - if (string.IsNullOrWhiteSpace(content)) + if (string.IsNullOrWhiteSpace(content) == true) { _logger.Warn($"The contents of '{url}' was empty. Unable to process JSON data."); diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs index 958899e5..6368ad8a 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/SqlDataListSource.cs @@ -99,7 +99,7 @@ public IEnumerable GetItems(Dictionary config) var query = config.GetValueAs("query", string.Empty); var connectionString = config.GetValueAs("connectionString", string.Empty); - if (string.IsNullOrWhiteSpace(query) || string.IsNullOrWhiteSpace(connectionString)) + if (string.IsNullOrWhiteSpace(query) == true || string.IsNullOrWhiteSpace(connectionString) == true) return items; var settings = ConfigurationManager.ConnectionStrings[connectionString]; @@ -107,7 +107,7 @@ public IEnumerable GetItems(Dictionary config) return items; // NOTE: SQLCE uses a different connection/command. I'm trying to keep this as generic as possible, without resorting to using NPoco. [LK] - if (settings.ProviderName.InvariantEquals(Core.Constants.DatabaseProviders.SqlCe)) + if (settings.ProviderName.InvariantEquals(Core.Constants.DatabaseProviders.SqlCe) == true) { items.AddRange(GetSqlItems(query, settings.ConnectionString)); } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs index 20db7ad4..17d6d40d 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/TextDelimitedDataListSource.cs @@ -112,7 +112,7 @@ public IEnumerable GetItems(Dictionary config) var iconIndex = config.GetValueAs("iconIndex", -1); var descriptionIndex = config.GetValueAs("descriptionIndex", -1); - if (string.IsNullOrWhiteSpace(url)) + if (string.IsNullOrWhiteSpace(url) == true) { return items; } @@ -129,7 +129,7 @@ public IEnumerable GetItems(Dictionary config) for (var i = 0; i < lines.Length; i++) { - if (i == 0 && ignoreFirstLine) + if (i == 0 && ignoreFirstLine == true) { continue; } @@ -174,7 +174,7 @@ public IEnumerable GetItems(Dictionary config) private string[] GetTextLines(string url) { - if (url.InvariantStartsWith("http")) + if (url.InvariantStartsWith("http") == true) { try { @@ -192,7 +192,7 @@ private string[] GetTextLines(string url) { // assume local file var path = IOHelper.MapPath(url); - if (File.Exists(path)) + if (File.Exists(path) == true) { return File.ReadAllLines(path); } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs index 46b14b21..03520903 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/UmbracoContentDataListSource.cs @@ -81,7 +81,7 @@ public IEnumerable GetItems(Dictionary config) startNode = umbracoContext.Content.GetSingleByXPath(preview, parsed); } } - else if (GuidUdi.TryParse(parentNode, out var udi) && udi.Guid.Equals(Guid.Empty) == false) + else if (GuidUdi.TryParse(parentNode, out var udi) == true && udi.Guid != Guid.Empty) { startNode = _umbracoContextAccessor.UmbracoContext.Content.GetById(preview, udi.Guid); } @@ -106,7 +106,7 @@ public Type GetValueType(Dictionary config) public object ConvertValue(Type type, string value) { - if (type == typeof(IPublishedContent) && Udi.TryParse(value, out var udi)) + if (type == typeof(IPublishedContent) && Udi.TryParse(value, out var udi) == true) { return _umbracoContextAccessor.UmbracoContext.Content.GetById(udi); } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs index e5e9cfcc..10250209 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/DataSources/XmlDataListSource.cs @@ -103,7 +103,7 @@ public IEnumerable GetItems(Dictionary config) var url = config.GetValueAs("url", string.Empty); - if (string.IsNullOrWhiteSpace(url)) + if (string.IsNullOrWhiteSpace(url) == true) return items; var path = url.InvariantStartsWith("http") == false @@ -133,7 +133,7 @@ public IEnumerable GetItems(Dictionary config) var itemsXPath = config.GetValueAs("itemsXPath", string.Empty); - if (string.IsNullOrWhiteSpace(itemsXPath)) + if (string.IsNullOrWhiteSpace(itemsXPath) == true) { return items; } diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/data-list.preview.css b/src/Umbraco.Community.Contentment/DataEditors/DataList/data-list.preview.css new file mode 100644 index 00000000..17c8a8e5 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/data-list.preview.css @@ -0,0 +1,17 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +/* "Well" container - limiting the width, to match property width limit. */ +.contentment .well.well--limit-width { + max-width: 760px; +} + +.contentment .well.well-small.well--limit-width { + max-width: 780px; +} + +.contentment .well.well-large.well--limit-width { + max-width: 760px; +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/data-list.preview.html b/src/Umbraco.Community.Contentment/DataEditors/DataList/data-list.preview.html new file mode 100644 index 00000000..483eefbf --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/data-list.preview.html @@ -0,0 +1,61 @@ + + +
+
+
+ Please select and configure a data source and list editor. +
+
+ Please select and configure a list editor. +
+
+ Please select and configure a data source. +
+
+ +
+
+ The data source returned no items to preview. +
+
+ + + + +
+
+ +
+
+
+
+
+
+
Name
+
Value
+
Description
+
Enabled
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Umbraco.Community.Contentment/DataEditors/DataList/data-list.preview.js b/src/Umbraco.Community.Contentment/DataEditors/DataList/data-list.preview.js new file mode 100644 index 00000000..772dceab --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/DataList/data-list.preview.js @@ -0,0 +1,87 @@ +/* Copyright © 2020 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors.DataList.Preview.Controller", [ + "$scope", + "$http", + "eventsService", + "umbRequestHelper", + function ($scope, $http, eventsService, umbRequestHelper) { + + //console.log("data-list.preview.model", $scope.model); + + var config = Object.assign({ alias: $scope.model.alias }, $scope.model.config); + var vm = this; + + function init() { + + vm.property = {}; + vm.state = "loading"; + + vm.tabs = [{ + label: "Editor preview", + alias: "listEditor", + active: false, + }, { + label: "Data source items", + alias: "dataSource", + active: false, + }]; + + // set the first tab to active + vm.tabs[0].active = true; + vm.activeTab = vm.tabs[0].alias; + + vm.changeTab = function (tab) { + vm.tabs.forEach(function (x) { x.active = false; }); + vm.activeTab = tab.alias; + tab.active = true; + }; + + var events = []; + + events.push(eventsService.on("contentment.config-editor.model", function (event, args) { + config[args.alias] = args.value; + fetch(); + })); + + $scope.$on("$destroy", function () { + for (var event in events) { + eventsService.unsubscribe(events[event]); + } + }); + + }; + + function fetch() { + if (_.isEmpty(config.dataSource) === false && _.isEmpty(config.listEditor) === false) { + + vm.state = "loading"; + vm.property = null; + + umbRequestHelper.resourcePromise( + $http.post("backoffice/Contentment/DataListApi/GetPreview", config), + "Failed to retrieve data list preview.") + .then(function (result) { + + vm.property = result; + vm.state = result.config.items.length > 0 ? "loaded" : "noItems"; + + }); + } + else if (_.isEmpty(config.dataSource) === false) { + vm.state = "dataSourceConfigured"; + } + else if (_.isEmpty(config.listEditor) === false) { + vm.state = "listEditorConfigured"; + } + else { + vm.state = "init"; + } + }; + + init(); + } +]); diff --git a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs index 0bea99d7..38545921 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/DropdownListDataListEditor.cs @@ -25,7 +25,7 @@ public sealed class DropdownListDataListEditor : IDataListEditor { Key = AllowEmpty, Name = "Allow empty?", - Description = "Enable to allow an empty option at the top of the dropdown list.", + Description = "Enable to allow an empty option at the top of the dropdown list.
When disabled, the default value will be set to the first option.", View = "views/propertyeditors/boolean/boolean.html", Config = new Dictionary { @@ -35,7 +35,10 @@ public sealed class DropdownListDataListEditor : IDataListEditor new HtmlAttributesConfigurationField(), }; - public Dictionary DefaultValues => default; + public Dictionary DefaultValues => new Dictionary + { + { AllowEmpty, Constants.Values.True } + }; public Dictionary DefaultConfig => default; diff --git a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/dropdown-list.html b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/dropdown-list.html index 2e32e0a9..49fe1c15 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/dropdown-list.html +++ b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/dropdown-list.html @@ -10,6 +10,6 @@ ng-model="model.value" ng-change="vm.change()" ng-options="item.value as item.name disable when item.disabled for item in vm.items"> - +
diff --git a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/dropdown-list.js b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/dropdown-list.js index 25f96990..d0c423cd 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/DropdownList/dropdown-list.js +++ b/src/Umbraco.Community.Contentment/DataEditors/DropdownList/dropdown-list.js @@ -20,15 +20,21 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. var vm = this; function init() { + + config.showEmpty = Object.toBoolean(config.allowEmpty); + + vm.items = config.items.slice(); + $scope.model.value = $scope.model.value || config.defaultValue; if (Array.isArray($scope.model.value)) { $scope.model.value = $scope.model.value[0]; } + else if (config.showEmpty === false && $scope.model.value === '' && vm.items.length > 0) { + $scope.model.value = vm.items[0].value; + } - vm.items = config.items.slice(); - - vm.allowEmpty = Object.toBoolean(config.allowEmpty) && vm.items.some(x => x.value === $scope.model.value); + vm.showEmpty = config.showEmpty === true && vm.items.some(x => x.value === $scope.model.value); vm.htmlAttributes = config.htmlAttributes; @@ -40,7 +46,7 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors. }; function change() { - vm.allowEmpty = Object.toBoolean(config.allowEmpty) && vm.items.some(x => x.value === $scope.model.value); + vm.showEmpty = config.showEmpty === true && vm.items.some(x => x.value === $scope.model.value); }; init(); diff --git a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs index 2bf191ed..463d4a0e 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/Notes/NotesDataEditor.cs @@ -47,7 +47,7 @@ public IDataValueEditor GetValueEditor(object configuration) { var hideLabel = false; - if (configuration is Dictionary config && config.ContainsKey(HideLabelConfigurationField.HideLabelAlias)) + if (configuration is Dictionary config && config.ContainsKey(HideLabelConfigurationField.HideLabelAlias) == true) { hideLabel = config[HideLabelConfigurationField.HideLabelAlias].TryConvertTo().Result; } diff --git a/src/Umbraco.Community.Contentment/DataEditors/Notes/notes.css b/src/Umbraco.Community.Contentment/DataEditors/Notes/notes.css index 0e5012ab..c546d6db 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/Notes/notes.css +++ b/src/Umbraco.Community.Contentment/DataEditors/Notes/notes.css @@ -11,6 +11,11 @@ margin-bottom: 10px; } + .umb-readonlyvalue .well blockquote p { + font-size: 16px; + margin-bottom: 10px; + } + .umb-readonlyvalue .well hr { border-bottom: 1px solid #e0e0e5; margin: 15px 0; diff --git a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputValueConverter.cs b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputValueConverter.cs index d41c0fdd..66425714 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputValueConverter.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/NumberInput/NumberInputValueConverter.cs @@ -19,7 +19,7 @@ internal sealed class NumberInputValueConverter : PropertyValueConverterBase // TODO: [LK:2020-12-11] Commented out the value-type feature for the time being. Adds additional complexity that I don't currently need. //public override Type GetPropertyValueType(IPublishedPropertyType propertyType) //{ - // if (propertyType.DataType.Configuration is Dictionary config && config.TryGetValue(UmbConfigurationKeys.DataValueType, out var tmp) && tmp is string valueType) + // if (propertyType.DataType.Configuration is Dictionary config && config.TryGetValueAs(UmbConfigurationKeys.DataValueType, out string valueType) == true) // { // switch (valueType) // { diff --git a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs index 0e0f3e0b..5858f4a1 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/RenderMacro/RenderMacroDataEditor.cs @@ -47,7 +47,7 @@ public IDataValueEditor GetValueEditor(object configuration) { var hideLabel = false; - if (configuration is Dictionary config && config.ContainsKey(HideLabelConfigurationField.HideLabelAlias)) + if (configuration is Dictionary config && config.ContainsKey(HideLabelConfigurationField.HideLabelAlias) == true) { hideLabel = config[HideLabelConfigurationField.HideLabelAlias].TryConvertTo().Result; } diff --git a/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs new file mode 100644 index 00000000..cdd0fa49 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/Tags/TagsDataListEditor.cs @@ -0,0 +1,43 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +using System.Collections.Generic; +using Umbraco.Core.PropertyEditors; + +namespace Umbraco.Community.Contentment.DataEditors +{ + public sealed class TagsDataListEditor : IDataListEditor + { + internal const string DataEditorViewPath = Constants.Internals.EditorsPathRoot + "tags.html"; + + public string Name => "Tags"; + + public string Description => "Select items from an Umbraco Tags-like interface."; + + public string Icon => "icon-fa fa-tags"; + + public IEnumerable Fields => new ConfigurationField[] + { + new ShowIconsConfigurationField(), + new ConfigurationField + { + Key ="confirmRemoval", + Name = "Confirm removals?", + Description = "Select to enable a confirmation prompt when removing an item.", + View = "boolean", + } + }; + + public Dictionary DefaultValues => default; + + public Dictionary DefaultConfig => default; + + public bool HasMultipleValues(Dictionary config) => true; + + public OverlaySize OverlaySize => OverlaySize.Small; + + public string View => DataEditorViewPath; + } +} diff --git a/src/Umbraco.Community.Contentment/DataEditors/Tags/tags.css b/src/Umbraco.Community.Contentment/DataEditors/Tags/tags.css new file mode 100644 index 00000000..7621e854 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/Tags/tags.css @@ -0,0 +1,20 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +.contentment .umb-tags .tag { + user-select: text; +} + + .contentment .umb-tags .tag .icon { + color: white !important; + } + + .contentment .umb-tags .tag .btn-reset { + line-height: 14px; + } + + .contentment .umb-tags .tag .icon-trash { + bottom: 0; + } diff --git a/src/Umbraco.Community.Contentment/DataEditors/Tags/tags.html b/src/Umbraco.Community.Contentment/DataEditors/Tags/tags.html new file mode 100644 index 00000000..cf76f598 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/Tags/tags.html @@ -0,0 +1,31 @@ + + +
+
+ +
+ + + + + + +
+
+
diff --git a/src/Umbraco.Community.Contentment/DataEditors/Tags/tags.js b/src/Umbraco.Community.Contentment/DataEditors/Tags/tags.js new file mode 100644 index 00000000..bd176a19 --- /dev/null +++ b/src/Umbraco.Community.Contentment/DataEditors/Tags/tags.js @@ -0,0 +1,174 @@ +/* Copyright © 2021 Lee Kelleher. + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +angular.module("umbraco").controller("Umbraco.Community.Contentment.DataEditors.Tags.Controller", [ + "$rootScope", + "$scope", + "$element", + "angularHelper", + "assetsService", + "localizationService", + "overlayService", + function ($rootScope, $scope, $element, angularHelper, assetsService, localizationService, overlayService) { + + //console.log("tags.model", $scope.model); + + var defaultConfig = { + confirmRemoval: 0, + defaultValue: [], + items: [], + showIcons: 0, + }; + var config = Object.assign({}, defaultConfig, $scope.model.config); + + var vm = this; + + function init() { + + $scope.model.value = $scope.model.value || config.defaultValue; + + if (Array.isArray($scope.model.value) === false) { + $scope.model.value = [$scope.model.value]; + } + + config.confirmRemoval = Object.toBoolean(config.confirmRemoval); + + vm.items = []; + + vm.showIcons = Object.toBoolean(config.showIcons); + + vm.uniqueId = $scope.model.hasOwnProperty("dataTypeKey") + ? ["tags", $scope.model.alias, $scope.model.dataTypeKey.substring(0, 8)].join("-") + : ["tags", $scope.model.alias].join("-"); + + vm.add = add; + vm.keyDown = keyDown; + vm.keyUp = keyUp; + vm.remove = remove; + + vm.loading = true; + + assetsService.loadJs("lib/typeahead.js/typeahead.bundle.min.js").then(function () { + + $scope.model.value.forEach(function (v) { + var item = config.items.find(x => x.value === v); + if (item) { + vm.items.push(Object.assign({}, item)); + } + }); + + vm.loading = false; + + var engine = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace("name", "value"), + queryTokenizer: Bloodhound.tokenizers.whitespace, + initialize: false, + local: config.items.filter(x => !x.disabled), + identify: d => d.value, + }); + + engine.initialize().then(function () { + + var opts = { + hint: true, + highlight: true, + minLength: 1 + }; + + var sources = { + display: "name", + minLength: 0, + source: function (q, sync) { + if (q && q.length > 0) { + engine.search(q, sync); + } else { + sync(engine.all()); + } + } + }; + + vm.editor = $element.find("input.typeahead") + .typeahead(opts, sources) + .bind("typeahead:select", add) + .bind("typeahead:autocomplete", add); + }); + + }); + + }; + + function add($event, item) { + angularHelper.safeApply($rootScope, function () { + + vm.items.push(Object.assign({}, item)); + + $scope.model.value.push(item.value); + + vm.editor.typeahead("val", ""); + + setDirty(); + }); + }; + + function keyDown($event) { + if ($event.keyCode == 13) { + var tt = vm.editor.data("ttTypeahead"); + if (tt && tt.menu && tt.menu.isOpen() === true) { + var selection = tt.menu.getActiveSelectable() || tt.menu.getTopSelectable(); + tt.menu.trigger("selectableClicked", selection); + $event.preventDefault(); + } + } + }; + + function keyUp($event, $index) { + console.log("keyUp", $event.keyCode, $index); + if ($event.keyCode === 8 || $event.keyCode === 46) { + remove($index); + } + }; + + function remove($index) { + if (config.confirmRemoval === true) { + var keys = ["contentment_removeItemMessage", "general_remove", "general_cancel", "contentment_removeItemButton"]; + localizationService.localizeMany(keys).then(function (data) { + overlayService.open({ + title: data[1], + content: data[0], + closeButtonLabel: data[2], + submitButtonLabel: data[3], + submitButtonStyle: "danger", + submit: function () { + removeItem($index); + overlayService.close(); + }, + close: function () { + overlayService.close(); + } + }); + }); + } else { + removeItem($index); + } + }; + + function removeItem($index) { + + vm.items.splice($index, 1); + + $scope.model.value.splice($index, 1); + + setDirty(); + }; + + function setDirty() { + if ($scope.propertyForm) { + $scope.propertyForm.$setDirty(); + } + }; + + init(); + } +]); diff --git a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs index e013651b..8ffb59b0 100644 --- a/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs +++ b/src/Umbraco.Community.Contentment/DataEditors/TextInput/TextInputConfigurationEditor.cs @@ -21,7 +21,7 @@ public TextInputConfigurationEditor(ConfigurationEditorUtility utility) { _utility = utility; - var dataSources = _utility.GetConfigurationEditorModels(); + var dataSources = _utility.GetConfigurationEditorModels().ToList(); Fields.Add(new ConfigurationField { @@ -43,6 +43,7 @@ public TextInputConfigurationEditor(ConfigurationEditorUtility utility) { DisableSortingConfigurationField.DisableSorting, Constants.Values.True }, { Constants.Conventions.ConfigurationFieldAliases.OverlayView, IOHelper.ResolveUrl(ConfigurationEditorDataEditor.DataEditorOverlayViewPath) }, { EnableDevModeConfigurationField.EnableDevMode, Constants.Values.True }, + { EnableFilterConfigurationField.EnableFilter, dataSources.Count > 10 ? Constants.Values.True : Constants.Values.False }, { Constants.Conventions.ConfigurationFieldAliases.Items, dataSources }, }); @@ -75,10 +76,10 @@ public override IDictionary ToValueEditor(object configuration) { var config = base.ToValueEditor(configuration); - if (config.TryGetValueAs(Constants.Conventions.ConfigurationFieldAliases.Items, out JArray array) && array.Count > 0 && array[0] is JObject item) + if (config.TryGetValueAs(Constants.Conventions.ConfigurationFieldAliases.Items, out JArray array) == true && array.Count > 0 && array[0] is JObject item) { // NOTE: Patches a breaking-change. I'd renamed `type` to become `key`. - if (item.ContainsKey("key") == false && item.ContainsKey("type")) + if (item.ContainsKey("key") == false && item.ContainsKey("type") == true) { item.Add("key", item["type"]); item.Remove("type"); diff --git a/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs b/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs index f9eea8fd..0ebad52c 100644 --- a/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs +++ b/src/Umbraco.Community.Contentment/Properties/VersionInfo.cs @@ -1,5 +1,5 @@ using System.Reflection; -[assembly: AssemblyVersion("1.2")] -[assembly: AssemblyFileVersion("1.2.1")] -[assembly: AssemblyInformationalVersion("1.2.1-develop")] +[assembly: AssemblyVersion("1.3")] +[assembly: AssemblyFileVersion("1.3.0")] +[assembly: AssemblyInformationalVersion("1.3.0")] diff --git a/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs b/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs index 3d4e26c4..0fe9aeca 100644 --- a/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs +++ b/src/Umbraco.Community.Contentment/Telemetry/ContentmentTelemetryComponent.cs @@ -4,13 +4,16 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ using System; +using System.Collections.Generic; using System.Net; using System.Net.Mime; using System.Text; using System.Threading.Tasks; using ClientDependency.Core; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Umbraco.Community.Contentment.Configuration; +using Umbraco.Community.Contentment.DataEditors; using Umbraco.Core; using Umbraco.Core.Composing; using Umbraco.Core.Configuration; @@ -56,6 +59,51 @@ private void DataTypeService_Saved(IDataTypeService sender, SaveEventArgs(); + + if (entity.Configuration is Dictionary config) + { + void AddConfigurationEditorKey(string alias) + { + if (config.ContainsKey(alias) == true && + config.TryGetValueAs(alias, out JArray array) == true && + array.Count > 0 && + array[0] is JObject item && + item.ContainsKey("key") == true) + { + var key = item.Value("key"); + + if (key.InvariantStartsWith(Constants.Internals.ProjectNamespace) == true && key.Length > 73) + { + // Strips off the namespace and assembly. + // e.g. "Umbraco.Community.Contentment.DataEditors.[DataSourceName], Umbraco.Community.Contentment" + key = key.Substring(42, key.Length - 73); + } + + dataTypeConfig.Add(alias, key); + } + }; + + switch (entity.EditorAlias) + { + case DataListDataEditor.DataEditorAlias: + AddConfigurationEditorKey(DataListConfigurationEditor.DataSource); + AddConfigurationEditorKey(DataListConfigurationEditor.ListEditor); + break; + + case ContentBlocksDataEditor.DataEditorAlias: + AddConfigurationEditorKey(ContentBlocksConfigurationEditor.DisplayMode); + break; + + case TextInputDataEditor.DataEditorAlias: + AddConfigurationEditorKey(Constants.Conventions.ConfigurationFieldAliases.Items); + break; + + default: + break; + } + } + // TODO: [LK] After v8.10.0 bump, switch this to use `IUmbracoSettingsSection.BackOffice.Id`. var umbracoId = _umbracoContextAccessor.UmbracoContext?.HttpContext?.Server != null ? new Guid(_umbracoContextAccessor.UmbracoContext.HttpContext.Server.MachineName.GenerateMd5()) @@ -69,6 +117,7 @@ private void DataTypeService_Saved(IDataTypeService sender, SaveEventArgs + + @@ -311,6 +313,7 @@ + @@ -321,6 +324,7 @@ + @@ -415,6 +419,9 @@ + + + @@ -431,6 +438,9 @@ + + + @@ -445,6 +455,9 @@ + + + diff --git a/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs b/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs index b3b6dfb2..fe8c3872 100644 --- a/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs +++ b/src/Umbraco.Community.Contentment/Web/Controllers/EnumDataListSourceApiController.cs @@ -33,7 +33,7 @@ public IEnumerable GetAssemblies() { foreach (var assembly in assemblies) { - if (options.ContainsKey(assembly.FullName) || assembly.IsDynamic) + if (options.ContainsKey(assembly.FullName) == true || assembly.IsDynamic == true) continue; var hasEnums = false; @@ -41,7 +41,7 @@ public IEnumerable GetAssemblies() { foreach (var exportedType in assembly.ExportedTypes) { - if (exportedType.IsEnum) + if (exportedType.IsEnum == true) { hasEnums = true; break; @@ -52,7 +52,7 @@ public IEnumerable GetAssemblies() if (hasEnums == false) continue; - if (assembly.FullName.StartsWith(App_Code) && options.ContainsKey(App_Code) == false) + if (assembly.FullName.StartsWith(App_Code) == true && options.ContainsKey(App_Code) == false) { options.Add(App_Code, new DataListItem { Name = App_Code, Value = App_Code }); } diff --git a/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs b/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs index 33175758..67776567 100644 --- a/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs +++ b/src/Umbraco.Community.Contentment/Web/Serialization/PublishedContentContractResolver.cs @@ -2,16 +2,17 @@ * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -/* This code has been derived from Rasmus Fjord's code, supplied on an - * Our Umbraco forum post: +/* This code was originally based on code by Rasmus Fjord, + * supplied on an Our Umbraco forum post: * https://our.umbraco.com/forum/umbraco-8/98381-serializing-an-publishedcontentmodel-modelsbuilder-model-in-v8#comment-310148 * From searching GitHub, I also found René Pjengaard's code: * https://github.com/rpjengaard/merchelloshop/blob/master/dev/code/Json/Resolvers/PublishedContentContractResolver.cs * It is unknown (to me) who the original author is, and how the code - * has been licensed. I'll assume it's available under MIT license. */ + * has been licensed. My assumption is under the MIT license. */ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -20,26 +21,43 @@ namespace Umbraco.Community.Contentment.Web.Serialization { - public sealed class PublishedContentContractResolver : CamelCasePropertyNamesContractResolver + public sealed class PublishedContentContractResolver : DefaultContractResolver { public static readonly PublishedContentContractResolver Instance = new PublishedContentContractResolver(); - private readonly Dictionary _converterLookup; + private readonly Dictionary _converterLookup; + private readonly HashSet _ignoreFromCustom; + private readonly HashSet _ignoreFromElement; private readonly HashSet _ignoreFromContent; private readonly HashSet _ignoreFromProperty; + private readonly HashSet _systemProperties; public PublishedContentContractResolver() + : base() { - _converterLookup = new Dictionary(StringComparer.OrdinalIgnoreCase) + NamingStrategy = new CamelCaseNamingStrategy { - { nameof(IPublishedContent.ItemType), new StringEnumConverter() }, + OverrideSpecifiedNames = true, + ProcessDictionaryKeys = true, }; - _ignoreFromContent = new HashSet(StringComparer.OrdinalIgnoreCase) + _converterLookup = new Dictionary() + { + { typeof(PublishedItemType), new StringEnumConverter() } + }; + + _ignoreFromCustom = new HashSet(StringComparer.OrdinalIgnoreCase); + + _ignoreFromElement = new HashSet(StringComparer.OrdinalIgnoreCase) + { + nameof(IPublishedElement.ContentType), + nameof(IPublishedElement.Properties), + }; + + _ignoreFromContent = new HashSet(_ignoreFromElement, StringComparer.OrdinalIgnoreCase) { nameof(IPublishedContent.Children), nameof(IPublishedContent.ChildrenForAllCultures), - nameof(IPublishedContent.ContentType), nameof(IPublishedContent.CreatorId), nameof(IPublishedContent.Cultures), nameof(IPublishedContent.Parent), @@ -51,24 +69,80 @@ public PublishedContentContractResolver() { nameof(IPublishedProperty.PropertyType), }; + + _systemProperties = new HashSet(StringComparer.OrdinalIgnoreCase) + { + nameof(IPublishedContent.CreateDate), + nameof(IPublishedContent.CreatorName), + nameof(IPublishedContent.Id), + nameof(IPublishedContent.ItemType), + nameof(IPublishedElement.Key), + nameof(IPublishedContent.Level), + nameof(IPublishedContent.Name), + nameof(IPublishedContent.Path), + nameof(IPublishedContent.SortOrder), + nameof(IPublishedContent.UpdateDate), + nameof(IPublishedContent.Url), + nameof(IPublishedContent.UrlSegment), + nameof(IPublishedContent.WriterName), + }; + } + + public string[] PropertiesToIgnore + { + set => _ignoreFromCustom.UnionWith(value); + } + + public Dictionary PropertyTypeConverters + { + set + { + foreach (var item in value) + { + if (_converterLookup.ContainsKey(item.Key) == false) + { + _converterLookup.Add(item.Key, item.Value); + } + } + } + } + + public bool PrefixSystemPropertyNamesWithUnderscore { private get; set; } + + protected override IList CreateProperties(Type type, MemberSerialization memberSerialization) + { + return base.CreateProperties(type, memberSerialization).OrderBy(p => p.PropertyName).ToList(); } protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) { var property = base.CreateProperty(member, memberSerialization); - if (typeof(IPublishedContent).IsAssignableFrom(member.DeclaringType)) + if (_ignoreFromCustom.Contains(member.Name) == true) { - property.ShouldSerialize = _ => _ignoreFromContent.Contains(property.PropertyName) == false; + property.ShouldSerialize = _ => false; } - else if (typeof(IPublishedProperty).IsAssignableFrom(member.DeclaringType)) + else if (typeof(IPublishedContent).IsAssignableFrom(member.DeclaringType) == true) + { + property.ShouldSerialize = _ => _ignoreFromContent.Contains(member.Name) == false; + } + else if (typeof(IPublishedElement).IsAssignableFrom(member.DeclaringType) == true) + { + property.ShouldSerialize = _ => _ignoreFromElement.Contains(member.Name) == false; + } + else if (typeof(IPublishedProperty).IsAssignableFrom(member.DeclaringType) == true) + { + property.ShouldSerialize = _ => _ignoreFromProperty.Contains(member.Name) == false; + } + + if (_converterLookup.ContainsKey(property.PropertyType) == true) { - property.ShouldSerialize = _ => _ignoreFromProperty.Contains(property.PropertyName) == false; + property.Converter = _converterLookup[property.PropertyType]; } - if (_converterLookup.ContainsKey(property.PropertyName)) + if (PrefixSystemPropertyNamesWithUnderscore == true && _systemProperties.Contains(member.Name) == true) { - property.Converter = _converterLookup[property.PropertyName]; + property.PropertyName = "_" + property.PropertyName; } return property; diff --git a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/backoffice/contentment/index.html b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/backoffice/contentment/index.html index cda59ab8..219716a5 100644 --- a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/backoffice/contentment/index.html +++ b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/backoffice/contentment/index.html @@ -39,7 +39,7 @@ - + @@ -104,6 +104,8 @@
Tree dashboard
+
+
Telemetry

By default, the package sends telemetry data about which property-editors are being used (from Contentment only). For more details about the data being captured and transparency on the analysis, please visit leekelleher.com/umbraco/contentment/telemetry.

diff --git a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/da.xml b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/da.xml index e6f693f7..4702eb89 100644 --- a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/da.xml +++ b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/da.xml @@ -24,6 +24,11 @@ Vælg og konfigurer en datakilde Vælg og konfigurer en liste + Vælg og konfigurer en datakilde og en listeeditor, tak. + Vælg og konfigurer en datakilde, tak. + Vælg og konfigurer en liste, tak. + Datakilden returnerede ingen elementer til forhåndsvisning. + Indtast nøgleord (tryk på Enter efter hvert nøgleord)... diff --git a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/en.xml b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/en.xml index 903036df..08152da5 100644 --- a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/en.xml +++ b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/en.xml @@ -24,6 +24,11 @@ Select and configure a data source Select and configure a list editor + Please select and configure a data source and list editor. + Please select and configure a data source. + Please select and configure a list editor. + The data source returned no items to preview. + Type to select an item... diff --git a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/pt-br.xml b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/pt-br.xml index b749c22a..8aa8a340 100644 --- a/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/pt-br.xml +++ b/src/Umbraco.Community.Contentment/Web/UI/App_Plugins/Contentment/lang/pt-br.xml @@ -24,6 +24,11 @@ Selecionar e configurar a origem dos dados Selecionar e configurar o editor de lista + Selecionar e configurar a origem dos dados e o editor de lista, por favor. + Selecionar e configurar a origem dos dados, por favor. + Selecionar e configurar o editor de lista, por favor. + A fonte de dados não retornou nenhum item para visualização. + Digite para selecionar um item... diff --git a/src/Umbraco.Community.Contentment/Web/UI/backoffice-ui-shim.css b/src/Umbraco.Community.Contentment/Web/UI/backoffice-ui-shim.css index 0555aa78..c4d74d22 100644 --- a/src/Umbraco.Community.Contentment/Web/UI/backoffice-ui-shim.css +++ b/src/Umbraco.Community.Contentment/Web/UI/backoffice-ui-shim.css @@ -22,7 +22,7 @@ [class].icon-umbraco { background-image: url(/umbraco/assets/img/application/logo_black.png); background-repeat: no-repeat; - background-size: cover; + background-size: contain; } .umb-action-link .icon-umbraco { diff --git a/src/Umbraco.Community.Contentment/Web/UI/backoffice.js b/src/Umbraco.Community.Contentment/Web/UI/backoffice.js index 84fb3d3d..41c5eb03 100644 --- a/src/Umbraco.Community.Contentment/Web/UI/backoffice.js +++ b/src/Umbraco.Community.Contentment/Web/UI/backoffice.js @@ -63,7 +63,26 @@ angular.module("umbraco").controller("Umbraco.Community.Contentment.Tree.Control }; vm.vote = function (x) { - vm.nggyu = x == false; + if (x === false && !vm.nggyu) { + + vm.nggyu = true; + + // Kudos to Mathieu 'p01' Henri for this snippet: + // Music SoftSynth https://gist.github.com/p01/1285255 + var softSynth = function (f) { return eval("for(var t=0,S='RIFF_oO_WAVEfmt " + atob("EAAAAAEAAQBAHwAAQB8AAAEACAA") + "data';++t<3e5;)S+=String.fromCharCode(" + f + ")"); }; + var formula = "(t<<3)*[8/9,1,9/8,6/5,4/3,3/2,0][[0xd2d2c8,0xce4088,0xca32c8,0x8e4009][t>>14&3]>>(0x3dbe4688>>((t>>10&15)>9?18:t>>10&15)*3&7)*3&7]&255"; + vm.audio = new Audio("data:audio/wav;base64," + btoa(softSynth(formula))); + vm.audio.play(); + + } else { + + vm.nggyu = false; + + if (vm.audio) { + vm.audio.pause(); + } + + } }; vm.csharp = "csharp";