diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index e009ee2294..cea5859486 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -60,10 +60,10 @@ Great question! The short version goes like this:

- * **Switch to the correct branch** - switch to the v8-dev branch
+ * **Switch to the correct branch** - switch to the `v8/contrib` branch
* **Build** - build your fork of Umbraco locally as described in [building Umbraco from source code](BUILD.md)
* **Change** - make your changes, experiment, have fun, explore and learn, and don't be afraid. We welcome all contributions and will [happily give feedback](#questions)
- * **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v8/dev`, create a new branch first.
+ * **Commit** - done? Yay! 🎉 **Important:** create a new branch now and name it after the issue you're fixing, we usually follow the format: `temp-12345`. This means it's a temporary branch for the particular issue you're working on, in this case `12345`. When you have a branch, commit your changes. Don't commit to `v8/contrib`, create a new branch first.
* **Push** - great, now you can push the changes up to your fork on GitHub
* **Create pull request** - exciting! You're ready to show us your changes (or not quite ready, you just need some feedback to progress - you can now make use of GitHub's draft pull request status, detailed [here] (https://github.blog/2019-02-14-introducing-draft-pull-requests/)). GitHub has picked up on the new branch you've pushed and will offer to create a Pull Request. Click that green button and away you go.
@@ -158,7 +158,7 @@ To find the general areas for something you're looking to fix or improve, have a
### Which branch should I target for my contributions?
-We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v8/dev`. If you are working on v8, this is the branch you should be targetting. For v7 contributions, please target 'v7/dev'.
+We like to use [Gitflow as much as possible](https://jeffkreeftmeijer.com/git-flow/), but don't worry if you are not familiar with it. The most important thing you need to know is that when you fork the Umbraco repository, the default branch is set to something, usually `v8/contrib`. If you are working on v8, this is the branch you should be targetting. For v7 contributions, please target 'v7/dev'.
Please note: we are no longer accepting features for v7 but will continue to merge bug fixes as and when they arise.
@@ -184,10 +184,10 @@ Then when you want to get the changes from the main repository:
```
git fetch upstream
-git rebase upstream/v8/dev
+git rebase upstream/v8/contrib
```
-In this command we're syncing with the `v8/dev` branch, but you can of course choose another one if needed.
+In this command we're syncing with the `v8/contrib` branch, but you can of course choose another one if needed.
(More info on how this works: [http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated](http://robots.thoughtbot.com/post/5133345960/keeping-a-git-fork-updated))
diff --git a/.github/README.md b/.github/README.md
index d6d978c3d6..467ca6e5e6 100644
--- a/.github/README.md
+++ b/.github/README.md
@@ -1,4 +1,4 @@
-# [Umbraco CMS](https://umbraco.com) · [](../LICENSE.md) [](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [](CONTRIBUTING.md) [](https://twitter.com/intent/follow?screen_name=umbraco)
+# [Umbraco CMS](https://umbraco.com) · [](../LICENSE.md) [](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [](CONTRIBUTING.md) [](https://twitter.com/intent/follow?screen_name=umbraco)
Umbraco is the friendliest, most flexible and fastest growing ASP.NET CMS, and used by more than 500,000 websites worldwide. Our mission is to help you deliver delightful digital experiences by making Umbraco friendly, simpler and social.
diff --git a/.github/img/defaultbranch.png b/.github/img/defaultbranch.png
index f3a5b9efbc..3550b5c34c 100644
Binary files a/.github/img/defaultbranch.png and b/.github/img/defaultbranch.png differ
diff --git a/src/Umbraco.Core/Models/MediaTypeExtensions.cs b/src/Umbraco.Core/Models/MediaTypeExtensions.cs
new file mode 100644
index 0000000000..4e2ae5822a
--- /dev/null
+++ b/src/Umbraco.Core/Models/MediaTypeExtensions.cs
@@ -0,0 +1,10 @@
+namespace Umbraco.Core.Models
+{
+ internal static class MediaTypeExtensions
+ {
+ internal static bool IsSystemMediaType(this IMediaType mediaType) =>
+ mediaType.Alias == Constants.Conventions.MediaTypes.File
+ || mediaType.Alias == Constants.Conventions.MediaTypes.Folder
+ || mediaType.Alias == Constants.Conventions.MediaTypes.Image;
+ }
+}
diff --git a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs
index 69b0698a96..254e04d2d5 100644
--- a/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/IContentTypeRepositoryBase.cs
@@ -26,5 +26,10 @@ namespace Umbraco.Core.Persistence.Repositories
///
///
bool HasContainerInPath(string contentPath);
+
+ ///
+ /// Returns true or false depending on whether content nodes have been created based on the provided content type id.
+ ///
+ bool HasContentNodes(int id);
}
}
diff --git a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
index e2c3d8c9b5..6f714ff187 100644
--- a/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
+++ b/src/Umbraco.Core/Persistence/Repositories/Implement/ContentTypeRepositoryBase.cs
@@ -1323,6 +1323,17 @@ WHERE {Constants.DatabaseSchema.Tables.Content}.nodeId IN (@ids) AND cmsContentT
return Database.ExecuteScalar(sql) > 0;
}
+ ///
+ /// Returns true or false depending on whether content nodes have been created based on the provided content type id.
+ ///
+ public bool HasContentNodes(int id)
+ {
+ var sql = new Sql(
+ $"SELECT CASE WHEN EXISTS (SELECT * FROM {Constants.DatabaseSchema.Tables.Content} WHERE contentTypeId = @id) THEN 1 ELSE 0 END",
+ new { id });
+ return Database.ExecuteScalar(sql) == 1;
+ }
+
protected override IEnumerable GetDeleteClauses()
{
// in theory, services should have ensured that content items of the given content type
diff --git a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs
index 51e5d756eb..6ed3c85e91 100644
--- a/src/Umbraco.Core/Services/IContentTypeServiceBase.cs
+++ b/src/Umbraco.Core/Services/IContentTypeServiceBase.cs
@@ -39,6 +39,11 @@ namespace Umbraco.Core.Services
int Count();
+ ///
+ /// Returns true or false depending on whether content nodes have been created based on the provided content type id.
+ ///
+ bool HasContentNodes(int id);
+
IEnumerable GetAll(params int[] ids);
IEnumerable GetAll(IEnumerable ids);
diff --git a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
index 7ae330f8f1..da532e2765 100644
--- a/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
+++ b/src/Umbraco.Core/Services/Implement/ContentTypeServiceBaseOfTRepositoryTItemTService.cs
@@ -372,6 +372,15 @@ namespace Umbraco.Core.Services.Implement
}
}
+ public bool HasContentNodes(int id)
+ {
+ using (var scope = ScopeProvider.CreateScope(autoComplete: true))
+ {
+ scope.ReadLock(ReadLockIds);
+ return Repository.HasContentNodes(id);
+ }
+ }
+
#endregion
#region Save
diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj
index 1393971898..1c099fcc98 100755
--- a/src/Umbraco.Core/Umbraco.Core.csproj
+++ b/src/Umbraco.Core/Umbraco.Core.csproj
@@ -277,6 +277,7 @@
+
diff --git a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs
index f953b9cce6..bd80d6b154 100644
--- a/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs
+++ b/src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs
@@ -965,6 +965,32 @@ namespace Umbraco.Tests.Persistence.Repositories
}
}
+ [Test]
+ public void Can_Verify_Content_Type_Has_Content_Nodes()
+ {
+ // Arrange
+ var provider = TestObjects.GetScopeProvider(Logger);
+ using (var scope = provider.CreateScope())
+ {
+ ContentTypeRepository repository;
+ var contentRepository = CreateRepository((IScopeAccessor)provider, out repository);
+ var contentTypeId = NodeDto.NodeIdSeed + 1;
+ var contentType = repository.Get(contentTypeId);
+
+ // Act
+ var result = repository.HasContentNodes(contentTypeId);
+
+ var subpage = MockedContent.CreateTextpageContent(contentType, "Test Page 1", contentType.Id);
+ contentRepository.Save(subpage);
+
+ var result2 = repository.HasContentNodes(contentTypeId);
+
+ // Assert
+ Assert.That(result, Is.False);
+ Assert.That(result2, Is.True);
+ }
+ }
+
public void CreateTestData()
{
//Create and Save ContentType "umbTextpage" -> (NodeDto.NodeIdSeed)
diff --git a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less
index bf1167f950..3f93deaf56 100644
--- a/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less
+++ b/src/Umbraco.Web.UI.Client/lib/bootstrap/less/type.less
@@ -132,6 +132,10 @@ ol.inline {
display: inline-block;
padding-left: 5px;
padding-right: 5px;
+
+ &.-no-padding-left{
+ padding-left: 0;
+ }
}
}
diff --git a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js
index a75a7f1f3c..60118dbdb3 100644
--- a/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js
+++ b/src/Umbraco.Web.UI.Client/lib/markdown/markdown.editor.js
@@ -60,6 +60,7 @@
* its own image insertion dialog, this hook should return true, and the callback should be called with the chosen
* image url (or null if the user cancelled). If this hook returns false, the default dialog will be used.
*/
+ hooks.addFalse("insertLinkDialog");
this.getConverter = function () { return markdownConverter; }
@@ -1636,7 +1637,7 @@
var that = this;
// The function to be executed when you enter a link and press OK or Cancel.
// Marks up the link and adds the ref.
- var linkEnteredCallback = function (link) {
+ var linkEnteredCallback = function (link, title) {
if (link !== null) {
// ( $1
@@ -1667,10 +1668,10 @@
if (!chunk.selection) {
if (isImage) {
- chunk.selection = "enter image description here";
+ chunk.selection = title || "enter image description here";
}
else {
- chunk.selection = "enter link description here";
+ chunk.selection = title || "enter link description here";
}
}
}
@@ -1683,7 +1684,8 @@
ui.prompt('Insert Image', imageDialogText, imageDefaultText, linkEnteredCallback);
}
else {
- ui.prompt('Insert Link', linkDialogText, linkDefaultText, linkEnteredCallback);
+ if (!this.hooks.insertLinkDialog(linkEnteredCallback))
+ ui.prompt('Insert Link', linkDialogText, linkDefaultText, linkEnteredCallback);
}
return true;
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js
index b3948bd7c4..fe2a6aa40a 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/editor/umbeditorcontentheader.directive.js
@@ -17,8 +17,8 @@
scope.isNew = scope.content.state == "NotCreated";
localizationService.localizeMany([
- scope.isNew ? "placeholders_a11yCreateItem" : "placeholders_a11yEdit",
- "placeholders_a11yName",
+ scope.isNew ? "visuallyHiddenTexts_createItem" : "visuallyHiddenTexts_edit",
+ "visuallyHiddenTexts_name",
scope.isNew ? "general_new" : "general_edit"]
).then(function (data) {
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 431a05778c..87053c083c 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
@@ -233,8 +233,8 @@ Use this directive to construct a header inside the main editor window.
editorState.current.id === "-1";
var localizeVars = [
- scope.isNew ? "placeholders_a11yCreateItem" : "placeholders_a11yEdit",
- "placeholders_a11yName",
+ scope.isNew ? "visuallyHiddenTexts_createItem" : "visuallyHiddenTexts_edit",
+ "visuallyHiddenTexts_name",
scope.isNew ? "general_new" : "general_edit"
];
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js
index bc3993458e..fa1f4227a2 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/overlays/umboverlay.directive.js
@@ -213,7 +213,6 @@ Opens an overlay to show a custom YSOD.
var unsubscribe = [];
function activate() {
-
setView();
setButtonText();
@@ -247,10 +246,20 @@ Opens an overlay to show a custom YSOD.
setOverlayIndent();
+ focusOnOverlayHeading()
});
}
+ // Ideally this would focus on the first natively focusable element in the overlay, but as the content can be dynamic, it is focusing on the heading.
+ function focusOnOverlayHeading() {
+ var heading = el.find(".umb-overlay__title");
+
+ if(heading) {
+ heading.focus();
+ }
+ }
+
function setView() {
if (scope.view) {
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js
index 4fc22c4b74..a33fd4be53 100644
--- a/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js
+++ b/src/Umbraco.Web.UI.Client/src/common/directives/components/umbchildselector.directive.js
@@ -188,6 +188,22 @@ Use this directive to render a ui component for selecting child items to a paren
syncParentIcon();
}));
+ // sortable options for allowed child content types
+ scope.sortableOptions = {
+ axis: "y",
+ containment: "parent",
+ distance: 10,
+ opacity: 0.7,
+ tolerance: "pointer",
+ scroll: true,
+ zIndex: 6000,
+ update: function (e, ui) {
+ if(scope.onSort) {
+ scope.onSort();
+ }
+ }
+ };
+
// clean up
scope.$on('$destroy', function(){
// unbind watchers
@@ -209,7 +225,8 @@ Use this directive to render a ui component for selecting child items to a paren
parentIcon: "=",
parentId: "=",
onRemove: "=",
- onAdd: "="
+ onAdd: "=",
+ onSort: "="
},
link: link
};
diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/util/konami.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/util/konami.directive.js
deleted file mode 100644
index 7914dfc3f0..0000000000
--- a/src/Umbraco.Web.UI.Client/src/common/directives/util/konami.directive.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/**
- * Konami Code directive for AngularJS
- * @version v0.0.1
- * @license MIT License, https://www.opensource.org/licenses/MIT
- */
-
-angular.module('umbraco.directives')
- .directive('konamiCode', ['$document', function ($document) {
- var konamiKeysDefault = [38, 38, 40, 40, 37, 39, 37, 39, 66, 65];
-
- return {
- restrict: 'A',
- link: function (scope, element, attr) {
-
- if (!attr.konamiCode) {
- throw ('Konami directive must receive an expression as value.');
- }
-
- // Let user define a custom code.
- var konamiKeys = attr.konamiKeys || konamiKeysDefault;
- var keyIndex = 0;
-
- /**
- * Fired when konami code is type.
- */
- function activated() {
- if ('konamiOnce' in attr) {
- stopListening();
- }
- // Execute expression.
- scope.$eval(attr.konamiCode);
- }
-
- /**
- * Handle keydown events.
- */
- function keydown(e) {
- if (e.keyCode === konamiKeys[keyIndex++]) {
- if (keyIndex === konamiKeys.length) {
- keyIndex = 0;
- activated();
- }
- } else {
- keyIndex = 0;
- }
- }
-
- /**
- * Stop to listen typing.
- */
- function stopListening() {
- $document.off('keydown', keydown);
- }
-
- // Start listening to key typing.
- $document.on('keydown', keydown);
-
- // Stop listening when scope is destroyed.
- scope.$on('$destroy', stopListening);
- }
- };
- }]);
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js
index 64accc18c1..97bebef062 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/contenttype.resource.js
@@ -351,6 +351,16 @@ function contentTypeResource($q, $http, umbRequestHelper, umbDataFormatter, loca
return umbRequestHelper.resourcePromise(
$http.post(umbRequestHelper.getApiUrl("contentTypeApiBaseUrl", "PostCreateDefaultTemplate", { id: id })),
'Failed to create default template for content type with id ' + id);
+ },
+
+ hasContentNodes: function (id) {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "contentTypeApiBaseUrl",
+ "HasContentNodes",
+ [{ id: id }])),
+ 'Failed to retrieve indication for whether content type with id ' + id + ' has associated content nodes');
}
};
}
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js
index 9cf1181cfa..61d646afc0 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js
@@ -127,6 +127,25 @@ function entityResource($q, $http, umbRequestHelper) {
'Failed to retrieve url for id:' + id);
},
+ getUrlByUdi: function (udi, culture) {
+
+ if (!udi) {
+ return "";
+ }
+
+ if (!culture) {
+ culture = "";
+ }
+
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "entityApiBaseUrl",
+ "GetUrl",
+ [{ udi: udi }, {culture: culture }])),
+ 'Failed to retrieve url for UDI:' + udi);
+ },
+
/**
* @ngdoc method
* @name umbraco.resources.entityResource#getById
@@ -166,18 +185,22 @@ function entityResource($q, $http, umbRequestHelper) {
},
- getUrlAndAnchors: function (id) {
+ getUrlAndAnchors: function (id, culture) {
if (id === -1 || id === "-1") {
return null;
}
+ if (!culture) {
+ culture = "";
+ }
+
return umbRequestHelper.resourcePromise(
$http.get(
umbRequestHelper.getApiUrl(
"entityApiBaseUrl",
"GetUrlAndAnchors",
- [{ id: id }])),
+ [{ id: id }, {culture: culture }])),
'Failed to retrieve url and anchors data for id ' + id);
},
diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/tour.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/tour.resource.js
index 40baf0f389..485b0d299a 100644
--- a/src/Umbraco.Web.UI.Client/src/common/resources/tour.resource.js
+++ b/src/Umbraco.Web.UI.Client/src/common/resources/tour.resource.js
@@ -20,10 +20,21 @@
"GetTours")),
'Failed to get tours');
}
+
+ function getToursForDoctype(doctypeAlias) {
+ return umbRequestHelper.resourcePromise(
+ $http.get(
+ umbRequestHelper.getApiUrl(
+ "tourApiBaseUrl",
+ "GetToursForDoctype",
+ [{ doctypeAlias: doctypeAlias }])),
+ 'Failed to get tours');
+ }
var resource = {
- getTours: getTours
+ getTours: getTours,
+ getToursForDoctype: getToursForDoctype
};
return resource;
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 1d80d3a3ed..284a7db4d8 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
@@ -640,6 +640,23 @@ When building a custom infinite editor view you can use the same components as a
editor.view = "views/mediatypes/edit.html";
open(editor);
}
+
+ /**
+ * @ngdoc method
+ * @name umbraco.services.editorService#memberTypeEditor
+ * @methodOf umbraco.services.editorService
+ *
+ * @description
+ * Opens the member type editor in infinite editing, the submit callback returns the saved member type
+ * @param {Object} editor rendering options
+ * @param {Callback} editor.submit Submits the editor
+ * @param {Callback} editor.close Closes the editor
+ * @returns {Object} editor object
+ */
+ function memberTypeEditor(editor) {
+ editor.view = "views/membertypes/edit.html";
+ open(editor);
+ }
/**
* @ngdoc method
@@ -1011,6 +1028,7 @@ When building a custom infinite editor view you can use the same components as a
iconPicker: iconPicker,
documentTypeEditor: documentTypeEditor,
mediaTypeEditor: mediaTypeEditor,
+ memberTypeEditor: memberTypeEditor,
queryBuilder: queryBuilder,
treePicker: treePicker,
nodePermissions: nodePermissions,
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js
index 97a9ac5c4b..28daa3f245 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/editorstate.service.js
@@ -10,7 +10,7 @@
*
* it is possible to modify this object, so should be used with care
*/
-angular.module('umbraco.services').factory("editorState", function ($rootScope) {
+angular.module('umbraco.services').factory("editorState", function ($rootScope, eventsService) {
var current = null;
@@ -30,6 +30,7 @@ angular.module('umbraco.services').factory("editorState", function ($rootScope)
*/
set: function (entity) {
current = entity;
+ eventsService.emit("editorState.changed", { entity: entity });
},
/**
diff --git a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js
index 62af17146c..91b41cc68d 100644
--- a/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js
+++ b/src/Umbraco.Web.UI.Client/src/common/services/tour.service.js
@@ -134,32 +134,33 @@
var groupedTours = [];
tours.forEach(function (item) {
- var groupExists = false;
- var newGroup = {
- "group": "",
- "tours": []
- };
+ if (item.contentType === null || item.contentType === '') {
+ var groupExists = false;
+ var newGroup = {
+ "group": "",
+ "tours": []
+ };
- groupedTours.forEach(function(group){
- // extend existing group if it is already added
- if(group.group === item.group) {
- if(item.groupOrder) {
- group.groupOrder = item.groupOrder
- }
- groupExists = true;
+ groupedTours.forEach(function (group) {
+ // extend existing group if it is already added
+ if (group.group === item.group) {
+ if (item.groupOrder) {
+ group.groupOrder = item.groupOrder;
+ }
+ groupExists = true;
if(item.hidden === false){
group.tours.push(item);
}
- }
- });
+ }
+ });
- // push new group to array if it doesn't exist
- if(!groupExists) {
- newGroup.group = item.group;
- if(item.groupOrder) {
- newGroup.groupOrder = item.groupOrder
- }
+ // push new group to array if it doesn't exist
+ if (!groupExists) {
+ newGroup.group = item.group;
+ if (item.groupOrder) {
+ newGroup.groupOrder = item.groupOrder;
+ }
if(item.hidden === false){
newGroup.tours.push(item);
@@ -194,6 +195,24 @@
return deferred.promise;
}
+ /**
+ * @ngdoc method
+ * @name umbraco.services.tourService#getToursForDoctype
+ * @methodOf umbraco.services.tourService
+ *
+ * @description
+ * Returns a promise of the tours found by documenttype alias.
+ * @param {Object} doctypeAlias The doctype alias for which the tours which should be returned
+ * @returns {Array} An array of tour objects for the doctype
+ */
+ function getToursForDoctype(doctypeAlias) {
+ var deferred = $q.defer();
+ tourResource.getToursForDoctype(doctypeAlias).then(function (tours) {
+ deferred.resolve(tours);
+ });
+ return deferred.promise;
+ }
+
///////////
/**
@@ -275,7 +294,8 @@
completeTour: completeTour,
getCurrentTour: getCurrentTour,
getGroupedTours: getGroupedTours,
- getTourByAlias: getTourByAlias
+ getTourByAlias: getTourByAlias,
+ getToursForDoctype : getToursForDoctype
};
return service;
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less
index 70e4f3d372..2f9430ef41 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/application/umb-search.less
@@ -16,6 +16,10 @@
box-shadow: 0 10px 20px rgba(0,0,0,.12),0 6px 6px rgba(0,0,0,.14);
}
+.umb-search__label{
+ margin: 0;
+}
+
/*
Search field
*/
@@ -107,4 +111,4 @@
.umb-search-result__description {
color: @gray-5;
font-size: 13px;
-}
\ No newline at end of file
+}
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less
index b6cdc0e8d9..da690663d0 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-child-selector.less
@@ -30,6 +30,9 @@
.umb-child-selector__children-container {
margin-left: 30px;
+ .umb-child-selector__child {
+ cursor: move;
+ }
}
.umb-child-selector__child-description {
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less
index 479074fee9..26d61412ae 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-grid.less
@@ -162,6 +162,8 @@
}
.umb-grid .umb-row .umb-cell-placeholder {
+ display: block;
+ width: 100%;
min-height: 88px;
border-width: 1px;
border-style: dashed;
@@ -226,6 +228,7 @@
.umb-grid .cell-tools-add.-bar {
display: block;
+ width: calc(100% - 20px);
text-align: center;
padding: 5px;
border: 1px dashed @ui-action-discreet-border;
diff --git a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less
index e8a62f739d..98b2b1d72d 100644
--- a/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less
+++ b/src/Umbraco.Web.UI.Client/src/less/components/umb-iconpicker.less
@@ -15,7 +15,9 @@
overflow: hidden;
}
-.umb-iconpicker-item a {
+.umb-iconpicker-item button {
+ background: transparent;
+ border: 0 none;
display: flex;
justify-content: center;
align-items: center;
@@ -26,8 +28,8 @@
border-radius: 3px;
}
-.umb-iconpicker-item a:hover,
-.umb-iconpicker-item a:focus {
+.umb-iconpicker-item button:hover,
+.umb-iconpicker-item button:focus {
background: @gray-10;
outline: none;
}
@@ -39,7 +41,7 @@
box-sizing: border-box;
}
-.umb-iconpicker-item a:active {
+.umb-iconpicker-item button:active {
background: @gray-10;
}
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js
index 3323f2bfb3..268bfb3a8c 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.controller.js
@@ -1,7 +1,7 @@
(function () {
"use strict";
- function HelpDrawerController($scope, $routeParams, $timeout, dashboardResource, localizationService, userService, eventsService, helpService, appState, tourService, $filter) {
+ function HelpDrawerController($scope, $routeParams, $timeout, dashboardResource, localizationService, userService, eventsService, helpService, appState, tourService, $filter, editorState) {
var vm = this;
var evts = [];
@@ -18,6 +18,10 @@
vm.startTour = startTour;
vm.getTourGroupCompletedPercentage = getTourGroupCompletedPercentage;
vm.showTourButton = showTourButton;
+
+ vm.showDocTypeTour = false;
+ vm.docTypeTours = [];
+ vm.nodeName = '';
function startTour(tour) {
tourService.startTour(tour);
@@ -58,9 +62,16 @@
handleSectionChange();
}));
+ evts.push(eventsService.on("editorState.changed",
+ function (e, args) {
+ setDocTypeTour(args.entity);
+ }));
+
findHelp(vm.section, vm.tree, vm.userType, vm.userLang);
});
+
+ setDocTypeTour(editorState.getCurrent());
// check if a tour is running - if it is open the matching group
var currentTour = tourService.getCurrentTour();
@@ -84,7 +95,7 @@
setSectionName();
findHelp(vm.section, vm.tree, vm.userType, vm.userLang);
-
+ setDocTypeTour();
}
});
}
@@ -168,6 +179,26 @@
});
}
+ function setDocTypeTour(node) {
+ vm.showDocTypeTour = false;
+ vm.docTypeTours = [];
+ vm.nodeName = '';
+
+ if (vm.section === 'content' && vm.tree === 'content') {
+
+ if (node) {
+ tourService.getToursForDoctype(node.contentTypeAlias).then(function (data) {
+ if (data && data.length > 0) {
+ vm.docTypeTours = data;
+ var currentVariant = _.find(node.variants, (x) => x.active);
+ vm.nodeName = currentVariant.name;
+ vm.showDocTypeTour = true;
+ }
+ });
+ }
+ }
+ }
+
evts.push(eventsService.on("appState.tour.complete", function (event, tour) {
tourService.getGroupedTours().then(function(groupedTours) {
vm.tours = groupedTours;
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html
index 96f4a404bc..aa6126e73e 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/drawers/help/help.html
@@ -8,12 +8,36 @@
-
-
+
+
+
Need help editing current item '{{vm.nodeName}}' ?
-
Tours
+
-
+
+
+
+
+
+ {{ tour.name }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Tours
+
+
+
@@ -25,7 +49,9 @@
{{tourGroup.group}}
- Other
+
+ Other
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js
index b5043293e5..47607b7f0b 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js
+++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.controller.js
@@ -33,6 +33,7 @@ angular.module("umbraco").controller("Umbraco.Editors.LinkPickerController",
};
$scope.showTarget = $scope.model.hideTarget !== true;
+ $scope.showAnchor = $scope.model.hideAnchor !== true;
// this ensures that we only sync the tree once and only when it's ready
var oneTimeTreeSync = {
diff --git a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html
index a7d2dbbee2..ad0aaab57c 100644
--- a/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html
+++ b/src/Umbraco.Web.UI.Client/src/views/common/infiniteeditors/linkpicker/linkpicker.html
@@ -14,7 +14,7 @@
-