Merge pull request #2702 from nathanwoulfe/temp-U4-4732

U4-4732 Anchor missing in TinyMCE of Umbraco 7.x
This commit is contained in:
Sebastiaan Janssen
2018-07-12 12:39:23 +02:00
committed by GitHub
12 changed files with 1230 additions and 1073 deletions

View File

@@ -3,10 +3,24 @@
// --------------------------------------------------
.umb-editor {
min-width:66.6%;
&-pull {
float:left;
width:66.6%;
}
&-push {
float:right;
}
}
.umb-editor-tiny {
width: 60px;
&.umb-editor-push {
width:30%;
min-width:0;
}
}
.umb-editor-small {
@@ -28,6 +42,27 @@
width: 99%;
}
// displays property inline with preceeding
.umb-property {
&--pull {
float:left;
width:60%;
}
&--push {
float:right;
width:35%;
}
&--pull, &--push {
.umb-editor {
min-width:0;
width:100%;
}
}
}
//
// Content picker
// --------------------------------------------------

View File

@@ -1,149 +1,163 @@
//used for the media picker dialog
angular.module("umbraco").controller("Umbraco.Dialogs.LinkPickerController",
function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService) {
var dialogOptions = $scope.dialogOptions;
function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) {
var dialogOptions = $scope.dialogOptions;
var searchText = "Search...";
localizationService.localize("general_search").then(function (value) {
searchText = value + "...";
});
var searchText = "Search...";
localizationService.localize("general_search").then(function (value) {
searchText = value + "...";
});
$scope.dialogTreeEventHandler = $({});
$scope.target = {};
$scope.searchInfo = {
searchFromId: null,
searchFromName: null,
showSearch: false,
results: [],
selectedSearchResults: []
}
$scope.dialogTreeEventHandler = $({});
$scope.target = {};
$scope.searchInfo = {
searchFromId: null,
searchFromName: null,
showSearch: false,
results: [],
selectedSearchResults: []
}
if (dialogOptions.currentTarget) {
$scope.target = dialogOptions.currentTarget;
if (dialogOptions.currentTarget) {
$scope.target = dialogOptions.currentTarget;
//if we have a node ID, we fetch the current node to build the form data
if ($scope.target.id || $scope.target.udi) {
//if we have a node ID, we fetch the current node to build the form data
if ($scope.target.id || $scope.target.udi) {
var id = $scope.target.udi ? $scope.target.udi : $scope.target.id;
var id = $scope.target.udi ? $scope.target.udi : $scope.target.id;
if (!$scope.target.path) {
entityResource.getPath(id, "Document").then(function (path) {
$scope.target.path = path;
//now sync the tree to this path
$scope.dialogTreeEventHandler.syncTree({ path: $scope.target.path, tree: "content" });
});
}
if (!$scope.target.path) {
entityResource.getPath(id, "Document").then(function (path) {
$scope.target.path = path;
//now sync the tree to this path
$scope.dialogTreeEventHandler.syncTree({
path: $scope.target.path,
tree: "content"
});
});
}
contentResource.getNiceUrl(id).then(function (url) {
$scope.target.url = url;
});
}
}
// if a link exists, get the properties to build the anchor name list
contentResource.getById(id).then(function (resp) {
$scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties));
$scope.model.target.url = resp.urls[0];
});
} else if ($scope.target.url.length) {
// a url but no id/udi indicates an external link - trim the url to remove the anchor/qs
$scope.target.url = $scope.model.url.substring(0, $scope.model.url.search(/(#|\?)/));
}
}
function nodeSelectHandler(ev, args) {
args.event.preventDefault();
args.event.stopPropagation();
if (dialogOptions.anchors) {
$scope.anchorValues = dialogOptions.anchors;
}
if (args.node.metaData.listViewNode) {
//check if list view 'search' node was selected
function nodeSelectHandler(ev, args) {
args.event.preventDefault();
args.event.stopPropagation();
$scope.searchInfo.showSearch = true;
$scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
$scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
}
else {
eventsService.emit("dialogs.linkPicker.select", args);
if (args.node.metaData.listViewNode) {
//check if list view 'search' node was selected
if ($scope.currentNode) {
//un-select if there's a current one selected
$scope.currentNode.selected = false;
}
$scope.searchInfo.showSearch = true;
$scope.searchInfo.searchFromId = args.node.metaData.listViewNode.id;
$scope.searchInfo.searchFromName = args.node.metaData.listViewNode.name;
} else {
eventsService.emit("dialogs.linkPicker.select", args);
$scope.currentNode = args.node;
$scope.currentNode.selected = true;
$scope.target.id = args.node.id;
$scope.target.udi = args.node.udi;
$scope.target.name = args.node.name;
if ($scope.currentNode) {
//un-select if there's a current one selected
$scope.currentNode.selected = false;
}
if (args.node.id < 0) {
$scope.target.url = "/";
}
else {
contentResource.getNiceUrl(args.node.id).then(function (url) {
$scope.target.url = url;
});
}
$scope.currentNode = args.node;
$scope.currentNode.selected = true;
$scope.target.id = args.node.id;
$scope.target.udi = args.node.udi;
$scope.target.name = args.node.name;
if (!angular.isUndefined($scope.target.isMedia)) {
delete $scope.target.isMedia;
}
}
}
if (args.node.id < 0) {
$scope.target.url = "/";
} else {
contentResource.getById(args.node.id).then(function (resp) {
$scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties));
$scope.model.target.url = resp.urls[0];
});
}
function nodeExpandedHandler(ev, args) {
if (angular.isArray(args.children)) {
if (!angular.isUndefined($scope.target.isMedia)) {
delete $scope.target.isMedia;
}
}
}
//iterate children
_.each(args.children, function (child) {
//check if any of the items are list views, if so we need to add a custom
// child: A node to activate the search
if (child.metaData.isContainer) {
child.hasChildren = true;
child.children = [
{
level: child.level + 1,
hasChildren: false,
name: searchText,
metaData: {
listViewNode: child,
},
cssClass: "icon umb-tree-icon sprTree icon-search",
cssClasses: ["not-published"]
function nodeExpandedHandler(ev, args) {
if (angular.isArray(args.children)) {
//iterate children
_.each(args.children, function (child) {
//check if any of the items are list views, if so we need to add a custom
// child: A node to activate the search
if (child.metaData.isContainer) {
child.hasChildren = true;
child.children = [
{
level: child.level + 1,
hasChildren: false,
name: searchText,
metaData: {
listViewNode: child,
},
cssClass: "icon umb-tree-icon sprTree icon-search",
cssClasses: ["not-published"]
}
];
}
});
}
}
}
});
}
}
$scope.switchToMediaPicker = function () {
userService.getCurrentUser().then(function (userData) {
dialogService.mediaPicker({
startNodeId: userData.startMediaIds.length == 0 ? -1 : userData.startMediaIds[0],
callback: function(media) {
$scope.target.id = media.id;
$scope.target.isMedia = true;
$scope.target.name = media.name;
$scope.target.url = mediaHelper.resolveFile(media);
}
});
});
};
$scope.switchToMediaPicker = function () {
userService.getCurrentUser().then(function (userData) {
dialogService.mediaPicker({
startNodeId: userData.startMediaIds.length == 0 ? -1 : userData.startMediaIds[0],
callback: function (media) {
$scope.target.id = media.id;
$scope.target.isMedia = true;
$scope.target.name = media.name;
$scope.target.url = mediaHelper.resolveFile(media);
}
});
});
};
$scope.hideSearch = function () {
$scope.searchInfo.showSearch = false;
$scope.searchInfo.searchFromId = null;
$scope.searchInfo.searchFromName = null;
$scope.searchInfo.results = [];
}
$scope.hideSearch = function () {
$scope.searchInfo.showSearch = false;
$scope.searchInfo.searchFromId = null;
$scope.searchInfo.searchFromName = null;
$scope.searchInfo.results = [];
}
// method to select a search result
$scope.selectResult = function (evt, result) {
result.selected = result.selected === true ? false : true;
nodeSelectHandler(evt, {event: evt, node: result});
};
// method to select a search result
$scope.selectResult = function (evt, result) {
result.selected = result.selected === true ? false : true;
nodeSelectHandler(evt, {
event: evt,
node: result
});
};
//callback when there are search results
$scope.onSearchResults = function (results) {
$scope.searchInfo.results = results;
$scope.searchInfo.showSearch = true;
};
//callback when there are search results
$scope.onSearchResults = function (results) {
$scope.searchInfo.results = results;
$scope.searchInfo.showSearch = true;
};
$scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
$scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
$scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
$scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
$scope.$on('$destroy', function () {
$scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
$scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
});
});
$scope.$on('$destroy', function () {
$scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
$scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
});
});

View File

@@ -1,15 +1,17 @@
<div class="umb-panel" ng-controller="Umbraco.Dialogs.LinkPickerController">
<div class="umb-panel-body no-header with-footer compact">
<umb-pane>
<umb-control-group label="@defaultdialogs_urlLinkPicker">
<input type="text"
localize="placeholder"
placeholder="@general_url"
class="umb-editor umb-textstring"
ng-model="target.url"
ng-disabled="target.id"
/>
</umb-control-group>
<umb-control-group label="@defaultdialogs_urlLinkPicker" class="umb-property--pull">
<input type="text" localize="placeholder" placeholder="@general_url" class="umb-editor umb-textstring" ng-model="target.url" ng-disabled="target.id" />
</umb-control-group>
<umb-control-group label="@defaultdialogs_anchorLinkPicker" class="umb-property--push">
<input type="text" list="anchors" localize="placeholder" placeholder="@placeholders_anchor" class="umb-editor umb-textstring" ng-model="target.anchor" />
<datalist id="anchors">
<option value="{{a}}" ng-repeat="a in anchorValues"></option>
</datalist>
</umb-control-group>
<umb-control-group label="@defaultdialogs_nodeNameLinkPicker">
<input type="text"

View File

@@ -1,56 +1,66 @@
//used for the media picker dialog
angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController",
function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService) {
var dialogOptions = $scope.model;
function ($scope, eventsService, dialogService, entityResource, contentResource, mediaHelper, userService, localizationService, tinyMceService) {
var dialogOptions = $scope.model;
var searchText = "Search...";
localizationService.localize("general_search").then(function (value) {
searchText = value + "...";
});
var anchorPattern = /<a id=\\"(.*?)\\">/gi;
if(!$scope.model.title) {
$scope.model.title = localizationService.localize("defaultdialogs_selectLink");
var searchText = "Search...";
localizationService.localize("general_search").then(function (value) {
searchText = value + "...";
});
if (!$scope.model.title) {
$scope.model.title = localizationService.localize("defaultdialogs_selectLink");
}
$scope.dialogTreeEventHandler = $({});
$scope.model.target = {};
$scope.searchInfo = {
searchFromId: null,
searchFromName: null,
showSearch: false,
results: [],
selectedSearchResults: []
};
$scope.dialogTreeEventHandler = $({});
$scope.model.target = {};
$scope.searchInfo = {
searchFromId: null,
searchFromName: null,
showSearch: false,
results: [],
selectedSearchResults: []
};
$scope.showTarget = $scope.model.hideTarget !== true;
$scope.showTarget = $scope.model.hideTarget !== true;
if (dialogOptions.currentTarget) {
$scope.model.target = dialogOptions.currentTarget;
if (dialogOptions.currentTarget) {
$scope.model.target = dialogOptions.currentTarget;
//if we have a node ID, we fetch the current node to build the form data
if ($scope.model.target.id || $scope.model.target.udi) {
//if we have a node ID, we fetch the current node to build the form data
if ($scope.model.target.id || $scope.model.target.udi) {
//will be either a udi or an int
var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id;
//will be either a udi or an int
var id = $scope.model.target.udi ? $scope.model.target.udi : $scope.model.target.id;
if (!$scope.model.target.path) {
if (!$scope.model.target.path) {
entityResource.getPath(id, "Document").then(function (path) {
$scope.model.target.path = path;
//now sync the tree to this path
$scope.dialogTreeEventHandler.syncTree({ path: $scope.model.target.path, tree: "content" });
});
}
entityResource.getPath(id, "Document").then(function (path) {
$scope.model.target.path = path;
//now sync the tree to this path
$scope.dialogTreeEventHandler.syncTree({
path: $scope.model.target.path,
tree: "content"
});
});
}
contentResource.getNiceUrl(id).then(function (url) {
$scope.model.target.url = url;
});
}
}
// if a link exists, get the properties to build the anchor name list
contentResource.getById(id).then(function (resp) {
$scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties));
$scope.model.target.url = resp.urls[0];
});
} else if ($scope.model.target.url.length) {
// a url but no id/udi indicates an external link - trim the url to remove the anchor/qs
$scope.model.target.url = $scope.model.target.url.substring(0, $scope.model.target.url.search(/(#|\?)/));
}
} else if (dialogOptions.anchors) {
$scope.anchorValues = dialogOptions.anchors;
}
function nodeSelectHandler(ev, args) {
if(args && args.event) {
function nodeSelectHandler(ev, args) {
if (args && args.event) {
args.event.preventDefault();
args.event.stopPropagation();
}
@@ -70,79 +80,86 @@ angular.module("umbraco").controller("Umbraco.Overlays.LinkPickerController",
if (args.node.id < 0) {
$scope.model.target.url = "/";
}
else {
contentResource.getNiceUrl(args.node.id).then(function (url) {
$scope.model.target.url = url;
} else {
contentResource.getById(args.node.id).then(function (resp) {
$scope.anchorValues = tinyMceService.getAnchorNames(JSON.stringify(resp.properties));
$scope.model.target.url = resp.urls[0];
});
}
if (!angular.isUndefined($scope.model.target.isMedia)) {
delete $scope.model.target.isMedia;
}
}
}
function nodeExpandedHandler(ev, args) {
function nodeExpandedHandler(ev, args) {
// open mini list view for list views
if (args.node.metaData.isContainer) {
openMiniListView(args.node);
}
}
}
$scope.switchToMediaPicker = function () {
userService.getCurrentUser().then(function (userData) {
$scope.mediaPickerOverlay = {
view: "mediapicker",
startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0],
startNodeIsVirtual: userData.startMediaIds.length !== 1,
show: true,
submit: function(model) {
var media = model.selectedImages[0];
$scope.switchToMediaPicker = function () {
userService.getCurrentUser().then(function (userData) {
$scope.mediaPickerOverlay = {
view: "mediapicker",
startNodeId: userData.startMediaIds.length !== 1 ? -1 : userData.startMediaIds[0],
startNodeIsVirtual: userData.startMediaIds.length !== 1,
show: true,
submit: function (model) {
var media = model.selectedImages[0];
$scope.model.target.id = media.id;
$scope.model.target.udi = media.udi;
$scope.model.target.isMedia = true;
$scope.model.target.name = media.name;
$scope.model.target.url = mediaHelper.resolveFile(media);
$scope.model.target.id = media.id;
$scope.model.target.udi = media.udi;
$scope.model.target.isMedia = true;
$scope.model.target.name = media.name;
$scope.model.target.url = mediaHelper.resolveFile(media);
debugger;
$scope.mediaPickerOverlay.show = false;
$scope.mediaPickerOverlay = null;
}
};
});
};
$scope.mediaPickerOverlay.show = false;
$scope.mediaPickerOverlay = null;
}
};
});
};
$scope.hideSearch = function () {
$scope.searchInfo.showSearch = false;
$scope.searchInfo.searchFromId = null;
$scope.searchInfo.searchFromName = null;
$scope.searchInfo.results = [];
}
$scope.hideSearch = function () {
$scope.searchInfo.showSearch = false;
$scope.searchInfo.searchFromId = null;
$scope.searchInfo.searchFromName = null;
$scope.searchInfo.results = [];
}
// method to select a search result
$scope.selectResult = function (evt, result) {
result.selected = result.selected === true ? false : true;
nodeSelectHandler(evt, {event: evt, node: result});
};
// method to select a search result
$scope.selectResult = function (evt, result) {
result.selected = result.selected === true ? false : true;
nodeSelectHandler(evt, {
event: evt,
node: result
});
};
//callback when there are search results
$scope.onSearchResults = function (results) {
$scope.searchInfo.results = results;
$scope.searchInfo.showSearch = true;
};
//callback when there are search results
$scope.onSearchResults = function (results) {
$scope.searchInfo.results = results;
$scope.searchInfo.showSearch = true;
};
$scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
$scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
$scope.dialogTreeEventHandler.bind("treeNodeSelect", nodeSelectHandler);
$scope.dialogTreeEventHandler.bind("treeNodeExpanded", nodeExpandedHandler);
$scope.$on('$destroy', function () {
$scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
$scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
});
$scope.$on('$destroy', function () {
$scope.dialogTreeEventHandler.unbind("treeNodeSelect", nodeSelectHandler);
$scope.dialogTreeEventHandler.unbind("treeNodeExpanded", nodeExpandedHandler);
});
// Mini list view
$scope.selectListViewNode = function (node) {
node.selected = node.selected === true ? false : true;
nodeSelectHandler({}, { node: node });
nodeSelectHandler({}, {
node: node
});
};
$scope.closeMiniListView = function () {

View File

@@ -1,89 +1,62 @@
<div ng-controller="Umbraco.Overlays.LinkPickerController">
<umb-control-group label="@defaultdialogs_urlLinkPicker">
<input type="text"
localize="placeholder"
placeholder="@general_url"
class="umb-editor umb-textstring"
ng-model="model.target.url"
ng-disabled="model.target.id"
focus-when="{{true}} "/>
</umb-control-group>
<umb-control-group label="@defaultdialogs_urlLinkPicker" class="umb-property--pull">
<input type="text" localize="placeholder" placeholder="@general_url" class="umb-editor umb-textstring" ng-model="model.target.url" ng-disabled="model.target.id" />
</umb-control-group>
<umb-control-group label="@defaultdialogs_nodeNameLinkPicker">
<input type="text"
localize="placeholder"
placeholder="@placeholders_entername"
class="umb-editor umb-textstring"
ng-model="model.target.name" />
</umb-control-group>
<umb-control-group label="@defaultdialogs_anchorLinkPicker" class="umb-property--push">
<input type="text" list="anchors" localize="placeholder" placeholder="@placeholders_anchor" class="umb-editor umb-textstring" ng-model="model.target.anchor" />
<umb-control-group ng-if="showTarget" label="@content_target">
<label class="checkbox no-indent">
<datalist id="anchors">
<option value="{{a}}" ng-repeat="a in anchorValues"></option>
</datalist>
</umb-control-group>
<umb-control-group label="@defaultdialogs_nodeNameLinkPicker">
<input type="text" localize="placeholder" placeholder="@placeholders_entername" class="umb-editor umb-textstring" ng-model="model.target.name" />
</umb-control-group>
<umb-control-group ng-if="showTarget" label="@content_target">
<label class="checkbox no-indent">
<input type="checkbox" ng-model="model.target.target" ng-true-value="_blank" ng-false-value="" /> <localize key="defaultdialogs_openInNewWindow">Opens the linked document in a new window or tab</localize>
</label>
</umb-control-group>
</umb-control-group>
<div class="umb-control-group">
<h5>
<localize key="defaultdialogs_linkToPage">Link to page</localize>
</h5>
<div class="umb-control-group">
<h5>
<localize key="defaultdialogs_linkToPage">Link to page</localize>
</h5>
<div ng-hide="miniListView">
<umb-tree-search-box
hide-search-callback="hideSearch"
search-callback="onSearchResults"
search-from-id="{{searchInfo.searchFromId}}"
search-from-name="{{searchInfo.searchFromName}}"
show-search="{{searchInfo.showSearch}}"
section="{{section}}">
</umb-tree-search-box>
<div ng-hide="miniListView">
<umb-tree-search-box hide-search-callback="hideSearch" search-callback="onSearchResults" search-from-id="{{searchInfo.searchFromId}}" search-from-name="{{searchInfo.searchFromName}}" show-search="{{searchInfo.showSearch}}" section="{{section}}">
</umb-tree-search-box>
<br/>
<br/>
<umb-tree-search-results
ng-if="searchInfo.showSearch"
results="searchInfo.results"
select-result-callback="selectResult">
</umb-tree-search-results>
<umb-tree-search-results ng-if="searchInfo.showSearch" results="searchInfo.results" select-result-callback="selectResult">
</umb-tree-search-results>
<div ng-hide="searchInfo.showSearch">
<umb-tree
section="content"
hideheader="true"
hideoptions="true"
eventhandler="dialogTreeEventHandler"
enablelistviewexpand="true"
isdialog="true"
enablecheckboxes="true">
</umb-tree>
</div>
</div>
<div ng-hide="searchInfo.showSearch">
<umb-tree section="content" hideheader="true" hideoptions="true" eventhandler="dialogTreeEventHandler" enablelistviewexpand="true" isdialog="true" enablecheckboxes="true">
</umb-tree>
</div>
</div>
<umb-mini-list-view
ng-if="miniListView"
node="miniListView"
entity-type="Document"
on-select="selectListViewNode(node)"
on-close="closeMiniListView()">
</umb-mini-list-view>
<umb-mini-list-view ng-if="miniListView" node="miniListView" entity-type="Document" on-select="selectListViewNode(node)" on-close="closeMiniListView()">
</umb-mini-list-view>
</div>
</div>
<div class="umb-control-group">
<h5>
<localize key="defaultdialogs_linkToMedia">Link to media</localize>
</h5>
<a href ng-click="switchToMediaPicker()" class="btn">
<localize key="defaultdialogs_selectMedia">Select media</localize>
</a>
</div>
<div class="umb-control-group">
<h5>
<localize key="defaultdialogs_linkToMedia">Link to media</localize>
</h5>
<a href ng-click="switchToMediaPicker()" class="btn">
<localize key="defaultdialogs_selectMedia">Select media</localize>
</a>
</div>
<umb-overlay
ng-if="mediaPickerOverlay.show"
model="mediaPickerOverlay"
view="mediaPickerOverlay.view"
position="right">
</umb-overlay>
<umb-overlay ng-if="mediaPickerOverlay.show" model="mediaPickerOverlay" view="mediaPickerOverlay.view" position="right">
</umb-overlay>
</div>

View File

@@ -1,7 +1,7 @@
(function() {
"use strict";
function GridRichTextEditorController($scope, tinyMceService, macroService) {
function GridRichTextEditorController($scope, tinyMceService, macroService, editorState) {
var vm = this;
@@ -11,9 +11,11 @@
vm.openEmbed = openEmbed;
function openLinkPicker(editor, currentTarget, anchorElement) {
vm.linkPickerOverlay = {
view: "linkpicker",
currentTarget: currentTarget,
anchors: tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)),
show: true,
submit: function(model) {
tinyMceService.insertLinkInEditor(editor, model.target, anchorElement);

View File

@@ -1,6 +1,6 @@
angular.module("umbraco")
.controller("Umbraco.PropertyEditors.RTEController",
function ($rootScope, $scope, $q, $locale, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService) {
function ($rootScope, $scope, $q, $locale, dialogService, $log, imageHelper, assetsService, $timeout, tinyMceService, angularHelper, stylesheetResource, macroService, editorState) {
$scope.isLoading = true;
@@ -273,11 +273,12 @@ angular.module("umbraco")
syncContent(editor);
});
tinyMceService.createLinkPicker(editor, $scope, function(currentTarget, anchorElement) {
$scope.linkPickerOverlay = {
view: "linkpicker",
currentTarget: currentTarget,
anchors: tinyMceService.getAnchorNames(JSON.stringify(editorState.current.properties)),
show: true,
submit: function(model) {
tinyMceService.insertLinkInEditor(editor, model.target, anchorElement);

View File

@@ -338,6 +338,7 @@
<area alias="defaultdialogs">
<key alias="nodeNameLinkPicker">Link title</key>
<key alias="urlLinkPicker">Link</key>
<key alias="anchorLinkPicker">Anchor / querystring</key>
<key alias="anchorInsert">Name</key>
<key alias="assignDomain">Manage hostnames</key>
<key alias="closeThisWindow">Close this window</key>
@@ -456,6 +457,7 @@
<key alias="email">Enter your email...</key>
<key alias="enterMessage">Enter a message...</key>
<key alias="usernameHint">Your username is usually your email</key>
<key alias="anchor">#value or ?key=value</key>
</area>
<area alias="editcontenttype">
<key alias="allowAtRoot" version="7.2">Allow at root</key>

View File

@@ -339,6 +339,7 @@
<area alias="defaultdialogs">
<key alias="nodeNameLinkPicker">Link title</key>
<key alias="urlLinkPicker">Link</key>
<key alias="anchorLinkPicker">Anchor / querystring</key>
<key alias="anchorInsert">Name</key>
<key alias="closeThisWindow">Close this window</key>
<key alias="confirmdelete">Are you sure you want to delete</key>
@@ -456,6 +457,7 @@
<key alias="email">Enter your email...</key>
<key alias="enterMessage">Enter a message...</key>
<key alias="usernameHint">Your username is usually your email</key>
<key alias="anchor">#value or ?key=value</key>
</area>
<area alias="editcontenttype">
<key alias="allowAtRoot" version="7.2">Allow at root</key>

View File

@@ -67,7 +67,9 @@ namespace Umbraco.Web.Editors
public void Initialize(HttpControllerSettings controllerSettings, HttpControllerDescriptor controllerDescriptor)
{
controllerSettings.Services.Replace(typeof(IHttpActionSelector), new ParameterSwapControllerActionSelector(
new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetNiceUrl", "id", typeof(int), typeof(Guid), typeof(Udi))));
new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetNiceUrl", "id", typeof(int), typeof(Guid), typeof(Udi)),
new ParameterSwapControllerActionSelector.ParameterSwapInfo("GetById", "id", typeof(int), typeof(Guid), typeof(Udi))
));
}
}
@@ -100,7 +102,7 @@ namespace Umbraco.Web.Editors
var content = Services.ContentService.GetById(saveModel.ContentId);
if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
//current permissions explicitly assigned to this content item
var contentPermissions = Services.ContentService.GetPermissionsForEntity(content)
.ToDictionary(x => x.UserGroupId, x => x);
@@ -136,10 +138,10 @@ namespace Umbraco.Web.Editors
//if they are different we need to update, otherwise there's nothing to update
else if (contentPermissions.ContainsKey(userGroup.Id) == false || contentPermissions[userGroup.Id].AssignedPermissions.UnsortedSequenceEqual(groupPermissionCodes) == false)
{
Services.UserService.ReplaceUserGroupPermissions(userGroup.Id, groupPermissionCodes.Select(x => x[0]), content.Id);
}
}
}
}
}
return GetDetailedPermissions(content, allUserGroups);
@@ -158,7 +160,7 @@ namespace Umbraco.Web.Editors
{
if (contentId <= 0) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
var content = Services.ContentService.GetById(contentId);
if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
if (content == null) throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
//TODO: Should non-admins be able to see detailed permissions?
@@ -195,9 +197,9 @@ namespace Umbraco.Web.Editors
permission.Checked = false;
permission.Checked = assignedGroupPermission.AssignedPermissions.Contains(permission.PermissionCode, StringComparer.InvariantCulture);
}
}
return defaultPermissionsByGroup;
}
@@ -246,10 +248,10 @@ namespace Umbraco.Web.Editors
//set a custom path since the tree that renders this has the content type id as the parent
content.Path = string.Format("-1,{0},{1}", persistedContent.ContentTypeId, content.Id);
content.AllowedActions = new[] {"A"};
content.AllowedActions = new[] { "A" };
content.IsBlueprint = true;
var excludeProps = new[] {"_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template"};
var excludeProps = new[] { "_umb_urls", "_umb_releasedate", "_umb_expiredate", "_umb_template" };
var propsTab = content.Tabs.Last();
propsTab.Properties = propsTab.Properties
.Where(p => excludeProps.Contains(p.Alias) == false);
@@ -274,6 +276,43 @@ namespace Umbraco.Web.Editors
return content;
}
/// <summary>
/// Gets the content json for the content id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[OutgoingEditorModelEvent]
[EnsureUserPermissionForContent("id")]
public ContentItemDisplay GetById(Guid id)
{
var foundContent = GetObjectFromRequest(() => Services.ContentService.GetById(id));
if (foundContent == null)
{
HandleContentNotFound(id);
}
var content = AutoMapperExtensions.MapWithUmbracoContext<IContent, ContentItemDisplay>(foundContent, UmbracoContext);
return content;
}
/// <summary>
/// Gets the content json for the content id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[OutgoingEditorModelEvent]
[EnsureUserPermissionForContent("id")]
public ContentItemDisplay GetById(Udi id)
{
var guidUdi = id as GuidUdi;
if (guidUdi != null)
{
return GetById(guidUdi.Guid);
}
throw new HttpResponseException(HttpStatusCode.NotFound);
}
[EnsureUserPermissionForContent("id")]
public ContentItemDisplay GetWithTreeDefinition(int id)
{
@@ -373,7 +412,7 @@ namespace Umbraco.Web.Editors
{
return GetNiceUrl(guidUdi.Guid);
}
throw new HttpResponseException(HttpStatusCode.NotFound);
throw new HttpResponseException(HttpStatusCode.NotFound);
}
/// <summary>
@@ -459,7 +498,7 @@ namespace Umbraco.Web.Editors
{
var permissions = Services.UserService
.GetPermissions(Security.CurrentUser, nodeIds);
var permissionsDictionary = new Dictionary<int, string[]>();
foreach (var nodeId in nodeIds)
{
@@ -513,7 +552,7 @@ namespace Umbraco.Web.Editors
var notificationModel = new SimpleNotificationModel();
notificationModel.AddSuccessNotification(
Services.TextService.Localize("blueprints/createdBlueprintHeading"),
Services.TextService.Localize("blueprints/createdBlueprintMessage", new[]{ content.Name})
Services.TextService.Localize("blueprints/createdBlueprintMessage", new[] { content.Name })
);
return notificationModel;
@@ -563,7 +602,7 @@ namespace Umbraco.Web.Editors
[ModelBinder(typeof(ContentItemBinder))]
ContentItemSave contentItem)
{
return PostSaveInternal(contentItem,
return PostSaveInternal(contentItem,
content => Services.ContentService.WithResult().Save(contentItem.PersistedContent, Security.CurrentUser.Id));
}
@@ -1083,7 +1122,7 @@ namespace Umbraco.Web.Editors
{
throw new HttpResponseException(HttpStatusCode.NotFound);
}
var hasPathAccess = (nodeId == Constants.System.Root)
? user.HasContentRootAccess(entityService)
: (nodeId == Constants.System.RecycleBinContent)
@@ -1108,7 +1147,7 @@ namespace Umbraco.Web.Editors
var allowed = true;
foreach (var p in permissionsToCheck)
{
if (permission == null
if (permission == null
|| permission.GetAllPermissions().Contains(p.ToString(CultureInfo.InvariantCulture)) == false)
{
allowed = false;

View File

@@ -79,7 +79,23 @@ namespace Umbraco.Web.WebApi.Filters
if (parts.Length == 1)
{
nodeId = (int)actionContext.ActionArguments[parts[0]];
var argument = actionContext.ActionArguments[parts[0]].ToString();
// if the argument is an int, it will parse and can be assigned to nodeId
// if might be a udi, so check that next
// otherwise treat it as a guid - unlikely we ever get here
if (int.TryParse(argument, out int parsedId))
{
nodeId = parsedId;
}
else if (Udi.TryParse(argument, true, out Udi udi))
{
nodeId = ApplicationContext.Current.Services.EntityService.GetIdForUdi(udi).Result;
}
else
{
Guid.TryParse(argument, out Guid key);
nodeId = ApplicationContext.Current.Services.EntityService.GetIdForKey(key, UmbracoObjectTypes.Document).Result;
}
}
else
{
@@ -118,4 +134,4 @@ namespace Umbraco.Web.WebApi.Filters
}
}
}