diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index b9f5696553..2679eaa411 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -2,15 +2,15 @@ 👍🎉 First off, thanks for taking the time to contribute! 🎉👍 -The following is a set of guidelines for contributing to Umbraco CMS. +The following is a set of guidelines, for contributing to Umbraco CMS. -These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. +These are mostly guidelines, not rules. Use your best judgement, and feel free to propose changes to this document in a pull request. Remember, we're a friendly bunch and are happy with whatever contribution you might provide. Below are guidelines for success that we've gathered over the years. If you choose to ignore them then we still love you 💖. **Code of conduct** -This project and everyone participating in it is governed by the [our Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [Sebastiaan Janssen - sj@umbraco.dk](mailto:sj@umbraco.dk). +This project and everyone participating in it, is governed by the [our Code of Conduct](CODE_OF_CONDUCT.md). By participating, you are expected to uphold this code. Please report unacceptable behavior to [Sebastiaan Janssen - sj@umbraco.dk](mailto:sj@umbraco.dk). **Table of contents** @@ -169,9 +169,9 @@ If you make the corrections we ask for in the same branch and push them to your ### Keeping your Umbraco fork in sync with the main repository -We recommend you sync with our repository before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier. +We recommend you to sync with our repository before you submit your pull request. That way, you can fix any potential merge conflicts and make our lives a little bit easier. -Also, if you submitted a pull request three weeks ago and want to work on something new, you'll want to get the latest code to build against of course. +Also, if you have submitted a pull request three weeks ago and want to work on something new, you'll want to get the latest code to build against of course. To sync your fork with this original one, you'll have to add the upstream url. You only have to do this once: diff --git a/.github/README.md b/.github/README.md index 23142186df..d6d978c3d6 100644 --- a/.github/README.md +++ b/.github/README.md @@ -21,7 +21,7 @@ Please also see our [Code of Conduct](CODE_OF_CONDUCT.md). [Umbraco Cloud](https://umbraco.com/cloud) is the easiest and fastest way to use Umbraco yet, with full support for all your custom .NET code and integrations. You're up and running in less than a minute, and your life will be made easier with automated upgrades and a built-in deployment engine. We offer a free 14-day trial, no credit card needed. -If you want to DIY, you can [download Umbraco]((https://our.umbraco.com/download)) either as a ZIP file or via NuGet. It's the same version of Umbraco CMS that powers Umbraco Cloud, but you'll need to find a place to host it yourself, and handling deployments and upgrades will be all up to you. +If you want to DIY, then you can [download Umbraco]((https://our.umbraco.com/download)) either as a ZIP file or via NuGet. It's the same version of Umbraco CMS that powers Umbraco Cloud, but you'll need to find a place to host it yourself, and handling deployments and upgrades will be all up to you. ## Documentation @@ -29,7 +29,7 @@ The documentation for Umbraco CMS can be found [on Our Umbraco](https://our.umbr ## Join the Umbraco community -Our friendly community is available 24/7 at the community hub we call ["Our Umbraco"](https://our.umbraco.com/). Our Umbraco features forums for questions and answers, documentation, downloadable plugins for Umbraco, and a rich collection of community resources. +Our friendly community is available 24/7 at the community hub, we call ["Our Umbraco"](https://our.umbraco.com/). Our Umbraco features forums for questions and answers, documentation, downloadable plugins for Umbraco, and a rich collection of community resources. Besides "Our", we all support each other also via Twitter: [Umbraco HQ](https://twitter.com/umbraco), [Release Updates](https://twitter.com/umbracoproject), [#umbraco](https://twitter.com/hashtag/umbraco) diff --git a/src/Umbraco.Core/Cache/CacheKeys.cs b/src/Umbraco.Core/Cache/CacheKeys.cs index e8f93d636a..b8ee0e97c4 100644 --- a/src/Umbraco.Core/Cache/CacheKeys.cs +++ b/src/Umbraco.Core/Cache/CacheKeys.cs @@ -11,5 +11,6 @@ public const string TemplateFrontEndCacheKey = "template"; public const string MacroContentCacheKey = "macroContent_"; // used in MacroRenderers + public const string MacroFromAliasCacheKey = "macroFromAlias_"; } } diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 309dc97b81..ffe20afdb3 100755 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -10,6 +10,7 @@ Umbraco.Core ..\ + $(AdditionalFileItemNames);Content true @@ -60,6 +61,11 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all + + 3.3.0 + runtime; build; native; contentfiles; analyzers + all + 1.3.0 @@ -1565,4 +1571,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Examine/ContentIndexPopulator.cs b/src/Umbraco.Examine/ContentIndexPopulator.cs index dd4176a774..99ff4d7f87 100644 --- a/src/Umbraco.Examine/ContentIndexPopulator.cs +++ b/src/Umbraco.Examine/ContentIndexPopulator.cs @@ -15,7 +15,7 @@ namespace Umbraco.Examine /// /// Performs the data lookups required to rebuild a content index /// - public class ContentIndexPopulator : IndexPopulator + public class ContentIndexPopulator : IndexPopulator { private readonly IContentService _contentService; private readonly IValueSetBuilder _contentValueSetBuilder; @@ -36,7 +36,7 @@ namespace Umbraco.Examine /// public ContentIndexPopulator(IContentService contentService, ISqlContext sqlContext, IContentValueSetBuilder contentValueSetBuilder) : this(false, null, contentService, sqlContext, contentValueSetBuilder) - { + { } /// diff --git a/src/Umbraco.Examine/IUmbracoContentIndex.cs b/src/Umbraco.Examine/IUmbracoContentIndex.cs new file mode 100644 index 0000000000..3181ff663e --- /dev/null +++ b/src/Umbraco.Examine/IUmbracoContentIndex.cs @@ -0,0 +1,9 @@ +using Examine; + +namespace Umbraco.Examine +{ + public interface IUmbracoContentIndex : IIndex + { + + } +} diff --git a/src/Umbraco.Examine/IUmbracoMemberIndex.cs b/src/Umbraco.Examine/IUmbracoMemberIndex.cs new file mode 100644 index 0000000000..b1f325b2e9 --- /dev/null +++ b/src/Umbraco.Examine/IUmbracoMemberIndex.cs @@ -0,0 +1,9 @@ +using Examine; + +namespace Umbraco.Examine +{ + public interface IUmbracoMemberIndex : IIndex + { + + } +} diff --git a/src/Umbraco.Examine/MemberIndexPopulator.cs b/src/Umbraco.Examine/MemberIndexPopulator.cs index e20dab91ca..26a3b0aedd 100644 --- a/src/Umbraco.Examine/MemberIndexPopulator.cs +++ b/src/Umbraco.Examine/MemberIndexPopulator.cs @@ -7,7 +7,7 @@ using Umbraco.Core.Services; namespace Umbraco.Examine { - public class MemberIndexPopulator : IndexPopulator + public class MemberIndexPopulator : IndexPopulator { private readonly IMemberService _memberService; private readonly IValueSetBuilder _valueSetBuilder; diff --git a/src/Umbraco.Examine/Umbraco.Examine.csproj b/src/Umbraco.Examine/Umbraco.Examine.csproj index e30d355dfe..db623ecddd 100644 --- a/src/Umbraco.Examine/Umbraco.Examine.csproj +++ b/src/Umbraco.Examine/Umbraco.Examine.csproj @@ -10,6 +10,7 @@ Umbraco.Examine ..\ + $(AdditionalFileItemNames);Content true @@ -56,6 +57,11 @@ + + 3.3.0 + runtime; build; native; contentfiles; analyzers + all + @@ -64,10 +70,12 @@ + + @@ -104,4 +112,4 @@ - \ No newline at end of file + diff --git a/src/Umbraco.Examine/UmbracoContentIndex.cs b/src/Umbraco.Examine/UmbracoContentIndex.cs index a9e2c72cb6..e266ca789d 100644 --- a/src/Umbraco.Examine/UmbracoContentIndex.cs +++ b/src/Umbraco.Examine/UmbracoContentIndex.cs @@ -17,13 +17,13 @@ namespace Umbraco.Examine /// /// An indexer for Umbraco content and media /// - public class UmbracoContentIndex : UmbracoExamineIndex + public class UmbracoContentIndex : UmbracoExamineIndex, IUmbracoContentIndex { public const string VariesByCultureFieldName = SpecialFieldPrefix + "VariesByCulture"; protected ILocalizationService LanguageService { get; } #region Constructors - + /// /// Create an index at runtime /// @@ -141,6 +141,6 @@ namespace Umbraco.Examine base.PerformDeleteFromIndex(idsAsList, onComplete); } - + } } diff --git a/src/Umbraco.Examine/UmbracoMemberIndex.cs b/src/Umbraco.Examine/UmbracoMemberIndex.cs index fbf8a1cc0f..445707ab0c 100644 --- a/src/Umbraco.Examine/UmbracoMemberIndex.cs +++ b/src/Umbraco.Examine/UmbracoMemberIndex.cs @@ -11,7 +11,7 @@ namespace Umbraco.Examine /// /// Custom indexer for members /// - public class UmbracoMemberIndex : UmbracoExamineIndex + public class UmbracoMemberIndex : UmbracoExamineIndex, IUmbracoMemberIndex { /// /// Constructor to allow for creating an indexer at runtime @@ -32,6 +32,6 @@ namespace Umbraco.Examine base(name, luceneDirectory, fieldDefinitions, analyzer, profilingLogger, validator) { } - + } } diff --git a/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js b/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js index ee9ce0bb64..24a6e65540 100644 --- a/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js +++ b/src/Umbraco.Web.UI.Client/gulp/tasks/watchTask.js @@ -20,14 +20,14 @@ function watchTask(cb) { //Setup a watcher for all groups of JS files _.forEach(config.sources.js, function (group) { if(group.watch !== false) { - watch(group.files, { ignoreInitial: true, interval: watchInterval }, function JS_Group_Compile() { return processJs(group.files, group.out) }); + watch(group.files, { ignoreInitial: true, interval: watchInterval }, function JS_Group_Compile() { return processJs(group.files, group.out);}); } }); //Setup a watcher for all groups of LESS files _.forEach(config.sources.less, function (group) { if(group.watch !== false) { - watch(group.watch, { ignoreInitial: true, interval: watchInterval }, function Less_Group_Compile() { processLess(group.files, group.out) }); + watch(group.watch, { ignoreInitial: true, interval: watchInterval }, function Less_Group_Compile() { return processLess(group.files, group.out); }); } }); @@ -38,7 +38,7 @@ function watchTask(cb) { viewWatcher = watch(group.files, { ignoreInitial: true, interval: watchInterval }); viewWatcher.on('change', function(path, stats) { console.log("copying " + group.files + " to " + config.root + config.targets.views + group.folder); - src(group.files).pipe( dest(config.root + config.targets.views + group.folder) ) + src(group.files).pipe( dest(config.root + config.targets.views + group.folder) ); }); } }); diff --git a/src/Umbraco.Web.UI.Client/gulp/util/processLess.js b/src/Umbraco.Web.UI.Client/gulp/util/processLess.js index 389b827332..94150043c1 100644 --- a/src/Umbraco.Web.UI.Client/gulp/util/processLess.js +++ b/src/Umbraco.Web.UI.Client/gulp/util/processLess.js @@ -16,7 +16,7 @@ module.exports = function(files, out) { cssnano({zindex: false}) ]; - console.log("LESS: ", files, " -> ", config.root + config.targets.js + out) + console.log("LESS: ", files, " -> ", config.root + config.targets.css + out) var task = gulp.src(files) .pipe(less()) diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js index 5398038113..cab71842b1 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/content/edit.controller.js @@ -271,8 +271,7 @@ function createButtons(content) { // for trashed and element type items, the save button is the primary action - otherwise it's a secondary action - $scope.page.saveButtonStyle = content.trashed || content.isElement ? "primary" : "info"; - + $scope.page.saveButtonStyle = content.trashed || content.isElement || content.isBlueprint ? "primary" : "info"; // only create the save/publish/preview buttons if the // content app is "Conent" if ($scope.app && $scope.app.alias !== "umbContent" && $scope.app.alias !== "umbInfo" && $scope.app.alias !== "umbListView") { diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js index ebb780c36e..5607251a7f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorheader.directive.js @@ -195,22 +195,55 @@ Use this directive to construct a header inside the main editor window. @param {string=} icon Show and edit the content icon. Opens an overlay to change the icon. @param {boolean=} hideIcon Set to true to hide icon. @param {string=} alias show and edit the content alias. -@param {boolean=} aliasLocked Set to true to lock the alias. @param {boolean=} hideAlias Set to true to hide alias. @param {string=} description Add a description to the content. @param {boolean=} hideDescription Set to true to hide description. - +@param {boolean=} setpagetitle If true the page title will be set to reflect the type of data the header is working with +@param {string=} editorfor The localization to use to aid accessibility on the edit and create screen **/ (function () { 'use strict'; - function EditorHeaderDirective(editorService) { + function EditorHeaderDirective(editorService, localizationService, editorState) { + + function link(scope, $injector) { - function link(scope) { scope.vm = {}; scope.vm.dropdownOpen = false; scope.vm.currentVariant = ""; + scope.loading = true; + scope.accessibility = {}; + scope.accessibility.a11yMessage = ""; + scope.accessibility.a11yName = ""; + scope.accessibility.a11yMessageVisible = false; + scope.accessibility.a11yNameVisible = false; + + // need to call localizationService service outside of routine to set a11y due to promise requirements + if (editorState.current) { + //to do make work for user create/edit + // to do make it work for user group create/ edit + // to make it work for language edit/create + scope.isNew = editorState.current.id === 0 || + editorState.current.id === "0" || + editorState.current.id === -1 || + editorState.current.id === 0 || + editorState.current.id === "-1"; + + var localizeVars = [ + scope.isNew ? "placeholders_a11yCreateItem" : "placeholders_a11yEdit", + "placeholders_a11yName", + scope.isNew ? "general_new" : "general_edit" + ]; + + if (scope.editorfor) { + localizeVars.push(scope.editorfor); + } + localizationService.localizeMany(localizeVars).then(function (data) { + setAccessibilityForEditor(data); + scope.loading = false; + }); + } scope.goBack = function () { if (scope.onBack) { @@ -247,6 +280,57 @@ Use this directive to construct a header inside the main editor window. editorService.iconPicker(iconPicker); }; + function setAccessibilityForEditor(data) { + + if (editorState.current) { + if (scope.nameLocked) { + scope.accessibility.a11yName = scope.name; + SetPageTitle(scope.name); + } else { + + scope.accessibility.a11yMessage = data[0]; + scope.accessibility.a11yName = data[1]; + var title = data[2] + ":"; + if (!scope.isNew) { + scope.accessibility.a11yMessage += " " + scope.name; + title += " " + scope.name; + } else { + var name = ""; + if (editorState.current.contentTypeName) { + name = editorState.current.contentTypeName; + } else if (scope.editorfor) { + name = data[3]; + } + if (name !== "") { + scope.accessibility.a11yMessage += " " + name; + scope.accessibility.a11yName = name + " " + scope.accessibility.a11yName; + title += " " + name; + } + } + if (title !== data[2] + ":") { + SetPageTitle(title); + } + + } + scope.accessibility.a11yMessageVisible = !isEmptyOrSpaces(scope.accessibility.a11yMessage); + scope.accessibility.a11yNameVisible = !isEmptyOrSpaces(scope.accessibility.a11yName); + } + + } + + function isEmptyOrSpaces(str) { + return str === null || str===undefined || str.trim ===''; + } + + function SetPageTitle(title) { + var setTitle = false; + if (scope.setpagetitle !== undefined) { + setTitle = scope.setpagetitle; + } + if (setTitle) { + scope.$emit("$changeTitle", title); + } + } } var directive = { @@ -262,7 +346,6 @@ Use this directive to construct a header inside the main editor window. icon: "=", hideIcon: "@", alias: "=", - aliasLocked: "<", hideAlias: "=", description: "=", hideDescription: "@", @@ -271,7 +354,9 @@ Use this directive to construct a header inside the main editor window. onSelectNavigationItem: "&?", key: "=", onBack: "&?", - showBackButton: " { - return entry.unique !== key; - } - ); - - var entry = {unique:key, type:type, alias:alias, data:shallowCloneData}; - storage.entries.push(entry); - - if (saveStorage(storage) === true) { - notificationsService.success("Clipboard", "Copied to clipboard."); - } else { - notificationsService.success("Clipboard", "Couldnt copy this data to clipboard."); - } - - }; - - - /** - * @ngdoc method - * @name umbraco.services.supportsCopy#supported - * @methodOf umbraco.services.clipboardService - * - * @description - * Determins wether the current browser is able to performe its actions. - */ - service.isSupported = function() { - return localStorageService.isSupported; - }; - - /** - * @ngdoc method - * @name umbraco.services.supportsCopy#hasEntriesOfType - * @methodOf umbraco.services.clipboardService - * - * @param {string} type A string defining the type of data test for. - * @param {string} aliases A array of strings providing the alias of the data you want to test for. - * - * @description - * Determines whether the current clipboard has entries that match a given type and one of the aliases. - */ - service.hasEntriesOfType = function(type, aliases) { - - if(service.retriveEntriesOfType(type, aliases).length > 0) { - return true; - } - - return false; - }; - - /** - * @ngdoc method - * @name umbraco.services.supportsCopy#retriveEntriesOfType - * @methodOf umbraco.services.clipboardService - * - * @param {string} type A string defining the type of data to recive. - * @param {string} aliases A array of strings providing the alias of the data you want to recive. - * - * @description - * Returns an array of entries matching the given type and one of the provided aliases. - */ - service.retriveEntriesOfType = function(type, aliases) { - - var storage = retriveStorage(); - - // Find entries that are fulfilling the criteria for this nodeType and nodeTypesAliases. - var filteretEntries = storage.entries.filter( - (entry) => { - return (entry.type === type && aliases.filter(alias => alias === entry.alias).length > 0); - } - ); - - return filteretEntries; - }; - - /** - * @ngdoc method - * @name umbraco.services.supportsCopy#retriveEntriesOfType - * @methodOf umbraco.services.clipboardService - * - * @param {string} type A string defining the type of data to recive. - * @param {string} aliases A array of strings providing the alias of the data you want to recive. - * - * @description - * Returns an array of data of entries matching the given type and one of the provided aliases. - */ - service.retriveDataOfType = function(type, aliases) { - return service.retriveEntriesOfType(type, aliases).map((x) => x.data); - }; - - /** - * @ngdoc method - * @name umbraco.services.supportsCopy#retriveEntriesOfType - * @methodOf umbraco.services.clipboardService - * - * @param {string} type A string defining the type of data to remove. - * @param {string} aliases A array of strings providing the alias of the data you want to remove. - * - * @description - * Removes entries matching the given type and one of the provided aliases. - */ - service.clearEntriesOfType = function(type, aliases) { - - var storage = retriveStorage(); - - // Find entries that are NOT fulfilling the criteria for this nodeType and nodeTypesAliases. - var filteretEntries = storage.entries.filter( - (entry) => { - return !(entry.type === type && aliases.filter(alias => alias === entry.alias).length > 0); - } - ); - - storage.entries = filteretEntries; - - saveStorage(storage); - }; - - - - return service; -} -angular.module("umbraco.services").factory("clipboardService", clipboardService); +/** + * @ngdoc service + * @name umbraco.services.clipboardService + * + * @requires notificationsService + * @requires eventsService + * + * @description + * Service to handle clipboard in general across the application. Responsible for handling the data both storing and retrive. + * The service has a set way for defining a data-set by a entryType and alias, which later will be used to retrive the posible entries for a paste scenario. + * + */ +function clipboardService(notificationsService, eventsService, localStorageService, iconHelper) { + + + var STORAGE_KEY = "umbClipboardService"; + + var retriveStorage = function() { + if (localStorageService.isSupported === false) { + return null; + } + var dataJSON; + var dataString = localStorageService.get(STORAGE_KEY); + if (dataString != null) { + dataJSON = JSON.parse(dataString); + } + + if(dataJSON == null) { + dataJSON = new Object(); + } + + if(dataJSON.entries === undefined) { + dataJSON.entries = []; + } + + return dataJSON; + } + + var saveStorage = function(storage) { + var storageString = JSON.stringify(storage); + + try { + var storageJSON = JSON.parse(storageString); + localStorageService.set(STORAGE_KEY, storageString); + + eventsService.emit("clipboardService.storageUpdate"); + + return true; + } catch(e) { + return false; + } + + return false; + } + + var prepareEntryForStorage = function(entryData) { + + var shallowCloneData = Object.assign({}, entryData);// Notice only a shallow copy, since we dont need to deep copy. (that will happen when storing the data) + delete shallowCloneData.key; + delete shallowCloneData.$$hashKey; + + return shallowCloneData; + } + + var isEntryCompatible = function(entry, type, allowedAliases) { + return entry.type === type + && + ( + (entry.alias && allowedAliases.filter(allowedAlias => allowedAlias === entry.alias).length > 0) + || + (entry.aliases && entry.aliases.filter(entryAlias => allowedAliases.filter(allowedAlias => allowedAlias === entryAlias).length > 0).length === entry.aliases.length) + ); + } + + + var service = {}; + + /** + * @ngdoc method + * @name umbraco.services.clipboardService#copy + * @methodOf umbraco.services.clipboardService + * + * @param {string} type A string defining the type of data to storing, example: 'elementType', 'contentNode' + * @param {string} alias A string defining the alias of the data to store, example: 'product' + * @param {object} entry A object containing the properties to be saved, this could be the object of a ElementType, ContentNode, ... + * @param {string} displayLabel (optional) A string swetting the label to display when showing paste entries. + * + * @description + * Saves a single JS-object with a type and alias to the clipboard. + */ + service.copy = function(type, alias, data, displayLabel) { + + var storage = retriveStorage(); + + var uniqueKey = data.key || data.$$hashKey || console.error("missing unique key for this content"); + + // remove previous copies of this entry: + storage.entries = storage.entries.filter( + (entry) => { + return entry.unique !== uniqueKey; + } + ); + + var entry = {unique:uniqueKey, type:type, alias:alias, data:prepareEntryForStorage(data), label:displayLabel || data.name, icon:iconHelper.convertFromLegacyIcon(data.icon)}; + storage.entries.push(entry); + + if (saveStorage(storage) === true) { + notificationsService.success("Clipboard", "Copied to clipboard."); + } else { + notificationsService.error("Clipboard", "Couldnt copy this data to clipboard."); + } + + }; + + + /** + * @ngdoc method + * @name umbraco.services.clipboardService#copyArray + * @methodOf umbraco.services.clipboardService + * + * @param {string} type A string defining the type of data to storing, example: 'elementTypeArray', 'contentNodeArray' + * @param {string} aliases An array of strings defining the alias of the data to store, example: ['banana', 'apple'] + * @param {object} datas An array of objects containing the properties to be saved, example: [ElementType, ElementType, ...] + * @param {string} displayLabel A string setting the label to display when showing paste entries. + * @param {string} displayIcon A string setting the icon to display when showing paste entries. + * @param {string} uniqueKey A string prodiving an identifier for this entry, existing entries with this key will be removed to ensure that you only have the latest copy of this data. + * + * @description + * Saves a single JS-object with a type and alias to the clipboard. + */ + service.copyArray = function(type, aliases, datas, displayLabel, displayIcon, uniqueKey) { + + var storage = retriveStorage(); + + // Clean up each entry + var copiedDatas = datas.map(data => prepareEntryForStorage(data)); + + // remove previous copies of this entry: + storage.entries = storage.entries.filter( + (entry) => { + return entry.unique !== uniqueKey; + } + ); + + var entry = {unique:uniqueKey, type:type, aliases:aliases, data:copiedDatas, label:displayLabel, icon:displayIcon}; + + storage.entries.push(entry); + + if (saveStorage(storage) === true) { + notificationsService.success("Clipboard", "Copied to clipboard."); + } else { + notificationsService.error("Clipboard", "Couldnt copy this data to clipboard."); + } + + }; + + + /** + * @ngdoc method + * @name umbraco.services.supportsCopy#supported + * @methodOf umbraco.services.clipboardService + * + * @description + * Determins wether the current browser is able to performe its actions. + */ + service.isSupported = function() { + return localStorageService.isSupported; + }; + + /** + * @ngdoc method + * @name umbraco.services.supportsCopy#hasEntriesOfType + * @methodOf umbraco.services.clipboardService + * + * @param {string} type A string defining the type of data test for. + * @param {string} aliases A array of strings providing the alias of the data you want to test for. + * + * @description + * Determines whether the current clipboard has entries that match a given type and one of the aliases. + */ + service.hasEntriesOfType = function(type, aliases) { + + if(service.retriveEntriesOfType(type, aliases).length > 0) { + return true; + } + + return false; + }; + + /** + * @ngdoc method + * @name umbraco.services.supportsCopy#retriveEntriesOfType + * @methodOf umbraco.services.clipboardService + * + * @param {string} type A string defining the type of data to recive. + * @param {string} aliases A array of strings providing the alias of the data you want to recive. + * + * @description + * Returns an array of entries matching the given type and one of the provided aliases. + */ + service.retriveEntriesOfType = function(type, allowedAliases) { + + var storage = retriveStorage(); + + // Find entries that are fulfilling the criteria for this nodeType and nodeTypesAliases. + var filteretEntries = storage.entries.filter( + (entry) => { + return isEntryCompatible(entry, type, allowedAliases); + } + ); + + return filteretEntries; + }; + + /** + * @ngdoc method + * @name umbraco.services.supportsCopy#retriveEntriesOfType + * @methodOf umbraco.services.clipboardService + * + * @param {string} type A string defining the type of data to recive. + * @param {string} aliases A array of strings providing the alias of the data you want to recive. + * + * @description + * Returns an array of data of entries matching the given type and one of the provided aliases. + */ + service.retriveDataOfType = function(type, aliases) { + return service.retriveEntriesOfType(type, aliases).map((x) => x.data); + }; + + /** + * @ngdoc method + * @name umbraco.services.supportsCopy#retriveEntriesOfType + * @methodOf umbraco.services.clipboardService + * + * @param {string} type A string defining the type of data to remove. + * @param {string} aliases A array of strings providing the alias of the data you want to remove. + * + * @description + * Removes entries matching the given type and one of the provided aliases. + */ + service.clearEntriesOfType = function(type, allowedAliases) { + + var storage = retriveStorage(); + + // Find entries that are NOT fulfilling the criteria for this nodeType and nodeTypesAliases. + var filteretEntries = storage.entries.filter( + (entry) => { + return !isEntryCompatible(entry, type, allowedAliases); + } + ); + + storage.entries = filteretEntries; + + saveStorage(storage); + }; + + + + return service; +} + + +angular.module("umbraco.services").factory("clipboardService", clipboardService); + diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js index 02e85c278a..3c64401933 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/editor.service.js @@ -606,10 +606,12 @@ When building a custom infinite editor view you can use the same components as a * @methodOf umbraco.services.editorService * * @description - * Opens the document type editor in infinite editing, the submit callback returns the saved document type + * Opens the document type editor in infinite editing, the submit callback returns the alias of the saved document type. * @param {Object} editor rendering options - * @param {Callback} editor.submit Submits the editor - * @param {Callback} editor.close Closes the editor + * @param {Callback} editor.id Indicates the ID of the document type to be edited. Alternatively the ID may be set to `-1` in combination with `create` being set to `true` to open the document type editor for creating a new document type. + * @param {Callback} editor.create Set to `true` to open the document type editor for creating a new document type. + * @param {Callback} editor.submit Submits the editor. + * @param {Callback} editor.close Closes the editor. * @returns {Object} editor object */ function documentTypeEditor(editor) { diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index ca89f7824b..ce70e9f543 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -465,6 +465,8 @@ function navigationService($routeParams, $location, $q, $injector, eventsService throw "section cannot be null"; } + appState.setMenuState("currentNode", node); + if (action.metaData && action.metaData["actionRoute"] && angular.isString(action.metaData["actionRoute"])) { //first check if the menu item simply navigates to a route var parts = action.metaData["actionRoute"].split("?"); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js index 2165c1b7cb..0b8965e4fe 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/overlay.service.js @@ -65,10 +65,39 @@ open(overlay); } + function confirm(overlay) { + + if (!overlay.closeButtonLabelKey) overlay.closeButtonLabelKey = "general_cancel"; + if (!overlay.view) overlay.view = "views/common/overlays/confirm/confirm.html"; + if (!overlay.close) overlay.close = function () { close(); }; + + switch (overlay.confirmType) { + + case "delete": + if (!overlay.confirmMessageStyle) overlay.confirmMessageStyle = "danger"; + if (!overlay.submitButtonStyle) overlay.submitButtonStyle = "danger"; + if (!overlay.submitButtonLabelKey) overlay.submitButtonLabelKey = "contentTypeEditor_yesDelete"; + break; + + default: + if (!overlay.submitButtonLabelKey) overlay.submitButtonLabelKey = "general_confirm"; + + } + + open(overlay); + + } + + function confirmDelete(overlay) { + confirm(overlay); + } + var service = { open: open, close: close, - ysod: ysod + ysod: ysod, + confirm: confirm, + confirmDelete: confirmDelete }; return service; diff --git a/src/Umbraco.Web.UI.Client/src/common/services/propertyeditor.service.js b/src/Umbraco.Web.UI.Client/src/common/services/propertyeditor.service.js new file mode 100644 index 0000000000..0b24e78567 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/services/propertyeditor.service.js @@ -0,0 +1,29 @@ +(function() { + 'use strict'; + + function propertyEditorService() { + /** + * @ngdoc function + * @name umbraco.services.propertyEditorService#expose + * @methodOf umbraco.services.propertyEditorService + * @function + * + * @param {object} scope An object containing API for the PropertyEditor + */ + function exposeAPI(scope, api) { + if (!scope) { + throw "scope cannot be null"; + } + if (!api) { + throw "api cannot be null"; + } + scope.$emit("ExposePropertyEditorAPI", api); + } + + return { + exposeAPI: exposeAPI + }; + } + + angular.module('umbraco.services').factory('propertyEditorService', propertyEditorService); +})(); diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js index 1351da081a..4d4f8792cf 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/tinymce.service.js @@ -735,7 +735,7 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s //get all macro divs and load their content $(editor.dom.select(".umb-macro-holder.mceNonEditable")).each(function () { - self.loadMacroContent($(this), null); + self.loadMacroContent($(this), null, editor); }); }); @@ -850,14 +850,15 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s } var $macroDiv = $(editor.dom.select("div.umb-macro-holder." + uniqueId)); + editor.setDirty(true); //async load the macro content - this.loadMacroContent($macroDiv, macroObject); + this.loadMacroContent($macroDiv, macroObject, editor); }, /** loads in the macro content async from the server */ - loadMacroContent: function ($macroDiv, macroData) { + loadMacroContent: function ($macroDiv, macroData, editor) { //if we don't have the macroData, then we'll need to parse it from the macro div if (!macroData) { @@ -893,7 +894,11 @@ function tinyMceService($rootScope, $q, imageHelper, $locale, $http, $timeout, s $macroDiv.removeClass("loading"); htmlResult = htmlResult.trim(); if (htmlResult !== "") { + var wasDirty = editor.isDirty(); $ins.html(htmlResult); + if (!wasDirty) { + editor.undoManager.clear(); + } } }); }); diff --git a/src/Umbraco.Web.UI.Client/src/less/belle.less b/src/Umbraco.Web.UI.Client/src/less/belle.less index 83d254c73c..391fafb3fa 100644 --- a/src/Umbraco.Web.UI.Client/src/less/belle.less +++ b/src/Umbraco.Web.UI.Client/src/less/belle.less @@ -77,6 +77,7 @@ @import "listview.less"; @import "gridview.less"; @import "footer.less"; +@import "filter-toggle.less"; @import "forms/umb-validation-label.less"; @@ -129,6 +130,7 @@ @import "components/umb-media-grid.less"; @import "components/umb-folder-grid.less"; @import "components/umb-content-grid.less"; +@import "components/umb-contextmenu.less"; @import "components/umb-layout-selector.less"; @import "components/tooltip/umb-tooltip.less"; @import "components/tooltip/umb-tooltip-list.less"; @@ -137,6 +139,7 @@ @import "components/umb-grid.less"; @import "components/umb-empty-state.less"; @import "components/umb-property-editor.less"; +@import "components/umb-property-actions.less"; @import "components/umb-color-swatches.less"; @import "components/check-circle.less"; @import "components/umb-file-icon.less"; @@ -187,6 +190,8 @@ @import "components/users/umb-user-preview.less"; @import "components/users/umb-user-picker-list.less"; +@import "components/contextdialogs/umb-dialog-datatype-delete.less"; + // Utilities @import "utilities/layout/_display.less"; @@ -217,6 +222,7 @@ @import "dashboards/umbraco-forms.less"; @import "dashboards/examine-management.less"; @import "dashboards/healthcheck.less"; +@import "dashboards/nucache.less"; @import "typeahead.less"; @import "hacks.less"; diff --git a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less index 8b043c9045..b36c73a61a 100644 --- a/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less +++ b/src/Umbraco.Web.UI.Client/src/less/canvas-designer.less @@ -280,7 +280,7 @@ ul.sections { margin:0; padding:0; margin-left: -80px; - overflow: scroll; + overflow: auto; overflow-x: hidden; height: calc(100% - 91px); @@ -316,35 +316,40 @@ ul.sections li a { &:hover { span, i { opacity: 1; + color:#fff; } } } ul.sections li a i { font-size: 30px; + opacity: 0.8; } ul.sections li a span { - display:block; + display: block; font-size: 10px; line-height: 1.4em; - opacity: 0.4; + opacity: 0.8; } ul.sections li.current { - background-color: #2E2246; -} - -ul.sections li.current a i { - color: #ffffff; -} - -ul.sections li.current, ul.sections li:hover { border-left: 4px #f5c1bc solid; } -.fix-left-menu:hover ul.sections li a span, -.fix-left-menu:hover ul.sections li a i, +ul.sections li.current a i { + color: #f5c1bc; +} + +ul.sections li.current { + border-left: 4px #f5c1bc solid; +} + +ul.sections li:hover a i, +ul.sections li:hover a span { + opacity: 1; +} + .fix-left-menu:hover .help { opacity: 1; } diff --git a/src/Umbraco.Web.UI.Client/src/less/components/contextdialogs/umb-dialog-datatype-delete.less b/src/Umbraco.Web.UI.Client/src/less/components/contextdialogs/umb-dialog-datatype-delete.less new file mode 100644 index 0000000000..0e0b8f22bd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/contextdialogs/umb-dialog-datatype-delete.less @@ -0,0 +1,33 @@ +.umb-dialog-datatype-delete { + + + .umb-dialog-datatype-delete__table-head-column-name { + width: 140px; + } + + .umb-table-body__icon { + margin-right: 5px; + vertical-align: top; + display: inline-block; + } + + .table tbody td { + vertical-align: top; + } + .table tbody td > span { + margin: 5px 0; + vertical-align: middle; + } + .table tbody p { + line-height: 12px; + margin: 5px 0; + vertical-align: middle; + } + + .table tbody .icon { + vertical-align: top; + margin-right: 5px; + display: inline-block; + } + +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less b/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less index 1065082820..67038380ca 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/notifications/umb-notifications.less @@ -39,3 +39,8 @@ padding-top: 20px; padding-bottom: 20px; } + + +.emptySection .umb-notifications{ + left:0; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less index d060f34a36..609cf0af3d 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/overlays.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/overlays.less @@ -117,7 +117,7 @@ .umb-overlay.umb-overlay-center .umb-overlay-drawer { border: none; background: transparent; - padding: 0 30px 20px; + padding: 0 20px 20px; } /* ---------- OVERLAY TARGET ---------- */ diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-contextmenu.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-contextmenu.less new file mode 100644 index 0000000000..8512e2020d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-contextmenu.less @@ -0,0 +1,75 @@ +.umb-contextmenu { + margin: 0; + list-style: none; + user-select: none; + + overflow: hidden; + border-radius: 3px; + border: 1px solid @dropdownBorder; + .box-shadow(0 5px 20px rgba(0,0,0,.3)); + border-bottom: 1px solid rgba(0,0,0,.2); + + .sep { + display: block; + border-top: 1px solid @gray-9; + + &:first-child { + border-top: none; + } + } + +} + +.umb-contextmenu-item { + + .icon { + font-size: 18px; + vertical-align: middle; + } + + .menu-label { + display: inline-block; + vertical-align: middle; + margin-left: 5px; + } + + button { + + position: relative; + + display: block; + font-weight: normal; + line-height: @baseLineHeight; + white-space: nowrap; + + background-color: @ui-option; + border: 0; + padding: 7px 12px; + color: @ui-option-type; + width: 100%; + + font-size: 14px; + text-align: left; + + &:hover { + text-decoration: none; + color: @ui-option-type-hover; + background-color: @ui-option-hover; + } + } + + &.-opens-dialog { + .menu-label:after { + // adds an ellipsis (...) after the menu label for actions that open a dialog + content: '\2026'; + } + } + button:disabled { + cursor: not-allowed; + color: @ui-option-disabled-type; + &:hover { + color: @ui-option-disabled-type-hover; + background-color: @ui-option; + } + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less index 22975ec6bb..7f19c4933c 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-form-check.less @@ -12,6 +12,14 @@ line-height: 22px; cursor: pointer !important; + &.-small-text{ + font-size: 13px; + } + + &.-bold{ + font-weight: 700; + } + &__text { margin: 0 0 0 26px; position: relative; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less index 7b8845542e..76223589e4 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-logviewer.less @@ -1,5 +1,3 @@ -/* PACKAGE DETAILS */ - .umb-logviewer { display: flex; flex-flow: row wrap; @@ -41,7 +39,7 @@ flex: 1 1 auto; width: 100%; margin-bottom: 30px; - margin-right: 0; + margin-right: 0; } .umb-logviewer__sidebar { @@ -49,3 +47,99 @@ width: 100%; } } + +.umb-logviewer-search { + .filter-name { + margin-left: 5px; + margin-right: 3px; + max-width: 150px; + } + + .dropdown-item { + padding: 8px 20px 8px 16px; + } + + .filter { + position: relative; + + a.btn-link { + padding-left: 0px; + } + } + + .search-box { + width: 100%; + + .flex-auto { + position: relative; + + .search-input { + width: 100%; + padding-right: 160px; + } + + .icon-rate { + position: absolute; + top: 0; + line-height: 32px; + right: 140px; + color: #fdb45c; + cursor: pointer; + } + + .icon-wrong { + position: absolute; + top: 0; + line-height: 32px; + right: 120px; + color: #bbbabf; + cursor: pointer; + } + + .umb-variant-switcher__toggle { + top: 1px; + right: 0; + position: absolute; + + .icon-navigation-down { + margin-top: 0; + } + } + + .saved-searches { + width: 100%; + max-height: 250px; + overflow-y: scroll; + margin-top: -10px; + } + } + } + + .log-items { + .table { + table-layout: fixed; + + thead th:first-child, thead th:nth-child(3) { + width: 20%; + } + + thead th:nth-child(2) { + width: 15%; + } + + tr td:nth-child(3) { + word-break: break-word; + } + } + + .exception { + border-left: 4px solid #D42054; + padding: 0 10px 10px 10px; + box-shadow: rgba(0,0,0,0.07) 2px 2px 10px; + + .exception-message { + white-space: pre-wrap; + } + } + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less index 56b4e19122..455a147395 100644 --- a/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-nested-content.less @@ -1,5 +1,4 @@ .umb-nested-content { - text-align: center; position: relative; } @@ -170,6 +169,7 @@ .umb-nested-content__add-content { display: flex; + width: 100%; align-items: center; justify-content: center; border: 1px dashed @ui-action-discreet-border; diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less new file mode 100644 index 0000000000..3ce284870e --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-property-actions.less @@ -0,0 +1,96 @@ +.umb-property-actions { + display: inline; +} + +.umb-property-actions__toggle, +.umb-property-actions__menu-open-toggle { + position: relative; + display: flex; + flex: 0 0 auto; + padding: 6px 6px; + text-align: center; + cursor: pointer; + border-radius: 3px; + + background-color: @ui-action-hover; + + i { + height: 3px !important; + width: 3px !important; + border-radius: 3px; + background: @ui-action-type; + display: inline-block; + margin: 0 2px 0 0; + + &:last-child { + margin: 0; + } + } + &:hover { + i { + background: @ui-action-type-hover; + } + } +} +.umb-property-actions__menu-open-toggle { + position: absolute; + z-index:1; + outline: none;// this is not acceccible by keyboard, since we use the .umb-property-actions__toggle for that. + + top: -15px; + border-radius: 3px 3px 0 0; + + border-top-left-radius: 3px; + border-top-right-radius: 3px; + + border: 1px solid @dropdownBorder; + + border-bottom: 1px solid @gray-9; + + .box-shadow(0 5px 20px rgba(0,0,0,.3)); + + background-color: white; + +} + +.umb-property .umb-property-actions { + float: left; +} +.umb-property .umb-property-actions__toggle { + margin-top: 2px; + opacity: 0; + transition: opacity 120ms; +} +.umb-property:hover .umb-property-actions__toggle, +.umb-property .umb-property-actions__toggle:focus { + opacity: 1; +} +// Revert-style-hack that ensures that we only show property-actions on properties that are directly begin hovered. +.umb-property:hover .umb-property:not(:hover) .umb-property-actions__toggle { + opacity: 0; +} + +.umb-property-actions__menu { + + position: absolute; + z-index: 1000; + + display: block; + + float: left; + min-width: 160px; + list-style: none; + + .umb-contextmenu { + + border-top-left-radius: 0; + margin-top:1px; + + } + + .umb-contextmenu-item > button { + + z-index:2;// need to stay on top of menu-toggle-open shadow. + + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/dashboards/examine-management.less b/src/Umbraco.Web.UI.Client/src/less/dashboards/examine-management.less index 0c219af1e4..7b842c40ad 100644 --- a/src/Umbraco.Web.UI.Client/src/less/dashboards/examine-management.less +++ b/src/Umbraco.Web.UI.Client/src/less/dashboards/examine-management.less @@ -12,4 +12,9 @@ border-bottom-left-radius: 0; } } + + .umb-panel-group__details-status-action{ + background-color:transparent; + padding-left:0; + } } diff --git a/src/Umbraco.Web.UI.Client/src/less/dashboards/nucache.less b/src/Umbraco.Web.UI.Client/src/less/dashboards/nucache.less new file mode 100644 index 0000000000..4ebe1d47b0 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/dashboards/nucache.less @@ -0,0 +1,13 @@ +#nuCache { + .no-background { + background-color: transparent; + } + + .top-border { + border-top: 2px solid #f3f3f5; + } + + .no-left-padding { + padding-left: 0; + } +} diff --git a/src/Umbraco.Web.UI.Client/src/less/filter-toggle.less b/src/Umbraco.Web.UI.Client/src/less/filter-toggle.less new file mode 100644 index 0000000000..82f9f3f553 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/less/filter-toggle.less @@ -0,0 +1,20 @@ +.filter-toggle{ + margin: 0; + padding: 0 8px 0 0; + position: relative; +} + +.filter-toggle__level{ + display: inline-block; + font-weight: 700; + margin: 0 5px; + max-width: 150px; +} + +.filter-toggle__icon{ + position: absolute; + top: 0; + bottom: 0; + right: 0; + margin: auto 0; +} diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 920fcdb1eb..86a1acbeae 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -116,24 +116,42 @@ h5.-black { margin: 20px; } .umb-control-group { - border-bottom: 1px solid @gray-11; - padding-bottom: 20px; + position: relative; + &::after { + content: ''; + display:block; + margin-top: 20px; + width: 100%; + height: 1px; + background-color: @gray-11; + } } .umb-control-group.-no-border { - border: none; + &::after { + margin-top: 0px; + height: 0; + background-color: transparent; + } } .umb-property:last-of-type .umb-control-group { - border: none; - margin-bottom: 0 !important; - padding-bottom: 0; + &::after { + margin-top: 0px; + height: 0; + background-color: transparent; + } + margin-bottom: 0 !important; } /* BLOCK MODE */ .block-form .umb-control-group { - border-bottom: none; - padding-bottom: 0; + margin-top: 0px; + &::after { + margin-top: 0px; + height: 0; + background-color: transparent; + } } .block-form .umb-control-group label .help-block, @@ -163,7 +181,36 @@ h5.-black { } .umb-control-group .umb-el-wrap { - padding: 0 + padding: 0; +} +.form-horizontal .umb-control-group .control-header { + float: left; + width: 160px; + padding-top: 5px; + text-align: left; + + .control-label { + float: left; + width: auto; + padding-top: 0; + text-align: left; + } + + .control-description { + display: block; + clear: both; + max-width:480px;// avoiding description becoming too wide when its placed on top of property. + margin-bottom: 10px; + } +} +@media (max-width: 767px) { + + .form-horizontal .umb-control-group .control-header { + float: none; + width: 100%; + } + + } /* LABELS*/ diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index 84de751b12..fa23e08983 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -99,7 +99,7 @@ top: 0px; left: 0px; right: 0px; - bottom: 52px; + bottom: 49px; } .umb-dialog-body .umb-pane{margin-top: 15px;} @@ -111,7 +111,7 @@ left: 0px; right: 0px; bottom: 0px; - padding: 20px; + padding: 8px; margin: 0; .btn.umb-outline { diff --git a/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less b/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less index 6c38aa4a31..787e50f204 100644 --- a/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less +++ b/src/Umbraco.Web.UI.Client/src/less/utilities/_spacing.less @@ -67,4 +67,18 @@ .ml6 { margin-left: @spacing-extra-extra-large; } .ml7 { margin-left: @spacing-extra-extra-extra-large; } +.mr0 { margin-right: @spacing-none; } +.mr1 { margin-right: @spacing-extra-small; } +.mr2 { margin-right: @spacing-small; } +.mr3 { margin-right: @spacing-medium; } +.mr4 { margin-right: @spacing-large; } +.mr5 { margin-right: @spacing-extra-large; } +.mr6 { margin-right: @spacing-extra-extra-large; } +.mr7 { margin-right: @spacing-extra-extra-extra-large; } + .p0 { padding: @spacing-none; } + +.pt0 { padding-top: @spacing-none; } +.pb0 { padding-bottom: @spacing-none; } +.pl0 { padding-left: @spacing-none; } +.pr0 { padding-right: @spacing-none; } diff --git a/src/Umbraco.Web.UI.Client/src/less/variables.less b/src/Umbraco.Web.UI.Client/src/less/variables.less index 166640829b..e8f6d4ee58 100644 --- a/src/Umbraco.Web.UI.Client/src/less/variables.less +++ b/src/Umbraco.Web.UI.Client/src/less/variables.less @@ -132,6 +132,7 @@ @ui-option-type: @blueExtraDark; @ui-option-type-hover: @blueMid; +@ui-option: white; @ui-option-hover: @sand-7; @ui-option-disabled-type: @gray-6; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html index 071d093ab4..4096192081 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/compositions/compositions.html @@ -60,7 +60,7 @@ -
+
  • diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.controller.js index f2cc0dbecb..3de26ba99c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.controller.js @@ -5,8 +5,8 @@ var vm = this; - vm.field; - vm.defaultValue; + vm.field = null; + vm.defaultValue = null; vm.recursive = false; vm.showDefaultValue = false; @@ -16,10 +16,14 @@ function onInit() { + var labelKeys = [ + "template_insertPageField" + ]; + // set default title if(!$scope.model.title) { - localizationService.localize("template_insertPageField").then(function(value){ - $scope.model.title = value; + localizationService.localizeMany(labelKeys).then(function (data) { + $scope.model.title = data[0]; }); } @@ -37,42 +41,40 @@ function generateOutputSample() { - var fallback; + var fallback = null; - if(vm.recursive !== false && vm.defaultValue !== undefined){ + if (vm.recursive !== false && vm.defaultValue !== null) { fallback = "Fallback.To(Fallback.Ancestors, Fallback.DefaultValue)"; - }else if(vm.recursive !== false){ + } else if (vm.recursive !== false) { fallback = "Fallback.ToAncestors"; - }else if(vm.defaultValue !== undefined){ + } else if (vm.defaultValue !== null) { fallback = "Fallback.ToDefaultValue"; } - var pageField = (vm.field !== undefined ? '@Model.Value("' + vm.field + '"' : "") - + (fallback !== undefined? ', fallback: ' + fallback : "") - + (vm.defaultValue !== undefined ? ', defaultValue: new HtmlString("' + vm.defaultValue + '")' : "") + var pageField = (vm.field !== null ? '@Model.Value("' + vm.field + '"' : "") + + (fallback !== null? ', fallback: ' + fallback : "") + + (vm.defaultValue !== null ? ', defaultValue: new HtmlString("' + vm.defaultValue + '")' : "") + (vm.field ? ')' : ""); $scope.model.umbracoField = pageField; return pageField; - } function submit(model) { - if($scope.model.submit) { + if ($scope.model.submit) { $scope.model.submit(model); } } function close() { - if($scope.model.close) { + if ($scope.model.close) { $scope.model.close(); } } onInit(); - } angular.module("umbraco").controller("Umbraco.Editors.InsertFieldController", InsertFieldController); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.html index b2c6382b98..bbb2e8c798 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/insertfield/insertfield.html @@ -33,10 +33,9 @@
    - +
    @@ -52,13 +51,17 @@
    -
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html index 656c5f2ac1..373dfbcba7 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/mediapicker/mediapicker.html @@ -15,42 +15,40 @@
    -
    +
    - - +
    + @@ -120,6 +118,11 @@ current-folder-id="{{currentFolder.id}}"> + + + +
    - +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/querybuilder/querybuilder.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/querybuilder/querybuilder.controller.js index faca3b3fa0..4d537bd73c 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/querybuilder/querybuilder.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/querybuilder/querybuilder.controller.js @@ -31,6 +31,7 @@ vm.datePickerChange = datePickerChange; vm.submit = submit; vm.close = close; + vm.copyQuery = copyQuery; function onInit() { @@ -120,6 +121,11 @@ query.filters.push({}); } + function copyQuery() { + var copyText = $scope.model.result.queryExpression; + navigator.clipboard.writeText(copyText); + } + function trashFilter(query, filter) { for (var i = 0; i < query.filters.length; i++) { if (query.filters[i] == filter) { diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/querybuilder/querybuilder.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/querybuilder/querybuilder.html index 779ca739d2..f01f325265 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/querybuilder/querybuilder.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/querybuilder/querybuilder.html @@ -15,164 +15,160 @@ -
    +
    -
    -
    +
    +
    - I want + I want - - from + from - - + + -
    +
    -
    +
    - - where - - - and - + + where + + + and + -
    + +
    -
    +
    - - + + - - - - {{term.name}} - - - + + + + {{term.name}} + + + -
    +
    - + - - - + + + - - - - + + + + - + - - - + + + - - - + + + -
    +
    -
    +
    - order by + order by -
    + +
    - - + + -
    -
    +
    +
    {{model.result.resultCount}} items, returned in {{model.result.executionTime}} ms
    - + -
    {{model.result.queryExpression}}
    +
    {{model.result.queryExpression}}
    + + copy to clipboard + -
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js index afec0ae120..f71eb2c51e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/rollback/rollback.controller.js @@ -1,7 +1,7 @@ (function () { "use strict"; - function RollbackController($scope, contentResource, localizationService, assetsService) { + function RollbackController($scope, contentResource, localizationService, assetsService, dateHelper, userService) { var vm = this; @@ -90,11 +90,15 @@ const culture = $scope.model.node.variants.length > 1 ? vm.currentVersion.language.culture : null; return contentResource.getRollbackVersions(nodeId, culture) - .then(function(data){ - vm.previousVersions = data.map(version => { - version.displayValue = version.versionDate + " - " + version.versionAuthorName; - return version; - }); + .then(function (data) { + // get current backoffice user and format dates + userService.getCurrentUser().then(function (currentUser) { + vm.previousVersions = data.map(version => { + var timestampFormatted = dateHelper.getLocalDate(version.versionDate, currentUser.locale, 'LLL'); + version.displayValue = timestampFormatted + ' - ' + version.versionAuthorName; + return version; + }); + }); }); } diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.controller.js index 36d7c0f4ed..8c728150da 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.controller.js @@ -1,26 +1,42 @@ (function () { "use strict"; - function TemplateSectionsController($scope, formHelper) { + function TemplateSectionsController($scope, formHelper, localizationService) { var vm = this; + vm.labels = {}; + vm.select = select; vm.submit = submit; vm.close = close; $scope.model.mandatoryRenderSection = false; - if(!$scope.model.title) { - $scope.model.title = "Sections"; - } - function onInit() { - if($scope.model.hasMaster) { + if ($scope.model.hasMaster) { $scope.model.insertType = 'addSection'; } else { $scope.model.insertType = 'renderBody'; } + + var labelKeys = [ + "template_insertSections", + "template_sectionMandatory" + ]; + + localizationService.localizeMany(labelKeys).then(function (data) { + vm.labels.title = data[0]; + vm.labels.sectionMandatory = data[1]; + + setTitle(vm.labels.title); + }); + } + + function setTitle(value) { + if (!$scope.model.title) { + $scope.model.title = value; + } } function select(type) { @@ -34,13 +50,12 @@ } function close() { - if($scope.model.close) { + if ($scope.model.close) { $scope.model.close(); } } onInit(); - } angular.module("umbraco").controller("Umbraco.Editors.TemplateSectionsController", TemplateSectionsController); diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.html index 5b946976d7..045a1403e2 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/templatesections/templatesections.html @@ -44,10 +44,10 @@
    - - +
    + +
    +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html index fdd9b44a1e..0aab35ca21 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/application/umb-navigation.html @@ -10,12 +10,8 @@ diff --git a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html index 2f837331b3..c46efb7b74 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/editor/umb-editor-header.html @@ -1,6 +1,6 @@ 
    - -
    + +
    + + +
    diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/property-actions/umbpropertyactions.component.js b/src/Umbraco.Web.UI.Client/src/views/components/property/property-actions/umbpropertyactions.component.js new file mode 100644 index 0000000000..b0dc15d6cd --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/property-actions/umbpropertyactions.component.js @@ -0,0 +1,61 @@ +(function () { + 'use strict'; + + /** + * A component to render the property action toggle + */ + + function umbPropertyActionsController(keyboardService) { + + var vm = this; + + vm.isOpen = false; + + function initDropDown() { + keyboardService.bind("esc", vm.close); + } + function destroyDropDown() { + keyboardService.unbind("esc"); + } + + vm.toggle = function() { + if (vm.isOpen === true) { + vm.close(); + } else { + vm.open(); + } + } + vm.open = function() { + vm.isOpen = true; + initDropDown(); + } + vm.close = function() { + vm.isOpen = false; + destroyDropDown(); + } + + vm.executeAction = function(action) { + action.method(); + vm.close(); + } + + vm.$onDestroy = function () { + if (vm.isOpen === true) { + destroyDropDown(); + } + } + + } + + var umbPropertyActionsComponent = { + templateUrl: 'views/components/property/property-actions/umb-property-actions.html', + bindings: { + actions: "<" + }, + controllerAs: 'vm', + controller: umbPropertyActionsController + }; + + angular.module('umbraco.directives').component('umbPropertyActions', umbPropertyActionsComponent); + +})(); diff --git a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html index 9d2588484c..927f677463 100644 --- a/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html +++ b/src/Umbraco.Web.UI.Client/src/views/components/property/umb-property.html @@ -6,19 +6,25 @@
    -
  • @@ -82,22 +82,26 @@ - - - - + + - - - - + + - +
    + +
diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js index e12a26ba52..869104b5c3 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.controller.js @@ -25,6 +25,9 @@ vm.currentNode = null; vm.contentType = {}; + vm.header = {}; + vm.header.editorfor = "content_documentType"; + vm.header.setPageTitle = true; vm.labels = {}; vm.submitButtonKey = "buttons_save"; vm.generateModelsKey = "buttons_saveAndGenerateModels"; @@ -33,7 +36,7 @@ vm.page.loading = false; vm.page.saveButtonState = "init"; vm.page.navigation = []; - + var labelKeys = [ "general_design", "general_listView", diff --git a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.html b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.html index 52f4d710ba..cfbee358eb 100644 --- a/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/documenttypes/edit.html @@ -12,7 +12,9 @@ key="vm.contentType.key" description="vm.contentType.description" navigation="vm.page.navigation" - icon="vm.contentType.icon"> + icon="vm.contentType.icon" + editorfor="vm.header.editorfor" + setpagetitle="vm.header.setPageTitle"> diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/search.controller.js b/src/Umbraco.Web.UI.Client/src/views/logviewer/search.controller.js index eb1a0bb8b3..70cfd0e190 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/search.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/search.controller.js @@ -286,10 +286,11 @@ function deleteSavedSearch(searchItem) { var overlay = { - title: "Delete Search", - subtitle: "Are you sure you wish to delete", + title: "Delete Saved Search", + subtitle: "Are you sure you wish to delete?", closeButtonLabel: "Cancel", - submitButtonLabel: "Delete Search", + submitButtonLabel: "Delete Saved Search", + submitButtonStyle: "danger", view: "default", submit: function (model) { //Resource call with two params (name & query) diff --git a/src/Umbraco.Web.UI.Client/src/views/logviewer/search.html b/src/Umbraco.Web.UI.Client/src/views/logviewer/search.html index b84ca3d779..c5a1803eed 100644 --- a/src/Umbraco.Web.UI.Client/src/views/logviewer/search.html +++ b/src/Umbraco.Web.UI.Client/src/views/logviewer/search.html @@ -1,4 +1,4 @@ -
+