November Happy Hour will be moved to Thursday December 5th.
November Happy Hour will be moved to Thursday December 5th.
Hi!
It's possible to add a "Copy URL" to the context menu but it involves adding your own version of a built-in EPiServer component (epi-cms/component/MediaViewModel). Beware that this "hack" needs to be migrated if upgrading to EPiServer 8 because the MediaViewModel component looks slightly different. Below is some sample code for 7.5 (not tested though).
define("app/command/CopyMediaUrlToClipboard", [ // Dojo "dojo/_base/declare", // EPi Framework "epi/shell/TypeDescriptorManager", "epi/shell/command/_Command", "epi/shell/command/_SelectionCommandMixin" ], function ( // Dojo declare, // EPi Framework TypeDescriptorManager, _Command, _SelectionCommandMixin ) { return declare([_Command, _SelectionCommandMixin], { label: "Copy URL", tooltip: "Copy URL", iconClass: "epi-iconCopy", _copyTextToClipboard: function(text) { var textArea = document.createElement("textarea"); textArea.style.position = 'fixed'; textArea.style.top = 0; textArea.style.left = 0; textArea.style.width = '2em'; textArea.style.height = '2em'; textArea.style.padding = 0; textArea.style.border = 'none'; textArea.style.outline = 'none'; textArea.style.boxShadow = 'none'; textArea.style.background = 'transparent'; textArea.value = text; document.body.appendChild(textArea); textArea.select(); try { document.execCommand('copy'); } catch (err) { console && console.log("Unable to run document.execCommand('copy')."); } document.body.removeChild(textArea); }, _execute: function () { var contentData = this._getContentData(); if (!contentData) { return; } this._copyTextToClipboard(contentData.publicUrl); }, _onModelChange: function () { var contentData = this._getContentData(), isPublicUrlAvailable = contentData && contentData.publicUrl, typeIdentifier = contentData && contentData.typeIdentifier, typeShouldActAsAsset = typeIdentifier && TypeDescriptorManager.getValue(typeIdentifier, "actAsAnAsset"); this.set("canExecute", !!(typeShouldActAsAsset && isPublicUrlAvailable)); }, _getContentData: function () { // summary: // Get current selected content data by selection // tags: // private var selectionData = null; if (this.selection && this.selection.data && this.selection.data instanceof Array && this.selection.data.length === 1) { selectionData = this.selection.data[0].data; } return selectionData; } }); });
This is a copy of the built-in script from EPiServer with one modification: the _setupCommands method with the addition of the CopyUrlToClipboard command.
define("epi-cms/component/MediaViewModel", [ // dojo "dojo/_base/array", "dojo/_base/declare", "dojo/_base/lang", "dojo/dom-class", "dojo/when", // epi "epi", "epi/shell/widget/dialog/Dialog", "epi-cms/core/ContentReference", "epi-cms/widget/ContextualContentForestStoreModel", "epi-cms/widget/viewmodel/HierarchicalListViewModel", "epi-cms/widget/viewmodel/MultipleFileUploadViewModel", "epi-cms/widget/MultipleFileUpload", "epi-cms/widget/UploadUtil", "epi-cms/command/UploadContent", "epi-cms/command/EditImage", "epi-cms/command/DownloadMedia", // our custom command "app/command/CopyMediaUrlToClipboard", // resource "epi/i18n!epi/cms/nls/episerver.cms.components.media" ], function ( // dojo array, declare, lang, domClass, when, // epi epi, Dialog, ContentReference, ContextualContentForestStoreModel, HierarchicalListViewModel, MultipleFileUploadViewModel, MultipleFileUpload, UploadUtil, UploadContentCommand, EditImageCommand, DownloadCommand, // our custom command CopyMediaUrlToClipboardCommand, // resources resources ) { return declare([HierarchicalListViewModel], { // summary: // Handles search and tree to list browsing widgets. // tags: // internal // treeStoreModelClass: [const] Function // Class to use as model for the tree. treeStoreModelClass: ContextualContentForestStoreModel, // Dialog widget for uploading new media _dialog: null, _getTypesToCreate: function () { // No create commands for media since upload is used instead. return []; }, _setupCommands: function () { // summary: // Creates and registers the commands used. // tags: // protected this.inherited(arguments); var settings = { selection: this.selection, model: this }; var customCommands = { uploadDefault: { command: new UploadContentCommand(lang.mixin({ iconClass: "epi-iconPlus", label: resources.command.label, resources: resources, viewModel: this }, settings)) }, upload: { command: new UploadContentCommand(lang.mixin({ category: "context", iconClass: "epi-iconUpload", label: resources.linktocreateitem, viewModel: this }, settings)), isAvailable: this.menuType.ROOT | this.menuType.TREE, order: 2 }, editImage: { command: new EditImageCommand(lang.mixin({ category: "context", forceContextChange: true, label: resources.command.openineditor }, settings)), isAvailable: this.menuType.LIST, order: 3 }, download: { command: new DownloadCommand(lang.mixin({ category: "context", label: resources.command.download }, settings)), isAvailable: this.menuType.LIST, order: 4 }, copyUrlToClipboard: { command: new CopyMediaUrlToClipboardCommand(lang.mixin({ category: "context", label: "Copy URL" }, settings)), isAvailable: this.menuType.LIST, order: 5 } }; this._commandRegistry = lang.mixin(this._commandRegistry, customCommands); this.pseudoContextualCommands.push(this._commandRegistry.uploadDefault.command); this.pseudoContextualCommands.push(this._commandRegistry.upload.command); }, _updateTreeContextCommandModels: function (model) { // summary: // Update model of commands in case selected content is folder // tags: // private this.inherited(arguments); this._commandRegistry.uploadDefault.command.set("model", model); this._commandRegistry.upload.command.set("model", model); }, upload: function (/*Array*/fileList, /*String?*/targetId, /*Boolean?*/createAsLocalAsset) { // summary: // Upload multiple files. // fileList: [Array] // List files to upload. // When null, only show upload form to select files for uploading. // Otherwise, upload files in list. // targetId: [String?] // Parent content id // createAsLocalAsset: [Boolean?] // tags: // protected // only create diaglog if it is not available, otherwise, re-use it. var uploader = new MultipleFileUpload({ model: new MultipleFileUploadViewModel({ store: this.get("store"), query: this.get("listQuery") }) }); uploader.on("beforeUploaderChange", lang.hitch(this, function () { this._uploading = true; })); // close multiple files upload dialog when stop uploading uploader.on("close", lang.hitch(this, function (uploading) { this._dialog && (uploading ? this._dialog.hide() : this._dialog.destroy()); })); // Reload current folder of tree, to reflect changes uploader.on("uploadComplete", lang.hitch(this, function (/*Array*/uploadFiles) { // Set current tree item again to reload items in list. if (uploader.createAsLocalAsset) { when(this.treeStoreModel && typeof this.treeStoreModel.refreshRoots === "function" && this.treeStoreModel.refreshRoots(this), lang.hitch(this, function () { // Turn-off createAsLocalAsset uploader.set("createAsLocalAsset", false); // Update uploading directory after create a new real one local asset folder for the given content uploader.set("uploadDirectory", this.get("currentTreeItem").id); // Update content list query after create a new real one local asset folder for the given content uploader.model.set("query", this.get("listQuery")); })); } else { this.onListItemUpdated(uploadFiles); this.set("currentTreeItem", this.get("currentTreeItem")); } if (this._dialog && !this._dialog.open) { this._dialog.destroy(); } this._uploading = false; })); this._dialog = new Dialog({ title: resources.linktocreateitem, content: uploader, autofocus: UploadUtil.supportNativeDndFiles(), // Only autofocus if not using flash. defaultActionsVisible: false, closeIconVisible: false }); domClass.add(this._dialog.domNode, "epi-multiFileUploadDialog"); // only show close button for multiple files upload dialog this._dialog.definitionConsumer.add({ name: "close", label: epi.resources.action.close, action: function () { uploader.close(); } }); this._dialog.resize({ w: 700 }); this._dialog.show(); var selectedContent = createAsLocalAsset ? this.selection.data[0].data : this.store.get(targetId); when(selectedContent, lang.hitch(this, function (content) { // Update breadcumb on upload dialog. this._buildBreadcrumb(content, uploader); // Set destination is current tree item. uploader.set("uploadDirectory", targetId || this.get("currentTreeItem").id); uploader.set("createAsLocalAsset", createAsLocalAsset); uploader.upload(fileList); })); }, onListItemUpdated: function (updatedItems) { // summary: // Refresh the editing media if it have a new version // updatedItems: [Array] // Collection of the updated item. In this case, they are files. // tags: // public, extension var store = this.store; return when(this.getCurrentContext(), function (currentContext) { var contentWithoutVersion = (new ContentReference(currentContext.id)).createVersionUnspecificReference().toString(); return when(store.get(contentWithoutVersion), function (currentContent) { var editingMedia = array.filter(updatedItems, function (updatedItem) { return currentContent.name.toLowerCase() === updatedItem.fileName.toLowerCase(); })[0]; return editingMedia ? currentContent : null; }); }); }, _buildBreadcrumb: function (contentItem, uploader) { // summary: // Build breadcrumb for the provided content // contentItem: Object // The provided content // uploader: Object // The multiple file upload control // tags: // private if (!uploader) { return; } // Do not add more items when current content is sub root if (this.treeStoreModel.isTypeOfRoot(contentItem)) { uploader.set("breadcrumb", [contentItem]); return; } this.treeStoreModel.getAncestors(contentItem.contentLink, lang.hitch(this, function (ancestors) { var ancestor, paths = [contentItem]; for (var i = ancestors.length - 1; i >= 0; i--) { ancestor = ancestors[i]; paths.unshift(ancestor); // Break after first sub root or context root if (this.treeStoreModel.isTypeOfRoot(ancestor)) { break; } } uploader.set("breadcrumb", paths); })); }, getContextualParentLink: function ( /* Object */ contentItem, /* Object */ context) { // summary: // Retrieves the contextual parent link for given contentItem and context. If the found link is not in "DOM", then returns the last added item in the [roots]. // contentItem: // The contentItem object. // context: // The currently loaded context. // tags: // protected var link = this.inherited(arguments); // sometimes we hide the immidiate parentLink (in case of media gadget) then we need to set the parent as last added item in roots. var previousSelection = this.treeStoreModel.get("previousSelection"); if (previousSelection && previousSelection.selectedContent) { // we need to query the Tree in order to find out if the item is hidden or not var treeItem = previousSelection.selectedContent.tree.getNodesByItem(link)[0]; if (treeItem === undefined) { link = this.roots[this.roots.length - 1]; } } return link; } }); });
You need to add a couple of things to your module.config (create it if it doesn't exist). For this sample it looks like this:
<?xml version="1.0" encoding="utf-8"?> <module clientResourceRelativePath="" loadFromBin="false"> <dojo> <paths> <add name="app" path="ClientResources/Scripts" /> </paths> </dojo> <clientResources> <!-- Inject custom MediaViewModel to replace the built-in --> <add name="epi-cms.widgets.base" path="~/ClientResources/Scripts/component/MediaViewModel.js" resourceType="Script" /> </clientResources> </module>
Awesome, thanks Mattias! Works perfectly. I had no idea that the dojo components even existed.
The only issue I have is a duplicate reference to ClientResources... my path to the Scripts folder is 'ClientResources/ClientResources/Scripts'. I'll try to track down where that is coming from.
Thanks!
I'm wondering if there's a user friendly way for editor level users to retrieve the live /globalassets/ url for a document in the Media library. We're currently on 7.5.
Currently, we are having the users drag the document into the WYSIWYG area, then selecting HTML view, and extracting the url from the href or src of the object. This is not an ideal workaround.
Ideally, I'd like to add a "Copy document URL" option to the Media Library contextual menu if there is no built-in easy solution... is this even possible?
Thanks!