Merge branch 'v8/contrib' into v8/dev

# Conflicts:
#	src/Umbraco.Web.UI.Client/src/common/services/tour.service.js
This commit is contained in:
Sebastiaan Janssen
2020-01-21 18:57:46 +01:00
79 changed files with 846 additions and 292 deletions
+5 -5
View File
@@ -60,10 +60,10 @@ Great question! The short version goes like this:
![Clone the fork](img/clonefork.png)
* **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))
+1 -1
View File
@@ -1,4 +1,4 @@
# [Umbraco CMS](https://umbraco.com) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE.md) [![Build status](https://umbraco.visualstudio.com/Umbraco%20Cms/_apis/build/status/Cms%208%20Continuous?branchName=v8/dev)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) [![Twitter](https://img.shields.io/twitter/follow/umbraco.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=umbraco)
# [Umbraco CMS](https://umbraco.com) · [![GitHub license](https://img.shields.io/badge/license-MIT-blue.svg)](../LICENSE.md) [![Build status](https://umbraco.visualstudio.com/Umbraco%20Cms/_apis/build/status/Cms%208%20Continuous?branchName=v8/contrib)](https://umbraco.visualstudio.com/Umbraco%20Cms/_build?definitionId=75) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md) [![Twitter](https://img.shields.io/twitter/follow/umbraco.svg?style=social&label=Follow)](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.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 24 KiB

@@ -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;
}
}
@@ -26,5 +26,10 @@ namespace Umbraco.Core.Persistence.Repositories
/// <param name="contentPath"></param>
/// <returns></returns>
bool HasContainerInPath(string contentPath);
/// <summary>
/// Returns true or false depending on whether content nodes have been created based on the provided content type id.
/// </summary>
bool HasContentNodes(int id);
}
}
@@ -1323,6 +1323,17 @@ WHERE {Constants.DatabaseSchema.Tables.Content}.nodeId IN (@ids) AND cmsContentT
return Database.ExecuteScalar<int>(sql) > 0;
}
/// <summary>
/// Returns true or false depending on whether content nodes have been created based on the provided content type id.
/// </summary>
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<int>(sql) == 1;
}
protected override IEnumerable<string> GetDeleteClauses()
{
// in theory, services should have ensured that content items of the given content type
@@ -39,6 +39,11 @@ namespace Umbraco.Core.Services
int Count();
/// <summary>
/// Returns true or false depending on whether content nodes have been created based on the provided content type id.
/// </summary>
bool HasContentNodes(int id);
IEnumerable<TItem> GetAll(params int[] ids);
IEnumerable<TItem> GetAll(IEnumerable<Guid> ids);
@@ -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
+1
View File
@@ -277,6 +277,7 @@
<Compile Include="Models\Entities\MediaEntitySlim.cs" />
<Compile Include="Models\PagedResult.cs" />
<Compile Include="Models\Entities\MemberEntitySlim.cs" />
<Compile Include="Models\MediaTypeExtensions.cs" />
<Compile Include="Models\PublishedContent\ILivePublishedModelFactory.cs" />
<Compile Include="Models\PublishedContent\IPublishedContentType.cs" />
<Compile Include="Models\PublishedContent\IPublishedPropertyType.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)
@@ -132,6 +132,10 @@ ol.inline {
display: inline-block;
padding-left: 5px;
padding-right: 5px;
&.-no-padding-left{
padding-left: 0;
}
}
}
@@ -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;
}
@@ -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) {
@@ -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"
];
@@ -213,7 +213,6 @@ Opens an overlay to show a custom YSOD. </br>
var unsubscribe = [];
function activate() {
setView();
setButtonText();
@@ -247,10 +246,20 @@ Opens an overlay to show a custom YSOD. </br>
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) {
@@ -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
};
@@ -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);
}
};
}]);
@@ -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');
}
};
}
@@ -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);
},
@@ -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;
@@ -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,
@@ -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 });
},
/**
@@ -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;
@@ -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;
}
}
@@ -30,6 +30,9 @@
.umb-child-selector__children-container {
margin-left: 30px;
.umb-child-selector__child {
cursor: move;
}
}
.umb-child-selector__child-description {
@@ -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;
@@ -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;
}
@@ -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;
@@ -8,12 +8,36 @@
<umb-drawer-content>
<!-- Tours -->
<div class="umb-help-section" ng-if="vm.tours.length > 0" data-element="help-tours">
<!-- Doctype Tours -->
<div class="umb-help-section" ng-if="vm.showDocTypeTour" data-element="doctype-tour">
<h5>Need help editing current item '{{vm.nodeName}}' ?</h5>
<h5 class="umb-help-section__title">Tours</h5>
<div class="umb-help-list">
<div ng-repeat="tourGroup in vm.tours | orderBy:'groupOrder'">
<div ng-repeat="tour in vm.docTypeTours | orderBy:'groupOrder'">
<div data-element="tour-{{tour.alias}}" class="umb-help-list-item">
<div class="umb-help-list-item__content justify-between">
<div class="flex items-center">
<span class="umb-help-list-item__title">{{ tour.name }}</span>
</div>
<div>
<umb-button button-style="primary" size="xxs" type="button" label="Start" action="vm.startTour(tour)"></umb-button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Tours -->
<div class="umb-help-section" ng-if="vm.tours.length > 0" data-element="help-tours">
<h5 class="umb-help-section__title">
<localize key="help_tours">Tours</localize>
</h5>
<div ng-repeat="tourGroup in vm.tours | orderBy:'groupOrder'">
<div class="umb-help-list">
@@ -25,7 +49,9 @@
<span class="umb-help-list-item__group-title bold">
<i ng-class="{'icon-navigation-right': !tourGroup.open, 'icon-navigation-down': tourGroup.open}" aria-hidden="true"></i>
<span ng-if="tourGroup.group !== 'undefined'">{{tourGroup.group}}</span>
<span ng-if="tourGroup.group === 'undefined'">Other</span>
<span ng-if="tourGroup.group === 'undefined'">
<localize key="general_other">Other</localize>
</span>
</span>
<umb-progress-circle
percentage="{{tourGroup.completedPercentage}}"
@@ -51,33 +77,35 @@
</div>
</div>
</div>
</div>
<!-- Show in custom help dashboard -->
<div class="umb-help-section" data-element="help-custom-dashboard" ng-if="vm.customDashboard.length > 0">
<div ng-repeat="dashboard in vm.customDashboard">
<h5 ng-show="dashboard.label">{{dashboard.label}}</h5>
<div ng-repeat="property in dashboard.properties">
<div>
<div ng-include="property.view"></div>
<!-- Show in custom help dashboard -->
<div class="umb-help-section" data-element="help-custom-dashboard" ng-if="vm.customDashboard.length > 0">
<div ng-repeat="dashboard in vm.customDashboard">
<h5 ng-show="dashboard.label">{{dashboard.label}}</h5>
<div ng-repeat="property in dashboard.properties">
<div>
<div ng-include="property.view"></div>
</div>
</div>
</div>
</div>
</div>
<!-- Help Content -->
<div class="umb-help-section" data-element="help-articles" ng-if="vm.topics.length > 0">
<h5 class="umb-help-section__title">Articles</h5>
<ul class="umb-help-list">
<li class="umb-help-list-item" ng-repeat="topic in vm.topics track by $index">
<a class="umb-help-list-item__content" data-element="help-article-{{topic.name}}" target="_blank" ng-href="{{topic.url}}?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=tv">
<span>
<span class="umb-help-list-item__title">
<span class="bold">{{topic.name}}</span>
<span class="umb-help-list-item__open-icon icon-out"></span>
</span>
<span class="umb-help-list-item__description">{{topic.description}}</span>
</span>
<!-- Help Content -->
<div class="umb-help-section" data-element="help-articles" ng-if="vm.topics.length > 0">
<h5 class="umb-help-section__title">
<localize key="general_articles">Articles</localize>
</h5>
<ul class="umb-help-list">
<li class="umb-help-list-item" ng-repeat="topic in vm.topics track by $index">
<a class="umb-help-list-item__content" data-element="help-article-{{topic.name}}" target="_blank" ng-href="{{topic.url}}?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=tv">
<span>
<span class="umb-help-list-item__title">
<span class="bold">{{topic.name}}</span>
<span class="umb-help-list-item__open-icon icon-out"></span>
</span>
<span class="umb-help-list-item__description">{{topic.description}}</span>
</span>
</a>
</li>
</ul>
@@ -85,7 +113,9 @@
<!-- Umbraco tv content -->
<div class="umb-help-section" data-element="help-videos" ng-if="vm.hasAccessToSettings">
<h5 class="umb-help-section__title" ng-if="vm.videos.length > 0">Videos</h5>
<h5 class="umb-help-section__title" ng-if="vm.videos.length > 0">
<localize key="general_videos">Videos</localize>
</h5>
<ul class="umb-help-list">
<li class="umb-help-list-item" ng-repeat="video in vm.videos track by $index">
<a class="umb-help-list-item__content" data-element="help-article-{{video.title}}" target="_blank" ng-href="{{video.link}}?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=tv">
@@ -101,7 +131,9 @@
<div class="umb-help-section" data-element="help-links" ng-if="vm.hasAccessToSettings">
<a data-element="help-link-umbraco-tv" class="umb-help-badge" target="_blank" href="https://umbraco.tv?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=tv">
<i class="umb-help-badge__icon icon-tv-old" aria-hidden="true"></i>
<div class="umb-help-badge__title">Visit umbraco.tv</div>
<div class="umb-help-badge__title">
<localize key="help_umbracoTv">Visit umbraco.tv</localize>
</div>
<small>
<localize key="help_theBestUmbracoVideoTutorials">The best Umbraco video tutorials</localize>
</small>
@@ -110,7 +142,9 @@
<a data-element="help-link-our-umbraco" class="umb-help-badge" target="_blank" href="https://our.umbraco.com?utm_source=core&utm_medium=help&utm_content=link&utm_campaign=our">
<i class="umb-help-badge__icon icon-favorite" aria-hidden="true"></i>
<div class="umb-help-badge__title">Visit our.umbraco.com</div>
<div class="umb-help-badge__title">
<localize key="help_umbracoForum">Visit our.umbraco.com</localize>
</div>
<small>
<localize key="defaultdialogs_theFriendliestCommunity">The friendliest community</localize>
</small>
@@ -16,8 +16,9 @@
var dialogOptions = $scope.model;
var node = dialogOptions.currentNode;
$scope.model.relateToOriginal = true;
$scope.dialogTreeApi = {};
$scope.model.relateToOriginal = true;
$scope.model.includeDescendants = true;
$scope.dialogTreeApi = {};
vm.searchInfo = {
searchFromId: null,
@@ -33,14 +34,12 @@
function onInit() {
var labelKeys = [
"general_copy",
"defaultdialogs_relateToOriginalLabel"
"general_copy"
];
localizationService.localizeMany(labelKeys).then(function (data) {
vm.labels.title = data[0];
vm.labels.relateToOriginal = data[1];
setTitle(vm.labels.title);
});
@@ -132,6 +131,10 @@
if (type === "relate") {
$scope.model.relateToOriginal = !$scope.model.relateToOriginal;
}
// If the includeDescendants toggle is clicked
if (type === "descendants") {
$scope.model.includeDescendants = !$scope.model.includeDescendants;
}
}
onInit();
@@ -15,6 +15,10 @@
<umb-editor-container>
<umb-box>
<umb-box-content>
<p class="abstract">
<localize key="actions_infiniteEditorChooseWhereToCopy">Choose where to copy the selected item(s)</localize>
</p>
<div ng-hide="miniListView">
<div class="umb-control-group">
<umb-tree-search-box
@@ -64,6 +68,11 @@
<umb-toggle label-position="left" checked="model.relateToOriginal" on-click="vm.onToggle('relate')"></umb-toggle>
</umb-control-group>
</umb-pane>
<umb-pane>
<umb-control-group localize="label" label="@defaultdialogs_includeDescendants">
<umb-toggle label-position="left" checked="model.includeDescendants" on-click="vm.onToggle('descendants')"></umb-toggle>
</umb-control-group>
</umb-pane>
</umb-box-content>
</umb-box>
</umb-editor-container>
@@ -82,7 +91,8 @@
button-style="success"
label-key="general_submit"
state="vm.saveButtonState"
action="vm.submit(model)">
action="vm.submit(model)"
disabled="!model.target">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
@@ -46,9 +46,10 @@
<div class="umb-control-group" ng-show="!vm.loading && filtered.length > 0 ">
<ul class="umb-iconpicker" ng-class="vm.color.value" ng-show="vm.icons">
<li class="umb-iconpicker-item" ng-class="{'-selected': icon == model.icon}" ng-repeat="icon in filtered = (vm.icons | filter: searchTerm) track by $id(icon)">
<a href="#" title="{{icon}}" ng-click="vm.selectIcon(icon, vm.color.value)" prevent-default>
<i class="{{icon}} large"></i>
</a>
<button type="button" title="{{icon}}" ng-click="vm.selectIcon(icon, vm.color.value)" prevent-default>
<i class="{{icon}} large" aria-hidden="true"></i>
<span class="sr-only"><localize key="buttons_select">Select</localize> {{icon}}</span>
</button>
</li>
</ul>
</div>
@@ -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 = {
@@ -14,7 +14,7 @@
<umb-box>
<umb-box-content>
<div class="flex">
<div ng-class="{'flex': showAnchor}">
<umb-control-group label="@defaultdialogs_urlLinkPicker" class="umb-linkpicker__url">
<input type="text"
@@ -27,7 +27,7 @@
ng-disabled="model.target.id || model.target.udi" />
</umb-control-group>
<umb-control-group label="@defaultdialogs_anchorLinkPicker" class="umb-linkpicker__anchor">
<umb-control-group label="@defaultdialogs_anchorLinkPicker" class="umb-linkpicker__anchor" ng-if="showAnchor">
<input type="text"
list="anchors"
localize="placeholder"
@@ -13,6 +13,9 @@
<umb-editor-container>
<umb-box>
<umb-box-content>
<p class="abstract" ng-hide="success">
<localize key="actions_infiniteEditorChooseWhereToMove">Choose where to move the selected item(s)</localize>
</p>
<div ng-hide="miniListView">
<div class="umb-control-group">
@@ -75,7 +78,8 @@
button-style="success"
label-key="general_submit"
state="vm.saveButtonState"
action="vm.submit(model)">
action="vm.submit(model)"
disabled="!model.target">
</umb-button>
</umb-editor-footer-content-right>
</umb-editor-footer>
@@ -19,14 +19,13 @@
</div>
<ul class="umb-card-grid" ng-class="{'-three-in-row': model.availableItems.length < 7, '-four-in-row': model.availableItems.length >= 7}">
<li ng-repeat="pasteItem in model.pasteItems | filter:searchTerm"
ng-click="model.clickPasteItem(pasteItem)">
<a class="umb-card-grid-item" href="" title="{{ pasteItem.name }}">
<li ng-repeat="pasteItem in model.pasteItems | filter:searchTerm">
<button class="umb-card-grid-item btn-reset" ng-click="model.clickPasteItem(pasteItem)">
<span>
<i class="{{ pasteItem.icon }}"></i>
{{ pasteItem.name | truncate:true:36 }}
</span>
</a>
</button>
</li>
</ul>
@@ -35,15 +34,13 @@
</div>
<ul class="umb-card-grid" ng-class="{'-three-in-row': model.availableItems.length < 7, '-four-in-row': model.availableItems.length >= 7}">
<li ng-repeat="availableItem in model.availableItems | compareArrays:model.selectedItems:'alias' | orderBy:model.orderBy | filter:searchTerm"
ng-click="selectItem(availableItem)">
<a class="umb-card-grid-item" href="" title="{{ availableItem.name }}">
<li ng-repeat="availableItem in model.availableItems | compareArrays:model.selectedItems:'alias' | orderBy:model.orderBy | filter:searchTerm">
<button class="umb-card-grid-item btn-reset" ng-click="selectItem(availableItem)">
<span>
<i class="{{ availableItem.icon }}"></i>
{{ availableItem.name }}
</span>
</a>
</button>
</li>
</ul>
</div>
@@ -2,15 +2,21 @@
<div class="umb-search" on-outside-click="vm.closeSearch()" ng-keydown="vm.handleKeyDown($event)">
<div class="flex items-center">
<i class="umb-search-input-icon icon-search" ng-click="vm.focusSearch()"></i>
<input
class="umb-search-input"
type="text"
<label for="app-search" class="umb-search__label">
<i class="umb-search-input-icon icon-search" aria-hidden="true"></i>
<span class="sr-only">
<localize key="general_search">Search...</localize>
</span>
</label>
<input
class="umb-search-input"
type="text"
ng-model="vm.searchQuery"
ng-model-options="{ debounce: 200 }"
ng-change="vm.search(vm.searchQuery)"
localize="placeholder"
placeholder="@placeholders_search"
id="app-search"
focus-when="{{vm.searchHasFocus}}" />
<button ng-show="vm.searchQuery.length > 0" tabindex="-1" class="umb-search-input-clear umb-animated" ng-click="vm.clearSearch()">
<localize key="general_clear">Clear</localize>
@@ -14,7 +14,7 @@
</div>
</div>
<div class="umb-child-selector__children-container">
<div class="umb-child-selector__children-container" ui-sortable="sortableOptions" ng-model="selectedChildren">
<div class="umb-child-selector__child" ng-repeat="selectedChild in selectedChildren">
<div class="umb-child-selector__child-description">
@@ -6,11 +6,11 @@
'-left': direction === 'left'}"
on-outside-click="clickCancel()">
<a class="umb_confirm-action__overlay-action -confirm" href="" ng-click="clickConfirm()" localize="title" title="@buttons_confirmActionConfirm">
<button class="umb_confirm-action__overlay-action -confirm btn-reset" ng-click="clickConfirm()" localize="title" title="@buttons_confirmActionConfirm" type="button">
<i class="icon-check"></i>
</a>
</button>
<a class="umb_confirm-action__overlay-action -cancel" href="" ng-click="clickCancel()" localize="title" title="@buttons_confirmActionCancel">
<button class="umb_confirm-action__overlay-action -cancel btn-reset" ng-click="clickCancel()" localize="title" title="@buttons_confirmActionCancel" type="button">
<i class="icon-delete"></i>
</a>
</button>
</div>
@@ -36,7 +36,7 @@
<ul class="umb-actions umb-actions-child" ng-if="selectContentType && allowedTypes.length > 0" aria-labelledby="selectContentType">
<li class="umb-action" data-element="action-create-{{docType.alias}}" ng-repeat="docType in allowedTypes | orderBy:'name':false">
<li class="umb-action" data-element="action-create-{{docType.alias}}" ng-repeat="docType in allowedTypes">
<button class="umb-action-link umb-outline btn-reset" ng-click="createOrSelectBlueprintIfAny(docType)" umb-auto-focus ng-if="$index === 0">
<i class="large icon {{docType.icon}}"></i>
<span class="menu-label">
@@ -2,20 +2,25 @@
<div class="installer-wrapper">
<div class="installer-intro">
<i class="icon icon-umb-contour"></i>
<h3>Umbraco Forms</h3>
<i class="icon icon-umb-contour" aria-hidden="true"></i>
<h3>
<localize key="formsDashboard_formsHeadline">Umbraco Forms</localize>
</h3>
</div>
<!-- STEP one -->
<div class="step-one" ng-hide="vm.state">
<p class="step-text">Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it!</p>
<p class="step-text">
<localize key="formsDashboard_formsDescription">Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it!</localize>
</p>
<umb-button
class="forms-install-button"
type="button"
button-style="success"
label="Install"
label-key="general_install"
action="vm.installForms()">
</umb-button>
@@ -23,7 +28,7 @@
<!-- STEP two -->
<div class="step-two" ng-if="vm.state">
<p id="installer" class="step-text">Installing...</p>
<p id="installer" class="step-text"><localize key="general_installing">Installing</localize>...</p>
<div class="installing">
@@ -6,7 +6,7 @@
* @description
* The controller for the info view of the datatype editor
*/
function DataTypeInfoController($scope, $routeParams, dataTypeResource, eventsService, $timeout) {
function DataTypeInfoController($scope, $routeParams, dataTypeResource, eventsService, $timeout, editorService) {
var vm = this;
var evts = [];
@@ -17,6 +17,9 @@ function DataTypeInfoController($scope, $routeParams, dataTypeResource, eventsSe
vm.view = {};
vm.view.loading = true;
vm.openDocumentType = openDocumentType;
vm.openMediaType = openMediaType;
vm.openMemberType = openMemberType;
/** Loads in the data type references one time */
function loadRelations() {
@@ -31,6 +34,57 @@ function DataTypeInfoController($scope, $routeParams, dataTypeResource, eventsSe
}
}
function openDocumentType(id, event) {
open(id, event, "documentType");
}
function openMediaType(id, event) {
open(id, event, "mediaType");
}
function openMemberType(id, event) {
open(id, event, "memberType");
}
function open(id, event, type) {
// targeting a new tab/window?
if (event.ctrlKey ||
event.shiftKey ||
event.metaKey || // apple
(event.button && event.button === 1) // middle click, >IE9 + everyone else
) {
// yes, let the link open itself
return;
}
event.stopPropagation();
event.preventDefault();
const editor = {
id: id,
submit: function (model) {
editorService.close();
vm.view.loading = true;
referencesLoaded = false;
loadRelations();
},
close: function () {
editorService.close();
}
};
switch (type) {
case "documentType":
editorService.documentTypeEditor(editor);
break;
case "mediaType":
editorService.mediaTypeEditor(editor);
break;
case "memberType":
editorService.memberTypeEditor(editor);
break;
}
}
// load data type references when the references tab is activated
evts.push(eventsService.on("app.tabChange", function (event, args) {
$timeout(function () {
@@ -43,7 +43,7 @@
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell --noOverflow"><span>{{::reference.properties | umbCmsJoinArray:', ':'name'}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/documentTypes/edit/{{::reference.id}}"><localize key="general_open">Open</localize></a></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/documentTypes/edit/{{::reference.id}}" ng-click="vm.openDocumentType(reference.id, $event)"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
@@ -73,7 +73,7 @@
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell --noOverflow"><span>{{::reference.properties | umbCmsJoinArray:', ':'name'}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/mediaTypes/edit/{{::reference.id}}"><localize key="general_open">Open</localize></a></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/mediaTypes/edit/{{::reference.id}}" ng-click="vm.openMediaType(reference.id, $event)"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
@@ -104,7 +104,7 @@
<div class="umb-table-cell umb-table__name"><span>{{::reference.name}}</span></div>
<div class="umb-table-cell"><span title="{{::reference.alias}}">{{::reference.alias}}</span></div>
<div class="umb-table-cell --noOverflow"><span>{{::reference.properties | umbCmsJoinArray:', ':'name'}}</span></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/memberTypes/edit/{{::reference.id}}"><localize key="general_open">Open</localize></a></div>
<div class="umb-table-cell umb-table-cell--nano"><a href="#/settings/memberTypes/edit/{{::reference.id}}" ng-click="vm.openMemberType(reference.id, $event)"><localize key="general_open">Open</localize></a></div>
</div>
</div>
</div>
@@ -9,7 +9,7 @@
(function() {
'use strict';
function PermissionsController($scope, contentTypeResource, iconHelper, contentTypeHelper, localizationService, overlayService) {
function PermissionsController($scope, $timeout, contentTypeResource, iconHelper, contentTypeHelper, localizationService, overlayService) {
/* ----------- SCOPE VARIABLES ----------- */
@@ -23,8 +23,10 @@
vm.addChild = addChild;
vm.removeChild = removeChild;
vm.sortChildren = sortChildren;
vm.toggleAllowAsRoot = toggleAllowAsRoot;
vm.toggleAllowCultureVariants = toggleAllowCultureVariants;
vm.canToggleIsElement = false;
vm.toggleIsElement = toggleIsElement;
/* ---------- INIT ---------- */
@@ -48,9 +50,16 @@
if($scope.model.id === 0) {
contentTypeHelper.insertChildNodePlaceholder(vm.contentTypes, $scope.model.name, $scope.model.icon, $scope.model.id);
}
});
// Can only switch to an element type if there are no content nodes already created from the type.
if ($scope.model.id > 0 && !$scope.model.isElement ) {
contentTypeResource.hasContentNodes($scope.model.id).then(function (result) {
vm.canToggleIsElement = !result;
});
} else {
vm.canToggleIsElement = true;
}
}
function addChild($event) {
@@ -84,6 +93,13 @@
$scope.model.allowedContentTypes.splice(selectedChildIndex, 1);
}
function sortChildren() {
// we need to wait until the next digest cycle for vm.selectedChildren to be updated
$timeout(function () {
$scope.model.allowedContentTypes = _.pluck(vm.selectedChildren, "id");
});
}
// note: "safe toggling" here ie handling cases where the value is undefined, etc
function toggleAllowAsRoot() {
@@ -33,7 +33,8 @@
parent-icon="model.icon"
parent-id="model.id"
on-add="vm.addChild"
on-remove="vm.removeChild">
on-remove="vm.removeChild"
on-sort="vm.sortChildren">
</umb-child-selector>
</div>
@@ -61,11 +62,13 @@
<div class="sub-view-column-left">
<h5><localize key="contentTypeEditor_elementHeading" /></h5>
<small><localize key="contentTypeEditor_elementDescription" /></small>
<small ng-if="!vm.canToggleIsElement"><br/><localize key="contentTypeEditor_elementCannotToggle" /></small>
</div>
<div class="sub-view-column-right">
<umb-toggle data-element="permissions-is-element"
checked="model.isElement"
disabled="!vm.canToggleIsElement"
on-click="vm.toggleIsElement()">
</umb-toggle>
</div>
@@ -9,6 +9,7 @@
<umb-editor-header
name="vm.contentType.name"
alias="vm.contentType.alias"
alias-locked="vm.contentType.isSystemMediaType"
key="vm.contentType.key"
description="vm.contentType.description"
navigation="vm.page.navigation"
@@ -1,7 +1,7 @@
(function() {
'use strict';
function PermissionsController($scope, mediaTypeResource, iconHelper, contentTypeHelper, localizationService, overlayService) {
function PermissionsController($scope, $timeout, mediaTypeResource, iconHelper, contentTypeHelper, localizationService, overlayService) {
/* ----------- SCOPE VARIABLES ----------- */
@@ -13,6 +13,7 @@
vm.addChild = addChild;
vm.removeChild = removeChild;
vm.sortChildren = sortChildren;
vm.toggle = toggle;
/* ---------- INIT ---------- */
@@ -71,6 +72,13 @@
$scope.model.allowedContentTypes.splice(selectedChildIndex, 1);
}
function sortChildren() {
// we need to wait until the next digest cycle for vm.selectedChildren to be updated
$timeout(function () {
$scope.model.allowedContentTypes = _.pluck(vm.selectedChildren, "id");
});
}
/**
* Toggle the $scope.model.allowAsRoot value to either true or false
*/
@@ -27,7 +27,8 @@
parent-icon="model.icon"
parent-id="model.id"
on-add="vm.addChild"
on-remove="vm.removeChild">
on-remove="vm.removeChild"
on-sort="vm.sortChildren">
</umb-child-selector>
</div>
</div>
@@ -13,8 +13,13 @@
var evts = [];
var vm = this;
var infiniteMode = $scope.model && $scope.model.infiniteMode;
var memberTypeId = infiniteMode ? $scope.model.id : $routeParams.id;
var create = infiniteMode ? $scope.model.create : $routeParams.create;
vm.save = save;
vm.close = close;
vm.editorfor = "visuallyHiddenTexts_newMember";
vm.header = {};
vm.header.editorfor = "content_membergroup";
@@ -25,6 +30,7 @@
vm.page.loading = false;
vm.page.saveButtonState = "init";
vm.labels = {};
vm.saveButtonKey = infiniteMode ? "buttons_saveAndClose" : "buttons_save";
var labelKeys = [
"general_design",
@@ -86,7 +92,7 @@
vm.page.defaultButton = {
hotKey: "ctrl+s",
hotKeyWhenHidden: true,
labelKey: "buttons_save",
labelKey: vm.saveButtonKey,
letter: "S",
type: "submit",
handler: function () { vm.save(); }
@@ -94,7 +100,7 @@
vm.page.subButtons = [{
hotKey: "ctrl+g",
hotKeyWhenHidden: true,
labelKey: "buttons_saveAndGenerateModels",
labelKey: infiniteMode ? "buttons_generateModelsAndClose" : "buttons_saveAndGenerateModels",
letter: "G",
handler: function () {
@@ -147,12 +153,12 @@
}
});
if ($routeParams.create) {
if (create) {
vm.page.loading = true;
//we are creating so get an empty data type item
memberTypeResource.getScaffold($routeParams.id)
memberTypeResource.getScaffold(memberTypeId)
.then(function (dt) {
init(dt);
@@ -163,10 +169,12 @@
vm.page.loading = true;
memberTypeResource.getById($routeParams.id).then(function (dt) {
memberTypeResource.getById(memberTypeId).then(function (dt) {
init(dt);
syncTreeNode(vm.contentType, dt.path, true);
if(!infiniteMode) {
syncTreeNode(vm.contentType, dt.path, true);
}
vm.page.loading = false;
});
@@ -219,10 +227,16 @@
}
}).then(function (data) {
//success
syncTreeNode(vm.contentType, data.path);
if(!infiniteMode) {
syncTreeNode(vm.contentType, data.path);
}
vm.page.saveButtonState = "success";
if(infiniteMode && $scope.model.submit) {
$scope.model.submit();
}
deferred.resolve(data);
}, function (err) {
//error
@@ -307,6 +321,12 @@
});
}
function close() {
if(infiniteMode && $scope.model.close) {
$scope.model.close();
}
}
evts.push(eventsService.on("editors.groupsBuilder.changed", function(name, args) {
angularHelper.getCurrentForm($scope).$setDirty();
@@ -40,6 +40,14 @@
<umb-editor-footer-content-right>
<umb-button ng-if="model.infiniteMode"
type="button"
button-style="link"
label-key="general_close"
shortcut="esc"
action="vm.close()">
</umb-button>
<umb-button
ng-if="!vm.page.modelsBuilder"
type="button"
@@ -47,8 +55,7 @@
state="vm.page.saveButtonState"
button-style="success"
shortcut="ctrl+s"
label="Save"
label-key="buttons_save">
label-key="{{vm.saveButtonKey}}">
</umb-button>
<umb-button-group
@@ -31,7 +31,7 @@
<span class="sr-only">...</span>
</button>
<div class="umb-contentpicker__min-max-help" ng-if="model.config.multiPicker === true">
<div class="umb-contentpicker__min-max-help" ng-if="model.config.multiPicker === true && (model.config.maxNumber > 1 || model.config.minNumber > 0)">
<!-- Both min and max items -->
<span ng-if="model.config.minNumber && model.config.maxNumber && model.config.minNumber !== model.config.maxNumber">
@@ -85,13 +85,27 @@ function dateTimePickerController($scope, angularHelper, dateHelper, validationM
};
$scope.datePickerChange = function(date) {
setDate(date);
const momentDate = moment(date);
setDate(momentDate);
setDatePickerVal();
};
$scope.inputChanged = function() {
setDate($scope.model.datetimePickerValue);
setDatePickerVal();
$scope.inputChanged = function () {
if ($scope.model.datetimePickerValue === "" && $scope.hasDatetimePickerValue) {
// $scope.hasDatetimePickerValue indicates that we had a value before the input was changed,
// but now the input is empty.
$scope.clearDate();
} else if ($scope.model.datetimePickerValue) {
var momentDate = moment($scope.model.datetimePickerValue, $scope.model.config.format, true);
if (!momentDate || !momentDate.isValid()) {
momentDate = moment(new Date($scope.model.datetimePickerValue));
}
if (momentDate && momentDate.isValid()) {
setDate(momentDate);
}
setDatePickerVal();
flatPickr.setDate($scope.model.value, false);
}
}
//here we declare a special method which will be called whenever the value has changed from the server
@@ -103,15 +117,14 @@ function dateTimePickerController($scope, angularHelper, dateHelper, validationM
var newDate = moment(newVal);
if (newDate.isAfter(minDate)) {
setDate(newVal);
setDate(newDate);
} else {
$scope.clearDate();
}
}
};
function setDate(date) {
const momentDate = moment(date);
function setDate(momentDate) {
angularHelper.safeApply($scope, function() {
// when a date is changed, update the model
if (momentDate && momentDate.isValid()) {
@@ -123,12 +136,11 @@ function dateTimePickerController($scope, angularHelper, dateHelper, validationM
$scope.hasDatetimePickerValue = false;
$scope.model.datetimePickerValue = null;
}
updateModelValue(date);
updateModelValue(momentDate);
});
}
function updateModelValue(date) {
const momentDate = moment(date);
function updateModelValue(momentDate) {
if ($scope.hasDatetimePickerValue) {
if ($scope.model.config.pickTime) {
//check if we are supposed to offset the time
@@ -402,6 +402,15 @@ angular.module("umbraco")
eventsService.emit("grid.rowAdded", { scope: $scope, element: $element, row: row });
// TODO: find a nicer way to do this without relying on setTimeout
setTimeout(function () {
var newRowEl = $element.find("[data-rowid='" + row.$uniqueId + "']");
if(newRowEl !== null) {
newRowEl.focus();
}
}, 0);
};
$scope.removeRow = function (section, $index) {
@@ -95,7 +95,7 @@
</div>
<div class="cell-tools-remove row-tool">
<i class="icon-trash" ng-click="togglePrompt(row)" localize="title" title="@delete"></i>
<button class="icon-trash btn-reset" ng-click="togglePrompt(row)" type="button"></button>
<umb-confirm-action
ng-if="row.deletePrompt"
direction="left"
@@ -161,12 +161,12 @@
<div class="umb-cell-inner" ui-sortable="sortableOptionsCell" umb-grid-hack-scope ng-model="area.controls">
<!-- Control placeholder -->
<div class="umb-cell-placeholder" ng-if="area.controls.length === 0" ng-click="openEditorOverlay($event, area, 0, area.$uniqueId);">
<button class="umb-cell-placeholder btn-reset" type="button" ng-if="area.controls.length === 0" ng-click="openEditorOverlay($event, area, 0, area.$uniqueId);">
<div class="cell-tools-add -center">
<localize ng-if="!sortMode" key="grid_addElement" />
<localize ng-if="sortMode" key="grid_dropElement" />
</div>
</div>
</button>
<!-- for each control in areas -->
<div class="umb-control"
@@ -196,11 +196,11 @@
<div class="umb-tools" ng-if="control === active">
<div class="umb-control-tool" ng-if="control.editor.config.settings">
<i class="umb-control-tool-icon icon-settings" ng-click="editGridItemSettings(control, 'control')"></i>
<button class="umb-control-tool-icon icon-settings btn-reset" ng-click="editGridItemSettings(control, 'control')" type="button"></button>
</div>
<div class="umb-control-tool">
<i class="umb-control-tool-icon icon-trash" ng-click="togglePrompt(control)" localize="title" title="@delete"></i>
<button class="umb-control-tool-icon icon-trash btn-reset" ng-click="togglePrompt(control)" type="button"></button>
<umb-confirm-action ng-if="control.deletePrompt"
direction="left"
on-confirm="removeControl(area, $index)"
@@ -226,7 +226,7 @@
<!-- if area is empty tools -->
<div class="umb-grid-add-more-content" ng-if="area.controls.length > 0 && !sortMode && (area.maxItems == undefined || area.maxItems == '' || area.maxItems == 0 || area.maxItems > area.controls.length)">
<div class="cell-tools-add -bar newbtn" ng-click="openEditorOverlay($event, area, 0, area.$uniqueId);"><localize key="grid_addElement" /></div>
<button class="cell-tools-add -bar newbtn btn-reset" type="button" ng-click="openEditorOverlay($event, area, 0, area.$uniqueId);"><localize key="grid_addElement" /></button>
</div>
</div>
@@ -249,14 +249,16 @@
<div class="umb-add-row" ng-if="!sortMode">
<a href=""
class="iconBox"
<button
class="iconBox btn-reset"
ng-click="toggleAddRow()"
ng-if="!showRowConfigurations">
<i class=" icon icon-add" title="@general_add" localize="title"></i>
</a>
<i class="icon icon-add" ></i>
<span class="sr-only">
<localize key="visuallyHiddenTexts_addNewRow">Add new role</localize>
</span>
</button>
</div>
@@ -264,10 +266,11 @@
<p ng-hide="section.rows.length > 0"><strong><localize key="grid_addRows" /></strong></p>
<div class="preview-rows columns"
<button class="preview-rows columns btn-reset"
ng-repeat="layout in section.$allowedLayouts"
ng-show="layout.areas.length > 0"
ng-click="addRow(section, layout)">
ng-click="addRow(section, layout)"
type="button">
<div class="preview-row">
@@ -285,7 +288,7 @@
<small>{{layout.label || layout.name}}</small>
</div> <!-- .templates-preview-rows -->
</button> <!-- .templates-preview-rows -->
</div> <!-- .templates-preview -->
<!-- column tools end -->
@@ -620,7 +620,7 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time
currentNode: $scope.contentId,
submit: function (model) {
if (model.target) {
performCopy(model.target, model.relateToOriginal);
performCopy(model.target, model.relateToOriginal, model.includeDescendants);
}
editorService.close();
},
@@ -631,9 +631,9 @@ function listViewController($scope, $interpolate, $routeParams, $injector, $time
editorService.copy(copyEditor);
};
function performCopy(target, relateToOriginal) {
function performCopy(target, relateToOriginal, includeDescendants) {
applySelected(
function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal }); },
function (selected, index) { return contentResource.copy({ parentId: target.id, id: getIdCallback(selected[index]), relateToOriginal: relateToOriginal, recursive: includeDescendants }); },
function (count, total) {
var key = (total === 1 ? "bulk_copiedItemOfItem" : "bulk_copiedItemOfItems");
return localizationService.localize(key, [count, total]);
@@ -21,11 +21,11 @@
<!-- Renders when it's only possible to create one specific document type for which a blueprint exits-->
<div class="btn-group" ng-show="createAllowedButtonSingleWithBlueprints" deep-blur="leaveDropdown()">
<button type="button" class="btn btn-outline umb-outline dropdown-toggle" aria-expanded="{{page.createDropdownOpen}}" data-toggle="dropdown" ng-click="toggleDropdown()" prevent-default>
<button type="button" class="btn btn-white dropdown-toggle" aria-expanded="{{page.createDropdownOpen}}" ng-click="toggleDropdown()" prevent-default>
<span>
<localize key="actions_create">Create</localize> {{listViewAllowedTypes[0].name}}
</span>
<span class="caret"></span>
<span class="caret" aria-hidden="true"></span>
</button>
@@ -46,7 +46,7 @@
<!-- Renders when it's possible to create multiple document types and blueprints for one or more of the document types-->
<div class="btn-group" ng-show="createAllowedButtonMultiWithBlueprints" deep-blur="leaveDropdown()">
<button type="button" class="btn btn-outline umb-outline dropdown-toggle" aria-expanded="{{page.createDropdownOpen === undefined ? false : page.createDropdownOpen}}" data-toggle="dropdown" ng-click="toggleDropdown()">
<button type="button" class="btn btn-white dropdown-toggle" aria-expanded="{{page.createDropdownOpen === undefined ? false : page.createDropdownOpen}}" ng-click="toggleDropdown()">
<localize key="actions_create">Create</localize>
<span class="caret" aria-hidden="true"></span>
</button>
@@ -70,21 +70,21 @@
<ul class="umb-actions umb-actions-child">
<li ng-repeat="blueprint in docType.blueprints track by blueprint.id | orderBy:'name':false">
<a ng-click="createFromBlueprint(blueprint.id)">
<i class="large {{docType.icon}}"></i>
<button type="button" ng-click="createFromBlueprint(blueprint.id)">
<i class="large {{docType.icon}}" aria-hidden="true"></i>
<span class="menu-label">
{{blueprint.name}}&nbsp;
</span>
</a>
</button>
</li>
<li class="sep" ng-show="allowBlank">
<a ng-click="createBlank(docType)">
<i class="large {{docType.icon}}"></i>
<button type="button" ng-click="createBlank(docType)">
<i class="large {{docType.icon}}" aria-hidden="true"></i>
<span class="menu-label">
<localize key="blueprints_blankBlueprint">Blank</localize>
</span>
</a>
</button>
</li>
</ul>
@@ -1,10 +1,16 @@
<div>
<ng-form name="listViewOrderDirectionForm" class="flex">
<umb-radiobutton name="orderDirection" value="asc" model="model.value" text="Ascending [a-z]" required></umb-radiobutton>
<umb-radiobutton name="orderDirection" value="desc" model="model.value" text="Descending [z-a]" required></umb-radiobutton>
<ul class="inline">
<li class="-no-padding-left">
<umb-radiobutton name="orderDirection" value="asc" model="model.value" text="Ascending [a-z]" required></umb-radiobutton>
</li>
<li>
<umb-radiobutton name="orderDirection" value="desc" model="model.value" text="Descending [z-a]" required></umb-radiobutton>
</li>
</ul>
<span ng-messages="listViewOrderDirectionForm.orderDirection.$error" show-validation-on-submit>
<span class="help-inline" ng-message="required">Required</span>
<span class="help-inline" ng-message="required"><localize key="general_required">Required</localize></span>
</span>
</ng-form>
</div>
@@ -27,6 +27,20 @@ function MarkdownEditorController($scope, $element, assetsService, editorService
editorService.mediaPicker(mediaPicker);
}
function openLinkPicker(callback) {
var linkPicker = {
hideTarget: true,
submit: function(model) {
callback(model.target.url, model.target.name);
editorService.close();
},
close: function() {
editorService.close();
}
};
editorService.linkPicker(linkPicker);
}
assetsService
.load([
"lib/markdown/markdown.converter.js",
@@ -53,6 +67,12 @@ function MarkdownEditorController($scope, $element, assetsService, editorService
return true; // tell the editor that we'll take care of getting the image url
});
//subscribe to the link dialog clicks
editor2.hooks.set("insertLinkDialog", function (callback) {
openLinkPicker(callback);
return true; // tell the editor that we'll take care of getting the link url
});
editor2.hooks.set("onPreviewRefresh", function () {
// We must manually update the model as there is no way to hook into the markdown editor events without exstensive edits to the library.
if ($scope.model.value !== $("textarea", $element).val()) {
@@ -82,6 +82,7 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en
currentTarget: target,
dataTypeKey: $scope.model.dataTypeKey,
ignoreUserStartNodes : ($scope.model.config && $scope.model.config.ignoreUserStartNodes) ? $scope.model.config.ignoreUserStartNodes : "0",
hideAnchor: $scope.model.config && $scope.model.config.hideAnchor ? true : false,
submit: function (model) {
if (model.target.url || model.target.anchor) {
// if an anchor exists, check that it is appropriately prefixed
@@ -143,6 +144,16 @@ function multiUrlPickerController($scope, angularHelper, localizationService, en
if ($scope.model.validation && $scope.model.validation.mandatory && !$scope.model.config.minNumber) {
$scope.model.config.minNumber = 1;
}
_.each($scope.model.value, function (item){
// we must reload the "document" link URLs to match the current editor culture
if (item.udi.indexOf("/document/") > 0) {
item.url = null;
entityResource.getUrlByUdi(item.udi).then(function (data) {
item.url = data;
});
}
});
}
init();
@@ -41,7 +41,7 @@
if (vm.maxItems === 0)
vm.maxItems = 1000;
vm.singleMode = vm.minItems === 1 && vm.maxItems === 1;
vm.singleMode = vm.minItems === 1 && vm.maxItems === 1 && model.config.contentTypes.length === 1;;
vm.showIcons = Object.toBoolean(model.config.showIcons);
vm.wideMode = Object.toBoolean(model.config.hideLabel);
vm.hasContentTypes = model.config.contentTypes.length > 0;
@@ -131,6 +131,7 @@
setCurrentNode(newNode);
setDirty();
validate();
};
vm.openNodeTypePicker = function ($event) {
@@ -234,12 +235,22 @@
}
};
vm.canDeleteNode = function (idx) {
return (vm.nodes.length > vm.minItems)
? true
: model.config.contentTypes.length > 1;
}
function deleteNode(idx) {
vm.nodes.splice(idx, 1);
setDirty();
updateModel();
validate();
};
vm.requestDeleteNode = function (idx) {
if (!vm.canDeleteNode(idx)) {
return;
}
if (model.config.confirmDeletes === true) {
localizationService.localizeMany(["content_nestedContentDeleteItem", "general_delete", "general_cancel", "contentTypeEditor_yesDelete"]).then(function (data) {
const overlay = {
@@ -468,8 +479,8 @@
}
}
// Auto-fill with elementTypes, but only if we have one type to choose from, and if this property is empty.
if (vm.singleMode === true && vm.nodes.length === 0 && model.config.minItems > 0) {
// Enforce min items if we only have one scaffold type
if (vm.nodes.length < vm.minItems && vm.scaffolds.length === 1) {
for (var i = vm.nodes.length; i < model.config.minItems; i++) {
addNode(vm.scaffolds[0].contentTypeAlias);
}
@@ -480,6 +491,8 @@
setCurrentNode(vm.nodes[0]);
}
validate();
vm.inited = true;
updatePropertyActionStates();
@@ -585,25 +598,28 @@
updateModel();
});
var validate = function () {
if (vm.nodes.length < vm.minItems) {
$scope.nestedContentForm.minCount.$setValidity("minCount", false);
}
else {
$scope.nestedContentForm.minCount.$setValidity("minCount", true);
}
if (vm.nodes.length > vm.maxItems) {
$scope.nestedContentForm.maxCount.$setValidity("maxCount", false);
}
else {
$scope.nestedContentForm.maxCount.$setValidity("maxCount", true);
}
}
var watcher = $scope.$watch(
function () {
return vm.nodes.length;
},
function () {
//Validate!
if (vm.nodes.length < vm.minItems) {
$scope.nestedContentForm.minCount.$setValidity("minCount", false);
}
else {
$scope.nestedContentForm.minCount.$setValidity("minCount", true);
}
if (vm.nodes.length > vm.maxItems) {
$scope.nestedContentForm.maxCount.$setValidity("maxCount", false);
}
else {
$scope.nestedContentForm.maxCount.$setValidity("maxCount", true);
}
validate();
}
);
@@ -56,11 +56,13 @@
<div class="umb-nested-content__help-text" ng-show="showHelpText">
<p>
<b><localize key="general_group">Group</localize>:</b><br />
Select the group whose properties should be displayed. If left blank, the first group on the element type will be used.
<localize key="content_nestedContentGroupHelpText">Select the group whose properties should be displayed. If left blank, the first group on the element type will be used.</localize>
</p>
<p>
<b><localize key="template_template">Template</localize>:</b><br />
Enter an angular expression to evaluate against each item for its name. Use <code ng-non-bindable>{{$index}}</code> to display the item index
<localize key="content_nestedContentTemplateHelpTextPart1">Enter an angular expression to evaluate against each item for its name. Use</localize>
<code ng-non-bindable>{{$index}}</code>
<localize key="content_nestedContentTemplateHelpTextPart2">to display the item index</localize>
</p>
</div>
</div>
@@ -2,13 +2,13 @@
<umb-load-indicator class="mt2" ng-if="!vm.inited"></umb-load-indicator>
<ng-form name="nestedContentForm">
<ng-form name="nestedContentForm" ng-show="vm.inited">
<div class="umb-nested-content__items" ng-hide="vm.nodes.length === 0" ui-sortable="vm.sortableOptions" ng-model="vm.nodes">
<div class="umb-nested-content__item" ng-repeat="node in vm.nodes" ng-class="{ 'umb-nested-content__item--active' : vm.currentNode.key === node.key, 'umb-nested-content__item--single' : vm.singleMode }">
<div class="umb-nested-content__header-bar" ng-click="vm.editNode($index)" ng-hide="vm.singleMode">
<div class="umb-nested-content__header-bar" ng-click="vm.editNode($index)" ng-hide="vm.singleMode" umb-auto-focus="{{vm.currentNode.key === node.key ? 'true' : 'false'}}">
<div class="umb-nested-content__heading"><i ng-if="vm.showIcons" class="icon" ng-class="vm.getIcon($index)"></i><span class="umb-nested-content__item-name" ng-class="{'--has-icon': vm.showIcons}" ng-bind="vm.getName($index)"></span></div>
@@ -17,7 +17,7 @@
<i class="icon icon-documents" aria-hidden="true"></i>
<span class="sr-only">{{vm.labels.copy_icon_title}}</span>
</button>
<button type="button" class="umb-nested-content__icon umb-nested-content__icon--delete" localize="title" title="general_delete" ng-click="vm.requestDeleteNode($index); $event.stopPropagation();">
<button type="button" class="umb-nested-content__icon umb-nested-content__icon--delete" localize="title" title="general_delete" ng-class="{ 'umb-nested-content__icon--disabled': !vm.canDeleteNode($index) }" ng-click="vm.requestDeleteNode($index); $event.stopPropagation();">
<i class="icon icon-trash" aria-hidden="true"></i>
<span class="sr-only">
<localize key="general_delete">Delete</localize>
@@ -40,8 +40,8 @@
</div>
</div>
<div class="umb-nested-content__footer-bar" ng-hide="!vm.inited || vm.hasContentTypes === false">
<button type="button" class="btn-reset umb-nested-content__add-content umb-focus" ng-class="{ '--disabled': (!vm.scaffolds.length || vm.nodes.length >= maxItems) }" ng-click="vm.openNodeTypePicker($event)" prevent-default>
<div class="umb-nested-content__footer-bar" ng-hide="!vm.inited || vm.hasContentTypes === false || vm.singleMode === true">
<button type="button" class="btn-reset umb-nested-content__add-content umb-focus" ng-class="{ '--disabled': (!vm.scaffolds.length || vm.nodes.length >= vm.maxItems) }" ng-click="vm.openNodeTypePicker($event)" prevent-default>
<localize key="grid_addElement"></localize>
</button>
</div>
@@ -451,7 +451,7 @@
var search = _.debounce(function () {
$scope.$apply(function () {
getUsers();
changePageNumber(1);
});
}, 500);
@@ -512,7 +512,7 @@
}
updateLocation("userStates", vm.usersOptions.userStates.join(","));
getUsers();
changePageNumber(1);
}
function setUserGroupFilter(userGroup) {
@@ -529,7 +529,7 @@
}
updateLocation("userGroups", vm.usersOptions.userGroups.join(","));
getUsers();
changePageNumber(1);
}
function setOrderByFilter(value, direction) {
+15
View File
@@ -7,6 +7,21 @@ body {
}
.container, .navbar-static-top .container, .navbar-fixed-top .container, .navbar-fixed-bottom .container {
max-width: 1500px;
width: 95%;
}
.span3 {
width: 220px;
width: calc(90% / 12 * 3);
}
.span9 {
width: 700px;
width: calc(90% / 12 * 9);
}
.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
font-family: inherit;
font-weight: 400;
@@ -23,7 +23,7 @@
{
if (success)
{
@* This message will show if RedirectOnSucces is set to false (default) *@
@* This message will show if profileModel.RedirectUrl is not defined (default) *@
<p>Profile updated</p>
}
@@ -45,7 +45,7 @@
@if (success)
{
@* This message will show if RedirectOnSucces is set to false (default) *@
@* This message will show if registerModel.RedirectUrl is not defined (default) *@
<p>Registration succeeded.</p>
}
else
+12 -3
View File
@@ -35,7 +35,9 @@
<key alias="SetPermissionsForThePage">Sæt rettigheder for siden %0%</key>
<key alias="chooseWhereToCopy">Vælg hvor du vil kopiere</key>
<key alias="chooseWhereToMove">Vælg hvortil du vil flytte</key>
<key alias="infiniteEditorChooseWhereToMove">Vælg hvor du vil flytte de valgte elementer hen</key>
<key alias="toInTheTreeStructureBelow">til i træstrukturen nedenfor</key>
<key alias="infiniteEditorChooseWhereToCopy">Vælg hvor du vil kopiere de valgte elementer til</key>
<key alias="wasMovedTo">blev flyttet til</key>
<key alias="wasCopiedTo">blev kopieret til</key>
<key alias="wasDeleted">blev slettet</key>
@@ -446,6 +448,7 @@
<key alias="viewCacheItem">Se cache element</key>
<key alias="relateToOriginalLabel">Relatér til original</key>
<key alias="includeDescendants">Inkludér undersider</key>
<key alias="theFriendliestCommunity">Det venligste community</key>
<key alias="linkToPage">Link til side</key>
<key alias="openInNewWindow">Åben linket i et nyt vindue eller fane</key>
<key alias="linkToMedia">Link til medie</key>
@@ -539,9 +542,6 @@
<key alias="anchor">#value eller ?key=value</key>
<key alias="enterAlias">Indtast alias...</key>
<key alias="generatingAlias">Genererer alias...</key>
<key alias="a11yCreateItem">Opret element</key>
<key alias="a11yEdit">Rediger</key>
<key alias="a11yName">Navn</key>
</area>
<area alias="editcontenttype">
<key alias="createListView" version="7.2">Opret brugerdefineret listevisning</key>
@@ -751,6 +751,9 @@
<key alias="current">nuværende</key>
<key alias="embed">Indlejring</key>
<key alias="selected">valgt</key>
<key alias="other">Andet</key>
<key alias="articles">Artikler</key>
<key alias="videos">Videoer</key>
</area>
<area alias="colors">
<key alias="blue">Blå</key>
@@ -1115,7 +1118,10 @@ Mange hilsner fra Umbraco robotten
<key alias="forms">Formularer</key>
</area>
<area alias="help">
<key alias="tours">Tours</key>
<key alias="theBestUmbracoVideoTutorials">De bedste Umbraco video tutorials</key>
<key alias="umbracoForum">Besøg our.umbraco.com</key>
<key alias="umbracoTv">Besøg umbraco.tv</key>
</area>
<area alias="settings">
<key alias="defaulttemplate">Standardskabelon</key>
@@ -1733,6 +1739,9 @@ Mange hilsner fra Umbraco robotten
<key alias="currentLanguage">Aktivt sprog</key>
<key alias="switchLanguage">Skift sprog til</key>
<key alias="createNewFolder">Opret ny mappe</key>
<key alias="createItem">Opret element</key>
<key alias="edit">Rediger</key>
<key alias="name">Navn</key>
</area>
<area alias="references">
<key alias="tabName">Referencer</key>
+25 -8
View File
@@ -36,6 +36,8 @@
<key alias="chooseWhereToCopy">Choose where to copy</key>
<key alias="chooseWhereToMove">Choose where to move</key>
<key alias="toInTheTreeStructureBelow">to in the tree structure below</key>
<key alias="infiniteEditorChooseWhereToCopy">Choose where to copy the selected item(s)</key>
<key alias="infiniteEditorChooseWhereToMove">Choose where to move the selected item(s)</key>
<key alias="wasMovedTo">was moved to</key>
<key alias="wasCopiedTo">was copied to</key>
<key alias="wasDeleted">was deleted</key>
@@ -280,6 +282,9 @@
<key alias="nestedContentNoContentTypes">No content types are configured for this property.</key>
<key alias="nestedContentAddElementType">Add element type</key>
<key alias="nestedContentSelectElementTypeModalTitle">Select element type</key>
<key alias="nestedContentGroupHelpText">Select the group whose properties should be displayed. If left blank, the first group on the element type will be used.</key>
<key alias="nestedContentTemplateHelpTextPart1">Enter an angular expression to evaluate against each item for its name. Use</key>
<key alias="nestedContentTemplateHelpTextPart2">to display the item index</key>
<key alias="addTextBox">Add another text box</key>
<key alias="removeTextBox">Remove this text box</key>
<key alias="contentRoot">Content root</key>
@@ -548,10 +553,6 @@
<key alias="anchor">#value or ?key=value</key>
<key alias="enterAlias">Enter alias...</key>
<key alias="generatingAlias">Generating alias...</key>
<key alias="a11yCreateItem">Create item</key>
<key alias="a11yCreate">Create</key>
<key alias="a11yEdit">Edit</key>
<key alias="a11yName">Name</key>
</area>
<area alias="editcontenttype">
<key alias="createListView" version="7.2">Create custom list view</key>
@@ -759,6 +760,11 @@
<key alias="current">current</key>
<key alias="embed">Embed</key>
<key alias="selected">selected</key>
<key alias="other">Other</key>
<key alias="articles">Articles</key>
<key alias="videos">Videos</key>
<key alias="clear">Clear</key>
<key alias="installing">Installing</key>
</area>
<area alias="colors">
<key alias="blue">Blue</key>
@@ -1339,7 +1345,10 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="users">Users</key>
</area>
<area alias="help">
<key alias="tours">Tours</key>
<key alias="theBestUmbracoVideoTutorials">The best Umbraco video tutorials</key>
<key alias="umbracoForum">Visit our.umbraco.com</key>
<key alias="umbracoTv">Visit umbraco.tv</key>
</area>
<area alias="settings">
<key alias="defaulttemplate">Default template</key>
@@ -1634,10 +1643,10 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="variantsHeading">Allow varying by culture</key>
<key alias="variantsDescription">Allow editors to create content of this type in different languages.</key>
<key alias="allowVaryByCulture">Allow varying by culture</key>
<key alias="elementType">Element type</key>
<key alias="elementHeading">Is an Element type</key>
<key alias="elementDescription">An Element type is meant to be used for instance in Nested Content, and not in the tree.</key>
<key alias="elementDoesNotSupport">This is not applicable for an Element type</key>
<key alias="elementHeading">Is an element type</key>
<key alias="elementDescription">An element type is meant to be used for instance in Nested Content, and not in the tree.</key>
<key alias="elementCannotToggle">A document type cannot be changed to an element type once it has been used to create one or more content items.</key>
<key alias="elementDoesNotSupport">This is not applicable for an element type</key>
<key alias="propertyHasChanges">You have made changes to this property. Are you sure you want to discard them?</key>
</area>
<area alias="languages">
@@ -2202,6 +2211,10 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="redirectDashboardSearchLabel">Search the redirect dashboard</key>
<key alias="userGroupSearchLabel">Search the user group section</key>
<key alias="userSearchLabel">Search the users section</key>
<key alias="createItem">Create item</key>
<key alias="create">Create</key>
<key alias="edit">Edit</key>
<key alias="name">Name</key>
</area>
<area alias="references">
<key alias="tabName">References</key>
@@ -2365,4 +2378,8 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="fallbackHeadline">Welcome to The Friendly CMS</key>
<key alias="fallbackDescription">Thank you for choosing Umbraco - we think this could be the beginning of something beautiful. While it may feel overwhelming at first, we've done a lot to make the learning curve as smooth and fast as possible.</key>
</area>
<area alias="formsDashboard">
<key alias="formsHeadline">Umbraco Forms</key>
<key alias="formsDescription">Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it!</key>
</area>
</language>
@@ -36,6 +36,8 @@
<key alias="chooseWhereToCopy">Choose where to copy</key>
<key alias="chooseWhereToMove">Choose where to move</key>
<key alias="toInTheTreeStructureBelow">to in the tree structure below</key>
<key alias="infiniteEditorChooseWhereToCopy">Choose where to copy the selected item(s)</key>
<key alias="infiniteEditorChooseWhereToMove">Choose where to move the selected item(s)</key>
<key alias="wasMovedTo">was moved to</key>
<key alias="wasCopiedTo">was copied to</key>
<key alias="wasDeleted">was deleted</key>
@@ -284,6 +286,9 @@
<key alias="nestedContentNoContentTypes">No content types are configured for this property.</key>
<key alias="nestedContentAddElementType">Add element type</key>
<key alias="nestedContentSelectElementTypeModalTitle">Select element type</key>
<key alias="nestedContentGroupHelpText">Select the group whose properties should be displayed. If left blank, the first group on the element type will be used.</key>
<key alias="nestedContentTemplateHelpTextPart1">Enter an angular expression to evaluate against each item for its name. Use</key>
<key alias="nestedContentTemplateHelpTextPart2">to display the item index</key>
<key alias="addTextBox">Add another text box</key>
<key alias="removeTextBox">Remove this text box</key>
<key alias="contentRoot">Content root</key>
@@ -551,10 +556,6 @@
<key alias="anchor">#value or ?key=value</key>
<key alias="enterAlias">Enter alias...</key>
<key alias="generatingAlias">Generating alias...</key>
<key alias="a11yCreateItem">Create item</key>
<key alias="a11yCreate">Create</key>
<key alias="a11yEdit">Edit</key>
<key alias="a11yName">Name</key>
</area>
<area alias="editcontenttype">
<key alias="createListView" version="7.2">Create custom list view</key>
@@ -764,6 +765,11 @@
<key alias="current">current</key>
<key alias="embed">Embed</key>
<key alias="selected">selected</key>
<key alias="other">Other</key>
<key alias="articles">Articles</key>
<key alias="videos">Videos</key>
<key alias="clear">Clear</key>
<key alias="installing">Installing</key>
</area>
<area alias="colors">
<key alias="blue">Blue</key>
@@ -1339,7 +1345,10 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="users">Users</key>
</area>
<area alias="help">
<key alias="tours">Tours</key>
<key alias="theBestUmbracoVideoTutorials">The best Umbraco video tutorials</key>
<key alias="umbracoForum">Visit our.umbraco.com</key>
<key alias="umbracoTv">Visit umbraco.tv</key>
</area>
<area alias="settings">
<key alias="defaulttemplate">Default template</key>
@@ -1649,9 +1658,10 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="variantsDescription">Allow editors to create content of this type in different languages.</key>
<key alias="allowVaryByCulture">Allow varying by culture</key>
<key alias="elementType">Element type</key>
<key alias="elementHeading">Is an Element type</key>
<key alias="elementDescription">An Element type is meant to be used for instance in Nested Content, and not in the tree.</key>
<key alias="elementDoesNotSupport">This is not applicable for an Element type</key>
<key alias="elementHeading">Is an element type</key>
<key alias="elementDescription">An element type is meant to be used for instance in Nested Content, and not in the tree.</key>
<key alias="elementCannotToggle">A document type cannot be changed to an element type once it has been used to create one or more content items.</key>
<key alias="elementDoesNotSupport">This is not applicable for an element type</key>
<key alias="propertyHasChanges">You have made changes to this property. Are you sure you want to discard them?</key>
</area>
<area alias="languages">
@@ -2218,6 +2228,10 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="redirectDashboardSearchLabel">Search the redirect dashboard</key>
<key alias="userGroupSearchLabel">Search the user group section</key>
<key alias="userSearchLabel">Search the users section</key>
<key alias="createItem">Create item</key>
<key alias="create">Create</key>
<key alias="edit">Edit</key>
<key alias="name">Name</key>
</area>
<area alias="references">
<key alias="tabName">References</key>
@@ -2381,4 +2395,8 @@ To manage your website, simply open the Umbraco back office and start adding con
<key alias="fallbackHeadline">Welcome to The Friendly CMS</key>
<key alias="fallbackDescription">Thank you for choosing Umbraco - we think this could be the beginning of something beautiful. While it may feel overwhelming at first, we've done a lot to make the learning curve as smooth and fast as possible.</key>
</area>
<area alias="formsDashboard">
<key alias="formsHeadline">Umbraco Forms</key>
<key alias="formsDescription">Create forms using an intuitive drag and drop interface. From simple contact forms that sends e-mails to advanced questionaires that integrate with CRM systems. Your clients will love it!</key>
</area>
</language>
@@ -70,6 +70,13 @@ namespace Umbraco.Web.Editors
return Services.ContentTypeService.Count();
}
[HttpGet]
[UmbracoTreeAuthorize(Constants.Trees.DocumentTypes)]
public bool HasContentNodes(int id)
{
return Services.ContentTypeService.HasContentNodes(id);
}
public DocumentTypeDisplay GetById(int id)
{
var ct = Services.ContentTypeService.Get(id);
@@ -425,11 +432,11 @@ namespace Umbraco.Web.Editors
}
var contentType = Services.ContentTypeBaseServices.GetContentTypeOf(contentItem);
var ids = contentType.AllowedContentTypes.Select(x => x.Id.Value).ToArray();
var ids = contentType.AllowedContentTypes.OrderBy(c => c.SortOrder).Select(x => x.Id.Value).ToArray();
if (ids.Any() == false) return Enumerable.Empty<ContentTypeBasic>();
types = Services.ContentTypeService.GetAll(ids).ToList();
types = Services.ContentTypeService.GetAll(ids).OrderBy(c => ids.IndexOf(c.Id)).ToList();
}
var basics = types.Where(type => type.IsElement == false).Select(Mapper.Map<IContentType, ContentTypeBasic>).ToList();
@@ -452,7 +459,7 @@ namespace Umbraco.Web.Editors
}
}
return basics;
return basics.OrderBy(c => contentId == Constants.System.Root ? c.Name : string.Empty);
}
/// <summary>
+32 -1
View File
@@ -206,6 +206,35 @@ namespace Umbraco.Web.Editors
throw new HttpResponseException(HttpStatusCode.NotFound);
}
/// <summary>
/// Gets the url of an entity
/// </summary>
/// <param name="udi">UDI of the entity to fetch URL for</param>
/// <param name="culture">The culture to fetch the URL for</param>
/// <returns>The URL or path to the item</returns>
public HttpResponseMessage GetUrl(Udi udi, string culture = "*")
{
var intId = Services.EntityService.GetId(udi);
if (!intId.Success)
throw new HttpResponseException(HttpStatusCode.NotFound);
UmbracoEntityTypes entityType;
switch(udi.EntityType)
{
case Constants.UdiEntityType.Document:
entityType = UmbracoEntityTypes.Document;
break;
case Constants.UdiEntityType.Media:
entityType = UmbracoEntityTypes.Media;
break;
case Constants.UdiEntityType.Member:
entityType = UmbracoEntityTypes.Member;
break;
default:
throw new HttpResponseException(HttpStatusCode.NotFound);
}
return GetUrl(intId.Result, entityType, culture);
}
/// <summary>
/// Gets the url of an entity
/// </summary>
@@ -303,7 +332,9 @@ namespace Umbraco.Web.Editors
[HttpGet]
public UrlAndAnchors GetUrlAndAnchors(int id, string culture = "*")
{
var url = UmbracoContext.UrlProvider.GetUrl(id);
culture = culture ?? ClientCulture();
var url = UmbracoContext.UrlProvider.GetUrl(id, culture: culture);
var anchorValues = Services.ContentService.GetAnchorValuesFromRTEs(id, culture);
return new UrlAndAnchors(url, anchorValues);
}
@@ -240,11 +240,11 @@ namespace Umbraco.Web.Editors
}
var contentType = Services.MediaTypeService.Get(contentItem.ContentTypeId);
var ids = contentType.AllowedContentTypes.Select(x => x.Id.Value).ToArray();
var ids = contentType.AllowedContentTypes.OrderBy(c => c.SortOrder).Select(x => x.Id.Value).ToArray();
if (ids.Any() == false) return Enumerable.Empty<ContentTypeBasic>();
types = Services.MediaTypeService.GetAll(ids).ToList();
types = Services.MediaTypeService.GetAll(ids).OrderBy(c => ids.IndexOf(c.Id)).ToList();
}
var basics = types.Select(Mapper.Map<IMediaType, ContentTypeBasic>).ToList();
@@ -255,7 +255,7 @@ namespace Umbraco.Web.Editors
basic.Description = TranslateItem(basic.Description);
}
return basics.OrderBy(x => x.Name);
return basics.OrderBy(c => contentId == Constants.System.Root ? c.Name : string.Empty);
}
/// <summary>
+33
View File
@@ -110,6 +110,39 @@ namespace Umbraco.Web.Editors
return result.Except(toursToBeRemoved).OrderBy(x => x.FileName, StringComparer.InvariantCultureIgnoreCase);
}
/// <summary>
/// Gets a tours for a specific doctype
/// </summary>
/// <param name="doctypeAlias">The documenttype alias</param>
/// <returns>A <see cref="BackOfficeTour"/></returns>
public IEnumerable<BackOfficeTour> GetToursForDoctype(string doctypeAlias)
{
var tourFiles = this.GetTours();
var doctypeAliasWithCompositions = new List<string>
{
doctypeAlias
};
var contentType = this.Services.ContentTypeService.Get(doctypeAlias);
if (contentType != null)
{
doctypeAliasWithCompositions.AddRange(contentType.CompositionAliases());
}
return tourFiles.SelectMany(x => x.Tours)
.Where(x =>
{
if (string.IsNullOrEmpty(x.ContentType))
{
return false;
}
var contentTypes = x.ContentType.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(ct => ct.Trim());
return contentTypes.Intersect(doctypeAliasWithCompositions).Any();
});
}
private void TryParseTourFile(string tourFile,
ICollection<BackOfficeTourFile> result,
List<BackOfficeTourFilter> filters,
+3
View File
@@ -40,5 +40,8 @@ namespace Umbraco.Web.Models
[DataMember(Name = "culture")]
public string Culture { get; set; }
[DataMember(Name = "contentType")]
public string ContentType { get; set; }
}
}
@@ -5,6 +5,7 @@ namespace Umbraco.Web.Models.ContentEditing
[DataContract(Name = "contentType", Namespace = "")]
public class MediaTypeDisplay : ContentTypeCompositionDisplay<PropertyTypeDisplay>
{
[DataMember(Name = "isSystemMediaType")]
public bool IsSystemMediaType { get; set; }
}
}
@@ -145,6 +145,7 @@ namespace Umbraco.Web.Models.Mapping
//default listview
target.ListViewEditorName = Constants.Conventions.DataTypes.ListViewPrefix + "Media";
target.IsSystemMediaType = source.IsSystemMediaType();
if (string.IsNullOrEmpty(source.Name)) return;
@@ -492,7 +493,7 @@ namespace Umbraco.Web.Models.Mapping
target.Udi = MapContentTypeUdi(source);
target.UpdateDate = source.UpdateDate;
target.AllowedContentTypes = source.AllowedContentTypes.Select(x => x.Id.Value);
target.AllowedContentTypes = source.AllowedContentTypes.OrderBy(c => c.SortOrder).Select(x => x.Id.Value);
target.CompositeContentTypes = source.ContentTypeComposition.Select(x => x.Alias);
target.LockedCompositeContentTypes = MapLockedCompositions(source);
}
@@ -16,6 +16,9 @@ namespace Umbraco.Web.PropertyEditors
Description = "Selecting this option allows a user to choose nodes that they normally don't have access to.")]
public bool IgnoreUserStartNodes { get; set; }
[ConfigurationField("hideAnchor",
"Hide anchor/query string input", "boolean",
Description = "Selecting this hides the anchor/query string input field in the linkpicker overlay.")]
public bool HideAnchor { get; set; }
}
}
@@ -1193,7 +1193,7 @@ namespace Umbraco.Web
/// if any. In addition, when the content type is multi-lingual, this is the url for the
/// specified culture. Otherwise, it is the invariant url.</para>
/// </remarks>
public static string Url(this IPublishedContent content, string culture = null, UrlMode mode = UrlMode.Auto)
public static string Url(this IPublishedContent content, string culture = null, UrlMode mode = UrlMode.Default)
{
var umbracoContext = Composing.Current.UmbracoContext;
@@ -120,7 +120,10 @@ namespace Umbraco.Web.Trees
}
menu.Items.Add<ActionCopy>(Services.TextService, opensDialog: true);
menu.Items.Add<ActionDelete>(Services.TextService, opensDialog: true);
if(ct.IsSystemMediaType() == false)
{
menu.Items.Add<ActionDelete>(Services.TextService, opensDialog: true);
}
menu.Items.Add(new RefreshNode(Services.TextService, true));
}